Commit Graph

74 Commits

Author SHA1 Message Date
Michael Gottesman
391ad21583 [rbi] Make all PartitionOpErrors noncopyable types.
I am going to be adding support to passes for emitting IsolationHistory behind a
flag. As part of this, we need to store the state of the partition that created
the error when the error is emitted. A partition stores heap memory so it makes
sense to make these types noncopyable types so we just move the heap memory
rather than copying it all over the place.
2025-08-29 15:18:55 -07:00
Michael Gottesman
8745ab00de [rbi] Teach RegionIsolation how to properly error when 'inout sending' params are returned.
We want 'inout sending' parameters to have the semantics that not only are they
disconnected on return from the function but additionally they are guaranteed to
be in their own disconnected region on return. This implies that we must emit
errors when an 'inout sending' parameter or any element that is in the same
region as the current value within an 'inout sending' parameter is
returned. This commit contains a new diagnostic for RegionIsolation that adds
specific logic for detecting and emitting errors in these situations.

To implement this, we introduce 3 new diagnostics with each individual
diagnostic being slightly different to reflect the various ways that this error
can come up in source:

* Returning 'inout sending' directly:

```swift
func returnInOutSendingDirectly(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
  return x // expected-warning {{cannot return 'inout sending' parameter 'x' from global function 'returnInOutSendingDirectly'}}
  // expected-note @-1 {{returning 'x' risks concurrent access since caller assumes that 'x' and the result of global function 'returnInOutSendingDirectly' can be safely sent to different isolation domains}}
}
```

* Returning a value in the same region as an 'inout sending' parameter. E.x.:

```swift
func returnInOutSendingRegionVar(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
  var y = x
  y = x
  return y // expected-warning {{cannot return 'y' from global function 'returnInOutSendingRegionVar'}}
  // expected-note @-1 {{returning 'y' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' and the result of global function 'returnInOutSendingRegionVar' can be safely sent to different isolation domains}}
}
```

* Returning the result of a function or computed property that is in the same
region as the 'inout parameter'.

```swift
func returnInOutSendingViaHelper(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
  let y = x
  return useNonSendableKlassAndReturn(y) // expected-warning {{cannot return result of global function 'useNonSendableKlassAndReturn' from global function 'returnInOutSendingViaHelper'}}
  // expected-note @-1 {{returning result of global function 'useNonSendableKlassAndReturn' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' and the result of global function 'returnInOutSendingViaHelper' can be safely sent to different isolation domains}}
}
```

Additionally, I had to introduce a specific variant for each of these
diagnostics for cases where due to us being in a method, we are actually in our
caller causing the 'inout sending' parameter to be in the same region as an
actor isolated value:

* Returning 'inout sending' directly:

```swift
extension MyActor {
  func returnInOutSendingDirectly(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
    return x // expected-warning {{cannot return 'inout sending' parameter 'x' from instance method 'returnInOutSendingDirectly'}}
    // expected-note @-1 {{returning 'x' risks concurrent access since caller assumes that 'x' is not actor-isolated and the result of instance method 'returnInOutSendingDirectly' is 'self'-isolated}}
  }
}
```

* Returning a value in the same region as an 'inout sending' parameter. E.x.:

```swift
extension MyActor {
  func returnInOutSendingRegionLet(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
    let y = x
    return y // expected-warning {{cannot return 'y' from instance method 'returnInOutSendingRegionLet'}}
    // expected-note @-1 {{returning 'y' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' is not actor-isolated and the result of instance method 'returnInOutSendingRegionLet' is 'self'-isolated}}
  }
}
```

* Returning the result of a function or computed property that is in the same region as the 'inout parameter'.

```swift
extension MyActor {
  func returnInOutSendingViaHelper(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
    let y = x
    return useNonSendableKlassAndReturn(y) // expected-warning {{cannot return result of global function 'useNonSendableKlassAndReturn' from instance method 'returnInOutSendingViaHelper'; this is an error in the Swift 6 language mode}}
    // expected-note @-1 {{returning result of global function 'useNonSendableKlassAndReturn' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' is not actor-isolated and the result of instance method 'returnInOutSendingViaHelper' is 'self'-isolated}}
  }
}
```

