Commit Graph

121 Commits

Author SHA1 Message Date
Michael Gottesman
d6b4f16382 [region-isolation] Add a SILLocation to SequenceBoundary in IsolationHistory.
We package all isolation history nodes from a single instruction by placing a
sequence boundary at the bottom. When ever we pop, we actually pop a PartitionOp
at a time meaning that we pop until we see a SequenceBoundary. Thus the sequence
boundary will always be the last element visited when popping meaning that it is
a convenient place to stick the SILLocation associated with the entire
PartitionOp. As a benefit, there was some unused space in IsolationHistory::Node
for that case since we were not using the std::variant field at all.
2024-04-06 00:58:10 -07:00
Michael Gottesman
aee5a37d9d [region-isolation] Add isolation history support but do not wire it up to the checker.
This means that I added an IsolationHistory field to Partition. Just upstreaming
the beginning part of this work. I added some unittests to exercise the code as
well. NOTE: This means that I did need to begin tracking an
IsolationHistoryFactory and propagating IsolationHistory in the pass
itself... but we do not use it for anything.

A quick overview of the design.

IsolationHistory is the head of an immutable directed acyclic graph. It is
actually represented as an immutable linked list with a special node that ties
in extra children nodes. The users of the information are expected to get a
SmallVectorImpl and process those sibling nodes afterwards. The reason why we
use an immutable approach is that it fits well with the problem and saves space
since different partitions could be pointing at the same linked list
node. Operations occur on an isolation history by pushing/popping nodes. It is
assumed that the user will push nodes in batches with a sequence boundary at the
bottom of the addition which signals to stop processing nodes.

Tieing this together, each Partition within it contains an IsolationHistory. As
the PartitionOpEvaluator applies PartitionOps to Partition in
PartitionOpEvaluator::apply, the evaluator also updates the isolation history in
the partition by first pushing a SequenceBoundary node and then pushing nodes
that will undo the operation that it is performing. This information is used by
the method Partition::popHistory. This pops linked list nodes from its history,
performing the operation in reverse until it hits a SequenceBoundary node.

This allows for one to rewind Partition history. And if one stashes an isolation
history as a target, one can even unwind a partition to what its state was at a
specific transfer point or earlier. Once we are at that point, we can begin
going one node back at a time and see when two values that we are searching for
no longer are apart of the same region. That is a place where we want to emit a
diagnostic. We then process until we find for both of our values history points
where they were the immediate reason why the two regions merge.

rdar://123479934
2024-04-06 00:58:05 -07:00
Michael Gottesman
c2350dfe7e [region-isolation] Change SILIsolationInfo to only traffic in ActorIsolation instead of that or nominal type decls.
This should be NFC since the only case where I used this was with self... and I
found another way of doing that using the API I added in the previous commit.
2024-04-04 10:58:57 -07:00
Michael Gottesman
1cf4e99454 Propagate global actor-ness of functions/closures.
I fixed a bunch of small issues around here that resulted in a bunch of radars
being fixed. Specifically:

1. I made it so that we treat function_refs that are from an actor isolated
function as actor isolated instead of sendable.

2. I made it so that autoclosures which return global actor isolated functions
are treated as producing a global actor isolated function.

3. I made it so that we properly handle SILGen code patterns produced by
Sendable GlobalActor isolated things.

rdar://125452372
rdar://121954871
rdar://121955895
rdar://122692698
2024-03-29 14:39:34 -07:00
Michael Gottesman
77dccacbd3 Make ActorIsolation on SILFunction non-optional.
ActorIsolation already has a "I have no value case": unspecified. Lets just use
that.

Just a mistake I made that I am trying to fix before anything further depends on
this code.
2024-03-29 14:39:26 -07:00
Michael Gottesman
6f66849610 [region-isolation] Do not squelch errors in the unittests.
To squelch errors, we need access to functionality not available in the
unittests. The unittests do not require this functionality anyways, so just
disable squelching during the unittests.
2024-03-26 10:06:21 -07:00
Michael Gottesman
e36aab6301 [region-isolation] Squelch use after transfer if the use has effectively the same isolation as the transfer inst.
To be more specific this means that either:

