When determining where the "latest opening instruction" is, consider not
just `OpenedArchetypeType`s but any `LocalArchetypeType` which includes
`PackArchetypeType`s.
When rewriting arguments, the index used is into the callee's argument
list. For full applies, that is identical to the index into the
instruction's argument list. For partial applies, it is not.
Previously, though, the index was used as if it were an index into the
instruction's argument list to get an argument ref. Here, use instead
the newly added convenience to get the argument ref using the index into
the callee's arguments.
Full apply instructions have the same number of arguments as the callee
has parameters. Partial apply instructions have some number less than
or equal to the number of callee parameters.
In opaque values mode, all arguments are passed to a partial_apply (just
like all other flavors of apply) directly. AddressLowering needs to
rewrite the operands whose convention is indirect as it does for other
applies.
A function which returns a value of opaque result type
func f() -> some P { S() }
has a lowered, substituted signature
@convention(thin) () -> @out @_opaqueReturnTypeOf("$s4main1fQryF", 0) opaque
featuring an _opaqueReturnTypeOf attr. The SILFunctionArgument for that
@out result, however, must be of the type that the opaque result
substitutes to in the expansion context of the function.
Previously, `end_borrow`s were rewritten last in order to be able to
find them when inserting `end_borrow`s on behalf of newly created
`load_borrow`s. Generalize this to rewriting all lifetime-ending users
last. This is necessary for the lifetime utilities used by `isLoadCopy`
to remain accurate when rewriting a `copy_value` previously determined
to be from a load-copy pair.
Projections may involve opened archetypes. They must be dominated by
the instructions which open those archetypes.
When determining the earliest storage point for a value and the point at
which to insert its projections, look for such obstructions in the
projection chain. The first one found is the earliest storage point.
The pass rewrites opaque values in reverse post order of their
definitions. When an opaque value is rewritten, both its definition and
its uses are rewritten. When a def or a use is rewritten, in order to
materialize an address with which to rewrite, projection instructions
are created. Previously, these projections were created at the site of
the def or use. Some of these projection instructions may be reused when
rewriting later opaque values.
As a result, it's possible to have two opaque values `A` and `B` (which
are rewritten in that order) such that rewriting a use of `A` which
occurs "after" the def (or a use) of `B` creates a projection `P` which
is then used by that "earlier" def (or use) of `B`:
```
A =
B = // relies on P
use B
use A // creates some projection P
```
When rewriting the def (or that use, respectively) of `B`, the
projection which was created for the use of `A` will be reused. And
previously, the projection would be created at the use of A. But that
projection instruction was "after" the place where it is used when
rewriting the def or use of `B`. That's invalid!
To address this, rather than creating projections at the instruction
being rewritten, instead create them "as early as possible". The
locations where they will be created are chosen so as to dominate all
uses.
Moved the assert that the value which opens an archetype exists before
the call to get its defining instruction which would be an NPE if the
value were in fact null.
Promoted the functionality from createStackAllocation to a named member
AddressLoweringState::getLatestOpeningInst so that it can be employed
elsewhere.
When determining whether a copy_value is part of a copy->store pair, if
the value being copied is guaranteed, it is checked whether the store is
within the lifetime of all its guaranteed roots. If one of those
guaranteed roots is a function argument, the store is certainly within
the lifetime, so exit early.
An extract may have users that have escaping operand ownership.
Consequently, we can't rely on findInnerTransitiveGuaranteedUses as used
by emitEndBorrows.
A destructure may have users that have escaping operand ownership.
Consequently, we can't rely on findInnerTransitiveGuaranteedUses as used
by emitEndBorrows.
When a `load_borrow` is created on behalf of a `begin_apply`, it's
possible that it may have escaping users. When it does,
`findInnerTransitiveGuaranteedUses` returns `false` and the uses it
finds are incomplete. As a result the boundary computed in
`emitEndBorrows` will be incomplete.
In preparation for supporting move_value which should be treated exactly
the same way as begin_borrow, factored visitBeginBorrow through the new
visitLifetimeIntroducer.
- 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.