Skip to content

Commit 79fb05b

Browse files
committed
Concurrency: Hop back to the previous executor after actor calls.
Tasks shouldn't normally hog the actor context indefinitely after making a call that's bound to that actor, since that prevents the actor from potentially taking on other jobs it needs to be able to address. Set up SILGen so that it saves the current executor (using a new runtime entry point) and hops back to it after every actor call, not only ones where the caller context is also actor-bound. The added executor hopping here also exposed a bug in the runtime implementation while processing DefaultActor jobs, where if an actor job returned to the processing loop having already yielded the thread back to a generic executor, we would still attempt to make the actor give up the thread again, corrupting its state. rdar://71905765
1 parent c4c91e2 commit 79fb05b

20 files changed

+233
-62
lines changed

include/swift/AST/Builtins.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,13 @@ BUILTIN_MISC_OPERATION(DestroyDefaultActor, "destroyDefaultActor", "", Special)
736736
BUILTIN_MISC_OPERATION(Id, Name, Attrs, Overload)
737737
#endif
738738

739+
// getCurrentExecutor: () async -> Builtin.Word
740+
//
741+
// Retrieve the ExecutorRef on which the current asynchronous
742+
// function is executing.
743+
// Does not retain an actor executor.
744+
BUILTIN_MISC_OPERATION_WITH_SILGEN(GetCurrentExecutor, "getCurrentExecutor", "n", Special)
745+
739746
// getCurrentAsyncTask: () -> Builtin.NativeObject
740747
//
741748
// Retrieve the pointer to the task in which the current asynchronous

include/swift/Runtime/RuntimeFunctions.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,14 @@ FUNCTION(TaskSwitchFunc,
15811581
ARGS(SwiftContextPtrTy, Int8PtrTy, SwiftExecutorPtrTy),
15821582
ATTRS(NoUnwind))
15831583

1584+
// ExecutorRef swift_task_getCurrentExecutor();
1585+
FUNCTION(TaskGetCurrentExecutor,
1586+
swift_task_getCurrentExecutor, SwiftCC,
1587+
ConcurrencyAvailability,
1588+
RETURNS(IntPtrTy),
1589+
ARGS(),
1590+
ATTRS(NoUnwind, ArgMemOnly))
1591+
15841592
// void swift_defaultActor_initialize(DefaultActor *actor);
15851593
FUNCTION(DefaultActorInitialize,
15861594
swift_defaultActor_initialize, SwiftCC,

lib/AST/Builtins.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,13 @@ static ValueDecl *getGetCurrentAsyncTask(ASTContext &ctx, Identifier id) {
13421342
return getBuiltinFunction(id, { }, ctx.TheNativeObjectType);
13431343
}
13441344

1345+
static ValueDecl *getGetCurrentExecutor(ASTContext &ctx, Identifier id) {
1346+
BuiltinFunctionBuilder builder(ctx);
1347+
builder.setResult(makeConcrete(BuiltinIntegerType::getWordType(ctx)));
1348+
builder.setAsync();
1349+
return builder.build(id);
1350+
}
1351+
13451352
static ValueDecl *getCancelAsyncTask(ASTContext &ctx, Identifier id) {
13461353
return getBuiltinFunction(
13471354
id, { ctx.TheNativeObjectType }, ctx.TheEmptyTupleType);
@@ -2558,6 +2565,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
25582565
case BuiltinValueKind::GetCurrentAsyncTask:
25592566
return getGetCurrentAsyncTask(Context, Id);
25602567

2568+
case BuiltinValueKind::GetCurrentExecutor:
2569+
return getGetCurrentExecutor(Context, Id);
2570+
25612571
case BuiltinValueKind::CancelAsyncTask:
25622572
return getCancelAsyncTask(Context, Id);
25632573

lib/IRGen/GenBuiltin.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,16 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
219219
return;
220220
}
221221

222+
// getCurrentActor has no arguments.
223+
if (Builtin.ID == BuiltinValueKind::GetCurrentExecutor) {
224+
auto *call = IGF.Builder.CreateCall(IGF.IGM.getTaskGetCurrentExecutorFn(),
225+
{});
226+
call->setDoesNotThrow();
227+
call->setCallingConv(IGF.IGM.SwiftCC);
228+
out.add(call);
229+
return;
230+
}
231+
222232
// Everything else cares about the (rvalue) argument.
223233

224234
if (Builtin.ID == BuiltinValueKind::CancelAsyncTask) {

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ BUILTIN_OPERAND_OWNERSHIP(TrivialUse, AutoDiffCreateLinearMapContext)
798798
"Builtin should never be visited! E.x.: It may not have arguments"); \
799799
}
800800
SHOULD_NEVER_VISIT_BUILTIN(GetCurrentAsyncTask)
801+
SHOULD_NEVER_VISIT_BUILTIN(GetCurrentExecutor)
801802
#undef SHOULD_NEVER_VISIT_BUILTIN
802803