1. The use is actually isolated to the same actor. This could mean that the
use is global actor isolated to the same function.

2. The use is nonisolated but is executing within a function that is globally
isolated to the same isolation domain.

rdar://123474616
2024-03-25 23:32:17 -07:00
Michael Gottesman
8243b5cb74 [region-isolation] If a value is dynamically actor isolated, do not consider it transferred if the transfer statically was to that same actor isolation.
This issue can come up when a value is initially statically disconnected, but
after we performed dataflow, we discovered that it was actually actor isolated
at the transfer point, implying that we are not actually transferring.

Example:

```swift
@MainActor func testGlobalAndGlobalIsolatedPartialApplyMatch2() {
  var ns = (NonSendableKlass(), NonSendableKlass())
  // Regions: (ns.0, ns.1), {(mainActorIsolatedGlobal), @MainActor}

  ns.0 = mainActorIsolatedGlobal
  // Regions: {(ns.0, ns.1, mainActorIsolatedGlobal), @MainActor}

  // This is not a transfer since ns is already main actor isolated.
  let _ = { @MainActor in
    print(ns)
  }

  useValue(ns)
}
```

To do this, I also added to SILFunction an actor isolation that SILGen puts on
the SILFunction during pre function visitation. We don't print it or serialize
it for now.

rdar://123474616
2024-03-25 22:58:17 -07:00
Michael Gottesman
901c7c2b6c [region-isolation] Shrink PartitionUtils.h by moving large functions to cpp file.
Just making PartitionUtils.h a little easier to walk through by moving more of
the impl into the .cpp file. This reduces the header from ~1500 lines to ~950
lines which is more managable. This is especially important since I am going
to be adding IsolationHistory to the header file which will expand it even
further.
2024-03-25 14:15:10 -07:00
Michael Gottesman
357a53ab48 [region-isolation] Clean up use after transfer error to use the dynamic isolation information of the transfered operand value in its diagnostic message.
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.
2024-03-22 13:12:51 -07:00
Michael Gottesman
608493c15c [region-isolation] Move computation of SILIsolationInfo for a SILFunctionArgument onto SILIsolationInfo::get(...). 2024-03-21 14:16:20 -07:00
Michael Gottesman
5b8171240d [region-isolation] Rename IsolationRegionInfo -> SILIsolationInfo.
That is really what IsolationRegionInfo is.
2024-03-21 14:16:20 -07:00
Michael Gottesman
f991517644 [region-isolation] Refactor out dynamic getIsolationRegionInfo for an entire region into a helper.
I also did a little bit of renaming of variable names to make the code a little
clearer.
2024-03-21 14:16:20 -07:00
Michael Gottesman
e00fbfa6b6 [region-isolation] Refactor RegionAnalysis AST pattern matching onto a helper factory method on IsolationRegionInfo.
Long term I would like to get region analysis and transfer non sendable out of
the business of directly interpreting the AST... but if we have to do it now, I
would rather us do it through a helper struct. At least the helper struct can be
modified later to work with additional SIL concurrency support when it is added.
2024-03-21 14:16:20 -07:00
Michael Gottesman
d72f078be4 [region-isolation] Add ability to Profile an IsolationRegionInfo.
Just slicing down a larger diff to make it easier to review.
2024-03-21 14:16:20 -07:00
Michael Gottesman
12aad7c251 [region-isolation] Define an == on IsolationRegionInfo.
Importantly, if we have an actor isolation, we always defer to it.
2024-03-21 14:16:20 -07:00
Michael Gottesman
0d96a16491 [region-isolation] Move IsolationRegionInfo higher up in PartitionUtils.h
This is in preparation for beginning to track an IsolationRegionInfo in
TransferringOperand.

Just splitting this into a separate commit to make it easier to read.
2024-03-21 14:16:20 -07:00
Michael Gottesman
e0fdce1fa3 [region-isolation] Convert TransferringOperand to be a bump allocated ptr type from a pointer type.
I need to start tracking the dynamic IsolationRegionInfo for the transferring
operand so I can ignore uses that are part of the same
IsolationRegionInfo. IsolationRegionInfo doesn't fit into a pointer, so just to
keep things the same, I am going to just allocate it.

