These instructions have the following attributes:
1. copyably_to_moveonlywrapper takes in a 'T' and maps it to a '@moveOnly
T'. This is semantically used when initializing a new moveOnly binding from a
copyable value. It semantically destroys its input @owned value and returns a
brand new independent @owned @moveOnly value. It also is used to convert a
trivial copyable value with type 'Trivial' into an owned non-trivial value of
type '@moveOnly Trivial'. If one thinks of '@moveOnly' as a monad, this is how
one injects a copyable value into the move only space.
2. moveonlywrapper_to_copyable takes in a '@moveOnly T' and produces a new 'T'
value. This is a 'forwarding' instruction where at parse time, we only allow for
one to choose it to be [owned] or [guaranteed].
* moveonlywrapper_to_copyable [owned] is used to signal the end of lifetime of
the '@moveOnly' wrapper. SILGen inserts these when ever a move only value has
its ownership passed to a situation where a copyable value is needed. Since it
is consuming, we know that the no implicit copy checker will ensure that if we
need a copy for it, the program will emit a diagnostic.
* moveonlywrapper_to_copyable [guaranteed] is used to pass a @moveOnly T value
as a copyable guaranteed parameter with type 'T' to a function. In the case of
using no-implicit-copy checking this is always fine since no-implicit-copy is a
local pattern. This would be an error when performing no escape
checking. Importantly, this instruction also is where in the case of an
@moveOnly trivial type, we convert from the non-trivial representation to the
trivial representation.
Some important notes:
1. In a forthcoming commit, I am going to rebase the no implicit copy checker on
top of these instructions. By using '@moveOnly' in the type system, we can
ensure that later in the SIL pipeline, we can have optimizations easily ignore
the code.
2. Be aware of is that due to SILGen only emitting '@moveOnly T' along immediate
accesses to the variable and always converts to a copyable representation when
calling other code, we can simply eliminate from the IR all moveonly-ness from
the IR using a lowering pass (that I am going to upstream). In the evil scheme
we are accomplishing here, we perform lowering of trivial values right after
ownership lowering and before diagnostics to simplify the pipeline.
On another note, I also fixed a few things in SILParsing around getASTType() vs
getRawASTType().
This fix unblocks unrelated optimizer commits. A unit test will be
introduced with those commits.
The RAUW utility does not correctly handle reborrows. It is in the
middle of being rewritten. For now, simply bail out since this isn't
an important case to optimize.
Add NonTypeDependentOperandToValue predicate for composability.
Add a getNonTypeDependentOperandValues(), which can be used as a functor.
The skipTypeDependentOperands parameter complicated the API.
Previously, visitTransitiveEndBorrows took BorrowedValues. However,
there is at least one kind of borrow--namely,
unchecked_ownership_conversion insts--that is not currently permitted by
the BorrowedValue API. The long term fix is to make BorrowedValue
handle such instructions. For now, change visitTransitiveEndBorrows to
take SILValues so that unchecked_ownership_conversion can be passed to
the API.
rdar://87985420
Replaced findInnerTransitiveGuaranteeedUsesOfBorrowedValue with
findExtendedUsesOfSimpleBorrowedValue. Starting from a borrowed value,
it finds all extended (i.e., seeing through copies) uses of the borrow
and its projections within the simple (i.e. without considering
reborrowing) borrow scope.
More bugs related to dead-end blocks and OSSA lifetimes that aren't
well formed because of that.
Independently, I'm working on prohibiting ill-formed OSSA even in the
presence of dead-end blocks. That makes these workarounds unnecessary,
but we urgently need a narrow fix here.
SemanticARCOptVisitor::performGuaranteedCopyValueOptimization was
converting this SIL
%borrow = begin_borrow %copiedValue
%copy = copy_value %borrow
%borrowCopy = begin_borrow %copy
end_borrow %borrow
end_borrow %borrowCopy
destroy_value %copy
// something something
unreachable
into
%borrow = begin_borrow %copiedValue
%innerBorrow = begin_borrow %borrow
end_borrow %borrow
end_borrow %innerBorrow
// something something
unreachable
Dead-end blocks are simply irrelevant for this
optimization. Unfortunately, there were multiple layers of attempted
workarounds that were hiding the real problem, except in rare cases.
Thanks Nate Chandler for reducing the test.
During copy propagation (for which -enable-copy-propagation must still
be passed), also try to shrink borrow scopes by hoisting end_borrows
using the newly added ShrinkBorrowScope utility.
Allow end_borrow instructions to be hoisted over instructions that are
not deinit barriers for the value which is borrowed. Deinit barriers
include uses of the value, loads of memory, loads of weak references
that may be zeroed during deinit, and "synchronization points".
rdar://79149830
This instruction is similar to a copy_addr except that it marks a move of an
address that has to be checked. In order to keep the memory lifetime verifier
happy, the semantics before the checker runs are the mark_unresolved_move_addr is
equivalent to copy_addr [init] (not copy_addr [take][init]).
The use of this instruction is that Mandatory Inlining converts builtin "move"
to a mark_unresolved_move_addr when inlining the function "_move" (the only
place said builtin is invoked).
This is then run through a special checker (that is later in this PR) that
either proves that the mark_unresolved_move_addr can actually be a move in which
case it converts it to copy_addr [take][init] or if it can not be a move, emit
an error and convert the instruction to a copy_addr [init]. After this is done
for all instructions, we loop back through again and emit an error on any
mark_unresolved_move_addr that were not processed earlier allowing for us to
know that we have completeness.
NOTE: The move kills checker for addresses is going to run after Mandatory
Inlining, but before predictable memory opts and friends.
The reason why I am doing this is that currently checked_cast_br of an AnyObject
may return a different value due to boxing. As an example, this can occur when
performing a checked_cast_br of a __SwiftValue(AnyHashable(Klass())).
To model this in SIL, I added to OwnershipForwardingMixin a bit of information
that states if the instruction directly forwards ownership with a default value
of true. In checked_cast_br's constructor though I specialize this behavior if
the source type is AnyObject and thus mark the checked_cast_br as not directly
forwarding its operand. If an OwnershipForwardingMixin directly forwards
ownership, one can assume that if it forwards ownership, it will always do so in
a way that ensures that each forwarded value is rc-identical to whatever result
it algebraically forwards ownership to. So in the context of checked_cast_br, it
means that the source value is rc-identical to the argument of the success and
default blocks.
I added a verifier check to CheckedCastBr that makes sure that it can never
forward guaranteed ownership and have a source type of AnyObject.
In SemanticARCOpts, I modified the code that builds extended live ranges of
owned values (*) to check if a OwnershipForwardingMixin is directly
forwarding. If it is not directly forwarding, then we treat the use just as an
unknown consuming use. This will then prevent us from converting such an owned
value to guaranteed appropriately in such a case.
I also in SILGen needed to change checked_cast_br emission in SILGenBuilder to
perform an ensurePlusOne on the input operand (converting it to +1 with an
appropriate cleanup) if the source type is an AnyObject. I found this via the
verifier check catching this behavior from SILGen when compiling libswift. This
just shows how important IR verification of invariants can be.
(*) For those unaware, SemanticARCOpts contains a model of an owned value and
all forwarding uses of it called an OwnershipLiveRange.
rdar://85710101
Replaced a SmallSetVector worklist with a GraphNodeWorklist to avoid
infinitely pushing and popping the same value the same value into and
out from the worklist.
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.
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.
This replaces the recent BorrowedAddress utility (the address may not
be borrowed!)
APIs:
AddressOwnership::hasLocalOwnershipLifetime() - convenience on top of
AccessBase::hasLocalOwnershipLifetime().
AddressOwnership::getOwnershipReferenceRoot() - convenience on top of
AccessBase::getOwnershipReferenceRoot().
AddressOwnership::findTransitiveUses() - wrapper API over the internal
helper findTransitiveUsesForAddress()
AddressOwnership::areUsesWithinLifetime() - wrapper adds a quick check
on top of BorrowedValue::areUsesWithinLifetime()
To visitExtendedScopeEndingUses.
We need to talk about the "local" scope, which does not cross
reborrows, in contrast to the extended scope. So the name was a
contradiction that could cause confusion elsewhere.
Temporary check for malformed borrow scopes. Def-use traversals expect
at least one scope-ending use. Otherwise we can end up missing the
borrow entirely and miscompiling. SIL should guarantee scope-ending
uses on all paths, but that isn't fully enforced yet.
This abstraction is heavily used to ask about the current borrow scope
enclosing some uses. Give it the functionality it needs.
Add BorrowedValue::computeLiveness(PrunedLiveness). Get the borrow
scope's live range. A trivial 4-line implementation.
Cleanup BorrowedValue::areUsesWithinScope(). Quick check to see if a set
of uses known to be dominated by the borrow are within the borrow scope.
Add BorrowedValue::hasReborrow(). Is this local borrow scope
reborrowed? If not, then it's local scope-ending operations are the
end of its required lifetime.
Simplify BorrowedValue::gatherReborrows() to use OperandOwnership::Reborrow.
Handle SSA update (phi creation) when extending an owned lifetime over
a borrowed lifetime.
This is a layer of logic above BorrowedValue but below
OwnershipLifetimeExtender and other higher-level utilities.
Add OwnershipForwardingTermInst::createResult(SILBasicBlock, SILType)
Add SwitchEnumInst::createDefaultResult()
Add SwitchEnumInst::createOptionalSomeResult(), which handles most
compiler-generated switches.
Basic API for creating terminator results with consistent
ownership. This allows enabling OSSA verification on terminator
results. It fixes current issues, but is also a prerequisite for OSSA
simplify-cfg.
For switch_enum, this ensures that the default argument consistently
either forwards the original value, or handles the payload of the
unique case (the unique payload was already being inferred for
ownership, but the block argument was inconsistent with that fact).
switch_enum and checked_cast_br specify their forwarding
ownership. This can differ from their operand ownership.
For example:
%e = enum $Optional<AnyObject>, #Optional.none!enumelt
switch_enum %e : $Optional<AnyObject>,
case #Optional.some!enumelt: bb2...
bb2(%arg : @owned T):
Independent forwarding ownership is only supported with terminators in this
change, but in the near term it will be used for all forwarding
operations to support implicit borrow scopes.
Determines whether an address might be inside a borrowed scope. If so,
then any address substitution needs to find that scope boundary to
avoid violating its basic guarantee that all uses are within scope.
The store_borrow's result is a sub-interior pointer that ensures that any uses
of the interior pointer are within the lifetime of the borrowed value that is
being stored. But fundamentally this is just embedding lifetime ownership on
def-use edges and once ossa is lowered the result is just the destination
address. So it makes sense to include the uses of the result of the store_borrow
as the dest's uses.
These both are already classified as interior pointers. Thus, we would have
already handled them at the top of the loop where we handle interior pointer
operands.
TLDR: This is just an NFC rename in preparation for changing
SILValue::getOwnershipKind() of any forwarding instructions to return
OwnershipKind::None if they have a trivial result despite forwarding ownership
that isn't OwnershipKind::None (consider an unchecked_enum_data of a trivial
payload from a non-trivial enum).
This ensures that one does not by mistake use this routine instead of
SILValue::getOwnershipKind(). The reason why these two things must be
distinguished is that the forwarding ownership kind of an instruction that
inherits from OwnershipForwardingMixin is explicitly not the ValueOwnershipKind
of the result of the instruction. Instead it is a separate piece of state that:
1. For certain forwarding instructions, defines the OwnershipConstraint of the
forwarding instruction.
2. Defines the ownership kind of the result of the value. If the result of the
value is non-trivial then it is exactly the set ownership kind. If the result is
trivial, we use OwnershipKind::None instead. As an example of this, consider an
unchecked_enum_data that extracts from a non-trivial enum a trivial payload:
```
enum Either {
case int(Int)
case obj(Klass)
}
%1 = load_borrow %0 : $*Either
%2 = unchecked_enum_data %1 : $Either, #Either.int!enumelt.1 // Int type
end_borrow %1 : $Either
```
If we were to identify the forwarding ownership kind (guaranteed) of
unchecked_enum_data with the value ownership kind of its result, we would
violate ownership since we would be passing a guaranteed value to the operand of
the unchecked_enum_data that will only accept values with
OwnershipKind::None. =><=.
This visits unique pairs of reborrowPhi and its base value.
The code for the utility is mostly refactored from the ReborrowVerifier.
This utility will be used later in DCE.