803804
// Builtins that should be lowered to SIL instructions so we should never see

lib/SIL/IR/ValueOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ CONSTANT_OWNERSHIP_BUILTIN(None, DestroyDefaultActor)
548548
CONSTANT_OWNERSHIP_BUILTIN(Owned, AutoDiffCreateLinearMapContext)
549549
CONSTANT_OWNERSHIP_BUILTIN(None, AutoDiffProjectTopLevelSubcontext)
550550
CONSTANT_OWNERSHIP_BUILTIN(None, AutoDiffAllocateSubcontext)
551+
CONSTANT_OWNERSHIP_BUILTIN(None, GetCurrentExecutor)
551552

552553
#undef CONSTANT_OWNERSHIP_BUILTIN
553554

lib/SILGen/ExecutorBreadcrumb.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===--- ExecutorBreadcrumb.h - executor hop tracking for SILGen ----------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
namespace swift {
14+
namespace Lowering {
15+
16+
/// Represents the information necessary to return to a caller's own
17+
/// active executor after making a hop to an actor for actor-isolated calls.
18+
class ExecutorBreadcrumb {
19+
SILValue Executor;
20+
21+
public:
22+
// An empty breadcrumb, indicating no hop back is necessary.
23+
ExecutorBreadcrumb() : Executor() {}
24+
25+
// A breadcrumb representing the need to hop back to the executor
26+
// represented by the given value.
27+
explicit ExecutorBreadcrumb(SILValue executor)
28+
: Executor(executor) {}
29+
30+
// Emits the hop back sequence, if any, necessary to get back to
31+
// the executor represented by this breadcrumb.
32+
void emit(SILGenFunction &SGF, SILLocation loc);
33+
};
34+
35+
} // namespace Lowering
36+
} // namespace swift

lib/SILGen/SILGenApply.cpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "ArgumentSource.h"
1515
#include "Callee.h"
1616
#include "Conversion.h"
17+
#include "ExecutorBreadcrumb.h"
1718
#include "FormalEvaluation.h"
1819
#include "Initialization.h"
1920
#include "LValue.h"
@@ -1707,7 +1708,8 @@ static void emitRawApply(SILGenFunction &SGF,
17071708
CanSILFunctionType substFnType,
17081709
ApplyOptions options,
17091710
ArrayRef<SILValue> indirectResultAddrs,
1710-
SmallVectorImpl<SILValue> &rawResults) {
1711+
SmallVectorImpl<SILValue> &rawResults,
1712+
ExecutorBreadcrumb prevExecutor) {
17111713
SILFunctionConventions substFnConv(substFnType, SGF.SGM.M);
17121714
// Get the callee value.
17131715
bool isConsumed = substFnType->isCalleeConsumed();
@@ -1783,7 +1785,7 @@ static void emitRawApply(SILGenFunction &SGF,
17831785
rawResults.push_back(result);
17841786

17851787
SILBasicBlock *errorBB =
1786-
SGF.getTryApplyErrorDest(loc, substFnType,
1788+
SGF.getTryApplyErrorDest(loc, substFnType, prevExecutor,
17871789
substFnType->getErrorResult(),
17881790
options.contains(ApplyFlags::DoesNotThrow));
17891791

@@ -3815,7 +3817,7 @@ SILGenFunction::emitBeginApply(SILLocation loc, ManagedValue fn,
38153817
// Emit the call.
38163818
SmallVector<SILValue, 4> rawResults;
38173819
emitRawApply(*this, loc, fn, subs, args, substFnType, options,
3818-
/*indirect results*/ {}, rawResults);
3820+
/*indirect results*/ {}, rawResults, ExecutorBreadcrumb());
38193821

38203822
auto token = rawResults.pop_back_val();
38213823
auto yieldValues = llvm::makeArrayRef(rawResults);
@@ -4385,6 +4387,8 @@ RValue SILGenFunction::emitApply(ResultPlanPtr &&resultPlan,
43854387
subs.getGenericSignature().getCanonicalSignature());
43864388
}
43874389

4390+
ExecutorBreadcrumb breadcrumb;
4391+
43884392
// The presence of `implicitlyAsyncApply` indicates that the callee is a
43894393
// synchronous function isolated to an actor other than our own.
43904394
// Such functions require the caller to hop to the callee's executor
@@ -4399,24 +4403,28 @@ RValue SILGenFunction::emitApply(ResultPlanPtr &&resultPlan,
43994403
if (args.size() > 0)
44004404
actorSelf = args.back();
44014405

4402-
auto didHop = emitHopToTargetActor(loc, getActorIsolation(funcDecl),
4403-
actorSelf);
4404-
assert(didHop);
4406+
breadcrumb = emitHopToTargetActor(loc, getActorIsolation(funcDecl),
4407+
actorSelf);
44054408
}
4409+
} else if (actor && substFnType->isAsync()) {
4410+
// Otherwise, if we're in an actor method ourselves, and we're calling into
4411+
// any sort of async function, we'll want to make sure to hop back to our
4412+
// own executor afterward, since the callee could have made arbitrary hops
4413+
// out of our isolation domain.
4414+
breadcrumb = ExecutorBreadcrumb(actor);
44064415
}
44074416

44084417
SILValue rawDirectResult;
44094418
{
44104419
SmallVector<SILValue, 1> rawDirectResults;
44114420
emitRawApply(*this, loc, fn, subs, args, substFnType, options,
4412-
indirectResultAddrs, rawDirectResults);
4421+
indirectResultAddrs, rawDirectResults, breadcrumb);
44134422
assert(rawDirectResults.size() == 1);
44144423
rawDirectResult = rawDirectResults[0];
44154424
}
44164425

