Skip to content

Commit b001b0b

Browse files
authored
Merge pull request #37898 from atrick/add-mandatory-hop
Add mandatory hop
2 parents 8687e61 + d3ac3d4 commit b001b0b

21 files changed

+178
-38
lines changed

include/swift/AST/Builtins.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,9 @@ BUILTIN_SIL_OPERATION(WithUnsafeContinuation, "withUnsafeContinuation", Special)
512512
/// the continuation is resumed.
513513
BUILTIN_SIL_OPERATION(WithUnsafeThrowingContinuation, "withUnsafeThrowingContinuation", Special)
514514

515+
/// Force the current task to be rescheduled on the specified actor.
516+
BUILTIN_SIL_OPERATION(HopToActor, "hopToActor", None)
517+
515518
#undef BUILTIN_SIL_OPERATION
516519

517520
// BUILTIN_RUNTIME_CALL - A call into a runtime function.

include/swift/SIL/SILBuilder.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2107,9 +2107,11 @@ class SILBuilder {
21072107
Throws));
21082108
}
21092109

2110-
HopToExecutorInst *createHopToExecutor(SILLocation Loc, SILValue Actor) {
2110+
HopToExecutorInst *createHopToExecutor(SILLocation Loc, SILValue Actor,
2111+
bool mandatory) {
21112112
return insert(new (getModule()) HopToExecutorInst(getSILDebugLocation(Loc),
2112-
Actor, hasOwnership()));
2113+
Actor, hasOwnership(),
2114+
mandatory));
21132115
}
21142116

21152117
ExtractExecutorInst *createExtractExecutor(SILLocation Loc, SILValue Actor) {

include/swift/SIL/SILCloner.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3009,7 +3009,8 @@ ::visitHopToExecutorInst(HopToExecutorInst *Inst) {
30093009
recordClonedInstruction(Inst,
30103010
getBuilder().createHopToExecutor(
30113011
getOpLocation(Inst->getLoc()),
3012-
getOpValue(Inst->getTargetExecutor())));
3012+
getOpValue(Inst->getTargetExecutor()),
3013+
Inst->isMandatory()));
30133014
}
30143015

30153016
template <typename ImplClass>

include/swift/SIL/SILInstruction.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3477,11 +3477,15 @@ class HopToExecutorInst
34773477
friend SILBuilder;
34783478

34793479
HopToExecutorInst(SILDebugLocation debugLoc, SILValue executor,
3480-
bool hasOwnership)
3481-
: UnaryInstructionBase(debugLoc, executor) { }
3480+
bool hasOwnership, bool isMandatory)
3481+
: UnaryInstructionBase(debugLoc, executor) {
3482+
SILNode::Bits.HopToExecutorInst.mandatory = isMandatory;
3483+
}
34823484

34833485
public:
34843486
SILValue getTargetExecutor() const { return getOperand(); }
3487+
3488+
bool isMandatory() const { return SILNode::Bits.HopToExecutorInst.mandatory; }
34853489
};
34863490

34873491
/// Extract the ex that the code is executing on the operand executor already.

include/swift/SIL/SILNode.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,10 @@ class alignas(8) SILNode :
342342
Immutable : 1
343343
);
344344

345+
SWIFT_INLINE_BITFIELD(HopToExecutorInst, NonValueInstruction, 1,
346+
mandatory : 1
347+
);
348+
345349
SWIFT_INLINE_BITFIELD(DestroyValueInst, NonValueInstruction, 1,
346350
PoisonRefs : 1);
347351

lib/AST/Builtins.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,6 +1845,17 @@ static ValueDecl *getWithUnsafeContinuation(ASTContext &ctx,
18451845
return builder.build(id);
18461846
}
18471847