To implement this, I used two different approaches depending on whether or not
the returned value was generic or not.

* Concrete

In the case where we had a concrete value, I was able to in simple cases emit
diagnostics based off of the values returned by the return inst. In cases where
we phied together results due to multiple results in the same function, we
determine which of the incoming phied values caused the error by grabbing the
exit partition information of each of the incoming value predecessors and seeing
if an InOutSendingAtFunctionExit would emit an error.

* Generic

In the case of generic code, it is a little more interesting since the result is
a value stored in an our parameter instead of being a value directly returned by
a return inst. To work around this, I use PrunedLiveness to determine the last
values stored into the out parameter in the function to avoid having to do a
full dataflow. Then I take the exit blocks where we assign each of those values
and run the same check as we do in the direct phi case to emit the appropriate
error.

rdar://152454571
2025-08-25 14:57:44 -07:00
Michael Gottesman
9f8b9898a7 [rbi] Hoist diagnostic evaluator earlier in the file to make review easier.
I am going to use this in the next commit. So it makes sense to hoist it as a
separate commit to make it easy to just look through the diff.
2025-08-18 16:52:15 -07:00
Doug Gregor
02c34bb830 [SE-0470] Track the potential introduction of isolated conformances in regions
When we introduce isolation due to a (potential) isolated conformance,
keep track of the protocol to which the conformance could be
introduced. Use this information for two reasons:

1. Downgrade the error to a warning in Swift < 7, because we are newly
diagnosing these
2. Add a note indicating where the isolated conformance could be introduced.
2025-07-08 11:18:30 -07:00
Michael Gottesman
010fa39f31 [rbi] Use interned StringRefs for diagnostics instead of SmallString<64>.
This makes the code easier to write and also prevents any lifetime issues from a
diagnostic outliving the SmallString due to diagnostic transactions.
2025-07-02 16:50:24 -07:00
Michael Gottesman
4433ab8d81 [rbi] Thread through a SILFunction into print routines so we can access LangOpts.Features so we can change how we print based off of NonisolatedNonsendingByDefault.
We do not actually use this information yet though... This is just to ease
review.
2025-07-02 12:13:52 -07:00
Michael Gottesman
4ce4fc4f95 [rbi] Wrap use ActorIsolation::printForDiagnostics with our own SILIsolationInfo::printActorIsolationForDiagnostics.
I am doing this so that I can change how we emit the diagnostics just for
SendNonSendable depending on if NonisolatedNonsendingByDefault is enabled
without touching the rest of the compiler.

This does not actually change any of the actual output though.
2025-07-02 12:13:50 -07:00
Michael Gottesman
f31236931b Change send-never-sendable of isolated partial applies to use SIL level info instead of AST info.
The reason I am doing this is that we have gotten reports about certain test
cases where we are emitting errors about self being captured in isolated
closures where the sourceloc is invalid. The reason why this happened is that
the decl returned by getIsolationCrossing did not have a SourceLoc since self
was being used implicitly.

In this commit I fix that issue by using SIL level information instead of AST
level information. This guarantees that we get an appropriate SourceLoc. As an
additional benefit, this fixed some extant errors where due to some sort of bug
in the AST, we were saying that a value was nonisolated when it was actor
isolated in some of the error msgs.

rdar://151955519
2025-06-10 08:09:32 -07:00
Anthony
c9b17383c8 Grammatical corrections for compound modifiers 2025-04-24 09:21:32 +02:00
Michael Gottesman
ecb745ea18 Merge pull request #80745 from gottesmm/rdar149019222
[rbi] Teach RBI how to handle non-Sendable bases of Sendable values
2025-04-10 19:57:01 -07:00
Michael Gottesman
23b6937cbc [rbi] Make it so that we correctly do not error on uses of Sendable values that are projected from non-Sendable bases.
Specifically, we only do this if the base is a let or if it is a var but not
captured by reference.

