Commit Graph

149 Commits

Author SHA1 Message Date
Michael Gottesman
b178843602 [region-isolation] Add support for yield, switch_enum_addr. 2023-12-14 19:11:06 -08:00
Michael Gottesman
5e1ca5ccb9 [region-isolation] Make instruction translation use a covered visitor and add a TranslationSemantics enum to guide users updating the code.
More specifically this patch does the following:

* Rather than having a large switch with misc code, I changed the partition op
translator to use a visitor that defines a declaration for all SILInstructions
and in translateSILInstruction visits all such instructions. This ensures via
the linker that when ever a new SILInstruction is added, a link error occurs.

* Rather than just have misc translation code from the switch in the visitor, I
created a new enum called TranslationSemantics that describes the semantics for
instructions and made it so that the visitor methods return an instance of the
enum. This enum is then switched over to determine the action to perform. This
handles the vast majority of cases and allows for a reader of the translation
code to read a small amount of code (< 20-30 lines) to understand at a glance
the available semantics rather than having to read a huge switch. To make it
easy to implement the constant semantics that most instructions have, I followed
what we did in OperandOwnership (the model that I followed here) by using
preprocessor macros to define explicitly the semantics for each instruction. In
the case where special handling is needed, we can create a custom method, handle
the translation by hand, and then return TranslationSemantics::Special to signal
to the handling loop to just not do anything.
2023-12-14 19:11:06 -08:00
Michael Gottesman
f328e7893b [region-isolation] Add support for representing ApplyIsolationCrossing at the SIL level on apply, begin_apply, try_apply.
Some notes:

This is not emitted by SILGen. This is just intended to be used so I can write
SIL test cases for transfer non sendable. I did this by adding an
ActorIsolationCrossing field to all FullApplySites rather than adding it into
the type system on a callee. The reason that this makes sense from a modeling
perspective is that an actor isolation crossing is a caller concept since it is
describing a difference in between the caller's and callee's isolation. As a
bonus it makes this a less viral change.

For simplicity, I made it so that the isolation is represented as an optional
modifier on the instructions:

  apply [callee_isolation=XXXX] [caller_isolation=XXXX]

where XXXX is a printed representation of the actor isolation.

When neither callee or caller isolation is specified then the
ApplyIsolationCrossing is std::nullopt. If only one is specified, we make the
other one ActorIsolation::Unspecified.

This required me to move ActorIsolationCrossing from AST/Expr.h ->
AST/ActorIsolation.h to work around compilation issues... Arguably that is where
it should exist anyways so it made sense.

rdar://118521597
2023-12-11 19:27:27 -06:00
Michael Gottesman
e3c0e67354 [region-isolation] Fix asan error. 2023-12-05 18:49:15 -06: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
e7a2a9f202 [region-isolation] Ensure that actor methods without a result properly mark their operands as being actor derived.
I did this by abstracting the representative value of an equivalence class into
two cases: the normal case of actually having a value and a second case which is
used only to inject into a region an actor derived value.

rdar://119113563
2023-12-04 00:40:09 -06:00
Michael Gottesman
dc33b2deb8 [region-isolation] Add support for final actor setters.
rdar://119113959
2023-12-04 00:40:09 -06:00
Michael Gottesman
520e12430e Merge pull request #70192 from gottesmm/pr-9508a33268dfb29c47747efbe4184b43b0352dd9
[region-isolation] Add support for global actors.
2023-12-03 15:12:24 -06:00
Michael Gottesman
24bb04bfff [region-isolation] Add support for global actors.
rdar://119099990
2023-12-03 12:34: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
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
1166e229c8 [region-isolation] Clean up diagnostics to use helper functions.
Just makes things a little cleaner than invoking the methods on ASTContext
directly.
2023-11-26 17:13:04 -08:00
Michael Gottesman
7653552b2d [region-isolation] Loosen handling around fields that are safe to access concurrently.
Specifically:

1. Classes. We allow for access to Sendable let fields.
2. Structs. We allow for access to Sendable let/var fields.
3. Tuples. We allow for access to Sendable let/var fields.

I am going to finish enums in a subsequent PR since I found that I need to mark
a bunch more instructions as look through to get that to work (e.x.:
load/load_borrow need to be viewed as a cast from address -> object so that we
can emit errors on the uses of the load instead of the load itself). These are
more invasive so I want to do it a little later.

