Skip to content

Commit 0db14c9

Browse files
committed
[SILOptimizer] Derive optional executor values from optional actor values
in LowerHopToActor. In order to project an optional executor value from an optional actor value, the executor lowering needs to modify the CFG. This is done by splitting LowerHopToActor into two passes. The first pass records all actor operands of hop_to_executor and extract_executor instructions and records the dominating instruction that will derive the executor value. The second pass iterates over the multi-map of dominating instructions, derives the executor value, and rewrites the operands of all reachable hop_to_executor and extract_executor instructions to reuse that executor value.
1 parent c0e9178 commit 0db14c9

File tree

2 files changed

+227
-132
lines changed

2 files changed

+227
-132
lines changed

lib/SILOptimizer/Mandatory/LowerHopToActor.cpp

Lines changed: 163 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#define DEBUG_TYPE "insert-hop-to-executor"
14+
#include "swift/Basic/FrozenMultiMap.h"
1415
#include "swift/SIL/SILBuilder.h"
1516
#include "swift/SIL/SILFunction.h"
1617
#include "swift/SIL/Dominance.h"
@@ -51,11 +52,18 @@ class LowerHopToActor {
5152
SILFunction *F;
5253
DominanceInfo *Dominance;
5354

54-
/// A map from an actor value to the executor we've derived for it.
55-
llvm::ScopedHashTable<SILValue, SILValue> ExecutorForActor;
55+
/// A map from an actor value to the dominating instruction that
56+
/// will derive the executor.
57+
llvm::ScopedHashTable<SILValue, SILInstruction *>
58+
ExecutorDerivationForActor;
5659

57-
bool processHop(HopToExecutorInst *hop);
58-
bool processExtract(ExtractExecutorInst *extract);
60+
/// A multi-map from a dominating {hop_to_|extract_}executor instruction
61+
/// to other reachable {hop_to_|extract_}executor instructions.
62+
SmallFrozenMultiMap<SILInstruction *, SILInstruction *, 4>
63+
DominatingActorHops;
64+
65+
void recordDominatingInstFor(SILInstruction *inst);
66+
void rewriteInstructions();
5967

6068
SILValue emitGetExecutor(SILBuilderWithScope &B,
6169
SILLocation loc,
@@ -73,21 +81,24 @@ class LowerHopToActor {
7381
};
7482

7583
bool LowerHopToActor::run() {
76-
bool changed = false;
77-
84+
// Record all actor operands to hop_to_executor and extract_executor
85+
// and the dominating instruction that will derive the executor.
7886
auto runOnBlock = [&](SILBasicBlock *block) {
7987
for (auto ii = block->begin(), ie = block->end(); ii != ie; ) {
8088
SILInstruction *inst = &*ii++;
81-
if (auto *hop = dyn_cast<HopToExecutorInst>(inst)) {
82-
changed |= processHop(hop);
83-
} else if (auto *extract = dyn_cast<ExtractExecutorInst>(inst)) {
84-
changed |= processExtract(extract);
85-
}
89+
recordDominatingInstFor(inst);
8690
}
8791
};
88-
runInDominanceOrderWithScopes(Dominance, runOnBlock, ExecutorForActor);
92+
runInDominanceOrderWithScopes(Dominance, runOnBlock,
93+
ExecutorDerivationForActor);
94+
95+
// If we didn't record any dominating actor hops that need
96+
// transformation, we're done.
97+
if (DominatingActorHops.empty())
98+
return false;
8999

90-
return changed;
100+
rewriteInstructions();
101+
return true;
91102
}
92103

93104
static bool isOptionalBuiltinExecutor(SILType type) {
@@ -96,49 +107,73 @@ static bool isOptionalBuiltinExecutor(SILType type) {
96107
return false;
97108
}
98109

99-
/// Search for hop_to_executor instructions with actor-typed operands.
100-
bool LowerHopToActor::processHop(HopToExecutorInst *hop) {
101-
auto actor = hop->getTargetExecutor();
110+
void LowerHopToActor::recordDominatingInstFor(SILInstruction *inst) {
111+
SILValue actor;
112+
if (auto *hop = dyn_cast<HopToExecutorInst>(inst)) {
113+
actor = hop->getTargetExecutor();
114+
// If hop_to_executor was emitted with an optional executor operand,
115+
// there's nothing to derive.
116+
if (isOptionalBuiltinExecutor(actor->getType())) {
117+
return;
118+
}
119+
} else if (auto *extract = dyn_cast<ExtractExecutorInst>(inst)) {
120+
actor = extract->getExpectedExecutor();
121+
} else {
122+
return;
123+
}
102124

103-
// Ignore hops that are already to Optional<Builtin.Executor>.
104125
if (isOptionalBuiltinExecutor(actor->getType()))
105-
return false;
126+
return;
106127

107-
SILBuilderWithScope B(hop);
108-
SILValue executor;
109-
if (actor->getType().is<BuiltinExecutorType>()) {
110-
// IRGen expects an optional Builtin.Executor, not a Builtin.Executor
111-
// but we can wrap it nicely
112-
executor = B.createOptionalSome(
113-
hop->getLoc(), actor,
114-
SILType::getOptionalType(actor->getType()));
128+
auto *dominatingInst = ExecutorDerivationForActor.lookup(actor);
129+
if (dominatingInst) {
130+
DominatingActorHops.insert(dominatingInst, inst);
115131
} else {
116-
// Get the dominating executor value for this actor, if available,
117-
// or else emit code to derive it.
118-
executor = emitGetExecutor(B, hop->getLoc(), actor, /*optional*/true);
132+
DominatingActorHops.insert(inst, inst);
133+
ExecutorDerivationForActor.insert(actor, inst);
119134
}
120-
assert(executor && "executor not set");
121135

122-
B.createHopToExecutor(hop->getLoc(), executor, /*mandatory*/ false);
136+
return;
137+
}
123138

124-
hop->eraseFromParent();
139+
void LowerHopToActor::rewriteInstructions() {
140+
// Lower the actor operands to executors. Dominating instructions
141+
// will perform the derivation, and the result will be reused in
142+
// all reachable instructions.
143+
DominatingActorHops.setFrozen();
144+
for (auto domInst : DominatingActorHops.getRange()) {
145+
auto derivationInst = domInst.first;
146+
147+
SILValue actor;
148+
bool makeOptional;
149+
if (auto *hop = dyn_cast<HopToExecutorInst>(derivationInst)) {
150+
actor = hop->getTargetExecutor();
151+
makeOptional = true;
152+
} else if (auto *extract = dyn_cast<ExtractExecutorInst>(derivationInst)) {
153+
actor = extract->getExpectedExecutor();
154+
makeOptional = false;
155+
} else {
156+
continue;
157+
}
125158

126-
return true;
127-
}
159+
// Emit the executor derivation at the dominating instruction.
160+
SILBuilderWithScope builder(derivationInst);
161+
auto executor = emitGetExecutor(
162+
builder, derivationInst->getLoc(), actor, makeOptional);
163+
derivationInst->setOperand(0, executor);
164+
165+
// Set the executor value as the operand for all reachable instructions.
166+
auto reachableInsts = domInst.second;
167+
for (auto inst : reachableInsts) {
168+
if (auto *extract = dyn_cast<ExtractExecutorInst>(inst)) {
169+
extract->replaceAllUsesWith(executor);
170+
extract->eraseFromParent();
171+
continue;
172+
}
128173

129-
bool LowerHopToActor::processExtract(ExtractExecutorInst *extract) {
130-
// Dig out the executor.
131-
auto executor = extract->getExpectedExecutor();
132-
if (!isOptionalBuiltinExecutor(executor->getType())) {
133-
SILBuilderWithScope B(extract);
134-
executor =
135-
emitGetExecutor(B, extract->getLoc(), executor, /*optional*/ false);
174+
inst->setOperand(0, executor);
175+
}
136176
}
137-
138-
// Unconditionally replace the extract with the executor.
139-
extract->replaceAllUsesWith(executor);
140-
extract->eraseFromParent();
141-
return true;
142177
}
143178

144179
static bool isDefaultActorType(CanType actorType, ModuleDecl *M,
@@ -162,40 +197,40 @@ static AccessorDecl *getUnownedExecutorGetter(ASTContext &ctx,
162197
SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
163198
SILLocation loc, SILValue actor,
164199
bool makeOptional) {
165-
// Get the dominating executor value for this actor, if available,
166-
// or else emit code to derive it.
167-
SILValue executor = ExecutorForActor.lookup(actor);
168-
if (executor) {
169-
if (makeOptional)
170-
executor = B.createOptionalSome(loc, executor,
171-
SILType::getOptionalType(executor->getType()));
172-
return executor;
173-
}
174-
175200
// This is okay because actor types have to be classes and so never
176201
// have multiple abstraction patterns.
177202
CanType actorType = actor->getType().getASTType();
178203

179-
auto &ctx = F->getASTContext();
180-
auto resultType = SILType::getPrimitiveObjectType(ctx.TheExecutorType);
181-
182-
// If the actor type is a default actor, go ahead and devirtualize here.
183-
auto module = F->getModule().getSwiftModule();
184-
SILValue unmarkedExecutor;
204+
// If the operand is already a BuiltinExecutorType, just wrap it
205+
// in an optional.
206+
if (makeOptional && actor->getType().is<BuiltinExecutorType>()) {
207+
return B.createOptionalSome(
208+
loc, actor,
209+
SILType::getOptionalType(actor->getType()));
210+
}
185211

186-
// Determine if the actor is a "default actor" in which case we'll build a default
187-
// actor executor ref inline, rather than calling out to the user-provided executor function.
188-
if (isDefaultActorType(actorType, module, F->getResilienceExpansion())) {
189-
auto builtinName = ctx.getIdentifier(
190-
getBuiltinName(BuiltinValueKind::BuildDefaultActorExecutorRef));
191-
auto builtinDecl = cast<FuncDecl>(getBuiltinValueDecl(ctx, builtinName));
192-
auto subs = SubstitutionMap::get(builtinDecl->getGenericSignature(),
193-
{actorType}, {});
194-
unmarkedExecutor =
195-
B.createBuiltin(loc, builtinName, resultType, subs, {actor});
212+
auto &ctx = F->getASTContext();
213+
auto executorType = SILType::getPrimitiveObjectType(ctx.TheExecutorType);
214+
auto optionalExecutorType = SILType::getOptionalType(executorType);
215+
216+
/// Emit the instructions to derive an executor value from an actor value.
217+
auto getExecutorFor = [&](SILValue actor) -> SILValue {
218+
// If the actor type is a default actor, go ahead and devirtualize here.
219+
auto module = F->getModule().getSwiftModule();
220+
CanType actorType = actor->getType().getASTType();
221+
222+
// Determine if the actor is a "default actor" in which case we'll build a default
223+
// actor executor ref inline, rather than calling out to the user-provided executor function.
224+
if (isDefaultActorType(actorType, module, F->getResilienceExpansion())) {
225+
auto builtinName = ctx.getIdentifier(
226+
getBuiltinName(BuiltinValueKind::BuildDefaultActorExecutorRef));
227+
auto builtinDecl = cast<FuncDecl>(getBuiltinValueDecl(ctx, builtinName));
228+
auto subs = SubstitutionMap::get(builtinDecl->getGenericSignature(),
229+
{actorType}, {});
230+
return B.createBuiltin(loc, builtinName, executorType, subs, {actor});
231+
}
196232

197233
// Otherwise, go through (Distributed)Actor.unownedExecutor.
198-
} else {
199234
auto actorKind = actorType->isDistributedActor() ?
200235
KnownProtocolKind::DistributedActor :
201236
KnownProtocolKind::Actor;
@@ -204,8 +239,6 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
204239
assert(req && "Concurrency library broken");
205240
SILDeclRef fn(req, SILDeclRef::Kind::Func);
206241

207-
// FIXME: Handle optional
208-
209242
// Open an existential actor type.
210243
if (actorType->isExistentialType()) {
211244
actorType = OpenedArchetypeType::get(
@@ -232,22 +265,64 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
232265
auto executorDecl = ctx.getUnownedSerialExecutorDecl();
233266
auto executorProps = executorDecl->getStoredProperties();
234267
assert(executorProps.size() == 1);
235-
unmarkedExecutor =
236-
B.createStructExtract(loc, witnessCall, executorProps[0]);
268+
return B.createStructExtract(loc, witnessCall, executorProps[0]);
269+
};
270+
271+
SILValue unmarkedExecutor;
272+
if (auto wrappedActor = actorType->getOptionalObjectType()) {
273+
assert(makeOptional);
274+
275+
// Unwrap the optional and call 'unownedExecutor'.
276+
auto *someDecl = B.getASTContext().getOptionalSomeDecl();
277+
auto *curBB = B.getInsertionPoint()->getParent();
278+
auto *contBB = curBB->split(B.getInsertionPoint());
279+
auto *someBB = B.getFunction().createBasicBlockAfter(curBB);
280+
auto *noneBB = B.getFunction().createBasicBlockAfter(someBB);
281+
282+
unmarkedExecutor = contBB->createPhiArgument(
283+
optionalExecutorType, actor->getOwnershipKind());
284+
285+
SmallVector<std::pair<EnumElementDecl *, SILBasicBlock *>, 1> caseBBs;
286+
caseBBs.push_back(std::make_pair(someDecl, someBB));
287+
B.setInsertionPoint(curBB);
288+
auto *switchEnum = B.createSwitchEnum(loc, actor, noneBB, caseBBs);
289+
290+
SILValue unwrappedActor;
291+
if (B.hasOwnership()) {
292+
unwrappedActor = switchEnum->createOptionalSomeResult();
293+
B.setInsertionPoint(someBB);
294+
} else {
295+
B.setInsertionPoint(someBB);
296+
unwrappedActor = B.createUncheckedEnumData(loc, actor, someDecl);
297+
}
298+
299+
// Call 'unownedExecutor' in the some block and wrap the result into
300+
// an optional.
301+
SILValue unwrappedExecutor = getExecutorFor(unwrappedActor);
302+
SILValue someValue =
303+
B.createOptionalSome(loc, unwrappedExecutor, optionalExecutorType);
304+
B.createBranch(loc, contBB, {someValue});
305+
306+
// In the none case, create a nil executor value, which represents
307+
// the generic executor.
308+
B.setInsertionPoint(noneBB);
309+
SILValue noneValue = B.createOptionalNone(loc, optionalExecutorType);
310+
B.createBranch(loc, contBB, {noneValue});
311+
B.setInsertionPoint(contBB->begin());
312+
} else {
313+
unmarkedExecutor = getExecutorFor(actor);
314+
315+
// Inject the result into an optional if requested.
316+
if (makeOptional) {
317+
unmarkedExecutor = B.createOptionalSome(loc, unmarkedExecutor,
318+
SILType::getOptionalType(unmarkedExecutor->getType()));
319+
}
237320
}
238321

239322
// Mark the dependence of the resulting value on the actor value to
240323
// force the actor to stay alive.
241-
executor = B.createMarkDependence(loc, unmarkedExecutor, actor,
242-
/*isNonEscaping*/false);
243-
244-
// Cache the non-optional result for later.
245-
ExecutorForActor.insert(actor, executor);
246-
247-
// Inject the result into an optional if requested.
248-
if (makeOptional)
249-
executor = B.createOptionalSome(loc, executor,
250-
SILType::getOptionalType(executor->getType()));
324+
SILValue executor = B.createMarkDependence(loc, unmarkedExecutor, actor,
325+
/*isNonEscaping*/false);
251326

252327
return executor;
253328
}
@@ -260,7 +335,7 @@ class LowerHopToActorPass : public SILFunctionTransform {
260335
auto domTree = getAnalysis<DominanceAnalysis>()->get(fn);
261336
LowerHopToActor pass(getFunction(), domTree);
262337
if (pass.run())
263-
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
338+
invalidateAnalysis(SILAnalysis::InvalidationKind::BranchesAndInstructions);
264339
}
265340
};
266341

0 commit comments

Comments
 (0)