44174426
// hop back to the current executor
4418-
if (substFnType->isAsync() || implicitlyAsyncApply.hasValue())
4419-
emitHopToCurrentExecutor(loc);
4427+
breadcrumb.emit(*this, loc);
44204428

44214429
// Pop the argument scope.
44224430
argScope.pop();

lib/SILGen/SILGenBuiltin.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,13 @@ static ManagedValue emitBuiltinCancelAsyncTask(
13761376
return SGF.emitCancelAsyncTask(loc, args[0].borrow(SGF, loc).forward(SGF));
13771377
}
13781378

1379+
// Emit SIL for the named builtin: getCurrentExecutor.
1380+
static ManagedValue emitBuiltinGetCurrentExecutor(
1381+
SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs,
1382+
PreparedArguments &&preparedArgs, SGFContext C) {
1383+
return ManagedValue::forUnmanaged(SGF.emitGetCurrentExecutor(loc));
1384+
}
1385+
13791386
// Helper to lower a function argument to be usable as the entry point of a
13801387
// new async task
13811388
static ManagedValue

lib/SILGen/SILGenFunction.h

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class ResultPlan;
4646
using ResultPlanPtr = std::unique_ptr<ResultPlan>;
4747
class ArgumentScope;
4848
class Scope;
49+
class ExecutorBreadcrumb;
4950

5051
struct LValueOptions {
5152
bool IsNonAccessing = false;
@@ -760,11 +761,7 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
760761
CanAnyFunctionType inputSubstType,
761762
CanAnyFunctionType outputSubstType,
762763
bool baseLessVisibleThanDerived);
763-
764-
/// If the current function is actor isolated, insert a hop_to_executor
765-
/// instruction.
766-
void emitHopToCurrentExecutor(SILLocation loc);
767-
764+
768765
//===--------------------------------------------------------------------===//
769766
// Control flow
770767
//===--------------------------------------------------------------------===//
@@ -836,11 +833,17 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
836833
/// Generates code to obtain the executor for the given actor isolation,
837834
/// as-needed, and emits a \c hop_to_executor to that executor.
838835
///
839-
/// \returns a non-null pointer if a \c hop_to_executor was emitted.
840-
HopToExecutorInst* emitHopToTargetActor(SILLocation loc,
836+
/// \returns an \c ExecutorBreadcrumb that saves the information necessary to hop
837+
/// back to what was previously the current executor after the actor-isolated
838+
/// region ends. Invoke \c emit on the breadcrumb to
839+
/// restore the previously-active executor.
840+
ExecutorBreadcrumb emitHopToTargetActor(SILLocation loc,
841841
Optional<ActorIsolation> actorIso,
842842
Optional<ManagedValue> actorSelf);
843843

844+
/// Gets a reference to the current executor for the task.
845+
SILValue emitGetCurrentExecutor(SILLocation loc);
846+
844847
/// Generates code to obtain the executor given the actor's decl.
845848
/// \returns a SILValue representing the executor.
846849
SILValue emitLoadActorExecutor(VarDecl *actorDecl);
@@ -1632,6 +1635,7 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
16321635

16331636
SILBasicBlock *getTryApplyErrorDest(SILLocation loc,
16341637
CanSILFunctionType fnTy,
1638+
ExecutorBreadcrumb prevExecutor,
16351639
SILResultInfo exnResult,
16361640
bool isSuppressed);
16371641

0 commit comments

Comments
 (0)