rdar://115124361
2023-11-16 15:10:39 -08:00
Michael Gottesman
7680332b93 Merge pull request #69906 from gottesmm/use-tracked-transfer-inst
[region-isolation] Since we now propagate the transferred instruction, use that to emit the error instead of attempting to infer the transfer instruction for a requires
2023-11-16 10:06:03 -08:00
Michael Gottesman
46c4da307e Delete new dead code 2023-11-15 18:58:06 -08:00
Michael Gottesman
c0b3efedbf [region-isolation] Instead of using the complex logic to emit all possible requires... just emit a diagnostic on the first require that we see.
This involved me removing the complex logic for emitting diagnostics we have
today in favor of a simple diagnostic that works by:

1. Instead of searching for transfer instructions, we use the transfer
instruction that we propagated by the dataflow... so there is no way for us to
possible not identify a transfer instruction... we always have it.

2. Instead of emitting diagnostics for all requires, just emit a warning for the
first require we see along a path. The reason that we need to do this is that in
certain cases we will have multiple "require" instructions from slightly
different source locations (e.x.: differing by column) but on the same line. I
saw this happen specifically with print where we treat stores into the array as
a require as well as the actual call to print itself where we pass the array.

An additional benefit of this is that this allowed me to get rid of the
cache of already seen require instructions. By doing this, we now emit errors
when the same apply needs to be required by different transfer instructions for
different arguments.

NOTE: I left in the original implementation code to make it easier to review
this new code. I deleted it in the next commit. Otherwise the git diff for this
patch is too difficult to read.
2023-11-15 18:58:06 -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
fc73210f67 [region-isolation] Do not look through type casts from a non-Sendable to a Sendable value.
This came up while I was debugging test cases from the other parts of this
work. The specific issue was around a pointer_to_address from a
RawPointer (which is considered non-Sendable) to a Sendable type. We were
identifying the RawPointer as being the representative of the Sendable value
implying we were processing Sendable values like they were
non-Sendable. =><=. I wish we had SIL test cases for region isolation since I
would add one for this...
2023-11-15 18:58:06 -08:00
Michael Gottesman
9e3f0b068f Rename SILApplyCrossesIsolation -> isIsolationBoundaryCrossingApply.
Just a better name with better camelCasing.
2023-11-15 18:58:06 -08:00
Michael Gottesman
ca663b1583 [region-isolation] Make it possible to "lookThrough" multiple result instructions and use it to lookthrough destructures.
Currently when one says that an instruction is not a "look through" instruction,
each of its results gets a separate element number and we track these results as
independent entities that can be in a region. The one issue with this is
whenever we perform this sort of operation we actually are at the same time
performing a require on the operand of the instruction. This causes us to emit
errors on non-side effect having instructions when we really want to emit an
error on their side-effect having results. As an example of the world before
this patch, the following example would force the struct_element_addr to have a
require so we would emit an error on it instead of the apply (the thing that we
actually care about):

```
%0 = ...

// We transferred %0, so we cannot use it again.
apply %transfer(%0)

// We track %1 and %0 as separate elements and we treat this as an assignment of
// %0 into %1 which forces %0 to be required live at this point causing us to
// emit an error here...
%1 = struct_element_addr %0

// Instead of in the SIL here on the actual side-effect having instruction.
apply %actualUse(%1)
```

the solution is to make instructions like struct_element_addr lookthrough
instructions which force their result to just be the same element as their
operand. As part of doing this, we have to ensure that getUnderlyingTrackedValue
knows how to look through these types. This ensures that they are not considered
roots.

----