rdar://149019222
2025-04-10 14:53:56 -07:00
Michael Gottesman
a045c9880a [rbi] Add the ability to add flags to PartitionOp.
I am doing this so I can mark requires as being on a mutable non-Sendable base
from a Sendable value.

I also took this as an opportunity to compress the size of PartitionOp to be 24
bytes instead of 40 bytes.
2025-04-10 14:53:56 -07:00
Michael Gottesman
c846c2279e [rbi] Refactor getUnderlyingTrackedValue so that for addresses we return both a value and a base in certain situations.
Previously, when we saw any Sendable type and attempted to look up an underlying
tracked value, we just bailed. This caused an issue in situations like the
following where we need to emit an error:

```swift
func test() {
  var x = 5
  Task.detached { x += 1 }
  print(x)
}
```

The problem with the above example is that despite value in x being Sendable,
'x' is actually in a non-Sendable box. We are passing that non-Sendable box into
the detached task by reference causing a race against the read from the
non-Sendable box later in the function. In SE-0414, this is explicitly banned in
the section called "Accessing Sendable fields of non-Sendable types after weak
transferring". In this example, the box is the non-Sendable type and the value
stored in the box is the Sendable field.

To properly represent this, we need to change how the underlying object part of
our layering returns underlying objects and vends TrackableValues to the actual
analysis for addresses. NOTE: We leave the current behavior alone for SIL
objects.

By doing this, in situations like the above, despite have a Sendable value (the
integer), we are able to ensure that we require that the non-Sendable box
containing the integer is not used after we have sent it into the other Task
despite us not actually using the box directly.

Below I describe the representation change in more detail and describe the
various cases here. In this commit, I only change the representation and do not
actually use the new base information. I do that in the next commit to make this
change easier for others to read and review. I made sure that change was NFC by
leaving RegionAnalysis.cpp:727 returning an optional.none if the value found was
a Sendable value.

----

The way we modify the representation is that we instead of just returning a
single TrackedValue return a pair of tracked values, one for the base and one
for the "value". We return this pair in what is labeled a
"TrackableValueLookupResult":

```c++
struct TrackableValueLookupResult {
  TrackableValue value;

  std::optional<TrackableValue> base;

  TrackableValueLookupResult(TrackableValue value)
    : value(value), base() {}
  TrackableValueLookupResult(TrackableValue value, TrackableValue base)
    : value(value), base(base) {}
};
```

In the case where we are accessing a projection path out of a non-Sendable type
that contains all non-Sendable fields, we do not do anything different than we
did previously. We just walk up from use->def until we find the access path base
which we use as the representative of the leaf of the chain and return
TrackableValueLookupResult(access path base).

In the case where we are accessing a Sendable leaf type projected from a
non-Sendable base, we store the leaf type as our value and return the actual
non-Sendable base in TrackableValueLookupResult. Importantly this ensures that
even though our Sendable value will be ignored by the rest of the analysis, the
rest of the analysis will ensure that our base is required if our base is a var
that had been escaped into a closure by reference.

