With incomplete lifetimes, a value may have an "incomplete lifetime"--it
may not be destroyed on paths into dead-end regions. When a value lacks
a destroy on a path into a dead-end region, it is effectively destroyed
at its availability boundary (e.g. an `unreachable` instruction).
Indeed, lifetime completion amounts to making such implicit destroys
explicit (with correct nesting).
`SemanticARCOpts`' `performGuaranteedCopyValueOptimization` attempts to
eliminate a `copy_value` of a guaranteed value. To do so, it computes
the live range (`OwnershipLiveRange`) of the copy_value. Among other
things, it checks that all destroys of the copy_value are within the
scope of all guaranteed bases of the copied value. If any destroys are
_not_ within any of those scopes, the copy cannot be eliminated (because
the copy would have been originally live beyond the range which it would
be live in after copy elimination).
A value with an incomplete lifetime is implicitly destroyed at its
availability boundary. So those implicit destroys along the
availability boundary must _also_ be within the scopes of those
guaranteed bases.
Previously, implicit destroys along availability boundaries were not
considered. This resulted in invalid shortening of lifetimes--before
the transformation a value would be unconsumed up to its availability
boundary; after the transformation it would be consumed somewhere before
that. For example:
```
sil [ossa] @f : $@convention(thin) (@owned Outer) -> () {
entry(%o : $*Outer):
%o_borrow = begin_borrow %o : $Outer
%i = struct_extract %o_borrow : $Outer, #Outer.inner
%i_copy = copy_value %i : $Inner
end_borrow %o_borrow : $Outer
destroy_value %o : $Outer
apply @g(%i_copy) : $@convention(thin) (@guaranteed Inner) -> ()
unreachable // %i_copy implicitly destroyed
}
```
Here, `%i_copy` is implicitly destroyed at `unreachable` but `%i` is
consumed at the scope end of `%o_borrow`. That means that an attempt to
RAUW `%i_copy` with `%i` would be invalid because uses of `%i_copy` may
be outside the live range of `%i`.
(Note that this happens not to occur in the above example because
there's a bailout in the case that there are no destroys, but perturbing
the example to include a cond_br on one branch of which the value is
destroyed results in the miscompile described above.)
Here, this is fixed by adding the instructions on the availability
boundary at which the value is implicitly destroyed to the live range.
Do this by adding the instructions on the availability boundary of each
def from which the live range is generated to the live range.
One wrinkle is that the OwnershipLiveRange utility is used in one place
by a utility when SIL is in an invalid state (phi uses have been
replaced with undef so values leaks on those paths). Account for this
by manually fixing up the liveness instance passed to the lifetime
completion utility.
rdar://157291161
Although I don't plan to bring over new assertions wholesale
into the current qualification branch, it's entirely possible
that various minor changes in main will use the new assertions;
having this basic support in the release branch will simplify that.
(This is why I'm adding the includes as a separate pass from
rewriting the individual assertions)
Compute, update and handle borrowed-from instruction in various utilities and passes.
Also, used borrowed-from to simplify `gatherBorrowIntroducers` and `gatherEnclosingValues`.
Replace those utilities by `Value.getBorrowIntroducers` and `Value.getEnclosingValues`, which return a lazily computed Sequence of borrowed/enclosing values.
In the absence of this bailout, reborrows can be introduced while replacing the @owned value with
a local borrow introducer. Since lifetime adjustment for this case was not implemented, disable here.
rdar://106757446
Andy some time ago already created the new API but didn't go through and update
the old occurences. I did that in this PR and then deprecated the old API. The
tree is clean, so I could just remove it, but I decided to be nicer to
downstream people by deprecating it first.
The specific problem here is that I am going to be adding some code to
SemanticARCOpts that eliminates reborrows and may need to create new phi
arguments and thus add arguments to edges. The weird thing about this is that
doing so actually requires us to create a new terminator!
This means that subtle pointer invalidation issues can occur here. To work
around that we store our terminators as SILBasicBlock, operand number since we
can always immediately find a terminator from its basic block. If we do not have
a terminator, we keep on just storing the SILInstruction itself.
NOTE: This only saves us from additive changes. Deletions are still an issue.
This makes it easier to understand conceptually why a ValueOwnershipKind with
Any ownership is invalid and also allowed me to explicitly document the lattice
that relates ownership constraints/value ownership kinds.
This patch moves state from the main SemanticARCOptVisitor struct to instead be
on a context object. Sub-transformations should not need to know about the
visitor since how it processes things is orthogonal from the transformations
themselves.