As an aside to implement this I needed to compose some functionality ontop of
getUnderlyingObject (specifically the look through behavior on destructures) in
a new helper routine called getUnderlyingTrackedObjectValue(). It just in a loop
calls getUnderlyingObject() and looks through destructures until its iterator
doesn't change.
2023-11-13 19:59:31 -08:00
Doug Gregor
273937a9fd Merge pull request #69824 from DougGregor/typed-throws-fixes
Yet more typed throws fixes
2023-11-13 19:30:48 -08:00
Doug Gregor
27b6a64761 Teach TransferNonSendable that not all Error BB's have arguments 2023-11-13 14:24:13 -08:00
Michael Gottesman
44e1e54e13 Merge pull request #69787 from gottesmm/region-isolation-track-consumption-separately
[region-isolation] Track elements -> regions and regions -> consuming separately.
2023-11-13 11:15:46 -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
389078d4fa [region-isolation] Remove count of emitted diagnostics from main diagnostic.
These are not actionable to the user.
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
b760fecd2b [region-isolation] Just iterate directly over a block state's partition ops rather than use a callback.
There isn't a strong reason to use a callback here since we aren't ever
composing transformations on partition ops. Better instead to go for simplicity
and just iterate directly. If we start doing complex transformations over
partition ops with composable APIs, we can always add this back in later. As a
nice benefit, one doesn't need to worry that the callback API is hiding actual
complexity... since just by using a for loop we communicate that nothing
interesting is happening here.

Just reducing the amount of code surface area in the pass.
2023-11-10 12:48:45 -08:00
Michael Gottesman
332ba1f7d1 [region-isolation] Simplifying how we emit diagnostic by inlining diagnoseFailures.
This is only used in one place in partition analysis which is a data structure
that does computation. In contrast, we want BlockPartitionState to be more of a
POD type of data that each BasicBlock has mapped to it.

Simplifying the code.