In the case where we are accessing a non-Sendable leaf type projected from a
Sendable type (which we may have continued to be projected subsequently out of
additional Sendable types or a non-Sendable type), we make the last type on the
projection path before the Sendable type, the value of the leaf type. We return
the eventual access path base as our underlying value base. The logic here is
that since we are dealing with access paths, our access path can only consist of
projections into a recursive value type (e.x.: struct/tuple/enum... never a
class). The minute that we hit a pointer or a class, we will no longer be along
the access path since we will be traversing a non-contiguous piece of
memory (consider a class vs the class's storage) and the traversal from use->def
will stop. Thus, we know that there are only two ways we can get a field in that
value type to be Sendable and have a non-Sendable field:

1. The struct can be @unchecked Sendable. In such a case, we want to treat the
leaf field as part of its own disconnected region.

2. The struct can be global actor isolated. In such a case, we want to treat the
leaf field as part of the global actor's region rather than whatever actor.

The reason why we return the eventual access path base as our tracked value base
is that we want to ensure that if the var value had been escaped by reference,
we can require that the var not be sent since we are going to attempt to access
state from the var in order to get the global actor guarded struct that we are
going to attempt to extract our non-Sendable leaf value out of.
2025-04-10 10:01:18 -07:00
Anthony Latsis
5c190b9613 AST: Cut down on DescriptiveDeclKind usage in DiagnosticsSIL.def 2025-04-05 12:31:20 +01:00
Slava Pestov
e37a2d065d Sema: Fix Sendable generic parameter fixit
Fixes rdar://problem/144644342.
2025-02-27 13:15:13 -05:00
Michael Gottesman
8f952d2f3d [rbi] Fix a thinko where while finding closure uses, I was not checking if functions had a body when looking for arguments.
rdar://145089562
2025-02-19 14:45:09 -08:00
Anthony Latsis
34f9b80cbc Merge pull request #78750 from AnthonyLatsis/oryza-sativa
[Gardening] Fix some set but not used variables
2025-01-31 04:29:05 +00:00
Anthony Latsis
a84dfc8387 [Gardening] Fix some set but not used variables 2025-01-30 21:34:38 +00:00
Michael Gottesman
8c96a8db1b [rbi] When finding closure uses in an immediately invoked closure, distinguish in between captures and parameters.
Otherwise, when one diagnoses code like the following:

```
Task {
  {
    use($0)
  }(x)
}
```

One gets that $0 was captured instead of x. Unfortunately, since function
parameters do not have locations associated with them, we do not mark x
itself... instead, we mark the closure... which is unfortunate.
2025-01-29 15:07:47 -08:00
Michael Gottesman
082b824a8e [rbi] Change Region Based Isolation for closures to not use the AST and instead just use SIL.
The reason why I am doing this is that in certain cases the AST captures indices
will never actually line up with partial apply capture indices since we seem to
"smush" together closures and locally defined functions.

NOTE: The reason for the really small amount of test changes is that this change
does not change the actual output by design. The only cases I had to change were
a case where we began to emit a better diagnostic and also where I added code
coverage around _ and let _ since those require ignored_use to be implemented so
that they would be diagnosed (previously we just did not emit anything so we
couldn't emit the diagnostic at the SIL level).

rdar://142661388
2025-01-22 21:12:36 -08:00
Michael Gottesman
cff835e061 [region-isolation] Perform checking of non-Sendable results using rbi rather than Sema.
In terms of the test suite the only difference is that we allow for non-Sendable
types to be returned from nonisolated functions. This is safe due to the rules
of rbi. We do still error when we return non-Sendable functions across isolation
boundaries though.

The reason that I am doing this now is that I am implementing a prototype that
allows for nonisolated functions to inherit isolation from their caller. This
would have required me to implement support both in Sema for results and
arguments in SIL. Rather than implement results in Sema, I just finished the
work of transitioning the result checking out of Sema and into SIL. The actual
prototype will land in a subsequent change.

rdar://127477211
2024-12-02 16:54:12 -05:00
Michael Gottesman
d94e4c4434 [region-isolation] Using the print method from the previous commit, ensure that we dump out SentNeverSendable, InOutSendingNotDisconnectedAtExit, AssignNeverSendableIntoSendingResult earlier when we initially detect them.
This just improves the ability to quickly triage bugs in SendNonSendable. It
used to be this way, but in the process of doing some refactoring, I moved the
logging too late by mistake.
2024-11-19 12:48:30 -08:00
Michael Gottesman
d33f819038 [region-isolation] Move freeform logging on the specific error we are emitting into a method on the error itself.
I am doing this since I discovered that we are not printing certain errors as
early as we used to (due to the refactoring I did here), which makes it harder
to see the errors that we are emitting while processing individual instructions
and before we run the actual dataflow.

A nice side-effect of this is that it will make it easy to dump the error in the
debugger rather than having to wait until the point in the code where the normal
logging takes place.
2024-11-19 12:48:30 -08:00
Michael Gottesman
32b4de60a9 Rename transfer -> send.
Accomplished using clangd's rename functionality.
2024-11-04 15:17:51 -08:00
Michael Gottesman
0a56827073 [region-isolation] Rename TransferNonSendable.cpp -> SendNonSendable.cpp.
Just beginning the elimination of the word transferred from the code base.
2024-11-02 16:58:17 -07:00
Michael Gottesman
0bad8f9b67 [region-isolation] Rename SendNonSendable.cpp -> TransferNonSendable.cpp. 2023-10-26 12:01:44 -07:00
Michael Gottesman
c10c4fc10f Merge pull request #69387 from gottesmm/more-region-stuff
[region-isolation] Fix actor region propagation and some other smaller issues.
2023-10-25 11:46:19 -07:00
Michael Gottesman
0b27c5ca16 [region-based-isolation] Rather than lazily translating while we visit all blocks during the dataflow... just do the translation when we initialize block state.
We are going to visit all of the blocks anyways... so it makes sense to just run
them as part of the initializing of the block state. Otherwise, the logic is
buried in the dataflow which makes it harder for someone who is not familiar
with the pass to reason about it.
2023-10-24 16:36:02 -07:00
Michael Gottesman
cb6ac6ca31 [region-isolation] Make sure to look through begin_access and don't treat it like an assignment.
If we treat a begin_access as an assignment then in cases like below we assume
that a value was used before we reassign.

```
  func testVarReassignStopActorDerived() async {
    var closure = {}
    await transferToMain(closure)

    // This re-assignment shouldn't error.
    closure = {}  // (1)
  }
```

rdar://117437299
2023-10-24 16:36:01 -07:00
Michael Gottesman
e1b17b9763 [region-isolation] Rather than cache a separate vector of argument ids, just cache the function entry partition.
We only ever used this cache of IDs to construct the function arg
partition. Since a Partition involves using memory, it makes sense to just cache
the Partition itself and eliminate the unnecessary second cache.
2023-10-24 16:35:01 -07:00
Michael Gottesman
7fff6cd037 [region-isolation] Only perform merge assignment if we capture an address in a partial_apply rather than if we send it to any function apply.
Semantically these are the only cases where we would want to force an address to
be merged. Some notes:

1. When we have a box, the project_box is considered to come from the box and is
considered non uniquely identified.

2. The only place this was really triggering was when we created temporary
partial applies to pass to a function when using address only types. Since this
temporaries are never reassigned to, there isn't any reason to require them to
be merged.

3. In the case where we have an alloc_stack grabbed by a partial_apply, we still
appropriately merge.
2023-10-24 16:35:01 -07:00
Michael Gottesman
9dfc9b6a2d [region-isolation] Track if a non-Sendable value was derived from an actor and treat its region as being within an actor's isolation domain.
Importantly, we determine at the error stage if a specific value that is
transferred is within the same region of a value that is Actor derived. This
means that we can in a flow insensitive manner determine the values that are
actor derived and then via propagating those values into various regions
determine the issue... using our region analysis to handle the flow sensitivity.

One important case to reason about here is that since we are relying on the
region propagation for flow sensitivity is that this solves the var case for
us. A var when declared is never marked as actor derived. Var uses only become
actor derived if the var was merged into a region that contain is actor
derived. That means that re-assigning to a non-actor derived value, eliminates
the actor derived bit.

As part of this, I also discovered I could just get rid of the captured uniquely
identified array in favor of just passing in an actor derived flag.

rdar://115656589
2023-10-24 16:35:00 -07:00
Michael Gottesman
d069169c0d Fix some typos. 2023-10-24 12:47:11 -07:00
Michael Gottesman
926b4b1bb3 [region-isolation] Change TransferredReason to use a std::multimap.
In a future commit, I think I am going to change this to use a
SmallFrozenMultiMap so we can avoid the heap cost in most cases. For now though
I am just changing it to a std::multimap to make it cleaner and avoid having to
reason about when to freeze the SmallFrozenMultiMap.
2023-10-24 12:47:11 -07:00
Michael Gottesman
81a20e8609 [region-isolation] Convert some if(!condition) { assert(false) } -> assert(condition). 2023-10-24 12:47:11 -07:00
Michael Gottesman
2c0efe6b8b [region-isolation] Go through the implementation again changing Consume -> Transfer.
I just missed these.
2023-10-23 13:52:14 -07:00
Michael Gottesman
01b1475d62 [region-isolation] Emit errors directly in ConsumeRequireAccumulator rather than passing in callbacks.
This indirection is premature abstraction since the only two places we actually
use these callbacks is to print the accumulated output and to emit our
errors. With this change, I was able to simplify how print works and inline in
the actual diagnostic emission making it easier to understand the code.

Additionally, in a subsequent commit I am going to need to ensure that we do not
emit an error here if the source cause of our error is due to an actor. To
implement that I would need to change tryDiagnoseAsCallsite to signal to the
machinery that it needs to not emit requires and then fail further up as
well. Given that there really wasn't a need for this abstraction in the first
place and the amount of munging needed to express this, I decided to just make
this change and make it easier. If we truly need this flexibility, we can always
add it back later.
2023-10-23 12:24:17 -07:00
Michael Gottesman
cbc12410b3 [region-isolation] Eliminate unnecessary helper.
SILValues always have a SILType... so just use getType().isAddress().
2023-10-23 12:24:17 -07:00
Michael Gottesman
a8886e7619 Delete an incorrect assert
If we consume a value in a block and then reinitialize it before the end of the
block, this assert will always fail.
2023-10-23 12:24:17 -07:00
Michael Gottesman
d598da596a [sil] Implement SILType::isActor().
Just a helper that calls getASTType()->isActorType().

I also updated the two places in SNS that we use this.
2023-10-23 12:24:17 -07:00
Michael Gottesman
8d46cc8c08 [isolation-regions] Rename Consume to Transfer.
Transfer is the terminology that we are using for something be transferred
across an isolation boundary, not consume. This also eliminates confusion with
consume which is a term being used around ownership.
2023-09-22 16:21:46 -05:00
Michael Gottesman
a9e8baa3b7 [isolation-regions] Eliminate last temporary std::vector usage from the non-error path. 2023-09-22 16:21:46 -05:00
Michael Gottesman
63db040a1d [isolation-regions] Eliminate all heap allocations from the main translation loop.
I did this by introducing a builder construct that has a SmallVector within it
that we append into instead of returning std::vector. I also used some passed in
SmallVectors in other places as well with large enough sizes that most times we
will not allocate.
2023-09-22 16:21:46 -05:00
Michael Gottesman
e5e0941e8b Remove clang optimize off that snuck into tree. 2023-09-13 12:59:02 -05:00
Michael Gottesman
4dcee7183a [send-non-sendable] Warn if a non-final field of an actor is transferred.
rdar://115132118
2023-09-12 14:12:54 -05:00
Michael Gottesman
82fbde0e04 [send-non-sendable] Ensure that we properly warn if a field of a final actor is transferred.
Previously, we were not recognizing that a ref_element_addr from an actor object
is equivalent to the actor object and we shouldn't allow for it to be consumed.

rdar://115132118
2023-09-12 12:46:37 -05:00
Michael Gottesman
3382352a62 [send-non-sendable] Make sure that we translate project_box into the pseudo-IR.
This is a case of an instruction that we missed translating into the pseudo-IR.
This was easy to do since we are not yet performing an exhaustive switch over
all instruction kinds when translating. Earlier I did the first part of the
translation by switching from doing conditional is<INST> to switch but haven't
yet eliminated the default yet from the switch. I am fixing this now since with
my load [copy] change from earlier causes us to crash in the earlier unorganized
tests.

rdar://115367810
2023-09-12 12:15:52 -05:00
Michael Gottesman
3a377a3e51 [send-non-sendable] When logging the pseudo-ir dump out the root representative value associated with pseudo-ir ids.
Really, we should just be using representative values here in general since it
serves the same purpose and makes it easier to trace back values. But this in
the short term makes the output easier to reason about.
2023-09-12 12:11:44 -05:00
Michael Gottesman
acd1063c95 [send-non-sendable] Convert some llvm::Optional -> std::optional. 2023-09-12 12:11:44 -05:00
Michael Gottesman
969477acea [send-non-sendable] Eliminate another pass by value of a std::vector. 2023-09-12 12:11:44 -05:00