- SILPackType carries whether the elements are stored directly
in the pack, which we're not currently using in the lowering,
but it's probably something we'll want in the final ABI.
Having this also makes it clear that we're doing the right
thing with substitution and element lowering. I also toyed
with making this a scalar type, which made it necessary in
various places, although eventually I pulled back to the
design where we always use packs as addresses.
- Pack boundaries are a core ABI concept, so the lowering has
to wrap parameter pack expansions up as packs. There are huge
unimplemented holes here where the abstraction pattern will
need to tell us how many elements to gather into the pack,
but a naive approach is good enough to get things off the
ground.
- Pack conventions are related to the existing parameter and
result conventions, but they're different on enough grounds
that they deserve to be separated.
When a copy_value's only use is a store, in some cases, as an
optimization, creating a copy_addr can be skipped. Whether a value is
such a copy_value is determined by the isStoreCopy predicate.
If the operand of the copy_value is a guaranteed value and any of its
guaranteed roots have live ranges which the store is outside of, skip
this optimization and create the copy_addr.
These APIs are essential for complete OSSA liveness analysis. The
existing ad-hoc OSSA logic always misses some of the cases handled by
these new utilities. We need to start replacing that ad-hoc logic with
new utilities built on top of these APIs to define away potential
latent bugs.
Add FIXMEs to the inverse API: visitAdjacentBorrowsOfPhi. It should
probably be redesigned in terms of these new APIs.
Previously, the type for the storage into which a loadable value which
was yielded via an indirect convention was obtained from the
SILFunctionConventions and the SILYieldIfo but not mapped into the
context of the current function. Here, that's fixed.
I've also fixed this so that it should work on instructions that
define multiple values. Someday we'll change all the open_existential
instructions to produce different values for the type dependency and
the value result; today is not that day, though.
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.
Thanks to the invariants of store_borrow, rewriting a store_borrow is a
simple matter of replacing its (non end_borrow) uses with uses of the
underlying address-only value whose use was stored.
When finding the PrunedLiveness on whose boundary end_borrows are to be
inserted, allow lifetime ending uses if they are end_borrows. Such
end_borrows appear validly when an inner use is a nested begin_borrow.
The begin_apply instruction introduces storage which is only valid until
the coroutine is ended via end_apply/abort_apply. Previously,
AddressLowering assumed the memory was valid "as long as it needed to
be": either as an argument to the function being lowered (so valid for
the whole lifetime) or as a new alloc_stack (whose lifetime is
determined by AddressLowering itself). The result was that the storage
made available by the begin_apply was used after the
end_apply/abort_apply when there were uses of [a copy of] [a projection
of] the yield after the end_apply/abort_apply.
Here, the behavior is fixed. Now, the storage is used "as soon as
possible". There are two cases:
(1) If an indirect value's ownership is transferred into the
caller (i.e. its convention is `@in` or `@in_constant`), the value is
`copy_addr [take]`n into caller-local storage when lowering the
begin_apply.
(2) If an indirect value's ownership is only lent to the caller (i.e.
its convention is `@in_guaranteed`), no equivalent operation could be
done: there's no `copy_addr [borrow]`; on the other hand, there's no
need to do it--uses of the value must have occurred within the
coroutine's range. Instead, it's necessary to disable the store-copy
optimization in this case: storage must be allocated for a copy of [a
projection of] the yielded value.
It is valid for owned values not to be destroyed on paths terminating in
unreachable. Similarly, it is valid for storage not to be deallocated
on paths terminating in unreachable. However, it is _not_ valid for
storage to be deallocated before being deinitialized, even in paths
terminating in unreachable. Consequently, when AddressLowering,
dealloc_stacks must not be created in dead-end blocks: because the input
OSSA may not destroy an owned value, the output may not deinitialize
owned storage; so a dealloc_stack in an unreachable block could dealloc
storage which was not deinitialized.
During storage allocation, create all alloc_stacks at the front of the
function entry block, except those for opened existential types. Do not
create any dealloc_stacks, even for opened existential types.
Then, after function rewriting, position the alloc_stacks according to
uses, except those for opened existential types. This means allocating
in the block that's the least common ancestor of all uses. At this
point, create dealloc_stacks on the dominance frontier of the
alloc_stacks, even for allocations for opened existential types.
The behavior for opened existential types diverges from all others in
order to maintain SIL validity (as much as possible) while the pass
runs. It would be invalid to create the alloc_stacks for opened
existential types at the front of the entry block because the type which
is opened is not yet available, but that type's opening must dominate
the corresponding alloc_stack.
On Windows CI builds, we have observed a failure to build the Swift
Standard Library after 10/27 (first noticed on 11/11 snapshot due to
other failures). The failure is an invalid `isa` cast where the
instance is a `nullptr`. The SILPipeline suggests that the
SILOptimizer might be the one triggering the incorrect use of `isa`.
The only instance of `isa` introduced in that range in the
SILOptimizer/Mandatory region for that duration is this particular
instance. Tighten the assertion to ensure that `oper->getUser()`
returns a non-`nullptr` value.
Thanks for @gwynne for helping narrow down the search area.
`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
When loading an argument, first check whether its type is trivial. If
so, produce a `load [trivial]`. Only then produce `load [take]`s or
`load_borrow`s. Fixes an issue where values passed @in_guaranteed were
`load [trivial]`'d and then `end_borrow`'d.
Previously, there were three places where unconditional_checked_cast
instructions were rewritten as unconditional_checked_cast_addr
instructions:
- from address-only
- to address-only
- neither
Here, all three are made to factor through the new
rewriteUnconditionalCheckedCast function.
When address lowering rewrites a return to use an indirect convention,
it creates a ParamDecl, using the function's decl context. Some
functions, such as default argument getters, don't have such contexts,
though. In such cases, fall back to module as decl context.
Previously, when lowering destructure_tuple and destructure_struct
instructions, either a load [trivial] or load [take] was always created
for each loadable field. When the operand to the destructure
instruction was @owned, this was the correct behavior; but when the
operand was @guaranteed, it was not. It would result in SIL like
```
(..., %elt_addr, ...) = destructure_ %addr
%value = load [take] %elt_addr
destroy_addr %addr
```
where (1) %elt_addr was destroyed twice (once by the load [take] and
once by the destroy_addr of the aggregate and (2) the loaded value was
leaked.
Here, this is fixed by creating load_borrows for this case.
When lowering an unconditional_checked_cast from an address-only value
to a loadable value, the value with which to replace the original is a
load of the destination address of the unconditional_checked_cast_addr
to which the instruction was lowered. In the case of non-trivial
values, that load must be a take.
When rewriting a store, insert the copy_addr after it rather than before
it.
Previously, after the copy_addr is created,
```
alloc_stack %target_addr $Ty, var, name "something"
...
copy_addr [take] %source_addr to [init] %target_addr
store %instance to [init] %addr
```
when deleting the store, debug info salvaging may result in a
debug_value instruction being created before the store,
```
alloc_stack %target_addr $Ty, var, name "something"
...
copy_addr [take] %source_addr to [init] %target_addr
debug_value %source_addr : $*Ty, var, name "something"
store %instance to [init] %addr
```
using the %source_addr. If the created copy_addr is a [take], this
results in the debug_value being a use-after-consume in the final SIL:
```
alloc_stack %target_addr $Ty, var, name "something"
...
copy_addr [take] %source_addr to [init] %target_addr
debug_value %source_addr : $*Ty, var, name "something"
```
Instead, now, the copy_addr is created after:
```
alloc_stack %target_addr $Ty, var, name "something"
...
store %instance to [init] %addr
copy_addr [take] %source_addr to [init] %target_addr
```
So when the debug_value instruction is created before the store during
debug info salvaging
```
alloc_stack %target_addr $Ty, var, name "something"
...
debug_value %source_addr : $*Ty, var, name "something"
store %instance to [init] %addr
copy_addr [take] %source_addr to [init] %target_addr
```
it is also before the newly added copy_addr. The result is that when
the store is deleted, we have valid SIL:
```
alloc_stack %target_addr $Ty, var, name "something"
...
debug_value %source_addr : $*Ty, var, name "something"
copy_addr [take] %source_addr to [init] %target_addr
When a coroutine yields a value via an indirect convention, an address
must be yielded. For address-only types, AddressLowering was already
handling this. Here, support is added for all loadable, indirect
operands.
While visiting the function, record not only applies which have indirect
formal _results_ but also applies which have indirect formal _yields_.
When rewriting indirect applies, check the apply site kind and rewrite
according to it.
Taught ApplyRewriter to handle rewriting indirect loadable yields.
Made the function available at the top-level so that it can be called
outside of the UseRewriter, specifically, so that it can be called when
rewriting begin_apply yields.
All checked casts are emitted as checked_cast_br
instructions. More than just the instructions which produce or consume
an opaque value must be rewritten as checked_cast_addr_br
instructions. In particular, all those instructions for which
canIRGenUseScalarCheckedCastInstructions returns false must be rewritten
as checked_cast_addr_br instructions.
Note the instructions to rewrite like that while visiting values and
then rewrite them near the end of rewriting.