Skip to content

Commit f245389

Browse files
committed
[concurrency] Fix a few issues with @execution(caller)/@execution(concurrent).
Specifically: 1. I made it so that thunks from caller -> concurrent properly ignore the isolated parameter of the thunk when calling the concurrent function. rdar://148112362 2. I made it so that thunks from concurrent -> caller properly create a Optional<any Actor>.none and pass that into the caller function. rdar://148112384 3. I made it so that in cases where we are assigning an @sendable caller to a non-sendable caller variable, we allow for the conversion as long as the parameters/results are sendable as well. rdar://148112532 4. I made it so that when we generate a thunk from @execution(caller) -> @GlobalActor, we mangle in @GlobalActor into the thunk. rdar://148112569 5. I discovered that due to the way we handle function conversion expr/decl ref expr, we were emitted two thunks when we assigned a global @caller function to a local @caller variable. The result is that we would first cast from @caller -> @Concurrent and then back to @caller. The result of this would be that the @caller function would always be called on the global queue. rdar://148112646 I also added a bunch of basic tests as well that showed that this behavior was broken.
1 parent 25c0aa3 commit f245389

File tree

8 files changed

+785
-33
lines changed

8 files changed

+785
-33
lines changed

include/swift/AST/Types.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3800,6 +3800,12 @@ class AnyFunctionType : public TypeBase {
38003800
/// Return the function type without the throwing.
38013801
AnyFunctionType *getWithoutThrowing() const;
38023802

3803+
/// Return the function type with the given \p isolation.
3804+
AnyFunctionType *withIsolation(FunctionTypeIsolation isolation) const;
3805+
3806+
/// Return the function type setting sendable to \p newValue.
3807+
AnyFunctionType *withSendable(bool newValue) const;
3808+
38033809
/// True if the parameter declaration it is attached to is guaranteed
38043810
/// to not persist the closure for longer than the duration of the call.
38053811
bool isNoEscape() const {
@@ -5767,6 +5773,10 @@ class SILFunctionType final
57675773
getLifetimeDependencies());
57685774
}
57695775

