-
Notifications
You must be signed in to change notification settings - Fork 10.5k
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
Conversation
@swift-ci test |
@swift-ci test source compatibility |
@swift-ci benchmark |
68d5bc7
to
2b8cfca
Compare
@swift-ci test |
@swift-ci test macOS Platform |
There was a problem hiding this 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: |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Note: this PR passed full testing. I'm only updating the comments. |
5c08a3f
to
3feaa46
Compare
@swift-ci smoke test |
Encapsulate all the complexity of reborrows and guaranteed phi in 3 ownership liveness interfaces: LinerLiveness, InteriorLiveness, and ExtendedLiveness.
3feaa46
to
13e1aa4
Compare
@swift-ci smoke test |
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.