This is an initial staging commit that tests out the bump ptr allocating without
expanding the type yet.
2024-03-21 14:16:20 -07:00
Michael Gottesman
a2d1adfcd0 Add include <variant> in a place that I missed. 2024-03-18 14:50:03 -07:00
Michael Gottesman
f796f11c97 [region-isolation] Rename ValueIsolationRegionInfo -> IsolationRegionInfo. 2024-03-18 12:24:34 -07:00
Michael Gottesman
afbcf85727 [region-isolation] Change named transfer non transferable error to use the dynamic merged IsolationRegionInfo found during dataflow.
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
2024-03-18 12:13:36 -07:00
Michael Gottesman
f7d1f2acb9 [region-isolation] Define getIsolationRegionInfo(Element elt) on PartitionOpEvaluator.
This will let me look up the dynamic isolation region associated with \p elt
while performing dataflow.
2024-03-18 12:13:30 -07:00
Michael Gottesman
b23b791c1b [region-isolation] Move ValueIsolationRegionInfo from RegionAnalysis.h -> PartitionUtils.h.
I am going to need this so that I can use it when evaluating partition
ops. Specifically, so I can when I find two values that do not merge correctly,
emit an error.
2024-03-18 12:13:30 -07:00
Michael Gottesman
465bb230c4 [region-isolation] Rename callback handleFailure -> handleLocalUseAfterTransfer.
Now that we have other forms of error callbacks, having such a general name for
any specific failure is misleading and hinds intent.
2024-03-18 12:13:30 -07:00
Michael Gottesman
806cd7940e [region-isolation] Fix actor isolated parameters to get an actor isolated error instead of a task isolated error.
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.
2024-03-10 22:08:40 -07:00
Michael Gottesman
b2c85a8294 [region-isolation] Rather than tracking task isolated values via a separate non-transferrable array... just track it by using ValueIsolationRegionInfo on a value.
In a subsequent commit, this is going to let me begin handling parameters with
actor regions in a nice way (and standardize all of the errors).

This is meant to be a refactoring commit that uses the current tests in tree to
make sure I did it correctly, so no tests need to be updated.
2024-03-10 22:08:40 -07:00
Michael Gottesman
f3edb5730a [transferring] Add support for transferring results.
rdar://121324697
2024-02-14 14:39:02 -08:00
Michael Gottesman
434261b851 [region-isolation] When changing an elements region, if that element was the last element in a transferred regionl, remove that region from the transferredOpMap.
I also added some validation that we properly do this. It only runs when NDEBUG
is not set.

rdar://122280930
2024-02-04 14:16:24 -08:00
Michael Gottesman
bcb8f1b0d8 [region-isolation] Implement the dataflow correctly.
This involved fixing a few different bugs.

1. We were just performing dataflow by setting that only the initial block needs
to be updated. This means that if there isn’t anything in the initial dataflow
block, we won’t visit any successor blocks. Instead the correct thing to do here
is to visit all blocks in the initial round.

2. I also needed to fix a separate issue where we were updating our union-find
data structure incorrectly as found by an assert on transfernonsendable.swift
that was triggered once I fixed 1. Put simply, we needed to set a new max label
+ 1 when our new max element is less than or equal to the old max label + 1…
before we just did less than so if we had a new max element that is the same as
our fresh label, we wouldn’t increment the fresh label.

rdar://119584497
2023-12-15 17:14:09 -08:00
Michael Gottesman
398fa8b10f [region-isolation] Make PartitionOpEvaluator use CRTP instead of std::function callbacks.
Just a fixup requested by reviewers of incoming code that I wanted to do in a
follow on commit.
2023-12-04 13:03:15 -06:00
Michael Gottesman
df03cb40ef [region-isolation] Make PartitionOpEvaluator no longer a friend of Partition.
I left them as friends since that was in the original code. There isn't a reason
to do this and break the encapsulation of Partition. I just added reasonable
helpers that give PartitionOpEvaluator all of the functionality it needs.
2023-12-04 13:03:15 -06:00
Michael Gottesman
12573d6b52 [region-isolation] Instead of just tracking a single transferring instruction, track all of them.
Previously I avoided doing this since the only problem would be that in a case
where we had two transfer instructions that were in an if-else block, we would
just emit an error for one:

