For cases where init accessor field has a nonmutating set we need
ignore copies and borrows associated with load of "self" because
they are going to be erased together with the setter application
by DI.
It lowers let property accesses of classes.
Lowering consists of two tasks:
* In class initializers, insert `end_init_let_ref` instructions at places where all let-fields are initialized.
This strictly separates the life-range of the class into a region where let fields are still written during
initialization and a region where let fields are truly immutable.
* Add the `[immutable]` flag to all `ref_element_addr` instructions (for let-fields) which are in the "immutable"
region. This includes the region after an inserted `end_init_let_ref` in an class initializer, but also all
let-field accesses in other functions than the initializer and the destructor.
This pass should run after DefiniteInitialization but before RawSILInstLowering (because it relies on `mark_uninitialized` still present in the class initializer).
Note that it's not mandatory to run this pass. If it doesn't run, SIL is still correct.
Simplified example (after lowering):
bb0(%0 : @owned C): // = self of the class initializer
%1 = mark_uninitialized %0
%2 = ref_element_addr %1, #C.l // a let-field
store %init_value to %2
%3 = end_init_let_ref %1 // inserted by lowering
%4 = ref_element_addr [immutable] %3, #C.l // set to immutable by lowering
%5 = load %4
I was originally hoping to reuse mark_must_check for multiple types of checkers.
In practice, this is not what happened... so giving it a name specifically to do
with non copyable types makes more sense and makes the code clearer.
Just a pure rename.
`nonmutating set` needs to load of "self" but that load could be
ignored safely because it's only viable when self is determined
to be fully initialized by DI. The same applies to `assign_by_wrapper`.
This is a futile attempt to discourage future use of getType() by
giving it a "scary" name.
We want people to use getInterfaceType() like with the other decl kinds.
Reformatting everything now that we have `llvm` namespaces. I've
separated this from the main commit to help manage merge-conflicts and
for making it a bit easier to read the mega-patch.
This is phase-1 of switching from llvm::Optional to std::optional in the
next rebranch. llvm::Optional was removed from upstream LLVM, so we need
to migrate off rather soon. On Darwin, std::optional, and llvm::Optional
have the same layout, so we don't need to be as concerned about ABI
beyond the name mangling. `llvm::Optional` is only returned from one
function in
```
getStandardTypeSubst(StringRef TypeName,
bool allowConcurrencyManglings);
```
It's the return value, so it should not impact the mangling of the
function, and the layout is the same as `std::optional`, so it should be
mostly okay. This function doesn't appear to have users, and the ABI was
already broken 2 years ago for concurrency and no one seemed to notice
so this should be "okay".
I'm doing the migration incrementally so that folks working on main can
cherry-pick back to the release/5.9 branch. Once 5.9 is done and locked
away, then we can go through and finish the replacement. Since `None`
and `Optional` show up in contexts where they are not `llvm::None` and
`llvm::Optional`, I'm preparing the work now by going through and
removing the namespace unwrapping and making the `llvm` namespace
explicit. This should make it fairly mechanical to go through and
replace llvm::Optional with std::optional, and llvm::None with
std::nullopt. It's also a change that can be brought onto the
release/5.9 with minimal impact. This should be an NFC change.
In case of `var` initializations, SILGen creates a dynamic begin/end_access pair around the initialization store.
If it's an initialization (and not a re-assign) it's guanranteed that it's an exlusive access and we can convert the access to an `[init] [static]` access.
https://github.com/apple/swift/issues/66496
This closes a hole where an early return could leave some
properties from `initializes(...)` list uninitialized.
For example:
```swift
init(initialValue) initializes(_a, _b) {
_a = initialValue.0
if _a > 0 {
return
}
_b = initialValue.1
}
```
Here `_b` is not initialized when `_a > 0`.
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.
- 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.
Previously we would crash.
Since we are relatively late in 5.9, my solution is to just turn off the move
checker on functions whenever DI would emit an error. If we were earlier in the
development cycle, then I would make the error be a per allocation change.
rdar://108993297
When an accessor macro adds a non-observing accessor to a property, it
subsumes the initializer. We had previously modeled this as removing
the initializer, but doing so means that the initializer could not be
used for type inference and was lost in the AST.
Explicitly mark the initializer as "subsumed" here, and be more
careful when querying the initializer to distinguish between "the
initializer that was written" and "the initializer that will execute"
in more places. This distinction already existed at the
pattern-binding level, but not at the variable-declaration level.
This is the proper fix for the circular reference issue described in
rdar://108565923 (test case in the prior commit).
Code can only locally interact with a mutable memory location within a
formal access, and is only responsible for maintaining its invariants
during that access, so the move-only address checker does not need to,
and should not, observe operations that occur outside of the access
marked with the `mark_must_check` instruction. And for immutable
memory locations, although there are no explicit formal accesses, that's
because every access must be read-only, so although individual
accesses are not delimited, they are all compatible as far as
move-only checking is concerned. So we can back out the changes to SILGen
to re-project a memory location from its origin on every access, a
change which breaks invariants assumed by other SIL passes.
This is used to teach the checker that the thing being checked is supposed to be
uninitialized at the mark_must_check point so that we don't put a destroy_addr
there.
The way this is implemented is that we always initially add
assignable_but_not_consumable but in DI once we discover that the assign we are
guarding is an init, we convert the assignable to its initable variant.
rdar://106525988
- 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.
The request is updated to return attribute, wrapper declaration,
the declaration its attached to and whether or not it has been
inferred (opposite to being declared directly).
DefiniteInitialization used a running SILBuilder to insert all instructions and
didn't consistently set the scope to a meaningful value. This patch replaces all
uses of the running Builder with ad-hoc instances of SILBuilderWithScope which
should capture the intention of the code better.
rdar://102296138
`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
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
If `_storage` checking fails, let's stop because otherwise
DI would end up emitting extraneous errors about uninitialized
`self.$_storage` which are consequence of `_storage` failures.
Inject a call to $Storage.init(...) which is then used to initialize
type wrapper property `$storage` via `self.$storage = <TypeWrapper>(memberwise: <storage>)`
call at each point where `_storage` becomes fully initialized.
`_storage` is special because it emits assignments to compiler
synthesized stored property `$storage`, without that `self`
cannot be fully initialized.
While trying to reuse the liveness-points analysis originally in DI for
injecting actor hops for more general purposes, Pavel and I discovered
that the point at which we are injecting the hops might not have
fully-computed the liveness information.
That appears to be the case because we were computing the fully-initialized
points before having processed destroy/releases of TheMemory. While this
most likely had no influence on the actor hop injection, it does affect
what the outgoing AvailabilitySet contains for a block. In particular, for
this example:
```swift
struct X {
init(cond: Bool) {
var _storage: (name: String, age: Int)
_storage.name = ""
if cond {
_storage.age = 30
} else {
_storage.age = 40
}
}
}
```
But because we are determine the full initialization points before processing
the destroy, the liveness analysis doesn't iterate to correctly determine the
out-availability of block 1 and 3 (corresponding to the then and else blocks
in the example above). Here's the debug output showing that issue:
```
*** Definite Init looking at: %5 = mark_uninitialized [var] %4 : $*(name: String, age: Int) // users: %37, %12, %22, %32
Get liveness 0, #1 at assign %11 to %13 : $*String // id: %14
Get liveness 1, #1 at assign %21 to %23 : $*Int // id: %24
Get liveness for block 1
Iteration 0
Result: (yn)
Get liveness 1, #1 at assign %31 to %33 : $*Int // id: %34
Get liveness for block 3
add block 2 to worklist
Iteration 0
Block 2 out: (yn)
Iteration 1
Block 2 out: (yn)
Result: (yn)
full-init-finder: rejecting bb0 b/c non-Yes OUT avail
full-init-finder: rejecting bb1 b/c non-Yes OUT avail
full-init-finder: rejecting bb2 b/c no non-load uses.
full-init-finder: rejecting bb3 b/c non-Yes OUT avail
full-init-finder: rejecting bb4 b/c no non-load uses.
Get liveness 0, #2 at destroy_addr %5 : $*(name: String, age: Int) // id: %37
Get liveness for block 4
add block 3 to worklist
add block 1 to worklist
Iteration 0
Block 1 out: (yy)
Block 3 out: (yy)
Iteration 1
Block 1 out: (yy)
Block 3 out: (yy)
Result: (yy)
```
So, this patch basically just sinks the computation so it happens after, so that
we force the incremental liveness analysis to also consider the liveness at the
point of the destroy, but before having done any other transformations or modifications
to the CFG to handle a destroy of something partially initialized.
Logic that used to determine where `self` is completely
initialized to inject actor hops has been generalized
and is now available via `findFullInitializationPoints` method.
Since the only user of `@_compilerInitialized` is distributed
actors, I decided to make DI's diagnostics saying that such a
var was not initialized a second-tier note. If there is some other
var associated with the same Memory that is _not_ compiler initialized
AND is also _not_ initialized, then we suppress the suggestion to initialize
the @_compilerInitialized thing, because something else is problematic. Only
when there are @_compilerInitialized things that are still not initialized,
do we emit a note talking about that.