[concurrency] Add support for HopToMainActorIfNeededThunk.

It is behind the experimental flag GenerateForceToMainActorThunks.
This commit is contained in:
Michael Gottesman
2024-09-25 16:06:11 -07:00
parent f0f5ad54fd
commit 0e0665bfbd
25 changed files with 573 additions and 23 deletions
+8 -1
View File
@@ -34,7 +34,12 @@ struct SILThunkKind {
/// purposes of the underlying thunking machinery.
Identity = 1,
MaxValue = Identity,
/// A thunk that checks if a value is on the main actor and if it isn't
/// jumps to the main actor. It expects that the passed in function does not
/// have any arguments, results, is synchronous, and does not throw.
HopToMainActorIfNeeded = 2,
MaxValue = HopToMainActorIfNeeded,
};
InnerTy innerTy;
@@ -68,6 +73,8 @@ struct SILThunkKind {
return Demangle::MangledSILThunkKind::Invalid;
case Identity:
return Demangle::MangledSILThunkKind::Identity;
case HopToMainActorIfNeeded:
return Demangle::MangledSILThunkKind::HopToMainActorIfNeeded;
}
}
};
+3
View File
@@ -433,6 +433,9 @@ EXPERIMENTAL_FEATURE(CoroutineAccessorsUnwindOnCallerError, false)
/// modify/read coroutines use the callee-allocated ABI
EXPERIMENTAL_FEATURE(CoroutineAccessorsAllocateInCallee, false)
// When a parameter has unspecified isolation, infer it as main actor isolated.
EXPERIMENTAL_FEATURE(GenerateForceToMainActorThunks, false)
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE
+1
View File
@@ -139,6 +139,7 @@ enum class MangledDifferentiabilityKind : char {
enum class MangledSILThunkKind : char {
Invalid = 0,
Identity = 'I',
HopToMainActorIfNeeded = 'H',
};
/// The pass that caused the specialization to occur. We use this to make sure
@@ -292,6 +292,7 @@ NODE(ReflectionMetadataSuperclassDescriptor)
NODE(GenericTypeParamDecl)
NODE(CurryThunk)
NODE(SILThunkIdentity)
NODE(SILThunkHopToMainActorIfNeeded)
NODE(DispatchThunk)
NODE(MethodDescriptor)
NODE(ProtocolRequirementsBaseDescriptor)
+7
View File
@@ -1251,6 +1251,13 @@ public:
return createThunk(Loc, Op, ThunkInst::Kind::Identity, substitutionMap);
}
ThunkInst *
createHopToMainActorIfNeededThunk(SILLocation Loc, SILValue Op,
SubstitutionMap substitutionMap = {}) {
return createThunk(Loc, Op, ThunkInst::Kind::HopToMainActorIfNeeded,
substitutionMap);
}
ConvertEscapeToNoEscapeInst *
createConvertEscapeToNoEscape(SILLocation Loc, SILValue Op, SILType Ty,
bool lifetimeGuaranteed) {
+1
View File
@@ -199,6 +199,7 @@ UNINTERESTING_FEATURE(SameElementRequirements)
UNINTERESTING_FEATURE(UnspecifiedMeansMainActorIsolated)
UNINTERESTING_FEATURE(GlobalActorInferenceCutoff)
UNINTERESTING_FEATURE(KeyPathWithStaticMembers)
UNINTERESTING_FEATURE(GenerateForceToMainActorThunks)
static bool usesFeatureSendingArgsAndResults(Decl *decl) {
auto isFunctionTypeWithSending = [](Type type) {
+3
View File
@@ -2796,6 +2796,9 @@ NodePointer Demangler::demangleThunkOrSpecialization() {
switch (char c = nextChar()) {
case 'I':
return createWithChild(Node::Kind::SILThunkIdentity, popNode(isEntity));
case 'H':
return createWithChild(Node::Kind::SILThunkHopToMainActorIfNeeded,
popNode(isEntity));
default:
return nullptr;
}
+5
View File
@@ -368,6 +368,7 @@ private:
case Node::Kind::CoroutineContinuationPrototype:
case Node::Kind::CurryThunk:
case Node::Kind::SILThunkIdentity:
case Node::Kind::SILThunkHopToMainActorIfNeeded:
case Node::Kind::DispatchThunk:
case Node::Kind::Deallocator:
case Node::Kind::IsolatedDeallocator:
@@ -1430,6 +1431,10 @@ NodePointer NodePrinter::print(NodePointer Node, unsigned depth,
Printer << "identity thunk of ";
print(Node->getChild(0), depth + 1);
return nullptr;
case Node::Kind::SILThunkHopToMainActorIfNeeded:
Printer << "hop to main actor thunk of ";
print(Node->getChild(0), depth + 1);
return nullptr;
case Node::Kind::DispatchThunk:
Printer << "dispatch thunk of ";
print(Node->getChild(0), depth + 1);
+6
View File
@@ -2530,6 +2530,12 @@ ManglingError Remangler::mangleSILThunkIdentity(Node *node, unsigned depth) {
return ManglingError::Success;
}
ManglingError Remangler::mangleSILThunkHopToMainActorIfNeeded(Node *node,
unsigned depth) {
Buffer << "<sil-hop-to-main-actor-if-needed-thunk>";
return ManglingError::Success;
}
ManglingError Remangler::mangleDispatchThunk(Node *node, unsigned depth) {
Buffer << "<dispatch-thunk>";
return ManglingError::Success;
+8
View File
@@ -3382,6 +3382,14 @@ ManglingError Remangler::mangleSILThunkIdentity(Node *node, unsigned depth) {
return ManglingError::Success;
}
ManglingError Remangler::mangleSILThunkHopToMainActorIfNeeded(Node *node,
unsigned depth) {
RETURN_IF_ERROR(mangleSingleChildNode(node, depth + 1)); // type
Buffer << "TT"
<< "H";
return ManglingError::Success;
}
ManglingError Remangler::mangleDispatchThunk(Node *node, unsigned depth) {
RETURN_IF_ERROR(mangleSingleChildNode(node, depth + 1));
Buffer << "Tj";
+39
View File
@@ -2821,6 +2821,42 @@ bool ConvertFunctionInst::onlyConvertsSendable() const {
getNonSendableFuncType(getType());
}
static CanSILFunctionType getDerivedFunctionTypeForHopToMainActorIfNeeded(
SILFunction *fn, CanSILFunctionType inputFunctionType,
SubstitutionMap subMap) {
inputFunctionType = inputFunctionType->substGenericArgs(
fn->getModule(), subMap, fn->getTypeExpansionContext());
bool needsSubstFunctionType = false;
for (auto param : inputFunctionType->getParameters()) {
needsSubstFunctionType |= param.getInterfaceType()->hasTypeParameter();
}
for (auto result : inputFunctionType->getResults()) {
needsSubstFunctionType |= result.getInterfaceType()->hasTypeParameter();
}
for (auto yield : inputFunctionType->getYields()) {
needsSubstFunctionType |= yield.getInterfaceType()->hasTypeParameter();
}
SubstitutionMap appliedSubs;
if (needsSubstFunctionType) {
appliedSubs = inputFunctionType->getCombinedSubstitutions();
}
auto extInfoBuilder =
inputFunctionType->getExtInfo()
.intoBuilder()
.withRepresentation(SILFunctionType::Representation::Thick)
.withIsPseudogeneric(false);
return SILFunctionType::get(
nullptr, extInfoBuilder.build(), inputFunctionType->getCoroutineKind(),
ParameterConvention::Direct_Guaranteed,
inputFunctionType->getParameters(), inputFunctionType->getYields(),
inputFunctionType->getResults(),
inputFunctionType->getOptionalErrorResult(), appliedSubs,
SubstitutionMap(), inputFunctionType->getASTContext());
}
static CanSILFunctionType
getDerivedFunctionTypeForIdentityThunk(SILFunction *fn,
CanSILFunctionType inputFunctionType,
@@ -2873,6 +2909,9 @@ ThunkInst::Kind::getDerivedFunctionType(SILFunction *fn,
case Identity:
return getDerivedFunctionTypeForIdentityThunk(fn, inputFunctionType,
subMap);
case HopToMainActorIfNeeded:
return getDerivedFunctionTypeForHopToMainActorIfNeeded(
fn, inputFunctionType, subMap);
}
llvm_unreachable("Covered switch isn't covered?!");
+3
View File
@@ -2058,6 +2058,9 @@ public:
case ThunkInst::Kind::Identity:
*this << "[identity] ";
break;
case ThunkInst::Kind::HopToMainActorIfNeeded:
*this << "[hop_to_mainactor_if_needed] ";
break;
}
*this << Ctx.getID(ti->getOperand());
printSubstitutions(
+2
View File
@@ -4046,6 +4046,8 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
auto kind = llvm::StringSwitch<ThunkInst::Kind>(attrName)
.Case("identity", ThunkInst::Kind::Identity)
.Case("hop_to_mainactor_if_needed",
ThunkInst::Kind::HopToMainActorIfNeeded)
.Default(ThunkInst::Kind::Invalid);
if (!kind) {
P.diagnose(OpcodeLoc, diag::sil_thunkinst_failed_to_parse_kind, attrName);
+21
View File
@@ -5225,6 +5225,27 @@ public:
require(resTI == ti->getThunkKind().getDerivedFunctionType(
ti->getFunction(), objTI, ti->getSubstitutionMap()),
"resTI is not the thunk kind assigned derived function type");
auto originalCalleeFuncType =
ti->getOperand()->getType().castTo<SILFunctionType>();
switch (ti->getThunkKind()) {
case ThunkInst::Kind::Invalid:
break;
case ThunkInst::Kind::Identity:
break;
case ThunkInst::Kind::HopToMainActorIfNeeded:
require(originalCalleeFuncType->getParameters().empty(),
"Hop To Main Actor If Needed cannot have any parameters");
require(originalCalleeFuncType->getResults().empty(),
"Hop To Main Actor If Needed cannot have any results");
// We require that hop_to_main_actor inputs do not an error since we
// have to have no results.
require(!originalCalleeFuncType->hasErrorResult(),
"HopToMainActorIfNeeded thunks cannot have input without an "
"error result");
break;
}
}
void checkConvertEscapeToNoEscapeInst(ConvertEscapeToNoEscapeInst *ICI) {
+4
View File
@@ -324,6 +324,10 @@ public:
assert(!hasCleanup());
return getValue();
}
/// Treat getValue() as used so that we can access the value directly when
/// debugging without needing to interpret the flag field.
LLVM_ATTRIBUTE_USED
SILValue getValue() const { return valueAndFlag.getPointer(); }
SILType getType() const { return getValue()->getType(); }
+14
View File
@@ -1204,3 +1204,17 @@ ManagedValue SILGenBuilder::borrowObjectRValue(SILGenFunction &SGF,
}
return SGF.emitFormalEvaluationManagedBeginBorrow(loc, value);
}
ManagedValue SILGenBuilder::createHopToMainActorIfNeededThunk(
SILLocation loc, ManagedValue op, SubstitutionMap substitutionMap) {
if (op.getOwnershipKind() == OwnershipKind::None) {
auto *thunkedFunc = createHopToMainActorIfNeededThunk(
loc, op.forward(getSILGenFunction()), substitutionMap);
return SGF.emitManagedRValueWithCleanup(thunkedFunc);
}
CleanupCloner cloner(*this, op);
auto *thunkedFunc = createHopToMainActorIfNeededThunk(
loc, op.forward(getSILGenFunction()), substitutionMap);
return cloner.clone(thunkedFunc);
}
+5
View File
@@ -535,6 +535,11 @@ public:
createTupleAddrConstructor(loc, destAddr, values, isInitOfDest);
}
using SILBuilder::createHopToMainActorIfNeededThunk;
ManagedValue
createHopToMainActorIfNeededThunk(SILLocation loc, ManagedValue op,
SubstitutionMap substitutionMap = {});
};
} // namespace Lowering
+43
View File
@@ -1079,6 +1079,49 @@ SILGenFunction::emitClosureValue(SILLocation loc, SILDeclRef constant,
}
}
// If GenerateForceToMainActorThunks and Dynamic Actor Isolation Checking is
// enabled and we have a synchronous function...
if (F.getASTContext().LangOpts.hasFeature(
Feature::GenerateForceToMainActorThunks) &&
F.getASTContext().LangOpts.isDynamicActorIsolationCheckingEnabled() &&
!functionTy.castTo<SILFunctionType>()->isAsync()) {
// See if that function is a closure which requires dynamic isolation
// checking that doesn't take any parameters or results. In such a case, we
// create a hop to main actor if needed thunk.
if (auto *closureExpr = constant.getClosureExpr()) {
if (closureExpr->requiresDynamicIsolationChecking()) {
auto actorIsolation = closureExpr->getActorIsolation();
switch (actorIsolation) {
case ActorIsolation::Unspecified:
case ActorIsolation::Nonisolated:
case ActorIsolation::NonisolatedUnsafe:
case ActorIsolation::ActorInstance:
break;
case ActorIsolation::Erased:
llvm_unreachable("closure cannot have erased isolation");
case ActorIsolation::GlobalActor:
// For now only do this if we are using the main actor and are calling
// a function that doesn't depend on its result and doesn't have any
// parameters.
//
// NOTE: Since errors are results, this means that we cannot do this
// for throwing functions.
if (auto *args = closureExpr->getArgs();
(!args || args->empty()) && actorIsolation.isMainActor() &&
closureExpr->getResultType()->getCanonicalType() ==
B.getASTContext().TheEmptyTupleType &&
!result.getType().castTo<SILFunctionType>()->hasErrorResult()) {
result = B.createHopToMainActorIfNeededThunk(loc, result, subs);
}
break;
}
}
}
}
return result;
}
+214 -22
View File
@@ -30,6 +30,7 @@ getThunkFunctionType(ThunkInst::Kind kind, CanSILFunctionType inputFunctionType,
switch (kind) {
case ThunkInst::Kind::Invalid:
return CanSILFunctionType();
case ThunkInst::Kind::Identity: {
// Our thunk type is a thin function that takes the input function type and
// the input function type's parameters.
@@ -53,6 +54,29 @@ getThunkFunctionType(ThunkInst::Kind kind, CanSILFunctionType inputFunctionType,
inputFunctionType->getInvocationSubstitutions(),
inputFunctionType->getASTContext());
}
case ThunkInst::Kind::HopToMainActorIfNeeded: {
// Our thunk type is a thin function that takes the input function type and
// the input function type's parameters.
llvm::SmallVector<SILParameterInfo, 8> newParameters;
for (SILParameterInfo p : inputFunctionType->getParameters()) {
newParameters.push_back(p);
}
newParameters.push_back(SILParameterInfo(
inputFunctionType, ParameterConvention::Direct_Guaranteed));
SILExtInfoBuilder builder;
builder = builder.withRepresentation(SILFunctionTypeRepresentation::Thin)
.withAsync(inputFunctionType->isAsync());
return SILFunctionType::get(
inputFunctionType->getInvocationGenericSignature(), builder.build(),
inputFunctionType->getCoroutineKind(),
ParameterConvention::Direct_Unowned, newParameters,
inputFunctionType->getYields(), inputFunctionType->getResults(),
inputFunctionType->getOptionalErrorResult(),
inputFunctionType->getPatternSubstitutions(),
inputFunctionType->getInvocationSubstitutions(),
inputFunctionType->getASTContext());
}
}
llvm_unreachable("Covered switch isn't covered?!");
@@ -137,13 +161,14 @@ void ThunkBodyBuilder::callApplyThunkedFunction(SILValue function,
void ThunkBodyBuilder::callBeginApplyThunkedFunction(
SILValue function, ArrayRef<SILValue> arguments) {
auto calleeType = function->getType().castTo<SILFunctionType>();
auto conventions = SILFunctionConventions(calleeType, *function->getModule());
auto *ai = builder.createBeginApply(
RegularLocation::getAutoGeneratedLocation(), function,
thunk->getForwardingSubstitutionMap(), arguments);
auto *resumeBlock = thunk->createBasicBlock();
{
auto conventions = getThunkConventions();
SILBuilder resumeBlockBuilder(resumeBlock);
llvm::SmallVector<TupleTypeElt> directResultTypes;
@@ -186,7 +211,8 @@ void ThunkBodyBuilder::callBeginApplyThunkedFunction(
void ThunkBodyBuilder::callTryApplyThunkedFunction(
SILValue function, ArrayRef<SILValue> arguments) {
// Then handle the try_apply case.
auto conventions = getThunkConventions();
auto calleeType = function->getType().castTo<SILFunctionType>();
auto conventions = SILFunctionConventions(calleeType, *function->getModule());
// Create our normal block.
auto *normalBlock = thunk->createBasicBlock();
@@ -246,15 +272,15 @@ void ThunkBodyBuilder::callTryApplyThunkedFunction(
void ThunkBodyBuilder::callThunkedFunction(SILValue function,
ArrayRef<SILValue> arguments) {
auto thunkType = getThunkType();
auto calleeType = function->getType().castTo<SILFunctionType>();
// First see if we have a coroutine.
if (thunkType->getCoroutineKind() == SILCoroutineKind::YieldOnce) {
if (calleeType->getCoroutineKind() == SILCoroutineKind::YieldOnce) {
return callBeginApplyThunkedFunction(function, arguments);
}
// Then see if we have a normal apply.
if (!thunkType->hasErrorResult()) {
if (!calleeType->hasErrorResult()) {
return callApplyThunkedFunction(function, arguments);
}
@@ -366,15 +392,162 @@ SILFunction *IdentityLowering::createThunk() const {
return fn;
}
//===----------------------------------------------------------------------===//
// MARK: Hop To Main Actor If Needed
//===----------------------------------------------------------------------===//
namespace {
struct HopToMainActorIfNeededThunkBodyBuilder : ThunkBodyBuilder {
HopToMainActorIfNeededThunkBodyBuilder(SILFunction *fn)
: ThunkBodyBuilder(fn) {}
void generate();
SILValue adjustCalleeType(FunctionRefInst *runOnMainActor, SILValue callee);
};
} // namespace
SILValue HopToMainActorIfNeededThunkBodyBuilder::adjustCalleeType(
FunctionRefInst *runOnMainActor, SILValue originalCallee) {
SILValue callee = originalCallee;
auto calleeType = callee->getType().castTo<SILFunctionType>();
// If our original function is thin. Convert it to be a thick function.
if (calleeType->getRepresentation() == SILFunctionTypeRepresentation::Thin) {
auto extInfoBuilder = calleeType->getExtInfo().intoBuilder();
auto extInfo =
extInfoBuilder.withRepresentation(SILFunctionTypeRepresentation::Thick)
.build();
calleeType = calleeType->getWithExtInfo(extInfo);
callee = builder.createThinToThickFunction(
getLoc(), callee, SILType::getPrimitiveObjectType(calleeType));
}
return callee;
}
void HopToMainActorIfNeededThunkBodyBuilder::generate() {
createEntryBlockArguments();
assert(thunkArguments.size() == 1 && "We only support no thunk arguments");
// Create a function ref for _runOnMainActor. We have to use link all to
// ensure that we link in the closures we create so that hop to main executor
// lowering runs on them.
StringLiteral value = "$ss19_taskRunOnMainActor9operationyyyScMYcc_tF";
auto *function =
builder.getModule().loadFunction(value, SILModule::LinkingMode::LinkAll);
assert(function && "Cannot find runOnMainActor");
// Create the function ref.
auto *fri = builder.createFunctionRef(getLoc(), function);
callThunkedFunction(fri, {adjustCalleeType(fri, thunkArguments.back())});
}
namespace {
struct HopToMainActorIfNeededLowering {
SILOptFunctionBuilder &funcBuilder;
ThunkInst *ti;
// The number of thunks emitted into the function. Just an easy way to give
// multiple thunks in a function a unique name for prototyping purposes.
unsigned &thunkCount;
HopToMainActorIfNeededLowering(SILOptFunctionBuilder &funcBuilder,
ThunkInst *ti, unsigned &thunkCount)
: funcBuilder(funcBuilder), ti(ti), thunkCount(thunkCount) {}
void lower() &&;
void invalidate() {
ti->eraseFromParent();
ti = nullptr;
};
~HopToMainActorIfNeededLowering() {
assert(!ti && "Did not call consuming method to destroy value");
}
SILFunction *createThunk() const;
};
} // namespace
void HopToMainActorIfNeededLowering::lower() && {
SWIFT_DEFER { invalidate(); };
// Create the thunk.
auto *thunk = createThunk();
SILBuilderWithScope builder(ti);
SingleValueInstruction *thunkValue =
builder.createFunctionRef(ti->getLoc(), thunk);
thunkValue = builder.createPartialApply(
ti->getLoc(), thunkValue, ti->getSubstitutionMap(), ti->getOperand(),
ParameterConvention::Direct_Guaranteed);
ti->replaceAllUsesWith(thunkValue);
}
SILFunction *HopToMainActorIfNeededLowering::createThunk() const {
// Our type is going to be the result of the function.
auto inputFuncType = ti->getOperand()->getType().getAs<SILFunctionType>();
// We need to add our input type as a parameter and have our result type as
// the result of the function type.
GenericSignature genericSig;
auto thunkType =
getThunkFunctionType(ti->getThunkKind(), inputFuncType, ti->getModule());
Mangle::ASTMangler mangler;
auto name = mangler.mangleSILThunkKind(
ti->getFunction()->getName(), ThunkInst::Kind::HopToMainActorIfNeeded);
auto *fn = funcBuilder.getOrCreateSharedFunction(
RegularLocation::getAutoGeneratedLocation(), name, thunkType,
IsBare_t::IsNotBare, IsTransparent_t::IsNotTransparent,
SerializedKind_t::IsNotSerialized, ProfileCounter(), IsThunk_t::IsThunk,
IsDynamicallyReplaceable_t::IsNotDynamic,
IsDistributed_t::IsNotDistributed,
IsRuntimeAccessible_t::IsNotRuntimeAccessible);
// If we already have a body, we already generated a thunk for this
// function. Just return it.
if (!fn->empty())
return fn;
// These are only generated when not in Ownership SSA. Turn off Ownership SSA
// so that SILBuilder and other utilities do the right thing and so we can
// avoid having to run ownership lowering.
fn->setOwnershipEliminated();
// Set up our generic environment to be the same as our original function.
fn->setGenericEnvironment(ti->getFunction()->getGenericEnvironment());
// Move the thunk to be right before the generated function to ease FileCheck.
fn->getModule().moveBefore(ti->getFunction()->getIterator(), fn);
// Generate the body of the function.
HopToMainActorIfNeededThunkBodyBuilder thunkBodyBuilder(fn);
thunkBodyBuilder.generate();
return fn;
}
//===----------------------------------------------------------------------===//
// MARK: Top Level Entrypoint
//===----------------------------------------------------------------------===//
namespace {
class ThunkLoweringPass : public SILFunctionTransform {
class ThunkLoweringPass : public SILModuleTransform {
void run() override {
auto *fn = getFunction();
auto *mod = getModule();
SILOptFunctionBuilder funcBuilder(*this);
@@ -386,26 +559,45 @@ class ThunkLoweringPass : public SILFunctionTransform {
// It is assumed in this code that the only instruction we delete is the
// thunk itself. We leave cleaning everything else up to other passes just
// to make the invalidation rules in this pass simple.
for (auto &block : *fn) {
for (auto ii = block.begin(), ie = block.end(); ii != ie;) {
auto *ti = dyn_cast<ThunkInst>(&*ii);
++ii;
bool createdThunk = false;
if (!ti)
continue;
for (auto fi = mod->begin(), fe = mod->end(); fi != fe;) {
auto *fn = &*fi;
++fi;
switch (ti->getThunkKind()) {
case ThunkInst::Kind::Invalid:
llvm_unreachable("Should never see an invalid kind");
case ThunkInst::Kind::Identity:
IdentityLowering lowering(funcBuilder, ti, thunkCount);
std::move(lowering).lower();
continue;
for (auto &block : *fn) {
for (auto ii = block.begin(), ie = block.end(); ii != ie;) {
auto *ti = dyn_cast<ThunkInst>(&*ii);
++ii;
if (!ti)
continue;
switch (ti->getThunkKind()) {
case ThunkInst::Kind::Invalid:
llvm_unreachable("Should never see an invalid kind");
case ThunkInst::Kind::Identity: {
IdentityLowering lowering(funcBuilder, ti, thunkCount);
std::move(lowering).lower();
createdThunk = true;
continue;
}
case ThunkInst::Kind::HopToMainActorIfNeeded: {
HopToMainActorIfNeededLowering lowering(funcBuilder, ti,
thunkCount);
std::move(lowering).lower();
createdThunk = true;
continue;
}
}
llvm_unreachable("Covered switch isn't covered?!");
}
llvm_unreachable("Covered switch isn't covered?!");
}
}
if (createdThunk)
invalidateAll();
}
};
@@ -0,0 +1,4 @@
#define SWIFT_MAIN_ACTOR __attribute__((swift_attr("@MainActor")))
void useClosure(void (^block)() SWIFT_MAIN_ACTOR);
@@ -0,0 +1,6 @@
public func useClosure(_ x: @escaping @MainActor () -> ()) {
typealias Func = () -> ()
let x2 = unsafeBitCast(x, to: Func.self)
x2()
}
@@ -0,0 +1,40 @@
// RUN: %target-sil-opt -sil-thunk-lowering %s | %FileCheck %s
// REQUIRES: concurrency
// REQUIRES: concurrency_runtime
// REQUIRES: asserts
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: back_deploy_concurrency
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: freestanding
sil_stage canonical
import Swift
import SwiftShims
import Builtin
import _Concurrency
sil @noparam_noreturn_callee : $@convention(thin) () -> ()
// CHECK-LABEL: sil shared [thunk] @simple_testTTH : $@convention(thin) (@guaranteed @convention(thin) () -> ()) -> () {
// CHECK: bb0([[FUNC:%.*]] : $@convention(thin)
// CHECK: [[RUN_ON_MAIN_ACTOR:%.*]] = function_ref @$ss19_taskRunOnMainActor9operationyyyScMYcc_tF : $@convention(thin) (@guaranteed @callee_guaranteed () -> ()) -> ()
// CHECK: [[CVT:%.*]] = thin_to_thick_function [[FUNC]]
// CHECK: apply [[RUN_ON_MAIN_ACTOR]]([[CVT]])
// CHECK: } // end sil function 'simple_testTTH'
// CHECK-LABEL: sil @simple_test : $@convention(thin) () -> @error any Error {
// CHECK: [[FUNC:%.*]] = function_ref @noparam_noreturn_callee : $@convention(thin) () -> ()
// CHECK: [[THUNK:%.*]] = function_ref @simple_testTTH : $@convention(thin) (@guaranteed @convention(thin) () -> ()) -> ()
// CHECK: [[THUNKED_FUNC:%.*]] = partial_apply [callee_guaranteed] [[THUNK]]([[FUNC]])
// CHECK: apply [[THUNKED_FUNC]](
// CHECK: } // end sil function 'simple_test'
sil @simple_test : $@convention(thin) () -> @error any Error {
bb0:
%0 = function_ref @noparam_noreturn_callee : $@convention(thin) () -> ()
%1 = thunk [hop_to_mainactor_if_needed] %0() : $@convention(thin) () -> ()
apply %1() : $@callee_guaranteed () -> ()
%9999 = tuple ()
return %9999 : $()
}
@@ -0,0 +1,69 @@
// RUN: %target-swift-frontend -swift-version 6 -enable-experimental-feature GenerateForceToMainActorThunks -import-objc-header %S/Inputs/hoptomainactorifneeded.h -emit-silgen %s | %FileCheck %s
// RUN: %target-swift-frontend -swift-version 6 -enable-experimental-feature GenerateForceToMainActorThunks -import-objc-header %S/Inputs/hoptomainactorifneeded.h -emit-ir %s | %FileCheck -check-prefix=IR %s
// READ THIS: This test validates that basic lowering of hop to main actor if
// needed works. For fuller tests that validate that things actually hop, see
// hoptomainactorifneeded_interpreter.
// REQUIRES: objc_interop
// REQUIRES: asserts
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: back_deploy_concurrency
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: freestanding
// CHECK-LABEL: // testClosure()
// CHECK: // Isolation: global_actor. type: MainActor
// CHECK: sil hidden [ossa] @$s22hoptomainactorifneeded11testClosureyyYaF : $@convention(thin) @async () -> () {
// CHECK: [[FUNC:%.*]] = function_ref @$s22hoptomainactorifneeded11testClosureyyYaFyyScMYccfU_ : $@convention(thin) () -> ()
// CHECK: [[TTFI:%.*]] = thin_to_thick_function [[FUNC]] : $@convention(thin) () -> () to $@callee_guaranteed () -> ()
// CHECK: [[THUNKED:%.*]] = thunk [hop_to_mainactor_if_needed] [[TTFI]]()
// CHECK: [[BLOCK:%.*]] = alloc_stack $@block_storage @callee_guaranteed () -> () // users: %16, %12, %9
// CHECK: [[BLOCK_PROJECT:%.*]] = project_block_storage [[BLOCK]] : $*@block_storage @callee_guaranteed () -> () // users: %15, %10
// CHECK: store [[THUNKED]] to [init] [[BLOCK_PROJECT]] : $*@callee_guaranteed () -> () // id: %10
// CHECK: } // end sil function '$s22hoptomainactorifneeded11testClosureyyYaF'
// Check that we actually emit the thunk and call taskRunOnMainActor.
//
// IR-LABEL: define linkonce_odr hidden swiftcc void @"$s22hoptomainactorifneeded11testClosureyyYaFTTH"(ptr %0, ptr %1)
// IR-NEXT: entry:
// IR-NEXT: call swiftcc void @"$ss19_taskRunOnMainActor9operationyyyScMYcc_tF"(ptr %0, ptr %1)
// IR-NEXT: ret void
// IR-NEXT: }
// Test Closure. We just hop onto the main actor.
// IR-LABEL: define hidden swifttailcc void @"$s22hoptomainactorifneeded11testClosureyyYaF"(ptr swiftasync %0)
// IR: musttail call swifttailcc void @swift_task_switch(ptr swiftasync {{%.*}}, ptr @"$s22hoptomainactorifneeded11testClosureyyYaFTY0_",
// After we hop onto the main actor, we store the partial apply forwarder to the
// hop to main actor closure.
//
// IR: define internal swifttailcc void @"$s22hoptomainactorifneeded11testClosureyyYaFTY0_"(ptr swiftasync [[ASYNC_CONTEXT:%.*]])
// IR: %async.ctx.frameptr = getelementptr inbounds i8, ptr [[ASYNC_CONTEXT]], i32 16
// IR: [[FUNC1:%.*]] = getelementptr inbounds %"$s22hoptomainactorifneeded11testClosureyyYaF.Frame", ptr %async.ctx.frameptr, i32 0, i32 0
// IR: [[FUNC2:%.*]] = getelementptr inbounds { %objc_block, %swift.function }, ptr [[FUNC1]], i32 0, i32 1
// IR: [[FUNC3:%.*]] = getelementptr inbounds %swift.function, ptr [[FUNC2]], i32 0, i32 0
// IR: store ptr @"$s22hoptomainactorifneeded11testClosureyyYaFTTHTA", ptr [[FUNC3]]
// In the partial apply forwarder, we need to call the actual hop to main actor
// thunk.
//
// IR: define internal swiftcc void @"$s22hoptomainactorifneeded11testClosureyyYaFTTHTA"(ptr swiftself [[FRAME:%.*]])
// IR-NEXT: entry:
// IR-NEXT: [[FRAME_GEP:%.*]] = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, ptr [[FRAME]], i32 0, i32 1
// IR-NEXT: [[FUNC2:%.*]] = getelementptr inbounds %swift.function, ptr [[FRAME_GEP]], i32 0, i32 0
// IR-NEXT: [[FUNC2_LOADED:%.*]] = load ptr, ptr [[FUNC2]]
// IR-NEXT: [[DATA:%.*]] = getelementptr inbounds %swift.function, ptr [[FRAME_GEP]]
// IR-NEXT: [[DATA_LOADED:%.*]] = load ptr, ptr [[DATA]]
// IR-NEXT: tail call swiftcc void @"$s22hoptomainactorifneeded11testClosureyyYaFTTH"(ptr [[FUNC2_LOADED]], ptr [[DATA_LOADED]])
// IR-NEXT: ret void
// IR-NEXT: }
@MainActor
func testClosure() async {
useClosure {
}
}
await testClosure()
@@ -0,0 +1,65 @@
// RUN: %empty-directory(%t)
// Build the non-preconcurrency library.
// RUN: %target-build-swift-dylib(%t/%target-library-name(PreconcurrencyUnchecked)) %S/Inputs/hoptomainactorifneeded_interpreter.swift -Xfrontend -disable-availability-checking -swift-version 5 -module-name PreconcurrencyUnchecked -emit-module
// RUN: %target-codesign %t/%target-library-name(PreconcurrencyUnchecked)
// RUN: %target-swiftc_driver -swift-version 6 -enable-experimental-feature GenerateForceToMainActorThunks -I %t %s -o %t/main -L %t -lPreconcurrencyUnchecked %target-rpath(%t)
// RUN: %target-codesign %t/main
// RUN: %target-run %t/main %t/%target-library-name(PreconcurrencyUnchecked)
// RUN: %target-swiftc_driver -swift-version 6 -I %t %s -o %t/main_crash -L %t -lPreconcurrencyUnchecked -DCRASH %target-rpath(%t)
// RUN: %target-codesign %t/main_crash
// RUN: %target-run %t/main_crash %t/%target-library-name(PreconcurrencyUnchecked)
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: back_deploy_concurrency
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: freestanding
// READ THIS: This test validates that we properly crash in the closure if we do
// not have GenerateForceToMainActorThunks set, and the inverse (no crash) if we
// set the flag.
import PreconcurrencyUnchecked
import StdlibUnittest
actor Custom {
}
@globalActor
struct CustomActor {
static var shared: Custom {
return Custom()
}
}
let tests = TestSuite("HopToMainActor End To End")
tests.test("Check if we crash (or not) depending on the compilation mode") { @CustomActor () async -> () in
#if CRASH
expectCrashLater()
#endif
useClosure { @MainActor in
}
}
@CustomActor
func callUseClosureSync() {
useClosure { @MainActor in
}
}
tests.test("Check if we crash (or not) depending on the compilation mode 2") { @CustomActor () async -> () in
#if CRASH
expectCrashLater()
#endif
callUseClosureSync()
}
tests.test("No crash if run on main actor in both modes") { @MainActor () async -> () in
useClosure { @MainActor in
}
}
await runAllTestsAsync()
+1
View File
@@ -478,3 +478,4 @@ $s2hi1SV1iSivx ---> hi.S.i.modify2 : Swift.Int
$s2hi1SV1iSivy ---> hi.S.i.read2 : Swift.Int
$s2hi1SVIetMIy_TC ---> coroutine continuation prototype for @escaping @convention(thin) @convention(method) @yield_once_2 (@unowned hi.S) -> ()
$s4mainAAyyycAA1CCFTTI ---> identity thunk of main.main(main.C) -> () -> ()
$s4mainAAyyycAA1CCFTTH ---> hop to main actor thunk of main.main(main.C) -> () -> ()