This also let me get rid of the translator field in BasicBlockState. We only
need to pass it in as an argument to the constructor to initialize our
translation. It doesn't need to be stored anymore.
2023-11-10 12:48:45 -08:00
Michael Gottesman
80e1ebe623 [region-isolation] Add some MARK: to make the file a little easier to read. NFC. 2023-11-10 12:48:45 -08:00
Daniel Rodríguez Troitiño
36a15ceaf4 [sil] Fix the no-assert build by not calling an ifndef NDEBUG method (part 2) (#69716)
PR #69652 protected one call of `printID` but left another two in the
file. Create two small lambdas to print the ID with `printID` or just
print `NOASSERTS` depending on `NDEBUG` being defined. Change all the
callsites of `printID` to use that lambda.
2023-11-08 03:25:09 -08:00
Michael Gottesman
b1f69030fc [region-isolation] When assigning RValues into memory, use tuple_addr_constructor instead of doing it in pieces.
I also included changes to the rest of the SIL optimizer pipeline to ensure that
the part of the optimizer pipeline before we lower tuple_addr_constructor (which
is right after we run TransferNonSendable) work as before.

The reason why I am doing this is that this ensures that diagnostic passes can
tell the difference in between:

```
x = (a, b, c)
```

and

```
x.0 = a
x.1 = b
x.2 = c
```

This is important for things like TransferNonSendable where assigning over the
entire tuple element is treated differently from if one were to initialize it in
pieces using projections.

rdar://117880194
2023-11-07 15:38:33 -08:00
Michael Gottesman
d2b5bc33a1 [sil-optimizer] Add a small pass that runs after TransferNonSendable and eliminates tuple addr constructor.
This will limit the number of passes that need to be updated to handle
tuple_addr_constructor.
2023-11-06 15:47:15 -08:00
Michael Gottesman
ca1416f935 [sil] Fix the no-asserts build by not calling a ifndef NDEBUG method. 2023-11-03 16:03:53 -07:00
Michael Gottesman
345b4cc0a9 [region-isolation] Assigns to var with structs/tuples with multiple fields should be merges for now.
The reason that this is being done is that since currently region based
isolation is not field sensitive, when we assign to the struct or tuple field of
the var, the new region relationship is set for the entire struct, not just a
specific field. This means that we effectively lose any region information from
the other fields. For example in the following at (1), given the current rules, we
lose that s.y was assigned to x:

```swift
struct S {
  var x: NonSendableKlass
  var y: NonSendableKlass
}

func foo() {
  var s = S()
  // Regions: [s]
  let x = NonSendableKlass()
  let y = NonSendableKlass()
  // Regions: [(s), (x), (y)]
  s.y = x
  // Regions: [(s, x), (y)]
  s.x = y                       (1)
  // Regions: [(x), (s, y)]
}
```

The best solution to this is to track such var like bindings in a field
sensitive manner where the regions of the aggregate are the union of the
individual fields. This would let us represent the regions of the above as
follows:

```swift
func foo() {
  var s = S()
  // Regions: [((s.x), (s.y))]
  let x = NonSendableKlass()
  let y = NonSendableKlass()
  // Regions: [((s.x), (s.y)), (x), (y)]
  s.y = x
  // Regions: [((s.x), (s.y, x)), (y)]
  s.x = y                       (1)
  // Regions: [((s.x, y), (s.y, x))]
}
```

We cannot do this today so to plug this hole, we instead treat these operations
as merges. This provides a conservative answer. Thus we would have:"

```swift
func foo() {
  var s = S()
  // Regions: [s]
  let x = NonSendableKlass()
  let y = NonSendableKlass()
  // Regions: [(s), (x), (y)]
  s.y = x
  // Regions: [(s, x), (y)]
  s.x = y                       (1)
  // Regions: [(s, x, y])
}
```

This is because we are treating the assignment into s.y and s.x as merges into
s, so we do not lose that y was assigned into s before we assigned y into
it. The unfortunate side-effect of this is that if a struct or tuple has
multiple fields, the merge makes it so that if we assign over the same field, we
do not lose the region of the old value:

```swift
func foo() {
  var s = S()
  // Regions: [s]
  let x = NonSendableKlass()
  let y = NonSendableKlass()
  // Regions: [(s), (x), (y)]
  s.y = x
  // Regions: [(s, x), (y)]
  s.y = y                    (1)
  // Regions: [(s, x, y])
}
```

If we were not to do this, then the s.y at (1) would result in s and x being in
different regions.

rdar://117273952
2023-11-02 16:37:10 -07:00
Michael Gottesman
2835bb4603 [region-isolation] Instead of using AccessStorage to find our address root, use our own UseDefChainVisitor.
This gives me more control in getUnderlyingTrackedValue and also will allow me
to define another function that can detect if we see a projection on an address
so I can change it into merges in the next commit.
2023-11-01 21:57:17 -07:00
Michael Gottesman
9dc9de9aa8 [region-isolation] Be explicit if an instruction will never actually assign.
Currently when we create an assign instruction, if we find that the result of
the instruction and the operand of the instruction reduce to the same element
representative, then we do not actually emit an assign.

For certain instructions this makes sense, but this is misleading for
instructions like copies (copy_value) and geps (struct_element_addr) that this
is always true for. Instead of attempting to assign and just have the builder
always clean this up... make it explicit with a new routine called
translateSILLookThrough. When this is called, we just look up the value and
assert.
2023-11-01 21:57:17 -07: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
888c66a7a7 [region-isolation] Instead of passing in boolean flags to translateMultiAssign, pass in an OptionSet.
I am going to be adding a second flag to be passed into translateMultiAssign.
Having multiple boolean flags can lead to confusion and mistakes when using the
API, so before I do that I am using this as an NFC commit to convert the API to
take an option set so the options are explicit.
2023-10-27 15:13:43 -07:00
Michael Gottesman
069a65d5be [region-isolation] Convert a static global to a constexpr. 2023-10-27 15:08:55 -07:00
Michael Gottesman
dc8e4794a8 [region-isolation] Make sure that we treat Sendable functions as Sendable.
The reason for this issue is that we were originally checking in our NonSendable
type oracle just if a type conformed to Sendable. But function types do not
conform to protocols, so this would fail for protocols. To fix this, I added
some helper methods to call swift::isSendableType on SILType, called that in our
oracle, and then I added support in swift::isSendableType for both
SILFunctionType and AnyFunctionType so that we correctly handle them depending
on their sendability.

There was also a question if we also handled function conversions correctly. I
added a test case that shows that we do not error in either of the cases.

Another nice aspect of this change is that we no longer need to stash a pointer
to a looked up Sendable protocol to perform the check since that just happens
naturally inside SILType::isSendable() when it calls isSendableType. This is a
better separation of concerns.

rdar://116525224
2023-10-27 11:13:35 -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
a0e1507c33 [region-isolation] Fix typo. translater -> translator. 2023-10-26 12:02:20 -07:00
Michael Gottesman
cb46851194 [region-isolation] Rename the experimental feature to RegionBasedIsolation.
This ensures that the pass is called TransferNonSendable but the experimental
feature is RegionBasedIsolation.
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