Skip to content

[concurrency] Fix a few issues with @execution(caller)/@execution(concurrent). #80374

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
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
14 changes: 14 additions & 0 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -3800,6 +3800,12 @@ class AnyFunctionType : public TypeBase {
/// Return the function type without the throwing.
AnyFunctionType *getWithoutThrowing() const;

/// Return the function type with the given \p isolation.
AnyFunctionType *withIsolation(FunctionTypeIsolation isolation) const;

/// Return the function type setting sendable to \p newValue.
AnyFunctionType *withSendable(bool newValue) const;

/// True if the parameter declaration it is attached to is guaranteed
/// to not persist the closure for longer than the duration of the call.
bool isNoEscape() const {
Expand Down Expand Up @@ -5767,6 +5773,10 @@ class SILFunctionType final
getLifetimeDependencies());
}

/// Return a new SILFunctionType that is the same as this but has \p
/// newExtInfo as its ext info.
CanSILFunctionType withExtInfo(ExtInfo newExtInfo) const;

/// Returns the language-level calling convention of the function.
Language getLanguage() const {
return getExtInfo().getLanguage();
Expand Down Expand Up @@ -5816,6 +5826,10 @@ class SILFunctionType final
CanSILFunctionType
withPatternSubstitutions(SubstitutionMap subs) const;

/// Create a new SILFunctionType that is the same as this one with its
/// sendable bit changed to \p newValue.
CanSILFunctionType withSendable(bool newValue) const;

/// Create a SILFunctionType with the same structure as this one,
/// but replacing the invocation generic signature and pattern
/// substitutions. This type must either be polymorphic or have
Expand Down
9 changes: 9 additions & 0 deletions include/swift/SIL/SILType.h
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,15 @@ class SILType {
return funcTy->isCalleeConsumed() && !funcTy->isNoEscape();
}

bool isNonIsolatedCallerFunction() const {
auto funcTy = getAs<SILFunctionType>();
if (!funcTy)
return false;
auto isolatedParam = funcTy->maybeGetIsolatedParameter();
return isolatedParam &&
isolatedParam->hasOption(SILParameterInfo::ImplicitLeading);
}

bool isMarkedAsImmortal() const;

/// True if a value of this type can have its address taken by a
Expand Down
25 changes: 25 additions & 0 deletions lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4486,6 +4486,17 @@ AnyFunctionType *AnyFunctionType::getWithoutThrowing() const {
return withExtInfo(info);
}

AnyFunctionType *
AnyFunctionType::withIsolation(FunctionTypeIsolation isolation) const {
auto info = getExtInfo().intoBuilder().withIsolation(isolation).build();
return withExtInfo(info);
}

AnyFunctionType *AnyFunctionType::withSendable(bool newValue) const {
auto info = getExtInfo().intoBuilder().withSendable(newValue).build();
return withExtInfo(info);
}

std::optional<Type> AnyFunctionType::getEffectiveThrownErrorType() const {
// A non-throwing function... has no thrown interface type.
if (!isThrowing())
Expand Down Expand Up @@ -4937,6 +4948,20 @@ SILFunctionType::withPatternSpecialization(CanGenericSignature sig,
witnessConformance);
}

CanSILFunctionType SILFunctionType::withSendable(bool newValue) const {
return withExtInfo(getExtInfo().withSendable(newValue));
}

CanSILFunctionType SILFunctionType::withExtInfo(ExtInfo newExtInfo) const {
return SILFunctionType::get(
getInvocationGenericSignature(), newExtInfo, getCoroutineKind(),
getCalleeConvention(), getParameters(), getYields(), getResults(),
getOptionalErrorResult(), getPatternSubstitutions(),
getInvocationSubstitutions(),
const_cast<SILFunctionType *>(this)->getASTContext(),
getWitnessMethodConformanceOrInvalid());
}

APInt IntegerType::getValue() const {
return BuiltinIntegerWidth::arbitrary().parse(getDigitsText(), /*radix*/ 0,
isNegative());
Expand Down
182 changes: 180 additions & 2 deletions lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,16 @@ namespace {
}
RValue visitFunctionConversionExpr(FunctionConversionExpr *E,
SGFContext C);

/// Helper method for handling function conversion expr to
/// @execution(caller). Returns an empty RValue on failure.
RValue emitFunctionCvtToExecutionCaller(FunctionConversionExpr *E,
SGFContext C);
/// Helper method for handling function conversion expr to a global actor
/// from an @execution(caller) function.
RValue
emitFunctionCvtFromExecutionCallerToGlobalActor(FunctionConversionExpr *E,
SGFContext C);
RValue visitActorIsolationErasureExpr(ActorIsolationErasureExpr *E,
SGFContext C);
RValue visitExtractFunctionIsolationExpr(ExtractFunctionIsolationExpr *E,
Expand Down Expand Up @@ -1942,9 +1952,159 @@ static ManagedValue convertFunctionRepresentation(SILGenFunction &SGF,
llvm_unreachable("bad representation");
}

RValue
RValueEmitter::emitFunctionCvtToExecutionCaller(FunctionConversionExpr *e,
SGFContext C) {
CanAnyFunctionType destType =
cast<FunctionType>(e->getType()->getCanonicalType());
assert(destType->getIsolation().isNonIsolatedCaller() &&
"Should only call this if destType is non isolated caller");

auto *subExpr = e->getSubExpr();
CanAnyFunctionType subExprType =
cast<FunctionType>(subExpr->getType()->getCanonicalType());

// We are pattern matching the following two patterns:
//
// Swift 6:
//
// (fn_cvt_expr type="@execution(caller) () async -> ()"
// (fn_cvt_expr type="@execution(caller) @Sendable () async -> ()"
// (declref_expr type="() async -> ()"
//
// Swift 5:
//
// (fn_cvt_expr type="@execution(caller) () async -> ()"
// (declref_expr type="() async -> ()"
//
// The @Sendable in Swift 6 mode is due to us not representing
// @execution(caller) or @Sendable in the constraint evaluator.
//
// The reason why we need to evaluate this especially is that otherwise we
// generate multiple

bool needsSendableConversion = false;
if (auto *subCvt = dyn_cast<FunctionConversionExpr>(subExpr)) {
auto *subSubExpr = subCvt->getSubExpr();
CanAnyFunctionType subSubExprType =
cast<FunctionType>(subSubExpr->getType()->getCanonicalType());

if (subExprType->hasExtInfo() && subExprType->getExtInfo().isSendable() &&
subSubExprType->hasExtInfo() &&
!subExprType->getExtInfo().isSendable() &&
subExprType->withSendable(true) == subSubExprType) {
subExpr = subSubExpr;

// We changed our subExpr... so recompute our srcType.
subExprType = cast<FunctionType>(subExpr->getType()->getCanonicalType());
needsSendableConversion = true;
}
}

// Check if the only difference in between our destType and srcType is our
// isolation.
if (!subExprType->hasExtInfo() || !destType->hasExtInfo() ||
destType->withIsolation(subExprType->getIsolation()) != subExprType) {
return RValue();
}

// Ok, we know that our underlying types are the same. Lets try to emit.
auto *declRef = dyn_cast<DeclRefExpr>(subExpr);
if (!declRef)
return RValue();

auto *decl = dyn_cast<FuncDecl>(declRef->getDecl());
if (!decl || !getActorIsolation(decl).isCallerIsolationInheriting())
return RValue();

// Ok, we found our target.
SILDeclRef silDeclRef(decl);
assert(silDeclRef.getParameterListCount() == 1);
auto substType = cast<AnyFunctionType>(destType);
auto typeContext = SGF.getFunctionTypeInfo(substType);
ManagedValue result = SGF.emitClosureValue(
e, silDeclRef, typeContext, declRef->getDeclRef().getSubstitutions());
if (needsSendableConversion) {
auto funcType = cast<SILFunctionType>(result.getType().getASTType());
result = SGF.B.createConvertFunction(
e, result,
SILType::getPrimitiveObjectType(funcType->withSendable(true)));
}
return RValue(SGF, e, destType, result);
}

RValue RValueEmitter::emitFunctionCvtFromExecutionCallerToGlobalActor(
FunctionConversionExpr *e, SGFContext C) {
// We are pattern matching a conversion sequence like the following:
//
// (fn_cvt_expr implicit type="@GlobalActor @Sendable () async -> ()
// (fn_cvt_expr implicit type="@execution(caller) @Sendable () async -> ()"
// (declref_expr type="() async -> ()"
//
// Where the declref referred to by the declref_expr has execution(caller)
// attached to it but lacks it in its interface type since we do not put
// execution(attr) in interface types when we run the constraint solver but
// fix it up later.
//
// What we want to emit first a direct reference to the caller as an
// @execution(caller) function, then we convert it to @execution(caller)
// @Sendable. Finally, we thunk @execution(caller) to @GlobalActor. The
// thunking is important so that we can ensure that @execution(caller) runs on
// that specific @GlobalActor.

CanAnyFunctionType destType =
cast<FunctionType>(e->getType()->getCanonicalType());
assert(destType->getIsolation().isGlobalActor() &&
"Should only call this if destType is a global actor");

auto *subCvt = dyn_cast<FunctionConversionExpr>(e->getSubExpr());
if (!subCvt)
return RValue();

CanAnyFunctionType subCvtType =
cast<FunctionType>(subCvt->getType()->getCanonicalType());

// Src type should be isNonIsolatedCaller and they should only differ in
// isolation.
if (!subCvtType->getIsolation().isNonIsolatedCaller() ||
subCvtType->withIsolation(destType->getIsolation()) != destType)
return RValue();

// Grab our decl ref/its decl and make sure that our decl is actually caller
// isolation inheriting (ignoring what it's interface type is).
auto *declRef = dyn_cast<DeclRefExpr>(subCvt->getSubExpr());
if (!declRef)
return RValue();
auto *decl = dyn_cast<FuncDecl>(declRef->getDecl());
if (!decl || !getActorIsolation(decl).isCallerIsolationInheriting())
return RValue();

// Make sure that subCvt/declRefType only differ by isolation and sendability.
CanAnyFunctionType declRefType =
cast<FunctionType>(declRef->getType()->getCanonicalType());
assert(!declRefType->getIsolation().isNonIsolatedCaller() &&
"This should not be represented in interface types");
if (declRefType->isSendable() || !subCvtType->isSendable())
return RValue();

// Ok, we found our target.
SILDeclRef silDeclRef(decl);
assert(silDeclRef.getParameterListCount() == 1);
auto substType = cast<AnyFunctionType>(destType);
auto typeContext = SGF.getFunctionTypeInfo(substType);
ManagedValue result = SGF.emitClosureValue(
e, silDeclRef, typeContext, declRef->getDeclRef().getSubstitutions());
if (!result.getType().isSendable(&SGF.F)) {
auto funcType = cast<SILFunctionType>(result.getType().getASTType());
result = SGF.B.createConvertFunction(
e, result,
SILType::getPrimitiveObjectType(funcType->withSendable(true)));
}
return RValue(SGF, e, destType, result);
}

RValue RValueEmitter::visitFunctionConversionExpr(FunctionConversionExpr *e,
SGFContext C)
{
SGFContext C) {
CanAnyFunctionType srcType =
cast<FunctionType>(e->getSubExpr()->getType()->getCanonicalType());
CanAnyFunctionType destType =
Expand Down Expand Up @@ -2041,6 +2201,24 @@ RValue RValueEmitter::visitFunctionConversionExpr(FunctionConversionExpr *e,
}
}

// Check if we are converting a function to an @execution(caller) from a
// declref that is also @execution(caller). In such a case, this was a case
// that was put in by Sema. We do not need a thunk, but just need to recognize
// this case and elide the conversion. The reason why we need to do this is
// that otherwise, we put in extra thunks that convert @execution(caller) to
// @execution(concurrent) back to @execution(caller). This is done b/c we do
// not represent @execution(caller) in interface types, so the actual decl ref
// will be viewed as @async () -> ().
if (destType->getIsolation().isNonIsolatedCaller()) {
if (RValue rv = emitFunctionCvtToExecutionCaller(e, C))
return rv;
}

if (destType->getIsolation().isGlobalActor()) {
if (RValue rv = emitFunctionCvtFromExecutionCallerToGlobalActor(e, C))
return rv;
}

// Break the conversion into three stages:
// 1) changing the representation from foreign to native
// 2) changing the signature within the representation
Expand Down
10 changes: 9 additions & 1 deletion lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -2601,11 +2601,19 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
SILType loweredResultTy,
SGFContext ctx = SGFContext());

enum class ThunkGenFlag {
None,
ConvertingToNonIsolatedCaller,
ConvertingFromNonIsolatedCaller,
};
using ThunkGenOptions = OptionSet<ThunkGenFlag>;

/// Used for emitting SILArguments of bare functions, such as thunks.
void collectThunkParams(
SILLocation loc, SmallVectorImpl<ManagedValue> &params,
SmallVectorImpl<ManagedValue> *indirectResultParams = nullptr,
SmallVectorImpl<ManagedValue> *indirectErrorParams = nullptr);
SmallVectorImpl<ManagedValue> *indirectErrorParams = nullptr,
ThunkGenOptions options = {});

/// Build the type of a function transformation thunk.
CanSILFunctionType buildThunkType(CanSILFunctionType &sourceType,
Expand Down
Loading