Instance properties of non-sendable types cannot safely be
accessed within deinitializers. Make sure we respect `@preconcurrency`
when diagnosing these.
Just trying to slice off a larger change where I change these tests to actually
use 'sending'. This is nice to do now since it is algebraic to do.
rdar://128216574
To avoid breaking early adopters of this feature, accept attempts to `return`
a `let` binding in a noncopyable `switch` when it would be treated as a
borrow normally, with a warning that this behavior will change soon.
rdar://126775241
Specifically, I am transforming it from "may cause a race" -> "may cause a data
race". Adding data is a small thing, but it adds a bunch of nice clarity.
This PR implements first set of changes required to support autodiff for coroutines. It mostly targeted to `_modify` accessors in standard library (and beyond), but overall implementation is quite generic.
There are some specifics of implementation and known limitations:
- Only `@yield_once` coroutines are naturally supported
- VJP is a coroutine itself: it yields the results *and* returns a pullback closure as a normal return. This allows us to capture values produced in resume part of a coroutine (this is required for defers and other cleanups / commits)
- Pullback is a coroutine, we assume that coroutine cannot abort and therefore we execute the original coroutine in reverse from return via yield and then back to the entry
- It seems there is no semantically sane way to support `_read` coroutines (as we will need to "accept" adjoints via yields), therefore only coroutines with inout yields are supported (`_modify` accessors). Pullbacks of such coroutines take adjoint buffer as input argument, yield this buffer (to accumulate adjoint values in the caller) and finally return the adjoints indirectly.
- Coroutines (as opposed to normal functions) are not first-class values: there is no AST type for them, one cannot e.g. store them into tuples, etc. So, everywhere where AST type is required, we have to hack around.
- As there is no AST type for coroutines, there is no way one could register custom derivative for coroutines. So far only compiler-produced derivatives are supported
- There are lots of common things wrt normal function apply's, but still there are subtle but important differences. I tried to organize the code to enable code reuse, still it was not always possible, so some code duplication could be seen
- The order of how pullback closures are produced in VJP is a bit different: for normal apply's VJP produces both value and pullback closure via a single nested VJP apply. This is not so anymore with coroutine VJP's: yielded values are produced at `begin_apply` site and pullback closure is available only from `end_apply`, so we need to track the order in which pullbacks are produced (and arrange consumption of the values accordingly – effectively delay them)
- On the way some complementary changes were required in e.g. mangler / demangler
This patch covers the generation of derivatives up to SIL level, however, it is not enough as codegen of `partial_apply` of a coroutine is completely broken. The fix for this will be submitted separately as it is not directly autodiff-related.
---------
Co-authored-by: Andrew Savonichev <andrew.savonichev@gmail.com>
Co-authored-by: Richard Wei <rxwei@apple.com>
This diagnostic is useful around silgen_name where it validates that we do not
have any weird collisions. Sadly, it just points where one of the conflicting
elements is... with this patch, we also emit an error on the other function if
we have a SILLocation.
Specifically, I added a named version of the diagnostic:
func testSimpleTransferLet() {
let k = Klass()
- transferArg(k) // expected-warning {{binding of non-Sendable type 'Klass' accessed after being transferred; later accesses could race}}
+ transferArg(k) // expected-warning {{transferring 'k' may cause a race}}
+ // expected-note @-1 {{'k' used after being passed as a transferring parameter}}
useValue(k) // expected-note {{use here could race}}
}
and I also cleaned up the typed version of the diagnostic that is used
if we fail to find a name:
func testSimpleTransferLet() {
let k = Klass()
- transferArg(k) // expected-warning {{binding of non-Sendable type 'Klass' accessed after being transferred; later accesses could race}}
- transferArg(k) // expected-warning {{value of non-Sendable type 'Klass' accessed after being transferred; later accesses could race}}
useValue(k) // expected-note {{use here could race}}
}
This is the 2nd to the last part of a larger effort to rework all of
the region based diagnostics to first try and use names and only go back
to the old typed diagnostics when we fail to look up a name (which should
be pretty rare, but is always possible).
At some point if I really feel confident enough with the name lookup code, I am
most likely just going to get rid of the typed diagnostic code and just emit a
compiler doesnt understand error. The user will still not be able to ship the
code but would also be told to file a bug so that we can fix the name
inference.
I am doing this since it isn't always going to be an access. We may not have
memory. We are talking about uses here!
I was able to just use sed so it was an easy fix.
As an example of the change:
- // expected-note @-1 {{'x' is transferred from nonisolated caller to main actor-isolated callee. Later uses in caller could race with potential uses in callee}}
+ // expected-note @-1 {{transferring disconnected 'x' to main actor-isolated callee could cause races in between callee main actor-isolated and local nonisolated uses}}
Part of the reason I am doing this is that I am going to be ensuring that we
handle a bunch more cases and I wanted to fix this diagnostic before I added
more incaranations of it to the tests.
Enable KeyPath/AnyKeyPath/PartialKeyPath/WritableKeyPath in Embedded Swift, but
for compile-time use only:
- Add keypath optimizations into the mandatory optimizations pipeline
- Allow keypath optimizations to look through begin_borrow, to make them work
even in OSSA.
- If a use of a KeyPath doesn't optimize away, diagnose in PerformanceDiagnostics
- Make UnsafePointer.pointer(to:) transparent to allow the keypath optimization
to happen in the callers of UnsafePointer.pointer(to:).
I also eliminated the very basic "why is this task isolated" part of the warning
in favor of the larger, better, region history impl that I am going to land
soon. The diagnostic wasn't that good in the first place and also was fiddly. So
I removed it for now.
rdar://124960994
We do not emit these very much anymore. I am just keeping it in place a bit
longer... eventually, this should just be an unknown pattern error since if a
user sees this it won't compile and we would rather then send it to us so we can
fix it.
Specifically, instead of what I call an IsolationRegionInfo + Type error, e.x.:
> task-isolated value of type 'NonSendableType' passed as a strongly transferred parameter; later accesses could race
we use instead the standard warning of:
```swift
func transfer(_ x: transferring Type) {}
func test(_ x: Type) {
transfer(x) // expected-warning {{transferring 'x' may cause a race}}
// expected-note @-1 {{task-isolated 'x' is passed as a transferring parameter; Uses in callee may race with later task-isolated uses}}
}
```
Implementation wise, I left in the old type diagnostic as a backup for now. With
time, I am going to want to eliminate it completely.
Now that we actually know the region that non transferrable things belong to, we
can use this information to give a better diagnostic here.
A really nice effect of this is that we now emit that actor isolated parameters
are actually actor isolated instead of task isolated.
I just did a full pass through. There were some cases around nonisolated
closures defined in methods and global actor isolated things where we are now
emitting the wrong message. I am going to fix that in subsequent commits.
The main piece that's still missing here is support for closures;
they actually mostly work, but they infer the wrong isolation for
actor-isolated closures (it's not expressed in the type, so obviously
they're non-isolated), so it's not really functional. We also have
a significant problem where reabstraction thunks collide incorrectly
because we don't mangle (or represent!) formal isolation into
SILFunctionType; that's another follow-up. Otherwise, I think SILGen
is working.
To avoid dialecticization based on compilation mode, ban for
non-resilient modules partial consumption of aggregates which would be
illegal were those modules instead resilient.
Not de-virtualized value type deinits can require metatype in case the deinit needs to be called via the value witness table.
Usually this does not happen because deinits are mandatory de-virtualized. But it can show up if e.g. wrong build options are used.
rdar://122651706
Init accessor properties should be handled specifically because
they do not reference underlying storage directly via `ref_element_addr`
instructions when 'self' is fully initialized.
A name diagnostic looks as follows:
Some notes:
1. VariableNameUtils still doesn't pattern match all cases. In the cases where
we fail to pattern match, I fall back to the old diagnostics. I am going to be
adding tests/etc specific to VariableNameUtils in a later patch.
2. I took this as an opportunity to begin preparing to change the diagnostics
into diags of the following form:
a. The main diagnostic is changed to something short "transferring non-Sendable
%0 could yield races with later accesses".
b. I added a longer note at the same location that explains that our caller is
in a specific isolation domain and our callee is isolated differently.
c. I added a note on the definition location of the identified value.
There are several kinds of scopes at which it is required that an
address be initialized:
(1) the whole function -- for inout argument to the function
(2) the region a coroutine is active -- for an inout yielded by a
coroutine into the function
(3) the region of a memory access -- for a `begin_access [modify]`.
The move checker enforces that they are initialized at that point by
adding instructions at which the field must be live to liveness.
Previously, all such scopes used the end of the function as the point at
which the memory had to have been reinitialized. Here, the relevant end
of scope markers are used instead.
More importantly, here the diagnostic is made to vary--the diagnostic,
that is, that is issued in the face an address not being initialized at
the end of these different kind of scopes.
I also while doing this I replaced a bunch of places where we used to crash due
to invariants failing to instead emit a "I don't know error" since it is more
actionable for the user.
This commit makes it so that we treat values captured by an actor isolated
closure as being transferred to that closure. I also introduced a new diagnostic
for these warnings that puts the main warning on the capture point of the value
so the user is able to see the actual capture that causes the transfer to occur:
```swift
nonisolated func testLocal2() async {
let l = NonSendableKlass()
// This is not safe since we use l later.
self.assumeIsolated { isolatedSelf in
isolatedSelf.ns = l
}
useValue(l) // expected-note {{access here could race}}
}
```
```
test.swift:74:14: warning: main actor-isolated closure captures value of non-Sendable type 'NonSendableKlass' from nonisolated context; later accesses to value could race
useValue(x) // expected-warning {{main actor-isolated closure captures value of non-Sendable type 'NonSendableKlass' from nonisolated context; later accesses to value could race}}
^
test.swift:76:12: note: access here could race
useValue(x) // expected-note {{access here could race}}
^
```
One thing to keep in mind is that if we have a function argument being captured
in this way, we still emit the "call site passes `self`" error. I am going to
begin cleaning that up in the next commit in this PR so that we emit a better
error here. But it makes sense to split these into two separate commits since
they are doing different things.
rdar://121345525