Adjust DI to treat init accessor properties that have only 'accesses'
or no restrictions as if they are stored properties, this means that
if such property doesn't have a default initializer users would have
to reference it explicitly in their custom initializers.
We also need to suppress default init synthesis for such cases which
would be done in a followup commit.
Resolves: rdar://113401979
DI marks all of of the previously initialized properties and Raw SIL
lowering emits `destroy_addr` before calling init accessor for such
properties to destroy previously set value.
- Adds a missing check to `collectClassSelfUses` to find assign_or_init instructions;
- RawSIL lowering should start emitting access around synthesized member references.
- Record all properties listed in `accesses` as loads;
- Record all properties listed in `initialized` as init-or-assign;
- Detect situations when double-init could happen i.e. if one of
the properties listed in `initializes` attribute is explicitly
initialized before init accessor call.
- 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.
Adding a type wrapper attribute on a protocol does two things:
- Synthesizes `associatedtype $Storage` declaration with `internal` access
- Synthesizes `var $storage: <#Wrapper#><Self, Self.$Storage>` value requirement
`_storage` is used primary to support member-by-member initialization
of `$Storage`, so we need to refer to `$Storage` to determine whether
a partial field of `_storage` is `let` or not.
`_storage` is special because it emits assignments to compiler
synthesized stored property `$storage`, without that `self`
cannot be fully initialized.
This is staging for type wrappers that need to inject
`self.$storage = ...` call to user-defined initializers,
to be able to do that logic needs to be able to find
`self` that is being constructed.
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.
This is just the very beginning... I still need to implement more parts of
SILGen for this. But all great things start small. I am going to iterate on top
of this and just wanted to get some initial parts of the work in as I go.
The resignID call within the initializer moved into DI, because an assignment to
the actorSystem, and thus initialization of the id, is no longer guaranteed to happen.
Thus, while we previously could model the resignation as a clean-up emitted on all
unwind paths in an initializer, now it's conditional, based on whether the id was
initialized. This is exactly what DI is designed to do, so we inject the resignation
call just before we call the identity's deinit.
This attribute is designed for let-bound variables whose initializing
assignment is synthesized by the compiler. This assignment is
expected to happen at some point before DefiniteInitialization has
run, which is the pass that verifies whether the compiler truly
initialized the variable.
I generally expect that this will never be a user-facing feature, and
that the synthesized assignment happens in SILGen.
Flow-isolation is a diagnostic SIL pass that finds
unsafe accesses to properties in initializers and
deinitializers that cannot gain isolation to otherwise
protect those accesses from concurrent modifications.
See SE-327 for more details about how and why it exists.
This commit includes changes and features like:
- The removal of the escaping-use restriction
- Flow-isolation that works properly with `defer` statements
- Flow-isolation with an emphasis on helpful diagnostics.
It also includes known issues like:
- Local / nonescaping functions are not analyzed by
flow-isolation, despite it being technically possible.
The main challenge in supporting it efficiently is that
such functions do not have a single exit-point, like
a `defer`. In particular, arbitrary functions can throw
so there are points where nonisolation should _not_ flow
out of the function at a call-site in the initializer, etc.
- The implementation of the flow-isolation pass is not
particularly memory efficient; it relies on BitDataflow
even though the particular flow problem is simple.
So, a more efficient implementation would be specialized for
this particular problem, etc.
There are also some changes to the Swift language itself: defer
will respect its context when deciding its property access kind.
Previously, a defer in an initializer would always access a stored
property through its accessor methods, instead of doing so directly
like its enclosing function might. This inconsistency is unfortunate,
so for Swift 6+ we make this consistent. For Swift 5, only a defer
in a function that is a member of the following kinds of types
will gain this consistency:
- an actor type
- any nominal type that is actor-isolated, excluding UnsafeGlobalActor.
These types are still rather new, so there is much less of a chance of
breaking expected behaviors around defer. In particular, the danger is
that users are relying on the behavior of defer triggering a property
observer within an init or deinit, when it would not be triggering it
without the defer.
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.
I initially left-out these restrictions, while
the design of distributed actor inits was in flux.
Now that the dust is somewhat settled, we need the same
restrictions on `self` that regular actors have, because
we do not want the local instance to have races in the init.
Because `self` can be used unrestricted after it is
fully-initialized in an async actor init, we perform
a hop_to_executor(self) immediately after `self`
is fully-initialized on all paths within the
initializer.
If we did not, then code could race with the
initializer if it, e.g., spawns a task from
the initializer and mutates actor state that
the initializer then reads. By hopping to the
executor, we prevent that task from running
until _after_ the initializer completes.
Support for implicit closures was already added in c452e4cc13:
init() {
bool_member1 = false
bool_member2 = false || bool_member1 // implicit closure
}
But this didn't work for initializers of derived classes.
rdar://66420045
A uniqueness rule for 'self' is enforced throughout
the following types of actor initializers:
1. synchronous
2. global-actor isolated
This means that 'self' cannot be used for anything,
even after 'self' is fully initialized, other than
for access to stored properties. Method calls are
considered escaping uses of 'self', since it is passed
implicitly to the method of computed property when invoked.
Also, an actor's convenience init now treats self as
nonisolated.
This provides a way to perform follow-up work after
self is fully initialized, when creating the
actor from a synchronous context.
Resolves rdar://78790369
Boxes tend to have a small number of uses, so frequently finding the
unique projection isn't too bad. Non-boxes, however, can have a large
number of uses: for example, a class instance has expected uses
proportionate to the number of stored properties. So if we do a linear
scan of the uses on a non-box instruction, we'll scale quadratically.
Fixes SR-14532.
Correctly handle implicit closures in initializers, e.g. with boolean operators:
init() {
bool_member1 = false
bool_member2 = false || bool_member1 // implicit closure
}
The implicit closure ('bool_member1' at the RHS of the || operator) captures the whole self, but only uses 'bool_member1'.
If the whole captured 'self' is considered as use, we would get a "'self.bool_member2' not initialized" error at the partial_apply.
Therefore look into the body of the closure and only add the actually used members.
rdar://66420045
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.
Previously we would just forward the cleanup and create a normal "destroy"
cleanup resulting in early deallocation and use after frees along error paths.
As part of this I also had to tweak how DI recognizes self init writebacks to
not use SILLocations. This is approach is more robust (since we aren't relying
on SourceLocs/have verifiers to make sure we don't violate SIL) and also avoids
issues from the write back store not necessarily have the same SILLocation.
<rdar://problem/59830255>