This is phase-1 of switching from llvm::Optional to std::optional in the
next rebranch. llvm::Optional was removed from upstream LLVM, so we need
to migrate off rather soon. On Darwin, std::optional, and llvm::Optional
have the same layout, so we don't need to be as concerned about ABI
beyond the name mangling. `llvm::Optional` is only returned from one
function in
```
getStandardTypeSubst(StringRef TypeName,
bool allowConcurrencyManglings);
```
It's the return value, so it should not impact the mangling of the
function, and the layout is the same as `std::optional`, so it should be
mostly okay. This function doesn't appear to have users, and the ABI was
already broken 2 years ago for concurrency and no one seemed to notice
so this should be "okay".
I'm doing the migration incrementally so that folks working on main can
cherry-pick back to the release/5.9 branch. Once 5.9 is done and locked
away, then we can go through and finish the replacement. Since `None`
and `Optional` show up in contexts where they are not `llvm::None` and
`llvm::Optional`, I'm preparing the work now by going through and
removing the namespace unwrapping and making the `llvm` namespace
explicit. This should make it fairly mechanical to go through and
replace llvm::Optional with std::optional, and llvm::None with
std::nullopt. It's also a change that can be brought onto the
release/5.9 with minimal impact. This should be an NFC change.
"reborrow" flag on the SILArgument avoids transitive walk over the phi operandsi
to determine if it is a reborrow in multiple utilities.
SIL transforms must keep the flag up-to-date by calling SILArgument::setReborrow.
SILVerifier checks to ensure the flag is not invalidated.
Currently "escaping" is not used anywhere.
Add a separate 'verifyOwnership()' entry point so it's possible
to check OSSA lifetimes at various points.
Move SILGenCleanup into a SILGen pass pipeline.
After SILGen, verify incomplete OSSA.
After SILGenCleanup, verify ownership.
With f9861ec9c0 we consider SILPhiArgument in a
block with no predecessors as phis, remove assert that we expect non-empt predecessors
to reflect this change.
Add TermInst::forwardedOperand.
Add SILArgument::forwardedTerminatorResultOperand. This API will be
moved into a proper TerminatorResult abstraction.
Remove getSingleTerminatorOperand, which could be misused because it's
not necessarilly forwarding ownership.
Remove the isTransformationTerminator API, which is not useful or well
defined.
Rewrite several instances of complex logic to handle block arguments
with the simple terminator result API. This defines away potential
bugs where we don't detect casts that perform implicit conversion.
Replace uses of the SILPhiArgument type and code that explicitly
handle block arguments. Control flow is irrelevant in these
situations. SILPhiArgument needs to be deleted ASAP. Instead, use
simple APIs like SILArgument::isTerminatorResult(). Eventually this
will be replaced by a TerminatorResult type.
`getValue` -> `value`
`getValueOr` -> `value_or`
`hasValue` -> `has_value`
`map` -> `transform`
The old API will be deprecated in the rebranch.
To avoid merge conflicts, use the new API already in the main branch.
rdar://102362022
Add ScopedAddressOperand and ScopedAddressValue abstraction utilities
Introduce verification for store_borrow to validate its uses are correctly enclosed in their scope.
Include end_borrow/end_access as implicit uses while validating a borrow introducer
Add flow sensitive verifier rule for store_borrow/end_borrow pair
Make sure store_borrow is always to an alloc_stack
Make sure uses to store borrow location are via its return address only
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.
findInnerTransitiveUsesForAddress was incorrectly returning true for
pointer escapes.
Introduce enum AddressUseKind { NonEscaping, PointerEscape, Unknown };
Clients need to handle each of these cases differently.
Recording uses is now optional. This utility is also used simply to
check for PointerEscape. When uses are recorded, they are used to
extend a live range. There could be a large number of transitive uses
that we don't want to pass back to the caller.
Don't allow an owned call argument to be considered a valid BorrowingOperand.
More generally, make sure there is a perfect equivalence between valid
BorrowingOperand and the corresponding OperandOwnership kind.
A place to define invariants on OperandOwnership that passes can rely
on for convenience.
Starting with a simple invariant the OperandOwnership::Borrow is a
valid BorrowingOperand.
Instead, put the archetype->instrution map into SIlModule.
SILOpenedArchetypesTracker tried to maintain and reconstruct the mapping locally, e.g. during a use of SILBuilder.
Having a "global" map in SILModule makes the whole logic _much_ simpler.
I'm wondering why we didn't do this in the first place.
This requires that opened archetypes must be unique in a module - which makes sense. This was the case anyway, except for keypath accessors (which I fixed in the previous commit) and in some sil test files.
Migrating to this classification was made easy by the recent rewrite
of the OSSA constraint model. It's also consistent with
instruction-level abstractions for working with different kinds of
OperandOwnership that are being designed.
This classification vastly simplifies OSSA passes that rewrite OSSA
live ranges, making it straightforward to reason about completeness
and correctness. It will allow a simple utility to canonicalize OSSA
live ranges on-the-fly.
This avoids the need for OSSA-based utilities and passes to hard-code
SIL opcodes. This will allow several of those unmaintainable pieces of
code to be replaced with a trivial OperandOwnership check.
It's extremely important for SIL maintainers to see a list of all SIL
opcodes associated with a simple OSSA classification and set of
well-specified rules for each opcode class, without needing to guess
or reverse-engineer the meaning from the implementation. This
classification does that while eliminating a pile of unreadable
macros.
This classification system is the model that CopyPropagation was
initially designed to use. Now, rather than relying on a separate
pass, a simple, lightweight utility will canonicalize OSSA
live ranges.
The major problem with writing optimizations based on OperandOwnership
is that some operations don't follow structural OSSA requirements,
such as project_box and unchecked_ownership_conversion. Those are
classified as PointerEscape which prevents the compiler from reasoning
about, or rewriting the OSSA live range.
Functional Changes:
As a side effect, this corrects many operand constraints that should
in fact require trivial operand values.
I think what was happening here was that we were using one of the superclass
classofs and were getting lucky since in the place I was using this I was
guaranteed to have single value instructions and that is what I wrote as my
first case X ).
I also added more robust checks tieing the older isGuaranteed...* APIs to the
ForwardingOperand API. I also eliminated the notion of Branch being an owned
forwarding instruction. We only used this in one place in the compiler (when
finding owned value introducers), yet we treat a phi as an introducer, so we
would never hit a branch in our search since we would stop at the phi argument.
The bigger picture here is that this means that all "forwarding instructions"
either forward ownership for everything or for everything but owned/unowned.
And for those listening in, I did find one instruction that was from an
ownership forwarding subclass but was not marked as forwarding:
DifferentiableFunctionInst. With this change, we can no longer by mistake have
such errors enter the code base.
A reborrow occurs when a Borrowing Operand ends the lifetime of a borrowed value
and propagates forward a new guaranteed value that continues the guaranteed
lifetime of the value.
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 makes it clearer that isConsumingUse() is not an owned oriented API and
returns also for instructions that end the lifetime of guaranteed values like
end_borrow.
This updates how we model reborrow's lifetimes for ownership verification.
Today we follow and combine a borrow's lifetime through phi args as well.
Owned values lifetimes end at a phi arg. This discrepency in modeling
lifetimes leads to the OwnershipVerifier raising errors incorrectly for
cases such as this, where the borrow and the base value do not dominate
the end_borrow:
bb0:
cond_br undef, bb1, bb2
bb1:
%copy0 = copy_value %0
%borrow0 = begin_borrow %copy0
br bb3(%borrow0, %copy0)
bb2:
%copy1 = copy_value %1
%borrow1 = begin_borrow %copy1
br bb3(%borrow1, %copy1)
bb3(%borrow, %baseVal):
end_borrow %borrow
destroy_value %baseVal
This PR adds a new ReborrowVerifier. The ownership verifier collects borrow's
lifetime ending users and populates the worklist of the ReborrowVerifier
with reborrows and the corresponding base value.
ReborrowVerifier then verifies that the lifetime of the reborrow is
within the lifetime of the base value.
My upcoming SILGen change introduces more stores directly into
the result of a ref_element_addr or struct_element_addr in some
cases. Make sure we handle the failing combinations flagged by
the ownership verifier.
I think validating this was an oversight from the bringup of coroutines. I
discovered this while writing test cases for coroutine lifetime extension. I
realized it was possible to write a test case that should have triggered this
but was not.
I added some tests to make sure that we continue to flag this in the future.
rdar://69597888
When bringing up ownership I thought that it might be useful to distinguish
"normal users" that just require liveness and are real transitive users vs
implicit users that require liveness (I provide a constrasting example
below). Turns out this distinction did not provide any benefit, so I am ripping
it out so I can refactor this code to be used in other parts of the compiler
where we only want a single array of "normal users".
This is just a pure refactor. NFCI.
--------------------------------------------------------------------------------
An example of the former would be an apply that uses an owned value as a
guaranteed parameter:
```
bb0(%0 : @owned $Klass):
apply %func(%0) : $@convention(thin) (@guaranteed $Klass) -> ()
```
while an example of the latter is an end_apply of a coroutine that takes an
owned value as a guaranteed parameter:
```
bb0(%0 : @owned $Klass):
(%result, %token) = begin_apply %func(%0)
...
end_apply %token
destroy_value %0
```
In the latter, we can not move the destroy_value past the end_apply.
I am currently working on updating SimplifyCFG for ownership. One thing that I
am finding that I need is the ability to compute the lifetime of a guaranteed
argument from a checked_cast_br/switch_enum in order to convert said
instructions to a br. The issue comes from br acting as a consuming (lifetime
ending) use of a borrowed value, unlike checked_cast_br/switch_enum (which are
treated like forwarding instructions). This means that during the conversion we
need to insert a begin_borrow on the value before the new br instruction and
then create an end_borrow at the end of the successor block's argument's
lifetime.
By refactoring out this code from the ownership verifier, I can guarantee that
said lifetime computation will always match what the ownership verifier does
internally preventing them from getting out of sync.