```swift
if boolValue {
  transfer(x)
} else {
  transfer(x) // Only emit error for this transfer!
}

useValue(x)
```

Now that we are tracking at the transfer point if any element in the transfer
was captured in a closure, this becomes an actual semantic issue since if we
track the transfer instruction that isn't reachable from the closure capture, we
will not become more pessimistic:

```swift
if boolValue {
  closure = { useInOut(&x) }
  transfer(x)
} else {
  transfer(x)
}

// Since we grab from the else block, sendableField is allowed to be accessed
// since we do not track that x was captured by reference in a closure.
x.sendableField
useValue(x)
```

To be truly safe, we need to emit both errors.

rdar://119048779
2023-12-01 13:53:57 -08:00
Michael Gottesman
a2604dcafa [region-isolation] Ensure that we error if we access a Sendable field of a non-Sendable typed var and the var is captured in a closure
If the var is captured in a closure before it is transferred, it is not safe to
access the Sendable field since we may race on accessing the field with an
assignment to the field in another concurrency domain.

rdar://115124361
2023-12-01 13:53:56 -08:00
Michael Gottesman
d1870c1ae3 [region-isolation] Fix return vs break logic error in the evaluator.
I am not sure if this would ever actually ever cause a bug... but we should be
consistent. The issue here is that in the evaluator switch when processing
transfers in certain cases when we emitted an early error due to an actor
derived value we were performing a transfer and other times we weren't. With
this patch:

1. We now consistently do not actually transfer the value if it is actor
derived. It is always illegal to transfer an actor derived value... so we know
that if we always just error on transferring it, we have a safe model.

2. The switch we breaking so that we could do an assert that we canonicalized
the modified PartitionOp. Rather than do that and allow for people to
potentially return, I moved the assert into a SWIFT_DEFER and added an assert in
the continuation of the switch. This means that if an author ever breaks out of
the switch, they will hit the assert implying that the author in all cases must
explicitly return within the case statement guaranteeing this mistake does not
happen again.
2023-11-28 09:48:44 -08:00
Michael Gottesman
18f91c0acd [region-isolation] Add support for async let.
Specifically:

1. If the value is transferred such that it becomes part of an actor region, the
value is permanently part of the actor region as one would normally have.

2. If the value is just used in an async let or is used by a nonisolated async
function within the async let then while the async let is alive it cannot be
used. But once the async let has been awaited upon, we allow for it to be used
again.

rdar://117506395
2023-11-28 09:39:04 -08:00
Michael Gottesman
95669c5e9c [region-isolation] Instead of passing around an expression to get the original type, just derive the type from the transfer operand when we emit the error. 2023-11-15 18:58:06 -08:00
Michael Gottesman
957a79f82a [region-isolation] Track operands instead of SILInstructions for Transfer instructions.
This is another NFC refactor in preparation for changing how we emit
errors. Specifically, we need access to not only the instruction, but also the
specific operand that the transfer occurs at. This ensures that we can look up
the specific type information later when we emit an error rather than tracking
this information throughout the entire pass.
2023-11-15 18:58:06 -08:00
Michael Gottesman
c5259ad172 [region-isolation] Remove getExprForPartitionOp and instead just use SILLocation.
getExprForPartitionOp(...) just returned the expression from the loc of op.currentInst:

  SILInstruction *sourceInstr = op.getSourceInst(/*assertNonNull=*/true);
  Expr *expr = sourceInstr->getLoc().getAsASTNode<Expr>();

Instead of mucking around with exprs, just use the SILLocation from the
SILInstruction.

I also changed how we unique transfer instructions to just use the transfer
instruction itself instead of the AST/Expr of the transfer instruction.
2023-11-10 12:48:45 -08:00
Michael Gottesman
cf780b23a1 [region-isolation] Remove two sources of copying PartitionOps.
Was experimenting with making PartitionOps a noncopyable type and I discovered
these places where we copy PartitionOps when we could use a const reference. It
is good not to copy PartitionOps since they potentially contain a heap allocated
array.

