Skip to content

Add mandatory hop #37898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/AST/Builtins.def
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,9 @@ BUILTIN_SIL_OPERATION(WithUnsafeContinuation, "withUnsafeContinuation", Special)
/// the continuation is resumed.
BUILTIN_SIL_OPERATION(WithUnsafeThrowingContinuation, "withUnsafeThrowingContinuation", Special)

/// Force the current task to be rescheduled on the specified actor.
BUILTIN_SIL_OPERATION(HopToActor, "hopToActor", None)

#undef BUILTIN_SIL_OPERATION

// BUILTIN_RUNTIME_CALL - A call into a runtime function.
Expand Down
6 changes: 4 additions & 2 deletions include/swift/SIL/SILBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -2107,9 +2107,11 @@ class SILBuilder {
Throws));
}

HopToExecutorInst *createHopToExecutor(SILLocation Loc, SILValue Actor) {
HopToExecutorInst *createHopToExecutor(SILLocation Loc, SILValue Actor,
bool mandatory) {
return insert(new (getModule()) HopToExecutorInst(getSILDebugLocation(Loc),
Actor, hasOwnership()));
Actor, hasOwnership(),
mandatory));
}

ExtractExecutorInst *createExtractExecutor(SILLocation Loc, SILValue Actor) {
Expand Down
3 changes: 2 additions & 1 deletion include/swift/SIL/SILCloner.h
Original file line number Diff line number Diff line change
Expand Up @@ -3009,7 +3009,8 @@ ::visitHopToExecutorInst(HopToExecutorInst *Inst) {
recordClonedInstruction(Inst,
getBuilder().createHopToExecutor(
getOpLocation(Inst->getLoc()),
getOpValue(Inst->getTargetExecutor())));
getOpValue(Inst->getTargetExecutor()),
Inst->isMandatory()));
}

template <typename ImplClass>
Expand Down
8 changes: 6 additions & 2 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -3477,11 +3477,15 @@ class HopToExecutorInst
friend SILBuilder;

HopToExecutorInst(SILDebugLocation debugLoc, SILValue executor,
bool hasOwnership)
: UnaryInstructionBase(debugLoc, executor) { }
bool hasOwnership, bool isMandatory)
: UnaryInstructionBase(debugLoc, executor) {
SILNode::Bits.HopToExecutorInst.mandatory = isMandatory;
}

public:
SILValue getTargetExecutor() const { return getOperand(); }

bool isMandatory() const { return SILNode::Bits.HopToExecutorInst.mandatory; }
};

/// Extract the ex that the code is executing on the operand executor already.
Expand Down
4 changes: 4 additions & 0 deletions include/swift/SIL/SILNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ class alignas(8) SILNode :
Immutable : 1
);

SWIFT_INLINE_BITFIELD(HopToExecutorInst, NonValueInstruction, 1,
mandatory : 1
);

SWIFT_INLINE_BITFIELD(DestroyValueInst, NonValueInstruction, 1,
PoisonRefs : 1);

Expand Down
14 changes: 14 additions & 0 deletions lib/AST/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1845,6 +1845,17 @@ static ValueDecl *getWithUnsafeContinuation(ASTContext &ctx,
return builder.build(id);
}

static ValueDecl *getHopToActor(ASTContext &ctx, Identifier id) {
BuiltinFunctionBuilder builder(ctx);
auto *actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
// Create type parameters and add conformance constraints.
auto actorParam = makeGenericParam();
builder.addParameter(actorParam);
builder.addConformanceRequirement(actorParam, actorProto);
builder.setResult(makeConcrete(TupleType::getEmpty(ctx)));
return builder.build(id);
}

/// An array of the overloaded builtin kinds.
static const OverloadedBuiltinKind OverloadedBuiltinKinds[] = {
OverloadedBuiltinKind::None,
Expand Down Expand Up @@ -2826,6 +2837,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
case BuiltinValueKind::WithUnsafeThrowingContinuation:
return getWithUnsafeContinuation(Context, Id, /*throws=*/true);

case BuiltinValueKind::HopToActor:
return getHopToActor(Context, Id);

case BuiltinValueKind::AutoDiffCreateLinearMapContext:
return getAutoDiffCreateLinearMapContext(Context, Id);

Expand Down
2 changes: 2 additions & 0 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2154,6 +2154,8 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
}

