Skip to content

Add ownership liveness utilities #63404

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 11, 2023
Merged

Conversation

atrick
Copy link
Contributor

@atrick atrick commented Feb 3, 2023

Encapsulate all the complexity of reborrows and guaranteed phi in 3
ownership liveness interfaces:

LinerLiveness, InteriorLiveness, and ExtendedLiveness

Required for the "complete OSSA lifetimes" utility.

@atrick atrick marked this pull request as ready for review February 6, 2023 21:26
@atrick atrick requested a review from nate-chandler February 6, 2023 21:26
@atrick
Copy link
Contributor Author

atrick commented Feb 6, 2023

@swift-ci test

@atrick
Copy link
Contributor Author

atrick commented Feb 6, 2023

@swift-ci test source compatibility

@atrick
Copy link
Contributor Author

atrick commented Feb 6, 2023

@swift-ci benchmark

@atrick atrick force-pushed the ossa-liveness branch 2 times, most recently from 68d5bc7 to 2b8cfca Compare February 7, 2023 20:27
@atrick
Copy link
Contributor Author

atrick commented Feb 7, 2023

@swift-ci test

@atrick atrick marked this pull request as draft February 7, 2023 21:23
@atrick atrick marked this pull request as ready for review February 7, 2023 21:23
@atrick
Copy link
Contributor Author

atrick commented Feb 8, 2023

@swift-ci test macOS Platform

Copy link
Contributor

@eeckstein eeckstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial comments

//
//===----------------------------------------------------------------------===//
///
/// Terminology:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

/// Linear lifetime: All paths through the value's definition to the
/// function exit pass through exactly one lifetime-ending operation.
///
/// Complete OSSA: All owned values and borrow introducers have linear lifetime.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also add a definition (or example) for "borrow introducers", like in the comment for OSSALiveness.

nitpick: ... lifetimes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// Borrow introducer: defines a guaranteed SILValue and an associated explicit
/// borrow scope: begin_borrow, load_borrow, store_borrow, and phi.
/// A phi only introduces a scope when `isGuaranteedForwarding` returns
/// false. We refer to these as reborrows. Eventually, the phi will know whether
/// it is a reborrow based on a flag or subclass. A guaranteed function argument
/// conceptually introduces a borrow scope, but the scope is implicit in the
/// function entry and return points.

/// Complete OSSA: All owned values and borrow introducers have linear lifetime.
///
///
/// Linear SSA liveness:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a naive question: what is "liveness" (compared to "lifetime").
Can you add a first bullet point which actually defines that term?

Copy link
Contributor Author

@atrick atrick Feb 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is "liveness" (compared to "lifetime").

Good question. It's not that important to distinguish, but I find it useful.

A lifetime means different things depending on context:

  • Refcounted objects have a dynamic lifetime from allocation to deinit
  • In lexical SIL, variable declaration have a static lifetime
  • In ownership SIL, every owned value has a lifetime

In ownership SIL, rewriting or optimizing a lifetime requires computing liveness.

Liveness is the data flow indicating where values are used. Liveness can be interpreted in different ways depending on the context. We often need to extend liveness incrementally as we discover things that should be "covered" by the value's lifetime. We may conditionally extend it across access scopes, debug information, deinit barriers, etc.

Once all things are considered, you get back a liveness boundary (essentially an InstructionRange), then use that to do perform some query (check if certain things are "contained" by the boundary), or run some diagnostics. Sometimes, you want to keep liveness around after the check, because you might further extend it by adding more uses (after replace-all-uses). Note that isWithinBoundary is a lot more efficient though when you still have the liveness information, vs. just the boundary.

These interfaces are called "liveness" because they compute liveness and store the data flow result internally. The choice of algorithm depends of the kind of lifetime you're dealing with. For example, is the lifetime already valid, or did you just transform the SIL in a way that made it invalid.

You could throw away "liveness" and just keep a "liveness boundary", then use that to rewrite a value's "lifetime"

/// - Liveness cannot extend beyond lifetime-ending operations
/// (a.k.a. affine lifetimes).
///
/// - Assumes inner scopes *are* linear, including borrow and address scopes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "inner" mean in this context? Does that mean that outer scopes can have non-linear lifetimes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. These interfaces are designed to build complete lifetimes from the bottom up. You start by assuming nothing. Inner scopes are processed first.

///
/// - The definition dominates all use points (phis do not extend the lifetime)
///
/// - Does not assume the current lifetime is linear. Transitively follows
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the important difference to "Linear SSA liveness", right?
It would be nice to add an example which highlights the difference between both. E.g.

  %def = alloc_ref $C                
  %b = begin_borrow %def      // part of ... liveness
  %a = ref_element_addr %b    // part of ... liveness
  end_borrow %b               // part of ... liveness
  destroy_value %def          // part of ... liveness

