mirror of
https://github.com/apple/swift.git
synced 2026-06-20 15:42:51 +02:00
[concurrency] Add support for HopToMainActorIfNeededThunk.
It is behind the experimental flag GenerateForceToMainActorThunks.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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?!");
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -535,6 +535,11 @@ public:
|
||||
|
||||
createTupleAddrConstructor(loc, destAddr, values, isInitOfDest);
|
||||
}
|
||||
|
||||
using SILBuilder::createHopToMainActorIfNeededThunk;
|
||||
ManagedValue
|
||||
createHopToMainActorIfNeededThunk(SILLocation loc, ManagedValue op,
|
||||
SubstitutionMap substitutionMap = {});
|
||||
};
|
||||
|
||||
} // namespace Lowering
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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) -> () -> ()
|
||||
|
||||
Reference in New Issue
Block a user