1848+
static ValueDecl *getHopToActor(ASTContext &ctx, Identifier id) {
1849+
BuiltinFunctionBuilder builder(ctx);
1850+
auto *actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
1851+
// Create type parameters and add conformance constraints.
1852+
auto actorParam = makeGenericParam();
1853+
builder.addParameter(actorParam);
1854+
builder.addConformanceRequirement(actorParam, actorProto);
1855+
builder.setResult(makeConcrete(TupleType::getEmpty(ctx)));
1856+
return builder.build(id);
1857+
}
1858+
18481859
/// An array of the overloaded builtin kinds.
18491860
static const OverloadedBuiltinKind OverloadedBuiltinKinds[] = {
18501861
OverloadedBuiltinKind::None,
@@ -2826,6 +2837,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
28262837
case BuiltinValueKind::WithUnsafeThrowingContinuation:
28272838
return getWithUnsafeContinuation(Context, Id, /*throws=*/true);
28282839

2840+
case BuiltinValueKind::HopToActor:
2841+
return getHopToActor(Context, Id);
2842+
28292843
case BuiltinValueKind::AutoDiffCreateLinearMapContext:
28302844
return getAutoDiffCreateLinearMapContext(Context, Id);
28312845

lib/SIL/IR/SILPrinter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2154,6 +2154,8 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
21542154
}
21552155

21562156
void visitHopToExecutorInst(HopToExecutorInst *HTEI) {
2157+
if (HTEI->isMandatory())
2158+
*this << "[mandatory] ";
21572159
*this << getIDAndType(HTEI->getTargetExecutor());
21582160
}
21592161

lib/SIL/Parser/ParseSIL.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3024,7 +3024,6 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
30243024
UNARY_INSTRUCTION(EndBorrow)
30253025
UNARY_INSTRUCTION(DestructureStruct)
30263026
UNARY_INSTRUCTION(DestructureTuple)
3027-
UNARY_INSTRUCTION(HopToExecutor)
30283027
UNARY_INSTRUCTION(ExtractExecutor)
30293028
REFCOUNTING_INSTRUCTION(UnmanagedReleaseValue)
30303029
REFCOUNTING_INSTRUCTION(UnmanagedRetainValue)
@@ -3048,6 +3047,14 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
30483047
#undef UNARY_INSTRUCTION
30493048
#undef REFCOUNTING_INSTRUCTION
30503049

