Skip to content

Commit 22d9e85

Browse files
authored
Merge pull request #70758 from hborla/generalize-isolated-param
[Concurrency] Allow isolated parameters to have optional type.
2 parents 396ecdf + 74d7a25 commit 22d9e85

File tree

10 files changed

+410
-148
lines changed

10 files changed

+410
-148
lines changed

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ EXPERIMENTAL_FEATURE(Extern, true)
254254
// global functions and key paths.
255255
EXPERIMENTAL_FEATURE(InferSendableFromCaptures, false)
256256

257+
// Allow optional isolated parameters.
258+
EXPERIMENTAL_FEATURE(OptionalIsolatedParameters, true)
259+
257260
/// Enable the `@_staticExclusiveOnly` attribute.
258261
EXPERIMENTAL_FEATURE(StaticExclusiveOnly, true)
259262

lib/AST/ASTPrinter.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3827,6 +3827,25 @@ static bool usesFeatureInferSendableFromCaptures(Decl *decl) {
38273827
return false;
38283828
}
38293829

3830+
static bool usesFeatureOptionalIsolatedParameters(Decl *decl) {
3831+
auto *value = dyn_cast<ValueDecl>(decl);
3832+
if (!value)
3833+
return false;
3834+
3835+
auto *paramList = getParameterList(value);
3836+
if (!paramList)
3837+
return false;
3838+
3839+
for (auto param : *paramList) {
3840+
if (param->isIsolated()) {
3841+
auto paramType = param->getInterfaceType();
3842+
return !paramType->getOptionalObjectType().isNull();
3843+
}
3844+
}
3845+
3846+
return false;
3847+
}
3848+
38303849
static bool usesFeaturePlaygroundExtendedCallbacks(Decl *decl) {
38313850
return false;
38323851
}

lib/SILGen/SILGenProlog.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,15 +1507,6 @@ SILValue SILGenFunction::emitLoadActorExecutor(SILLocation loc,
15071507
else
15081508
actorV = actor.borrow(*this, loc).getValue();
15091509

1510-
// Open an existential actor type.
1511-
CanType actorType = actor.getType().getASTType();
1512-
if (actorType->isExistentialType()) {
1513-
actorType = OpenedArchetypeType::get(
1514-
actorType, F.getGenericSignature())->getCanonicalType();
1515-
SILType loweredActorType = getLoweredType(actorType);
1516-
actorV = B.createOpenExistentialRef(loc, actorV, loweredActorType);
1517-
}
1518-
15191510
// For now, we just want to emit a hop_to_executor directly to the
15201511
// actor; LowerHopToActor will add the emission logic necessary later.
15211512
return actorV;

lib/SILOptimizer/Mandatory/LowerHopToActor.cpp

Lines changed: 171 additions & 86 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);
8994

90-
return changed;
95+
// If we didn't record any dominating actor hops that need
96+
// transformation, we're done.
97+
if (DominatingActorHops.empty())
98+
return false;
99+
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,6 +239,14 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
204239
assert(req && "Concurrency library broken");
205240
SILDeclRef fn(req, SILDeclRef::Kind::Func);
206241

242+
// Open an existential actor type.
243+
if (actorType->isExistentialType()) {
244+
actorType = OpenedArchetypeType::get(
245+
actorType, F->getGenericSignature())->getCanonicalType();
246+
SILType loweredActorType = F->getLoweredType(actorType);
247+
actor = B.createOpenExistentialRef(loc, actor, loweredActorType);
248+
}
249+
207250
auto actorConf = module->lookupConformance(actorType, actorProtocol);
208251
assert(actorConf &&
209252
"hop_to_executor with actor that doesn't conform to Actor or DistributedActor");
@@ -222,22 +265,64 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
222265
auto executorDecl = ctx.getUnownedSerialExecutorDecl();
223266
auto executorProps = executorDecl->getStoredProperties();
224267
assert(executorProps.size() == 1);
225-
unmarkedExecutor =
226-
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+
}
227320
}
228321

229322
// Mark the dependence of the resulting value on the actor value to
230323
// force the actor to stay alive.
231-
executor = B.createMarkDependence(loc, unmarkedExecutor, actor,
232-
/*isNonEscaping*/false);
233-
234-
// Cache the non-optional result for later.
235-
ExecutorForActor.insert(actor, executor);
236-
237-
// Inject the result into an optional if requested.
238-
if (makeOptional)
239-
executor = B.createOptionalSome(loc, executor,
240-
SILType::getOptionalType(executor->getType()));
324+
SILValue executor = B.createMarkDependence(loc, unmarkedExecutor, actor,
325+
/*isNonEscaping*/false);
241326

242327
return executor;
243328
}
@@ -250,7 +335,7 @@ class LowerHopToActorPass : public SILFunctionTransform {
250335
auto domTree = getAnalysis<DominanceAnalysis>()->get(fn);
251336
LowerHopToActor pass(getFunction(), domTree);
252337
if (pass.run())
253-
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
338+
invalidateAnalysis(SILAnalysis::InvalidationKind::BranchesAndInstructions);
254339
}
255340
};
256341

0 commit comments

Comments
 (0)