5776+
/// Return a new SILFunctionType that is the same as this but has \p
5777+
/// newExtInfo as its ext info.
5778+
CanSILFunctionType withExtInfo(ExtInfo newExtInfo) const;
5779+
57705780
/// Returns the language-level calling convention of the function.
57715781
Language getLanguage() const {
57725782
return getExtInfo().getLanguage();
@@ -5816,6 +5826,10 @@ class SILFunctionType final
58165826
CanSILFunctionType
58175827
withPatternSubstitutions(SubstitutionMap subs) const;
58185828

5829+
/// Create a new SILFunctionType that is the same as this one with its
5830+
/// sendable bit changed to \p newValue.
5831+
CanSILFunctionType withSendable(bool newValue) const;
5832+
58195833
/// Create a SILFunctionType with the same structure as this one,
58205834
/// but replacing the invocation generic signature and pattern
58215835
/// substitutions. This type must either be polymorphic or have

include/swift/SIL/SILType.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,15 @@ class SILType {
925925
return funcTy->isCalleeConsumed() && !funcTy->isNoEscape();
926926
}
927927

928+
bool isNonIsolatedCallerFunction() const {
929+
auto funcTy = getAs<SILFunctionType>();
930+
if (!funcTy)
931+
return false;
932+
auto isolatedParam = funcTy->maybeGetIsolatedParameter();
933+
return isolatedParam &&
934+
isolatedParam->hasOption(SILParameterInfo::ImplicitLeading);
935+
}
936+
928937
bool isMarkedAsImmortal() const;
929938

930939
/// True if a value of this type can have its address taken by a

lib/AST/Type.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4486,6 +4486,17 @@ AnyFunctionType *AnyFunctionType::getWithoutThrowing() const {
44864486
return withExtInfo(info);
44874487
}
44884488

4489+
AnyFunctionType *
4490+
AnyFunctionType::withIsolation(FunctionTypeIsolation isolation) const {
4491+
auto info = getExtInfo().intoBuilder().withIsolation(isolation).build();
4492+
return withExtInfo(info);
4493+
}
4494+
4495+
AnyFunctionType *AnyFunctionType::withSendable(bool newValue) const {
4496+
auto info = getExtInfo().intoBuilder().withSendable(newValue).build();
4497+
return withExtInfo(info);
4498+
}
4499+
44894500
std::optional<Type> AnyFunctionType::getEffectiveThrownErrorType() const {
44904501
// A non-throwing function... has no thrown interface type.
44914502
if (!isThrowing())
@@ -4937,6 +4948,20 @@ SILFunctionType::withPatternSpecialization(CanGenericSignature sig,
49374948
witnessConformance);
49384949
}
49394950

4951+
CanSILFunctionType SILFunctionType::withSendable(bool newValue) const {
4952+
return withExtInfo(getExtInfo().withSendable(newValue));
4953+
}
4954+
4955+
CanSILFunctionType SILFunctionType::withExtInfo(ExtInfo newExtInfo) const {
4956+
return SILFunctionType::get(
4957+
getInvocationGenericSignature(), newExtInfo, getCoroutineKind(),
4958+
getCalleeConvention(), getParameters(), getYields(), getResults(),
4959+
getOptionalErrorResult(), getPatternSubstitutions(),
4960+
getInvocationSubstitutions(),
4961+
const_cast<SILFunctionType *>(this)->getASTContext(),
4962+
getWitnessMethodConformanceOrInvalid());
4963+
}
4964+
49404965
APInt IntegerType::getValue() const {
49414966
return BuiltinIntegerWidth::arbitrary().parse(getDigitsText(), /*radix*/ 0,
49424967
isNegative());

lib/SILGen/SILGenExpr.cpp

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,16 @@ namespace {
487487
}
488488
RValue visitFunctionConversionExpr(FunctionConversionExpr *E,
489489
SGFContext C);
490+
491+
/// Helper method for handling function conversion expr to
492+
/// @execution(caller). Returns an empty RValue on failure.
493+
RValue emitFunctionCvtToExecutionCaller(FunctionConversionExpr *E,
494+
SGFContext C);
495+
/// Helper method for handling function conversion expr to a global actor
496+
/// from an @execution(caller) function.
497+
RValue
498+
emitFunctionCvtFromExecutionCallerToGlobalActor(FunctionConversionExpr *E,
499+
SGFContext C);
490500
RValue visitActorIsolationErasureExpr(ActorIsolationErasureExpr *E,
491501
SGFContext C);
492502
RValue visitExtractFunctionIsolationExpr(ExtractFunctionIsolationExpr *E,
@@ -1942,9 +1952,159 @@ static ManagedValue convertFunctionRepresentation(SILGenFunction &SGF,
19421952
llvm_unreachable("bad representation");
19431953
}
19441954

1955+
RValue
1956+
RValueEmitter::emitFunctionCvtToExecutionCaller(FunctionConversionExpr *e,
1957+
SGFContext C) {
1958+
CanAnyFunctionType destType =
1959+
cast<FunctionType>(e->getType()->getCanonicalType());
1960+
assert(destType->getIsolation().isNonIsolatedCaller() &&
1961+
"Should only call this if destType is non isolated caller");
1962+
1963+
auto *subExpr = e->getSubExpr();
1964+
CanAnyFunctionType subExprType =
1965+
cast<FunctionType>(subExpr->getType()->getCanonicalType());
1966+
1967+
// We are pattern matching the following two patterns:
1968+
//
1969+
// Swift 6:
1970+
//
1971+
// (fn_cvt_expr type="@execution(caller) () async -> ()"
1972+
// (fn_cvt_expr type="@execution(caller) @Sendable () async -> ()"
1973+
// (declref_expr type="() async -> ()"
1974+
//
1975+
// Swift 5:
1976+
//
1977+
// (fn_cvt_expr type="@execution(caller) () async -> ()"
1978+
// (declref_expr type="() async -> ()"
1979+
//
1980+
// The @Sendable in Swift 6 mode is due to us not representing
1981+
// @execution(caller) or @Sendable in the constraint evaluator.
1982+
//
1983+
// The reason why we need to evaluate this especially is that otherwise we
1984+
// generate multiple
1985+
1986+
bool needsSendableConversion = false;
1987+
if (auto *subCvt = dyn_cast<FunctionConversionExpr>(subExpr)) {
1988+
auto *subSubExpr = subCvt->getSubExpr();
1989+
CanAnyFunctionType subSubExprType =
1990+
cast<FunctionType>(subSubExpr->getType()->getCanonicalType());
1991+
1992+
if (subExprType->hasExtInfo() && subExprType->getExtInfo().isSendable() &&
1993+
subSubExprType->hasExtInfo() &&
1994+
!subExprType->getExtInfo().isSendable() &&
1995+
subExprType->withSendable(true) == subSubExprType) {
1996+
subExpr = subSubExpr;
1997+
1998+
// We changed our subExpr... so recompute our srcType.
1999+
subExprType = cast<FunctionType>(subExpr->getType()->getCanonicalType());
2000+
needsSendableConversion = true;
2001+
}
2002+
}
2003+
2004+
// Check if the only difference in between our destType and srcType is our
2005+
// isolation.
2006+
if (!subExprType->hasExtInfo() || !destType->hasExtInfo() ||
2007+
destType->withIsolation(subExprType->getIsolation()) != subExprType) {
2008+
return RValue();
2009+
}
2010+
2011+
// Ok, we know that our underlying types are the same. Lets try to emit.
2012+
auto *declRef = dyn_cast<DeclRefExpr>(subExpr);
2013+
if (!declRef)
2014+
return RValue();
2015+
2016+
auto *decl = dyn_cast<FuncDecl>(declRef->getDecl());
2017+
if (!decl || !getActorIsolation(decl).isCallerIsolationInheriting())
2018+
return RValue();
2019+
2020+
// Ok, we found our target.
2021+
SILDeclRef silDeclRef(decl);
2022+
assert(silDeclRef.getParameterListCount() == 1);
2023+
auto substType = cast<AnyFunctionType>(destType);
2024+
auto typeContext = SGF.getFunctionTypeInfo(substType);
2025+
ManagedValue result = SGF.emitClosureValue(
2026+
e, silDeclRef, typeContext, declRef->getDeclRef().getSubstitutions());
2027+
if (needsSendableConversion) {
2028+
auto funcType = cast<SILFunctionType>(result.getType().getASTType());
2029+
result = SGF.B.createConvertFunction(
2030+
e, result,
2031+
SILType::getPrimitiveObjectType(funcType->withSendable(true)));
2032+
}
2033+
return RValue(SGF, e, destType, result);
2034+
}
2035+
2036+
RValue RValueEmitter::emitFunctionCvtFromExecutionCallerToGlobalActor(
2037+
FunctionConversionExpr *e, SGFContext C) {
2038+
// We are pattern matching a conversion sequence like the following:
2039+
//
2040+
// (fn_cvt_expr implicit type="@GlobalActor @Sendable () async -> ()
2041+
// (fn_cvt_expr implicit type="@execution(caller) @Sendable () async -> ()"
2042+
// (declref_expr type="() async -> ()"
2043+
//
2044+
// Where the declref referred to by the declref_expr has execution(caller)
2045+
// attached to it but lacks it in its interface type since we do not put
2046+
// execution(attr) in interface types when we run the constraint solver but
2047+
// fix it up later.
2048+
//
2049+
// What we want to emit first a direct reference to the caller as an
2050+
// @execution(caller) function, then we convert it to @execution(caller)
2051+
// @Sendable. Finally, we thunk @execution(caller) to @GlobalActor. The
2052+
// thunking is important so that we can ensure that @execution(caller) runs on
2053+
// that specific @GlobalActor.
2054+
2055+
CanAnyFunctionType destType =
2056+
cast<FunctionType>(e->getType()->getCanonicalType());
2057+
assert(destType->getIsolation().isGlobalActor() &&
2058+
"Should only call this if destType is a global actor");
2059+
2060+
auto *subCvt = dyn_cast<FunctionConversionExpr>(e->getSubExpr());
2061+
if (!subCvt)
2062+
return RValue();
2063+
2064+
CanAnyFunctionType subCvtType =
2065+
cast<FunctionType>(subCvt->getType()->getCanonicalType());
2066+
2067+
// Src type should be isNonIsolatedCaller and they should only differ in
2068+
// isolation.
2069+
if (!subCvtType->getIsolation().isNonIsolatedCaller() ||
2070+
subCvtType->withIsolation(destType->getIsolation()) != destType)
2071+
return RValue();
2072+
2073+
// Grab our decl ref/its decl and make sure that our decl is actually caller
2074+
// isolation inheriting (ignoring what it's interface type is).
2075+
auto *declRef = dyn_cast<DeclRefExpr>(subCvt->getSubExpr());
2076+
if (!declRef)
2077+
return RValue();
2078+
auto *decl = dyn_cast<FuncDecl>(declRef->getDecl());
2079+
if (!decl || !getActorIsolation(decl).isCallerIsolationInheriting())
2080+
return RValue();
2081+
2082+
// Make sure that subCvt/declRefType only differ by isolation and sendability.
2083+
CanAnyFunctionType declRefType =
2084+
cast<FunctionType>(declRef->getType()->getCanonicalType());
2085+
assert(!declRefType->getIsolation().isNonIsolatedCaller() &&
2086+
"This should not be represented in interface types");
2087+
if (declRefType->isSendable() || !subCvtType->isSendable())
2088+
return RValue();
2089+
2090+
// Ok, we found our target.
2091+
SILDeclRef silDeclRef(decl);
2092+
assert(silDeclRef.getParameterListCount() == 1);
2093+
auto substType = cast<AnyFunctionType>(destType);
2094+
auto typeContext = SGF.getFunctionTypeInfo(substType);
2095+
ManagedValue result = SGF.emitClosureValue(
2096+
e, silDeclRef, typeContext, declRef->getDeclRef().getSubstitutions());
2097+
if (!result.getType().isSendable(&SGF.F)) {
2098+
auto funcType = cast<SILFunctionType>(result.getType().getASTType());
2099+
result = SGF.B.createConvertFunction(
2100+
e, result,
2101+
SILType::getPrimitiveObjectType(funcType->withSendable(true)));
2102+
}
2103+
return RValue(SGF, e, destType, result);
2104+
}
2105+
19452106
RValue RValueEmitter::visitFunctionConversionExpr(FunctionConversionExpr *e,
1946-
SGFContext C)
1947-
{
2107+
SGFContext C) {
19482108
CanAnyFunctionType srcType =
19492109
cast<FunctionType>(e->getSubExpr()->getType()->getCanonicalType());
19502110
CanAnyFunctionType destType =
@@ -2041,6 +2201,24 @@ RValue RValueEmitter::visitFunctionConversionExpr(FunctionConversionExpr *e,
20412201
}
20422202
}
20432203

2204+
// Check if we are converting a function to an @execution(caller) from a
2205+
// declref that is also @execution(caller). In such a case, this was a case
2206+
// that was put in by Sema. We do not need a thunk, but just need to recognize
2207+
// this case and elide the conversion. The reason why we need to do this is
2208+
// that otherwise, we put in extra thunks that convert @execution(caller) to
2209+
// @execution(concurrent) back to @execution(caller). This is done b/c we do
2210+
// not represent @execution(caller) in interface types, so the actual decl ref
2211+
// will be viewed as @async () -> ().
2212+
if (destType->getIsolation().isNonIsolatedCaller()) {
2213+
if (RValue rv = emitFunctionCvtToExecutionCaller(e, C))
2214+
return rv;
2215+
}
2216+
2217+
if (destType->getIsolation().isGlobalActor()) {
2218+
if (RValue rv = emitFunctionCvtFromExecutionCallerToGlobalActor(e, C))
2219+
return rv;
2220+
}
2221+
20442222
// Break the conversion into three stages:
20452223
// 1) changing the representation from foreign to native
20462224
// 2) changing the signature within the representation

lib/SILGen/SILGenFunction.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2601,11 +2601,19 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
26012601
SILType loweredResultTy,
26022602
SGFContext ctx = SGFContext());
26032603

2604+
enum class ThunkGenFlag {
2605+
None,
2606+
ConvertingToNonIsolatedCaller,
2607+
ConvertingFromNonIsolatedCaller,
2608+
};
2609+
using ThunkGenOptions = OptionSet<ThunkGenFlag>;
2610+
26042611
/// Used for emitting SILArguments of bare functions, such as thunks.
26052612
void collectThunkParams(
26062613
SILLocation loc, SmallVectorImpl<ManagedValue> &params,
26072614
SmallVectorImpl<ManagedValue> *indirectResultParams = nullptr,
2608-
SmallVectorImpl<ManagedValue> *indirectErrorParams = nullptr);
2615+
SmallVectorImpl<ManagedValue> *indirectErrorParams = nullptr,
2616+
ThunkGenOptions options = {});
26092617

26102618
/// Build the type of a function transformation thunk.
26112619
CanSILFunctionType buildThunkType(CanSILFunctionType &sourceType,

0 commit comments

Comments
 (0)