3050+
case SILInstructionKind::HopToExecutorInst: {
3051+
bool mandatory = false;
3052+
if (parseSILOptional(mandatory, *this, "mandatory")
3053+
|| parseTypedValueRef(Val, B) || parseSILDebugLocation(InstLoc, B))
3054+
return true;
3055+
ResultVal = B.createHopToExecutor(InstLoc, Val, mandatory);
3056+
break;
3057+
}
30513058
case SILInstructionKind::DestroyValueInst: {
30523059
bool poisonRefs = false;
30533060
if (parseSILOptional(poisonRefs, *this, "poison")

lib/SILGen/SILGenBuiltin.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,14 @@ static ManagedValue emitBuiltinWithUnsafeThrowingContinuation(
15701570
/*throws=*/true);
15711571
}
15721572

1573+
static ManagedValue emitBuiltinHopToActor(SILGenFunction &SGF, SILLocation loc,
1574+
SubstitutionMap subs,
1575+
ArrayRef<ManagedValue> args,
1576+
SGFContext C) {
1577+
SGF.emitHopToActorValue(loc, args[0]);
1578+
return ManagedValue::forUnmanaged(SGF.emitEmptyTuple(loc));
1579+
}
1580+
15731581
static ManagedValue emitBuiltinAutoDiffCreateLinearMapContext(
15741582
SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs,
15751583
ArrayRef<ManagedValue> args, SGFContext C) {

lib/SILGen/SILGenConstructor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ void SILGenFunction::emitConstructorPrologActorHop(
692692

693693
if (auto executor = emitExecutor(loc, *maybeIso, None)) {
694694
ExpectedExecutor = *executor;
695-
B.createHopToExecutor(loc, *executor);
695+
B.createHopToExecutor(loc, *executor, /*mandatory*/ false);
696696
}
697697
}
698698

lib/SILGen/SILGenFunction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
840840
Optional<ActorIsolation> actorIso,
841841
Optional<ManagedValue> actorSelf);
842842

843+
/// Generate a hop directly to a dynamic actor instance. This can only be done
844+
/// inside an async actor-independent function. No hop-back is expected.
845+
void emitHopToActorValue(SILLocation loc, ManagedValue actor);
846+
843847
/// A version of `emitHopToTargetActor` that is specialized to the needs
844848
/// of various types of ConstructorDecls, like class or value initializers,
845849
/// because their prolog emission is not the same as for regular functions.

lib/SILGen/SILGenProlog.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ void SILGenFunction::emitProlog(CaptureInfo captureInfo,
572572
// For an async function, hop to the executor.
573573
B.createHopToExecutor(
574574
RegularLocation::getAutoGeneratedLocation(F.getLocation()),
575-
ExpectedExecutor);
575+
ExpectedExecutor, /*mandatory*/ false);
576576
} else {
577577
// For a synchronous function, check that we're on the same executor.
578578
// Note: if we "know" that the code is completely Sendable-safe, this
@@ -638,7 +638,7 @@ ExecutorBreadcrumb SILGenFunction::emitHopToTargetActor(SILLocation loc,
638638
// If we're calling from an actor method ourselves, then we'll want to hop
639639
// back to our own actor.
640640
auto breadcrumb = ExecutorBreadcrumb(emitGetCurrentExecutor(loc));
641-
B.createHopToExecutor(loc, executor.getValue());
641+
B.createHopToExecutor(loc, executor.getValue(), /*mandatory*/ false);
642642

643643
return breadcrumb;
644644
} else {
@@ -669,6 +669,24 @@ Optional<SILValue> SILGenFunction::emitExecutor(
669669
llvm_unreachable("covered switch");
670670
}
671671

672+
void SILGenFunction::emitHopToActorValue(SILLocation loc, ManagedValue actor) {
673+
// TODO: can the type system enforce this async requirement?
674+
if (!F.isAsync()) {
675+
llvm::report_fatal_error("Builtin.hopToActor must be in an async function");
676+
}
677+
auto isolation = getActorIsolationOfContext(FunctionDC);
678+
if (isolation != ActorIsolation::Independent
679+
&& isolation != ActorIsolation::Unspecified) {
680+
// TODO: Explicit hop with no hop-back should only be allowed in independent
681+
// async functions. But it needs work for any closure passed to
682+
// Task.detached, which currently has unspecified isolation.
683+
llvm::report_fatal_error(
684+
"Builtin.hopToActor must be in an actor-independent function");
685+
}
686+
SILValue executor = emitLoadActorExecutor(loc, actor);
687+
B.createHopToExecutor(loc, executor, /*mandatory*/ true);
688+
}
689+
672690
void SILGenFunction::emitPreconditionCheckExpectedExecutor(
673691
SILLocation loc, SILValue executorOrActor) {
674692
auto checkExecutor = SGM.getCheckExpectedExecutor();
@@ -697,7 +715,7 @@ void SILGenFunction::emitPreconditionCheckExpectedExecutor(
697715

698716
void ExecutorBreadcrumb::emit(SILGenFunction &SGF, SILLocation loc) {
699717
if (Executor)
700-
SGF.B.createHopToExecutor(loc, Executor);
718+
SGF.B.createHopToExecutor(loc, Executor, /*mandatory*/ false);
701719
}
702720

703721
SILValue SILGenFunction::emitGetCurrentExecutor(SILLocation loc) {

lib/SILOptimizer/Mandatory/LowerHopToActor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ bool LowerHopToActor::processHop(HopToExecutorInst *hop) {
105105
// or else emit code to derive it.
106106
SILValue executor = emitGetExecutor(hop->getLoc(), actor, /*optional*/true);
107107

108-
B.createHopToExecutor(hop->getLoc(), executor);
108+
B.createHopToExecutor(hop->getLoc(), executor, /*mandatory*/ false);
109109

110110
hop->eraseFromParent();
111111

lib/SILOptimizer/Mandatory/OptimizeHopToExecutor.cpp

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#define DEBUG_TYPE "insert-hop-to-executor"
13+
#define DEBUG_TYPE "optimize-hop-to-executor"
1414
#include "swift/SIL/SILBuilder.h"
1515
#include "swift/SIL/SILFunction.h"
1616
#include "swift/SIL/ApplySite.h"
@@ -233,17 +233,22 @@ bool OptimizeHopToExecutor::removeRedundantHopToExecutors(const Actors &actors)
233233
actorIdx = BlockState::Unknown;
234234
continue;
235235
}
236-
if (auto *hop = dyn_cast<HopToExecutorInst>(inst)) {
237-
int newActorIdx = actors.lookup(hop->getOperand());
238-
if (newActorIdx == actorIdx) {
239-
// There is a dominating hop_to_executor with the same operand.
240-
hop->eraseFromParent();
241-
changed = true;
242-
continue;
243-
}
236+
auto *hop = dyn_cast<HopToExecutorInst>(inst);
237+
if (!hop)
238+
continue;
239+
240+
int newActorIdx = actors.lookup(hop->getOperand());
241+
if (newActorIdx != actorIdx) {
244242
actorIdx = newActorIdx;
245243
continue;
246244
}
245+
if (hop->isMandatory())
246+
continue;
247+
248+
// There is a dominating hop_to_executor with the same operand.
249+
LLVM_DEBUG(llvm::dbgs() << "Redundant executor " << *hop);
250+
hop->eraseFromParent();
251+
changed = true;
247252
}
248253
assert(actorIdx == state.exit);
249254
}
@@ -279,8 +284,10 @@ bool OptimizeHopToExecutor::removeDeadHopToExecutors() {
279284
for (auto iter = state.block->rbegin(); iter != state.block->rend();) {
280285
SILInstruction *inst = &*iter++;
281286
auto *hop = dyn_cast<HopToExecutorInst>(inst);
282-
if (hop && needActor == BlockState::NoExecutorNeeded) {
287+
if (hop && !hop->isMandatory()
288+
&& needActor == BlockState::NoExecutorNeeded) {
283289
// Remove the dead hop_to_executor.
290+
LLVM_DEBUG(llvm::dbgs() << "Dead executor " << *hop);
284291
hop->eraseFromParent();
285292
changed = true;
286293
continue;

lib/Serialization/DeserializeSIL.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1852,7 +1852,6 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
18521852
UNARY_INSTRUCTION(IsUnique)
18531853
UNARY_INSTRUCTION(AbortApply)
18541854
UNARY_INSTRUCTION(EndApply)
1855-
UNARY_INSTRUCTION(HopToExecutor)
18561855
UNARY_INSTRUCTION(ExtractExecutor)
18571856
#undef UNARY_INSTRUCTION
18581857
#undef REFCOUNTING_INSTRUCTION
@@ -1868,6 +1867,16 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
18681867
break;
18691868
}
18701869

1870+
case SILInstructionKind::HopToExecutorInst: {
1871+
assert(RecordKind == SIL_ONE_OPERAND && "Layout should be OneOperand.");
1872+
unsigned mandatory = Attr;
1873+
ResultInst = Builder.createHopToExecutor(
1874+
Loc,
1875+
getLocalValue(ValID, getSILType(MF->getType(TyID),
1876+
(SILValueCategory)TyCategory, Fn)),
1877+
mandatory != 0);
1878+
break;
1879+
}
18711880
case SILInstructionKind::DestroyValueInst: {
18721881
assert(RecordKind == SIL_ONE_OPERAND && "Layout should be OneOperand.");
18731882
unsigned poisonRefs = Attr;

lib/Serialization/SerializeSIL.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,6 +1404,8 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) {
14041404
Attr = unsigned(SILValue(UOCI).getOwnershipKind());
14051405
} else if (auto *IEC = dyn_cast<IsEscapingClosureInst>(&SI)) {
14061406
Attr = IEC->getVerificationType();
1407+
} else if (auto *HTE = dyn_cast<HopToExecutorInst>(&SI)) {
1408+
Attr = HTE->isMandatory();
14071409
} else if (auto *DVI = dyn_cast<DestroyValueInst>(&SI)) {
14081410
Attr = DVI->poisonRefs();
14091411
} else if (auto *BCMI = dyn_cast<BeginCOWMutationInst>(&SI)) {

stdlib/public/Concurrency/Task.swift

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -739,30 +739,17 @@ public func _asyncMainDrainQueue() -> Never
739739

740740
@available(SwiftStdlib 5.5, *)
741741
public func _runAsyncMain(_ asyncFun: @escaping () async throws -> ()) {
742-
#if os(Windows)
743742
Task.detached {
744743
do {
744+
#if !os(Windows)
745+
Builtin.hopToActor(MainActor.shared)
746+
#endif
745747
try await asyncFun()
746748
exit(0)
747749
} catch {
748750
_errorInMain(error)
749751
}
750752
}
751-
#else
752-
@MainActor @Sendable
753-
func _doMain(_ asyncFun: @escaping () async throws -> ()) async {
754-
do {
755-
try await asyncFun()
756-
} catch {
757-
_errorInMain(error)
758-
}
759-
}
760-
761-
Task.detached {
762-
await _doMain(asyncFun)
763-
exit(0)
764-
}
765-
#endif
766753
_asyncMainDrainQueue()
767754
}
768755

test/Concurrency/builtin_silgen.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %target-swift-frontend -enable-experimental-concurrency %s -parse-as-library -parse-stdlib -emit-sil -o - | %FileCheck %s
2+
3+
// REQUIRES: concurrency
4+
5+
import _Concurrency
6+
7+
@MainActor
8+
func suspend() async {}
9+
10+
// Builtin.hopToActor should generate a mandator hop_to_executor
11+
// before releaseing the actor and reaching a suspend.
12+
//
13+
// CHECK-LABEL: sil private @$s14builtin_silgen11runDetachedyyFyyYaYbcfU_ : $@convention(thin) @Sendable @async () -> () {
14+
// CHECK: [[ACTOR:%.*]] = apply %1(%0) : $@convention(method) (@thick MainActor.Type) -> @owned MainActor
15+
// CHECK: hop_to_executor [mandatory] [[ACTOR]] : $MainActor
16+
// CHECK: strong_release [[ACTOR]] : $MainActor
17+
// CHECK: apply %{{.*}}() : $@convention(thin) @async () -> ()
18+
// CHECK-LABEL: } // end sil function '$sIeghH_ytIeghHr_TR'
19+
@available(SwiftStdlib 5.5, *)
20+
func runDetached() {
21+
Task.detached {
22+
Builtin.hopToActor(MainActor.shared)
23+
await suspend()
24+
}
25+
}

test/SIL/Parser/concurrency.sil

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ sil @test_hop_to_executor : $@convention(thin) (@guaranteed Actor) -> () {
1313
bb0(%0 : $Actor):
1414
// CHECK: hop_to_executor %0 : $Actor
1515
hop_to_executor %0 : $Actor
16+
// CHECK: hop_to_executor [mandatory] %0 : $Actor
17+
hop_to_executor [mandatory] %0 : $Actor
1618
%2 = tuple ()
1719
return %2 : $()
1820
}

0 commit comments

Comments
 (0)