Copy link
Contributor Author

@atrick atrick Feb 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// Interior liveness example:
/// 
///     %struct = struct ...
///     %f = struct_extract %s     // defines a guaranteed value
///     %b = begin_borrow %field
///     %a = ref_element_addr %b
///     _  = address_to_pointer %a
///     end_borrow %b              // the only interior use of %f
///
/// When computing interior liveness for %f, %b is an inner scope. Because inner
/// scopes are complete, the only relevant use is end_borrow %b. Despite the
/// address_to_pointer instruction, %f does not escape any dependent address.

** Transitive liveness is no longer needed with complete ownership lifetimes **

/// Transitive liveness example:
/// 
///     %struct = struct ...
///     %f = struct_extract %s     // defines a guaranteed value
///     %b = begin_borrow %field
///     %a = ref_element_addr %b
///     _  = address_to_pointer %a // a transitive use of %f escapes
///
/// When computing transitive liveness for %f, %b is an inner scope. Liveness
/// does not assume that an end_borrow exists. Instead it transitively considers
/// all uses of %b. As a result, %f escapes.

/// (e.g. begin_borrow, load_borrow, begin_apply, store_borrow, begin_access)
/// A callback may be used to complete inner scopes before updating liveness.
/// Rarely returns AddressUseKind::PointerEscape, because inner scopes hide
/// pointer escapes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This raises the question in which cases can it return a pointer escape?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// - Only returns AddressUseKind::PointerEscape if one of the uses of the
///   outer value has OperandOwnership::PointerEscape or
///   OperandOwnership::BitwiseEscape. An inner scope's guaranteed value may
///   escape without causing the outer scope's value to escape.

/// - The definition dominates all use points (phis do not extend the lifetime)
///
/// - Does not assume that any lifetimes are linear. Transitively follows uses
/// within inner scopes, recursively through nested scopes, including
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

" ... recursively through nested scopes ..."
Is this the main difference to "Interior SSA liveness? If yes, can you say this more prominently?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// Transitive SSA liveness
///
/// - Similar to Interior SSA liveness, but does not assume that any lifetimes
///   are linear. Transitively follows uses within inner scopes, recursively
///   through nested scopes, including forwarding operations and address uses.
///
/// - Much more likely to return AddressUseKind::PointerEscape
///
/// - Transitive liveness is no longer needed with complete OSSA lifetimes
///
/// Transitive liveness example:
///
///     %struct = struct ...
///     %f = struct_extract %s     // defines a guaranteed value (%f)
///     %b = begin_borrow %field
///     %a = ref_element_addr %b
///     _  = address_to_pointer %a // a transitive use of %f escapes
///
/// When computing transitive liveness for %f, %b is an inner scope. Liveness
/// does not assume that an end_borrow exists. Instead it transitively considers
/// all uses of %b. As a result, %f escapes.

/// - Liveness of a single "defining" value extended beyond its lifetime-ending
/// operations.
///
/// - May refer to copy-extension, reborrow-extension, or extension over
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe quickly say what a re-borrow is, like in the comment for OSSALiveness

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the mention of reborrow here.

/// Extended liveness
///
/// - Liveness of a single "defining" value extended beyond its lifetime-ending
/// operations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This includes following phi-arguments and therefore the definition does not necessarily dominate all use points, right?
Maybe you can add that for clarity

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// Extended liveness
///
/// - Liveness of a single "defining" value extended beyond its lifetime-ending
///   operations.
///
/// - May refer to copy-extension, phi-extension, or extension over barriers
///   such as access markers.
///
/// - For phi-extension, the definition no longer dominates the use
///   points. MultiDefPrunedLiveness must be used. Each phi is added as a new
///   definition.
///
/// Extended linear liveness
///
/// - Extended liveness that only considers lifetime-ending operations. Assumes
///   the definition already has a linear lifetime and that any phis that end
///   the current lifetime also have linear lifetimes.

@atrick
Copy link
Contributor Author

atrick commented Feb 9, 2023

Note: this PR passed full testing. I'm only updating the comments.

@atrick atrick force-pushed the ossa-liveness branch 2 times, most recently from 5c08a3f to 3feaa46 Compare February 10, 2023 08:43
@atrick
Copy link
Contributor Author

atrick commented Feb 10, 2023

@swift-ci smoke test

Encapsulate all the complexity of reborrows and guaranteed phi in 3
ownership liveness interfaces:

LinerLiveness, InteriorLiveness, and ExtendedLiveness.
@atrick
Copy link
Contributor Author

atrick commented Feb 10, 2023

@swift-ci smoke test

@atrick atrick merged commit 5523c56 into swiftlang:main Feb 11, 2023
@atrick atrick deleted the ossa-liveness branch February 11, 2023 00:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants