Previously, region-based isolation was not diagnosing cases where a non-Sendable
value projected from a Sendable base that was MainActor isolated. For example:
```swift
@MainActor
struct MainActorBox {
var value: NonSendableKlass?
mutating func take() -> sending NonSendableKlass {
if let value {
self.value = nil
return value // Should warn: main actor-isolated value cannot be 'sending'
} else {
preconditionFailure("Consumed twice")
}
}
}
```
This was caused by two issues:
1. A logic bug in `AddressBaseComputingVisitor::visitAll` where we overwrote an
already-found Sendable value when recursing. The visitor should only record
the first Sendable value found, not subsequent ones. This caused us to
incorrectly return the `@MainActor` `MainActorBox` as the base instead of
emitting a require for the projected value.
2. struct_element_addr being transparent unconditionally causing us to not even
emit a require at all.
This commit has two parts:
1. I fixed the first two issues by fixing the logic bug and by making
struct_element_addr only transparent if its operand and result are
non-Sendable. I added logic that we have used in other such cases to handle
non-Sendable operand/Sendable result as well as Sendable operand and
non-Sendable result. I then added the same support for tuple_element_addr and
unchecked_take_enum_data_addr since they suffered from a similar problem.
2. Adding the logic to handle variants where the operand was non-Sendable and
the result was Sendable, caused a bunch of tests to break so I had to fix
that.
To fix the broken test cases, I had to make the compiler smarter about how it
was inserting this require. To do this:
1. I checked if the access base of the projection was actually immutable or if
we are an alloc_stack that there was a prefix path in the projection path of
immutable projections that end in the alloc_stack. In such a case, we know
that we do not need to require that the operand is available in the current
isolation domain since we will never write to that piece of memory and once
we have loaded the result from memory, we know that the value is Sendable so
nothing bad can happen.
2. If the access base of the projection was mutable, I used the same logic that
we used for alloc_box that were non-Sendable that stored a Sendable thing by
changing operand to be a `require [mutable_base_of_sending]` on the result of
the projection instead of requiring the projection's operand. This ensures
that we handled important flow sensitive cases.
rdar://169626088
IRGen crashes on @isolated(any) functions if the Actor protocol isn't
available. It's safer to reject in the type checker in these cases.
Fixes rdar://165090740.
When declaration gets inferred as `@MainActor` that needs to be
reflected in its attributes as well because that's the only reliable
way propagate isolation across modules.
Resolves: rdar://164077275
For stores to unaliased, non-aggregate-projected destinations, switch from
translateSILAssignDirect(destValue, src) to translateSILAssignIndirect(dest, src).
This passes the Operand* rather than just the SILValue, enabling proper tracking
of the destination address in region analysis.
First in a series migrating instruction handlers to indirect assignment.
NOTE: I originally thought that pack_element_set would also have this property,
but alloc_pack today is always assumed to be to be MayAlias and thus we emit a
merge. I think this is fine since one can never actually assign over a pack
variable like one can other things (that is I think they are generally just used
for marshalling and the like). If I am wrong, I can just fix it later.
rdar://156024613
https://github.com/swiftlang/swift/issues/83121
This is important for things like instance methods of actors in
particular because otherwise it won't be possible to compute a
correct isolation for the thunk.
This fix enables fully unapplied references to actor-isolated
instance methods and other functions with isolated parameters.
Resolves: rdar://148395744
An internal type is inferred to be Sendable when it's instance storage
as Sendable. This complicates conformance lookup because all of the
additional checking has to happen there. `lookupConformance` already
has a check that breaks cycles with implicit known protocols, this
was defeated by tuple types at the moment because they are split later
by recursively calling `lookupConformance`.
One idea was to split storage checking out of lookup and do that as
part of availability checking but that won't help when check is requested
from SILGen or type is a in different file from its use.
As a narrow fix the change checks individual elements of tuple types
in `diagnoseNonSendableTypes` that helps to detect cycles early.
Resolves: https://github.com/swiftlang/swift/issues/62060
Resolves: https://github.com/swiftlang/swift/issues/82628
Resolves: rdar://168238300
Previously only the use a global actor different from
`MainActor` was diagnosed, now any attempt to use an
explicit isolation attribute that differs from `@MainActor`
would be as well.
Resolves: https://github.com/swiftlang/swift/issues/83790
Resolves: rdar://158608620
These two new invariants eliminate corner cases which caused bugs if optimization didn't handle them.
Also, it will significantly simplify lifetime completion.
The implementation basically consists of these changes:
* add a flag in SILFunction which tells optimization if they need to take care of infinite loops
* add a utility to break infinite loops
* let all optimizations remove unreachable blocks and break infinite loops if necessary
* add verification to check the new SIL invariants
The new `breakIfniniteLoops` utility breaks infinite loops in the control flow by inserting an "artificial" loop exit to a new dead-end block with an `unreachable`.
It inserts a `cond_br` with a `builtin "infinite_loop_true_condition"`:
```
bb0:
br bb1
bb1:
br bb1 // back-end branch
```
->
```
bb0:
br bb1
bb1:
%1 = builtin "infinite_loop_true_condition"() // always true, but the compiler doesn't know
cond_br %1, bb2, bb3
bb2: // new back-end block
br bb1
bb3: // new dead-end block
unreachable
```
This is safe because:
1. The box can never be written to after initialization due to
definite initialization, and that initialization must occur before the box
escapes to another isolation domain.
2. We restrict this to immutable boxes containing Sendable types since otherwise
we could load a non-Sendable value from the box and produce a fresh value that
could escape into multiple isolation domains, potentially allowing unsafe
concurrent writes.
The important use case that this fixes are as follows:
1. Sendable noncopyable nominal types. Since the type is noncopyable, we must
store it into a let box it to capture it in an escaping closure causing us to
previously error:
```swift
func testNoncopyableSendableStructWithEscapingMainActorAsync() {
let x = NoncopyableStructSendable()
let _ = {
escapingAsyncUse { @MainActor in
useValue(x) // Error!
}
}
}
```
2. Simple capture lists of Sendable noncopyable nominal types. When we put the
value into the capture list, we create a new let binding for the value which
would be a let box since the underlying type is noncopyable:
```swift
func testNoncopyableSendableStructWithEscapingMainActorAsyncNormalCapture() {
let x = NoncopyableStructSendable()
let _ = { [x] in
escapingAsyncUse { @MainActor in
useValue(x)
}
}
}
```
Originally developed as part of rdar://166081666, though it turned out to be
independent of that fix.
This is a follow-up to https://github.com/swiftlang/swift/pull/86557
When mangling a `@preconcurrency` declaration, `dropGlobalActor`
is set to `true`, and the original condition behind that used to
remove isolation from function types regardless of whether the
function type was actually global-actor isolated or not. We need
to maintain this behavior for mangling or risk breaking ABI for
the existing declarations that had isolation like `@isolated(any)`.
I have a patch out of tree that fixes these tests so that the error is not
emitted. But to make the overall change cherry-pickable, I decided to leave out
that change. Once the main change lands, I will commit the other change and
remove these diagnostics from the test file.
The pass works by walking functions in the modules looking for mutable alloc_box
that contains a weak variable and is knowably a capture. In such a case, the
pass checks all uses of the alloc_box interprocedurally including through
closures and if provably immutable marks the box and all closure parameters as
being inferred immutable.
This change also then subsequently changes SILIsolationInfo to make it so that
such boxes are considered Sendable in a conservative manner that pattern matches
the weak reference code emission pretty closely.
The reason why I am doing this is that issue #82427 correctly tightened region
isolation checking to catch unsafe concurrent access to mutable shared
state. However, this introduced a regression for a common Swift pattern:
capturing `self` weakly in escaping closures.
The problem occurs because:
1. Weak captures are stored in heap-allocated boxes.
2. By default, these boxes are **mutable** (`var`) even if never written to after initialization
3. Mutable boxes are non-Sendable (they could be unsafely mutated from multiple threads)
4. Region isolation now correctly errors when sending non-Sendable values across isolation boundaries
This breaks code like:
```swift
@MainActor class C {
func test() {
timer { [weak self] in // Captures self in a mutable box
Task { @MainActor in
self?.update() // ERROR: sending mutable box risks data races
}
}
}
}
```
Note how even though `self` is Sendable since it is MainActor-isolated, the *box
containing* the weak reference is not Sendable because it is mutable.
With the change in this commit, we now recognize that the box can safely be
treated as Sendable since we would never write to it.
rdar://166081666
These conversions are artificial conversions inserted by Sema since isolation is
not represented on interface types. We previously looked though:
1. A single conversion that added nonisolated(nonsending).
2. A two level conversion where the first conversion added Sendable and the
second added nonisolated(nonsending).
In this case, Sema is generating a single conversion that combines the Sendable
and isolation conversion.
To be consistent, I also updated the code that involved conversion from
execution caller to global actor as well in case we hit the same case there.
https://github.com/swiftlang/swift/issues/83812
rdar://158685169
Replace the use of rethrows and #isolation in
withTaskCancellationHandler with typed throws and
nonisolated(nonsending), respectively.
Fixes rdar://146901428.
We should expose the demangle functionality; It's been widely used by
calling into internal _swift_demangle and instead we should offer a real
API. It's also already used in the Runtime module already when forming
backtraces.
[Previous
discussions](https://forums.swift.org/t/demangle-function/25416/15)
happened between 2019 and 2024, and just never materialized in a
complete implementation and proposal.
Right now, even more tools are in need of this API, as we are building
continious profiling solutions etc, so it is a good time to revisit this
topic.
This PR is roughly based off @Azoy's
https://github.com/swiftlang/swift/pull/25314/files#diff-fd379a721cc9a1c9ef6486eae713e945da842b42170d4d069029a57334371eba
from 2019, however it brings it over to the new Runtime module which is
a great place to put this functionality - even Backtrace had to recently
reinvent calling the demangling infra in this module.
Pending SE review, [proposal
here](https://github.com/swiftlang/swift-evolution/pull/2989).
cc @azoy @al45tair
---------
Co-authored-by: Alastair Houghton <alastair@alastairs-place.net>