Specifically:
1. We assume in nonisolated(nonsending) that we are already on the relevant
actor. This lets us always eliminate the initial hop_to_executor.
2. We stopped treating nonisolated(nonsending) functions as suspension points
since we are guaranteed to always be on the same actor when we enter/return.
3. Now that nonisolated(nonsending) is no longer a suspension point, I could
sink the needs executor nonisolated(nonsending) specific code into the needs
executor code. For those unfamiliar it is that we: a. treat a
nonisolated(nonsending) callee as a needs executor since we are no longer
guaranteed to hop in callees and b. treat returns from nonisolated(nonsending)
functions as being a needs executor instruction since we are no longer
guaranteed to hop in the caller after such a function returns.
rdar://155465878
Specifically for 6.2, we are making optimize hop to executor more conservative
around caller isolation inheriting functions. This means that we are:
1. No longer treating calls to caller isolation inheriting functions as having a
hop in their prologue. In terms of this pass, it means that when determining
dead hop to executors, we no longer think that a caller isolation inheriting
function means that an earlier hop to executor is not required.
2. Treating returns from caller isolation inheriting callees as requiring a
hop. The reason why we are doing this is that we can no longer assume that our
caller will hop after we return.
Post 6.2, there are three main changes we are going to make:
* Forward Dataflow
Caller isolation inheriting functions will no longer be treated as suspension
points meaning that we will be able to propagate hops over them and can assume
that we know the actor that we are on when we enter the function. Practically
this means that trees of calls that involve just nonisolated(nonsending) async
functions will avoid /all/ hop to executor calls since we will be able to
eliminate all of them since the dataflow will just propagate forward from the
entrance that we are already on the actor.
* Backwards Dataflow
A caller isolation inheriting call site will still cause preceding
hop_to_executor functions to be live. This is because we need to ensure that we
are on the caller isolation inheriting actor before we hit the call site. If we
are already on that actor, the hop will be eliminated by the forward pass. But
if the hop has not been eliminated, then the hop must be needed to return us to
the appropriate actor.
We will also keep the behavior that returns from a caller isolation inheriting
function are considered to keep hop to executors alive. If we were able to
propagate to a hop to executor before the return inst with the forward dataflow,
then we know that we are guaranteed to still be on the relevant actor. If the
hop to executor is still there, then we need it to ensure that our caller can
treat the caller isolation inheriting function as a non-suspension point.
rdar://155905383
This requires two major changes.
The first is that we need to teach SILGen that the isolation of an initializer
is essentially dynamic (as far as SILGen is concerned) --- that it needs to emit
code in order to get the isolation reference. To make this work, I needed to
refactor how we store the expected executor of a function so that it's not
always a constant value; instead, we'll need to emit code that DI will lower
properly. Fortunately, I can largely build on top of the work that Doug previously
did to support #isolation in these functions. The SIL we emit here around delegating
initializer calls is not ideal --- the breadcrumb hop ends up jumping to the
generic executor, and then DI actually emits the hop to the actor. This is a little
silly, but it's hard to eliminate without special-casing the self-rebinding, which
honestly we should consider rather than the weirdly global handling of that in
SILGen today. The optimizer should eliminate this hop pretty reliably, at least.
The second is that we need to teach DI to handle the pattern of code we get in
delegating initializers, where the builtin actually has to be passed the self var
rather than a class reference. This is because we don't *have* a class reference
that's consistently correct in these cases. This ended up being a fairly
straightforward generalization.
I also taught the hop_to_executor optimizer to skip over the initialization of
the default-actor header; there are a lot of simple cases where we still do emit
the prologue generic-executor hop, but at least the most trivial case is handled.
To do this better, we'd need to teach this bit of the optimizer that the properties
of self can be stored to in an initializer prior to the object having escaped, and
we don't have that information easily at hand, I think.
Fixes rdar://87485045.
Although I don't plan to bring over new assertions wholesale
into the current qualification branch, it's entirely possible
that various minor changes in main will use the new assertions;
having this basic support in the release branch will simplify that.
(This is why I'm adding the includes as a separate pass from
rewriting the individual assertions)
Although they currently have MemoryBehavior::MayHaveSideEffects, the
begin_borrow and end_borrow instructions do not have side-effects of
interest to this pass.
The problem is in `OptimizeHopToExecutor::collectActors`, which is
causing `removeRedundantHopToExecutors` to drop the `hop_to_executor`. The issue
is triggered by the order in which the Actors map is populated, and the reuse
of a SILValue representing an executor. Suppose we have a function like this:
```
bb0:
%7 = builtin "getCurrentExecutor"() : $Optional<Builtin.Executor>
// ...
cond_br %12, bb2, bb1
bb1:
// ...
hop_to_executor %7 : $Optional<Builtin.Executor>
bb2:
// ...
hop_to_executor %7 : $Optional<Builtin.Executor>
// ...
hop_to_executor %28 : $MainActor
```
Since `collectActors` goes through each function's instructions top-down,
and assigns to each unique SILValue an ID equal to the size of the Actors map
prior to inserting the SILValue in the map, and we end up with the wrong actor IDs.
This commit changes the ID numbering scheme to correct the issue.
SILGen this builtin to a mandatory hop_to_executor with an actor type
operand.
e.g.
Task.detached {
Builtin.hopToActor(MainActor.shared)
await suspend()
}
Required to fix a bug in _runAsyncMain.
And rename MemoryDataflow -> BitDataflow.
MemoryLifetime contained MemoryLocations, MemoryDataflow and the MemoryLifetimeVerifier.
Three independent things, for which it makes sense to have them in three separated files.
NFC.
* Redundant hop_to_executor elimination: if a hop_to_executor is dominated by another hop_to_executor with the same operand, it is eliminated:
hop_to_executor %a
... // no suspension points
hop_to_executor %a // can be eliminated
* Dead hop_to_executor elimination: if a hop_to_executor is not followed by any code which requires to run on its actor's executor, it is eliminated:
hop_to_executor %a
... // no instruction which require to run on %a
return
rdar://problem/70304809