Sadly, my change to make PartitionOps noncopyable will have to wait until a
forthcoming commit here I overhaul how we emit errors since that older code
copies PartitionOps a lot and I would rather just delete that code and then fix
PartitionOps. But these are on the surface safe changes that makes sense to get
in separately to make that next patch easier to review.
2023-11-10 12:48:45 -08:00
Michael Gottesman
c336f3a47e [region-isolation] Track transferring separately from region information.
What this does is really split the one dataflow we are performing into two
dataflows we perform at the same time. The first dataflow is the region dataflow
that we already have with transferring never occurring. The second dataflow is a
simple gen/kill dataflow where we gen on a transfer instruction and kill on
AssignFresh. What it tracks are regions where a specific element is transferred
and propagates the region until the element is given a new value. This of course
means that once the dataflow has converged, we have to emit an error not if the
value was transferred, but if any value in its region was transferred.
2023-11-10 12:48:45 -08:00
Michael Gottesman
dbc3deb382 [region-isolation] Improve logging.
Specifically:

1. I changed Partition::apply so that it has an emitLog flag. The reason why I
did this is we run apply in a few different situations sometimes when we want to
emit logging other times when we really don't. For instance, we want to emit
logging when walking instructions and updating the entry partition. On the other
hand, we do not want to emit logging if we apply a value to a partition while
attempting to determine why an error needed to be emitted.

2. When we create an assign partition op and we see that our destination and
source are the same representative, we do not create the actual assign. Before
we did not log this so it looked like there was a logic error that was stopping
us from emitting a partition op when visiting said instructions. Now, we emit a
small logging message so it isn't possible to be confused.

3. Since I am adding another parameter to Partition::apply, I decided to
refactor Partition::apply to be in a separate PartitionOpEvaluator data
structure that contains the options that we used to pass into Partition::apply.
This prevents any mistakes around configuring Partition::apply since the fields
provide nice names/common sense default values.
2023-11-01 21:35:32 -07:00
Michael Gottesman
382609e3ac [region-isolation] More consuming -> transferring. 2023-10-26 12:29:23 -07:00
Michael Gottesman
c9e750ba18 [region-isolation] Clean up/standardize comments. 2023-10-26 12:25:21 -07:00
Michael Gottesman
6718dbf17e [region-isolation] Change how we compute max in separateRegions as we loop rather than using a functional 2nd loop before the main loop. 2023-10-26 12:01:44 -07:00
Michael Gottesman
a46025d040 [region-isolation] Remove Partition::getRegion().
This was a piece of code that I added early while adding better unittesting for
Partition. Instead in that patch I added a forward declared class in Partition
called PartitionTester that could implement getRegion. I just forgot to remove
this.
2023-10-26 12:01:44 -07:00
Michael Gottesman
0bad8f9b67 [region-isolation] Rename SendNonSendable.cpp -> TransferNonSendable.cpp. 2023-10-26 12:01:44 -07:00
Michael Gottesman
4c7e13d3cb [region-isolation] Fix join to be a true union operation including the symmetric difference.
We were performing a union on the intersection of the lhs/rhs but were dropping
the parts of lhs/rhs that were in the symmetric difference of the two sets.

Without this, we would not diagnose cases like this where we had elements on the
lhs/rhs that were not in the intersection.

```
var closure: () -> () = {}

await transferToMain(closure)

if await booleanFlag {
   closure = {
     print(self.klass)
   }
} else {
   closure = {}
}
// At this point we would lose closure since they were different elements

await transferToMain(closure) // We wouldn't error on this!
```

rdar://117437059
2023-10-25 17:05:11 -07:00
Michael Gottesman
cfd00e842f [region-isolation] Fix header in PartitionUtils.h
NFC.
2023-10-25 13:17:20 -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
30933db69f [region-isolation] When printing PartitionOps, print the instruction that they are derived from.
Otherwise, it is hard to tell what one is looking at when looking at the
pseudo-ir.
2023-10-24 16:36:02 -07:00