Operands are generally better to return than values since the operand also
enables you to get to the terminator instruction as well. Since so much code in
the compiler already uses the getIncomingPhiValue methods, I reimplemented them
on top of the operand methods.
Found the meaning by looking at when Definite Initialization marks alloc_stack
with that marker.
For those who are unware like me, it means the liveness of the value in the
alloc_stack is conditional in some way (i.e. conditional init, destroy, etc).
This is just better information to have since one wants to not only know the
instruction, but also the specific value used on the instruction since behavior
can vary depending upon that. The operand is what ties the two together so it is
a natural fit.
Now that this is done, I am going to work on refactoring out converting a
LiveRange from @owned -> @guaranteed. It will be a consuming operation using a
move operator since once the transformation has completed, the LiveRange no
longer exists.
I need this refactored functionality since I am going to need it when
eliminating phi-webs.
The only reason why BranchPropagatedUser existed was because early on in SIL, we
weren't sure if cond_br should be able to handle non-trivial values in
ossa. Now, we have reached the point where we have enough experience to make the
judgement that it is not worth having in the representation due to it not
holding its weight.
Now that in ToT we have banned cond_br from having non-trivial operands in ossa,
I can just eliminate BranchPropagatedUser and replace it with the operands that
we used to construct them!
A few notes:
1. Part of my motiviation in doing this is that I want to change LiveRange to
store operands instead of instructions. This is because we are interested in
being able to understand the LiveRange at a use granularity in cases where we
have multiple operands. While doing this, I discovered that I needed
SILInstructions to use the Linear Lifetime Checker. Then I realized that now was
the time to just unwind BranchPropagatedUser.
2. In certain places in SemanticARCOpts, I had to do add some extra copies to
transform arrays of instructions from LiveRange into their operand form. I am
going to remove them in a subsequent commit when I change LiveRange to work on
operands. I am doing this split to be incremental.
3. I changed isSingleInitAllocStack to have an out array of Operand *. The only
user of this code is today in SemanticARCOpts and this information is fed to the
Linear Lifetime Checker, so I needed to do it.
OwnershipUtils.h is growing a bit and I want to use it to store abstract higher
level utilities for working with ossa. LinearLifetimeChecker is just a low level
detail of that, so it makes sense to move it out now.
This is in prepration for other bug fixes.
Clarify the SIL utilities that return canonical address values for
formal access given the address used by some memory operation:
- stripAccessMarkers
- getAddressAccess
- getAccessedAddress
These are closely related to the code in MemAccessUtils.
Make sure passes use these utilities consistently so that
optimizations aren't defeated by normal variations in SIL patterns.
Create an isLetAddress() utility alongside these basic utilities to
make sure it is used consistently with the address corresponding to
formal access. When this query is used inconsistently, it defeats
optimization. It can also cause correctness bugs because some
optimizations assume that 'let' initialization is only performed on a
unique address value.
Functional changes to Memory Behavior:
- An instruction with side effects now conservatively still has side
effects even when the queried value is a 'let'. Let values are
certainly sensitive to side effects, such as the parent object being
deallocated.
- Return the correct MemBehavior for begin/end_access markers.
Specifically, I got rid of the constructors for BorrowScopeIntroducingValue and
made it so one can only construct BorrowScopeIntroducingValue from the static
constructor.
I also changed the place where we mapped values from BorrowScopeIntroducingValue
-> BorrowScopeIntroducingValueKind. This simplifies the whole thing.
This allows the usage of the whole remark infrastructure developed in
LLVM, which includes a new binary format, metadata in object files, etc.
This gets rid of the YAMLTraits-based remark serialization and does the
plumbing for hooking to LLVM's main remark streamer.
For more about the idea behind LLVM's main remark streamer, see the
docs/Remarks.rst changes in https://reviews.llvm.org/D73676.
The flags are now:
* -save-optimization-record: enable remarks, defaults to YAML
* -save-optimization-record=<format>: enable remarks, use <format> for
serialization
* -save-optimization-record-passes <regex>: only serialize passes that
match <regex>.
The YAMLTraits in swift had a different `flow` setting for the debug
location, resulting in some test changes.
I added a new API into OwnershipUtils called
getSingleBorrowIntroducingValue. This API returns a single
BorrowScopeIntroducingValue for a passed in guaranteed value. If we can not find
such a BorrowScopeIntroducingValue or we find multiple such, we return None.
Using that, I refactored StorageGuaranteesLoadVisitor::visitClassAccess(...) to
use this new API which should make it significantly more robust since the
routine uses the definitions of "guaranteed forwarding" that the ownership
verifier uses when it verifies meaning that we can rely on the routine to be
exhaustive and correct. This means that we now promote load [copy] ->
load_borrow even if our borrow scope introducer feeds through a switch_enum or
checked_cast_br result (the main reason I looked into this change).
To create getSingleBorrowIntroducingValue, I refactored
getUnderlyingBorrowIntroucingValues to use a generator to find all of its
underlying values. Then in getSingleBorrowIntroducingValue, I just made it so
that we call the generator 1-2 times (as appropriate) to implement this query.
Specifically, this PR adds support for optimizing simple cases where we do not
need to compute LiveRanges with the idea of first doing simple transforms that
involve small numbers of instructions first. With that in mind, we only optimize
cases where our copy_value has a single consuming user and our owned value has a
single destroy_value. To understand the transform here, consider the following
SIL:
```
%0 = ...
%1 = copy_value %0 (1)
apply %guaranteedUser(%0) (2)
destroy_value %0 (3)
apply %cviConsumer(%1) (4)
```
We want to eliminate (2) and (3), effectively joining the lifetimes of %0 and
%1, transforming the code to:
```
%0 = ...
apply %guaranteedUser(%0) (2)
apply %cviConsumer(%0) (4)
```
Easily, we can always do this transform in this case since we know that %0's
lifetime ends strictly before the end of %1's due to (3) being before (4). This
means that any uses that require liveness of %0 must be before (4) and thus no
use-after-frees can result from removing (3) since we are not shrinking the
underlying object's lifetime. Lets consider a different case where (3) and (4)
are swapped.
```
%0 = ...
%1 = copy_value %0 (1)
apply %guaranteedUser(%0) (2)
apply %cviConsumer(%1) (4)
destroy_value %0 (3)
```
In this case, since there aren't any liveness requiring uses of %0 in between
(4) and (3), we can still perform our transform. But what if there was a
liveness requiring user in between (4) and (3). To analyze this, lets swap (2)
and (4), yielding:
```
%0 = ...
%1 = copy_value %0 (1)
apply %cviConsumer(%1) (4)
apply %guaranteedUser(%0) (2)
destroy_value %0 (3)
```
In this case, if we were to perform our transform, we would get a use-after-free
due do the transform shrinking the lifetime of the underlying object here from
ending at (3) to ending at (4):
```
%0 = ...
apply %cviConsumer(%1) (4)
apply %guaranteedUser(%0) (2) // *kaboom*
```
So clearly, if (3) is after (4), clearly, we need to know that there aren't any
liveness requiring uses in between them to be able to perform this
optimization. But is this enough? Turns out no. There are two further issues
that we must consider:
1. If (4) is forwards owned ownership, it is not truly "consuming" the
underlying value in the sense of actually destroying the underlying value. This
can be worked with by using the LiveRange abstraction. That being said, this PR
is meant to contain simple transforms that do not need to use LiveRange. So, we
bail if we see a forwarding instruction.
2. At the current time, we may not be able to find all normal uses since all of
the instructions that are interior pointer constructs (e.x.: project_box) have
not been required yet to always be guarded by borrows (the eventual end
state). Thus we can not shrink lifetimes in general safely until that piece of
work is done.
Given all of those constraints, we only handle cases here where (3) is strictly
before (4) so we know 100% we are not shrinking any lifetimes. This effectively
is causing our correctness to rely on SILGen properly scoping lifetimes. Once
all interior pointers are properly guarded, we will be able to be more
aggressive here.
With that in mind, we perform this transform given the following conditions
noting that this pattern often times comes up around return values:
1. If the consuming user is a return inst. In such a case, we know that the
destroy_value must be before the actual return inst.
2. If the consuming user is in the exit block and the destroy_value is not.
3. If the consuming user and destroy_value are in the same block and the
consuming user is strictly later in that block than the destroy_value.
In all of these cases, we are able to optimize without the need for LiveRanges.
I am going to add support for this in a subsequent commit.
I also added a comment to getAllBorrowIntroducingValues(...) that explained the
situations where one could have multiple borrow introducing values:
1. True phi arguments.
2. Aggregate forming instructions.
Creating a @_dynamicReplacement function requires the creation of a
reference to the original function. We need to call SILGenModule's
getFunction to satisfy all the assertions in place.
rdar://59774606
* Simplified the logic for creating static initializers and constant folding for global variables: instead of creating a getter function, directly inline the constant value into the use-sites.
* Wired up the constant folder in GlobalOpt, so that a chains for global variables can be propagated, e.g.
let a = 1
let b = a + 10
let c = b + 5
* Fixed a problem where we didn't create a static initializer if a global is not used in the same module. E.g. a public let variable.
* Simplified the code in general.
rdar://problem/31515927
We can eliminate `convert_function`s that are immediately used as the callee of
an `apply` or `partial_apply`, as well as stacked `convert_function`s that may
arise from this transformation.
This patch implements movable guaranteed scopes in ossa. This pattern is
currently not generated anywhere in the compiler, but my hope is to begin
emitting these in SemanticARCOpts. The idea is that these model true phi nodes
and thus can be used to fuse multiple guaranteed scopes into one using br
instructions. This is treated similarly to how owned instructions are forwarded
through /all/ terminators. This will enable us to use the SILSSAUpdater with
guaranteed arguments as well as enable the expression of sets of borrow scopes
that minimally jointly-dominate a guaranteed argument. This will enable us to
express +0 merge points like the following:
```
bb1:
%0a = begin_borrow %0 : $Klass
br bb3(%0a : $Klass)
bb2:
%1a = load_borrow %1 : $*Klass
br bb3(%1a : $Klass)
bb3(%2 : @guaranteed $Klass)
...
end_borrow %2 : $Klass
```
I describe below what the semantics of guaranteed block arguments were
previously, what they are now, and a little bit of interesting things from a
semantic perspective around implicit sub-scope users.
Before this patch in ossa, guaranteed block arguments had two different sets of
semantics:
1. Given a checked_cast_br or a switch_enum, the guaranteed block argument was
treated like a forwarding instruction. As such, the guaranteed argument's did
not require an end_borrow and its uses were validated as part of the use list
of the switch_enum/checked_cast_br operand's borrow introducer. It also was
not classified as a BorrowScopeValueIntroducer since it was not introducing a
new scope.
2. Given any other predecessor terminator, we treated the guaranteed argument as
a complete sub-scope of its incoming values. Thus we required the guaranteed
argument to have its lifetime eneded by an end_borrow and that all incoming
values of the guaranteed argument to come from a borrow introducer whose set
of jointly post-dominating end_borrows also jointly post-dominates the set of
end_borrows associated with the guaranteed argument itself. Consider the
following example:
```
bb0:
%1 = begin_borrow %foo : $Foo // (1)
%2 = begin_borrow %foo2 : $Foo2 // (2)
cond_br ..., bb1, bb2
bb1:
br bb3(%1 : $Foo)
bb2:
br bb3(%2 : $Foo)
bb3(%3 : @guaranteed $Foo)
...
end_borrow %3 : $Foo // (3)
end_borrow %2 : $Foo // (4)
end_borrow %1 : $Foo // (5)
...
```
Notice how due to SSA, (1) and (2) must dominate (4) and (5) and thus must
dominate bb3, preventing the borrows from existing within bb1, bb2.
This dominance property is actively harmful to expressivity in SIL since it
means that guaranteed arguments can not be used to express (without contortion)
sil code patterns where an argument is jointly-dominated by a minimal set of
guaranteed incoming values. For instance, consider the following SIL example:
```
bb0:
cond_br ..., bb1, bb2
bb1:
%0 = load [copy] %globalAddr : $Foo
br bb3(%0 : $Foo)
bb2:
%1 = copy_value %guaranteedFunctionArg : $Foo
br bb3(%1 : $Foo):
bb3(%2 : @owned $Foo):
apply %useFoo(%2)
destroy_value %2 : $Foo
```
As a quick proof: Assume the previous rules for guaranteed arguments. Then to
promote the load [copy] -> load_borrow and the copy_value to a begin_borrow, we
would need to place an end_borrow in bb3. But neither bb1 or bb2 dominates bb3,
so we would violate SSA dominance rules.
To enable SIL to express this pattern, we introduce a third rule for terminator
in ossa that applies only to branch insts. All other branches that obeyed the
previous rules (cond_br), still follow the old rule. This is not on purpose, I
am just being incremental and changing things as I need to. Specifically,
guaranteed arguments whose incoming values are defined by branch instructions
now act as a move on guaranteed values. The intuition here is that these
arguments are acting as true phis in an SSA sense and thus are just new names
for the incoming values. This implies since it is just a new name (not a
semantic change) that the guaranteed incoming value's guaranteed scopes should
be fused into one scope. The natural way to model this is by treating branch
insts as consuming guaranteed values. This then lets us express the example
above without using copies as follows:
```
bb0:
cond_br ..., bb1, bb2
bb1:
%0 = load_borrow %globalAddr : $Foo
br bb3(%0 : $Foo) // consumes %0 and acts as %0's end_borrow.
bb2:
// We need to introduce a new begin_borrow here since function
// arguments are required to never be consumed.
%1 = begin_borrow %guaranteedFunctionArg : $Foo
br bb3(%1 : $Foo) // consumes %1 and acts as %1's end_borrow
// %2 continues the guaranteed scope of %0, %1. This time fused with one name.
bb3(%2 : @guaranteed $Foo):
apply %useFoo(%2)
// End the lifetime of %2 (which implicitly ends the lifetime of %0, %1).
end_borrow %2 : $Foo
...
```
The main complication for users is that now when attempting to discover the set
of implicit users on an owned or guaranteed value caused by their usage as an
argument of a borrow introducer like begin_borrow. For those who are unaware, a
begin_borrow places an implicit requirement on its parent value that the parent
value is alive for the entire part of the CFG where this begin_borrow is
live. Previously, one could just look for the end_borrows of the
begin_borrow. Now one must additionally look for consuming branch insts. This is
because the original value that is being borrowed from must be alive over the
entire web of guaranteed values. That is the entire web of guaranteed values act
as a liveness requirement on the begin_borrow's operand.
The way this is implemented is given a use that we are validating, if the use is
a BorrowScopeOperand (1), we see if the borrow scope operand consumes the given
guaranteed scope and forwards it into a borrow scope introducer. If so, we add
the list of consuming uses of the borrow scope introducer to the worklist to
visit and then iterate.
In order to avoid working with cycles, for now, the ownership verifier bans
liveness requiring uses that have cycles in them. This still allows us to have
loop carried guaranteed values.
(1) A BorrowScopeOperand is a concept that represents an operand to a SIL
instruction that begins a guaranteed scope of some sort. All BorrowScopeOperand
are thus at a minimum able to compute a compile time the static region in which
they implicitly use their operands. NOTE: We do not require the scope to be
represented as a SILValue in the same function.
We achieve some nice benefit by introducing this. Specifically:
1. We can optimize the pattern I mentioned above. This is a common pattern in
many frameworks that want to return a default object if a computation fails
(with the default object usually being some sort of global or static
var). This will let us optimize that case when the global is a let global.
2. The SSA Updater can now be used with guaranteed values without needing to
introduce extra copies. This will enable predictable mem opts to introduce
less copies and for semantic arc opts to optimize the remaining copies that
PMO exposes but does not insert itself.
rdar://56720519
Add ExecuteSILPipelineRequest which executes a
pipeline plan on a given SIL (and possibly IRGen)
module. This serves as a top-level request for
the SILOptimizer that we'll be able to hang
dependencies off.
This just provides a higher level of abstraction around determining if an
operand consumes its associated value needing to touch the ownership kind
map/etc.
I also refactored parts of the code base where it made sense to use this instead
of messing with the ownership kind map itself.
The `differentiability_witness_function` instruction looks up a
differentiability witness function (JVP, VJP, or transpose) for a referenced
function via SIL differentiability witnesses.
Add round-trip parsing/serialization and IRGen tests.
Notes:
- Differentiability witnesses for linear functions require more support.
`differentiability_witness_function [transpose]` instructions do not yet
have IRGen.
- Nothing currently generates `differentiability_witness_function` instructions.
The differentiation transform does, but it hasn't been upstreamed yet.
Resolves TF-1141.
The signature is:
(T, @inout @unowned(unsafe) Optional<T>) -> ()
The reason for the weird signature is that currently the Builtin infrastructure
does not handle results well.
The semantics of this builtin is that it enables one to store the first argument
into an unowned unsafe address without any reference counting operations. It
does this just by SILGening the relevant code. The optimizer chews through this
code well, so we get the expected behavior.
I also included a small proof of concept to validate that this builtin works as
expected.
SIL differentiability witnesses are a new top-level SIL construct mapping
an "original" SIL function and derivative configuration to derivative SIL
functions.
This patch adds `SILDifferentiabilityWitness` serialization/deserialization.
Resolves TF-1136.
SIL differentiability witnesses are a new top-level SIL construct mapping
"original" SIL functions to derivative SIL functions.
SIL differentiability witnesses have the following components:
- "Original" `SILFunction`.
- SIL linkage.
- Differentiability parameter indices (`IndexSubset`).
- Differentiability result indices (`IndexSubset`).
- Derivative `GenericSignature` representing differentiability generic
requirements (optional).
- JVP derivative `SILFunction` (optional).
- VJP derivative `SILFunction` (optional).
- "Is serialized?" bit.
This patch adds the `SILDifferentiabilityWitness` data structure, with
documentation, parsing, and printing.
Resolves TF-911.
Todos:
- TF-1136: upstream `SILDifferentiabilityWitness` serialization.
- TF-1137: upstream `SILDifferentiabilityWitness` verification.
- TF-1138: upstream `SILDifferentiabilityWitness` SILGen from
`@differentiable` and `@derivative` attributes.
- TF-20: robust mangling for `SILDifferentiabilityWitness` names.