void visitHopToExecutorInst(HopToExecutorInst *HTEI) {
if (HTEI->isMandatory())
*this << "[mandatory] ";
*this << getIDAndType(HTEI->getTargetExecutor());
}

Expand Down
9 changes: 8 additions & 1 deletion lib/SIL/Parser/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3024,7 +3024,6 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
UNARY_INSTRUCTION(EndBorrow)
UNARY_INSTRUCTION(DestructureStruct)
UNARY_INSTRUCTION(DestructureTuple)
UNARY_INSTRUCTION(HopToExecutor)
UNARY_INSTRUCTION(ExtractExecutor)
REFCOUNTING_INSTRUCTION(UnmanagedReleaseValue)
REFCOUNTING_INSTRUCTION(UnmanagedRetainValue)
Expand All @@ -3048,6 +3047,14 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
#undef UNARY_INSTRUCTION
#undef REFCOUNTING_INSTRUCTION

case SILInstructionKind::HopToExecutorInst: {
bool mandatory = false;
if (parseSILOptional(mandatory, *this, "mandatory")
|| parseTypedValueRef(Val, B) || parseSILDebugLocation(InstLoc, B))
return true;
ResultVal = B.createHopToExecutor(InstLoc, Val, mandatory);
break;
}
case SILInstructionKind::DestroyValueInst: {
bool poisonRefs = false;
if (parseSILOptional(poisonRefs, *this, "poison")
Expand Down
8 changes: 8 additions & 0 deletions lib/SILGen/SILGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,14 @@ static ManagedValue emitBuiltinWithUnsafeThrowingContinuation(
/*throws=*/true);
}

static ManagedValue emitBuiltinHopToActor(SILGenFunction &SGF, SILLocation loc,
SubstitutionMap subs,
ArrayRef<ManagedValue> args,
SGFContext C) {
SGF.emitHopToActorValue(loc, args[0]);
return ManagedValue::forUnmanaged(SGF.emitEmptyTuple(loc));
}

static ManagedValue emitBuiltinAutoDiffCreateLinearMapContext(
SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs,
ArrayRef<ManagedValue> args, SGFContext C) {
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ void SILGenFunction::emitConstructorPrologActorHop(

if (auto executor = emitExecutor(loc, *maybeIso, None)) {
ExpectedExecutor = *executor;
B.createHopToExecutor(loc, *executor);
B.createHopToExecutor(loc, *executor, /*mandatory*/ false);
}
}

Expand Down
4 changes: 4 additions & 0 deletions lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
Optional<ActorIsolation> actorIso,
Optional<ManagedValue> actorSelf);

/// Generate a hop directly to a dynamic actor instance. This can only be done
/// inside an async actor-independent function. No hop-back is expected.
void emitHopToActorValue(SILLocation loc, ManagedValue actor);

/// A version of `emitHopToTargetActor` that is specialized to the needs
/// of various types of ConstructorDecls, like class or value initializers,
/// because their prolog emission is not the same as for regular functions.
Expand Down
24 changes: 21 additions & 3 deletions lib/SILGen/SILGenProlog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ void SILGenFunction::emitProlog(CaptureInfo captureInfo,
// For an async function, hop to the executor.
B.createHopToExecutor(
RegularLocation::getAutoGeneratedLocation(F.getLocation()),
ExpectedExecutor);
ExpectedExecutor, /*mandatory*/ false);
} else {
// For a synchronous function, check that we're on the same executor.
// Note: if we "know" that the code is completely Sendable-safe, this
Expand Down Expand Up @@ -638,7 +638,7 @@ ExecutorBreadcrumb SILGenFunction::emitHopToTargetActor(SILLocation loc,
// If we're calling from an actor method ourselves, then we'll want to hop
// back to our own actor.
auto breadcrumb = ExecutorBreadcrumb(emitGetCurrentExecutor(loc));
B.createHopToExecutor(loc, executor.getValue());
B.createHopToExecutor(loc, executor.getValue(), /*mandatory*/ false);

return breadcrumb;
} else {
Expand Down Expand Up @@ -669,6 +669,24 @@ Optional<SILValue> SILGenFunction::emitExecutor(
llvm_unreachable("covered switch");
}

void SILGenFunction::emitHopToActorValue(SILLocation loc, ManagedValue actor) {
// TODO: can the type system enforce this async requirement?
if (!F.isAsync()) {
llvm::report_fatal_error("Builtin.hopToActor must be in an async function");
}
auto isolation = getActorIsolationOfContext(FunctionDC);
if (isolation != ActorIsolation::Independent
&& isolation != ActorIsolation::Unspecified) {
// TODO: Explicit hop with no hop-back should only be allowed in independent
// async functions. But it needs work for any closure passed to
// Task.detached, which currently has unspecified isolation.
llvm::report_fatal_error(
"Builtin.hopToActor must be in an actor-independent function");
}
SILValue executor = emitLoadActorExecutor(loc, actor);
B.createHopToExecutor(loc, executor, /*mandatory*/ true);
}

void SILGenFunction::emitPreconditionCheckExpectedExecutor(
SILLocation loc, SILValue executorOrActor) {
auto checkExecutor = SGM.getCheckExpectedExecutor();
Expand Down Expand Up @@ -697,7 +715,7 @@ void SILGenFunction::emitPreconditionCheckExpectedExecutor(

void ExecutorBreadcrumb::emit(SILGenFunction &SGF, SILLocation loc) {
if (Executor)
SGF.B.createHopToExecutor(loc, Executor);
SGF.B.createHopToExecutor(loc, Executor, /*mandatory*/ false);
}

SILValue SILGenFunction::emitGetCurrentExecutor(SILLocation loc) {
Expand Down
2 changes: 1 addition & 1 deletion lib/SILOptimizer/Mandatory/LowerHopToActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ bool LowerHopToActor::processHop(HopToExecutorInst *hop) {
// or else emit code to derive it.
SILValue executor = emitGetExecutor(hop->getLoc(), actor, /*optional*/true);

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

hop->eraseFromParent();

Expand Down
27 changes: 17 additions & 10 deletions lib/SILOptimizer/Mandatory/OptimizeHopToExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "insert-hop-to-executor"
#define DEBUG_TYPE "optimize-hop-to-executor"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/ApplySite.h"
Expand Down Expand Up @@ -233,17 +233,22 @@ bool OptimizeHopToExecutor::removeRedundantHopToExecutors(const Actors &actors)
actorIdx = BlockState::Unknown;
continue;
}
if (auto *hop = dyn_cast<HopToExecutorInst>(inst)) {
int newActorIdx = actors.lookup(hop->getOperand());
if (newActorIdx == actorIdx) {
// There is a dominating hop_to_executor with the same operand.
hop->eraseFromParent();
changed = true;
continue;
}
auto *hop = dyn_cast<HopToExecutorInst>(inst);
if (!hop)
continue;

int newActorIdx = actors.lookup(hop->getOperand());
if (newActorIdx != actorIdx) {
actorIdx = newActorIdx;
continue;
}
if (hop->isMandatory())
continue;

// There is a dominating hop_to_executor with the same operand.
LLVM_DEBUG(llvm::dbgs() << "Redundant executor " << *hop);
hop->eraseFromParent();
changed = true;
}
assert(actorIdx == state.exit);
}
Expand Down Expand Up @@ -279,8 +284,10 @@ bool OptimizeHopToExecutor::removeDeadHopToExecutors() {
for (auto iter = state.block->rbegin(); iter != state.block->rend();) {
SILInstruction *inst = &*iter++;
auto *hop = dyn_cast<HopToExecutorInst>(inst);
if (hop && needActor == BlockState::NoExecutorNeeded) {
if (hop && !hop->isMandatory()
&& needActor == BlockState::NoExecutorNeeded) {
// Remove the dead hop_to_executor.
LLVM_DEBUG(llvm::dbgs() << "Dead executor " << *hop);
hop->eraseFromParent();
changed = true;
continue;
Expand Down
11 changes: 10 additions & 1 deletion lib/Serialization/DeserializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1848,7 +1848,6 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
UNARY_INSTRUCTION(IsUnique)
UNARY_INSTRUCTION(AbortApply)
UNARY_INSTRUCTION(EndApply)
UNARY_INSTRUCTION(HopToExecutor)
UNARY_INSTRUCTION(ExtractExecutor)
#undef UNARY_INSTRUCTION
#undef REFCOUNTING_INSTRUCTION
Expand All @@ -1864,6 +1863,16 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
break;
}

case SILInstructionKind::HopToExecutorInst: {
assert(RecordKind == SIL_ONE_OPERAND && "Layout should be OneOperand.");
unsigned mandatory = Attr;
ResultInst = Builder.createHopToExecutor(
Loc,
getLocalValue(ValID, getSILType(MF->getType(TyID),
(SILValueCategory)TyCategory, Fn)),
mandatory != 0);
break;
}
case SILInstructionKind::DestroyValueInst: {
assert(RecordKind == SIL_ONE_OPERAND && "Layout should be OneOperand.");
unsigned poisonRefs = Attr;
Expand Down
2 changes: 2 additions & 0 deletions lib/Serialization/SerializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,8 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) {
Attr = unsigned(SILValue(UOCI).getOwnershipKind());
} else if (auto *IEC = dyn_cast<IsEscapingClosureInst>(&SI)) {
Attr = IEC->getVerificationType();
} else if (auto *HTE = dyn_cast<HopToExecutorInst>(&SI)) {
Attr = HTE->isMandatory();
} else if (auto *DVI = dyn_cast<DestroyValueInst>(&SI)) {
Attr = DVI->poisonRefs();
} else if (auto *BCMI = dyn_cast<BeginCOWMutationInst>(&SI)) {
Expand Down
19 changes: 3 additions & 16 deletions stdlib/public/Concurrency/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -739,30 +739,17 @@ public func _asyncMainDrainQueue() -> Never

@available(SwiftStdlib 5.5, *)
public func _runAsyncMain(_ asyncFun: @escaping () async throws -> ()) {
#if os(Windows)
Task.detached {
do {
#if !os(Windows)
Builtin.hopToActor(MainActor.shared)
#endif
try await asyncFun()
exit(0)
} catch {
_errorInMain(error)
}
}
#else
@MainActor @Sendable
func _doMain(_ asyncFun: @escaping () async throws -> ()) async {
do {
try await asyncFun()
} catch {
_errorInMain(error)
}
}

Task.detached {
await _doMain(asyncFun)
exit(0)
}
#endif
_asyncMainDrainQueue()
}

Expand Down
25 changes: 25 additions & 0 deletions test/Concurrency/builtin_silgen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: %target-swift-frontend -enable-experimental-concurrency %s -parse-as-library -parse-stdlib -emit-sil -o - | %FileCheck %s

// REQUIRES: concurrency

import _Concurrency

@MainActor
func suspend() async {}

// Builtin.hopToActor should generate a mandator hop_to_executor
// before releaseing the actor and reaching a suspend.
//
// CHECK-LABEL: sil private @$s14builtin_silgen11runDetachedyyFyyYaYbcfU_ : $@convention(thin) @Sendable @async () -> () {
// CHECK: [[ACTOR:%.*]] = apply %1(%0) : $@convention(method) (@thick MainActor.Type) -> @owned MainActor
// CHECK: hop_to_executor [mandatory] [[ACTOR]] : $MainActor
// CHECK: strong_release [[ACTOR]] : $MainActor
// CHECK: apply %{{.*}}() : $@convention(thin) @async () -> ()
// CHECK-LABEL: } // end sil function '$sIeghH_ytIeghHr_TR'
@available(SwiftStdlib 5.5, *)
func runDetached() {
Task.detached {
Builtin.hopToActor(MainActor.shared)
await suspend()
}
}
2 changes: 2 additions & 0 deletions test/SIL/Parser/concurrency.sil
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ sil @test_hop_to_executor : $@convention(thin) (@guaranteed Actor) -> () {
bb0(%0 : $Actor):
// CHECK: hop_to_executor %0 : $Actor
hop_to_executor %0 : $Actor
// CHECK: hop_to_executor [mandatory] %0 : $Actor
hop_to_executor [mandatory] %0 : $Actor
%2 = tuple ()
return %2 : $()
}
Expand Down
Loading