Skip to content
Merged
8 changes: 8 additions & 0 deletions include/swift/SIL/TypeLowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -1389,6 +1389,14 @@ CanSILFunctionType getNativeSILFunctionType(
std::optional<SubstitutionMap> reqtSubs = std::nullopt,
ProtocolConformanceRef witnessMethodConformance = ProtocolConformanceRef());

/// origConstant is the parent decl ref in the case of class methods and the
/// witness method decl ref if we are working with a protocol witness. Pass in
/// None otherwise.
std::optional<ActorIsolation>
getSILFunctionTypeActorIsolation(CanAnyFunctionType substFnInterfaceType,
std::optional<SILDeclRef> origConstant,
std::optional<SILDeclRef> constant);

/// The thunk kinds used in the differentiation transform.
enum class DifferentiationThunkKind {
/// A reabstraction thunk.
Expand Down
97 changes: 64 additions & 33 deletions lib/SIL/IR/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2386,6 +2386,68 @@ static void destructureYieldsForCoroutine(TypeConverter &TC,
}
}

std::optional<ActorIsolation>
swift::getSILFunctionTypeActorIsolation(CanAnyFunctionType substFnInterfaceType,
std::optional<SILDeclRef> origConstant,
std::optional<SILDeclRef> constant) {
// If we have origConstant then we are creating a protocol method thunk. In
// such a case, we want to use the origConstant's actor isolation.
if (origConstant && constant &&
*origConstant != *constant) {
if (auto *decl = origConstant->getAbstractFunctionDecl()) {
if (auto *nonisolatedAttr =
decl->getAttrs().getAttribute<NonisolatedAttr>()) {
if (nonisolatedAttr->isNonSending())
return ActorIsolation::forCallerIsolationInheriting();
}

if (decl->getAttrs().hasAttribute<ConcurrentAttr>()) {
return ActorIsolation::forNonisolated(false /*unsafe*/);
}
}

return getActorIsolationOfContext(origConstant->getInnermostDeclContext());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a generic global actor, does we need to do any kind of substitution to put it in the right generic signature?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gottesmm Do we need to do the generic substitution here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on what is the concern here and give me a more complete explanation on what type of test case would generate the problem/why? I am missing a little bit of context.

Copy link
Contributor

@rjmccall rjmccall May 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActorIsolation can store a type if the function is isolated to a global actor. If the global actor is generic, then that type might includes references to type parameters. The concern is that this code might be implicitly moving the type between generic signatures without rewriting the type parameters — e.g. taking a type expressed in terms of the type parameters of the protocol requirement and using it in a context that's generic in the same way as the witness.

}

if (constant) {
// TODO: It should to be possible to `getActorIsolation` if
// reference is to a decl instead of trying to get isolation
// from the reference kind, the attributes, or the context.

if (constant->kind == SILDeclRef::Kind::Deallocator) {
return ActorIsolation::forNonisolated(false);
}

if (auto *decl = constant->getAbstractFunctionDecl()) {
if (auto *nonisolatedAttr =
decl->getAttrs().getAttribute<NonisolatedAttr>()) {
if (nonisolatedAttr->isNonSending())
return ActorIsolation::forCallerIsolationInheriting();
}

if (decl->getAttrs().hasAttribute<ConcurrentAttr>()) {
return ActorIsolation::forNonisolated(false /*unsafe*/);
}
}

if (auto *closure = constant->getAbstractClosureExpr()) {
if (auto isolation = closure->getActorIsolation())
return isolation;
}

return getActorIsolationOfContext(constant->getInnermostDeclContext());
}

if (substFnInterfaceType->hasExtInfo() &&
substFnInterfaceType->getExtInfo().getIsolation().isNonIsolatedCaller()) {
// If our function type is a nonisolated caller and we can not infer from
// our constant, we must be caller isolation inheriting.
return ActorIsolation::forCallerIsolationInheriting();
}

return {};
}

/// Create the appropriate SIL function type for the given formal type
/// and conventions.
///
Expand Down Expand Up @@ -2617,39 +2679,8 @@ static CanSILFunctionType getSILFunctionType(
SmallBitVector addressableParams;
SmallBitVector conditionallyAddressableParams;
{
std::optional<ActorIsolation> actorIsolation;
if (constant) {
// TODO: It should to be possible to `getActorIsolation` if
// reference is to a decl instead of trying to get isolation
// from the reference kind, the attributes, or the context.

if (constant->kind == SILDeclRef::Kind::Deallocator) {
actorIsolation = ActorIsolation::forNonisolated(false);
} else if (auto *decl = constant->getAbstractFunctionDecl()) {
if (auto *nonisolatedAttr =
decl->getAttrs().getAttribute<NonisolatedAttr>()) {
if (nonisolatedAttr->isNonSending())
actorIsolation = ActorIsolation::forCallerIsolationInheriting();
} else if (decl->getAttrs().hasAttribute<ConcurrentAttr>()) {
actorIsolation = ActorIsolation::forNonisolated(false /*unsafe*/);
}
} else if (auto *closure = constant->getAbstractClosureExpr()) {
if (auto isolation = closure->getActorIsolation())
actorIsolation = isolation;
}

if (!actorIsolation) {
actorIsolation =
getActorIsolationOfContext(constant->getInnermostDeclContext());
}
} else if (substFnInterfaceType->hasExtInfo() &&
substFnInterfaceType->getExtInfo()
.getIsolation()
.isNonIsolatedCaller()) {
// If our function type is a nonisolated caller and we can not infer from
// our constant, we must be caller isolation inheriting.
actorIsolation = ActorIsolation::forCallerIsolationInheriting();
}
auto actorIsolation = getSILFunctionTypeActorIsolation(
substFnInterfaceType, origConstant, constant);
DestructureInputs destructurer(expansionContext, TC, conventions,
foreignInfo, actorIsolation, inputs,
parameterMap,
Expand Down
104 changes: 79 additions & 25 deletions lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7659,57 +7659,111 @@ void SILVTable::verify(const SILModule &M) const {
}

/// Verify that a witness table follows invariants.
void SILWitnessTable::verify(const SILModule &M) const {
if (!verificationEnabled(M))
void SILWitnessTable::verify(const SILModule &mod) const {
if (!verificationEnabled(mod))
return;

if (isDeclaration())
assert(getEntries().empty() &&
"A witness table declaration should not have any entries.");

for (const Entry &E : getEntries())
if (E.getKind() == SILWitnessTable::WitnessKind::Method) {
SILFunction *F = E.getMethodWitness().Witness;
if (F) {
// If a SILWitnessTable is going to be serialized, it must only
// reference public or serializable functions.
if (isAnySerialized()) {
assert(F->hasValidLinkageForFragileRef(getSerializedKind()) &&
"Fragile witness tables should not reference "
"less visible functions.");
}
for (const Entry &entry : getEntries()) {
if (entry.getKind() != SILWitnessTable::WitnessKind::Method)
continue;

auto *witnessFunction = entry.getMethodWitness().Witness;
if (!witnessFunction)
continue;

// If a SILWitnessTable is going to be serialized, it must only
// reference public or serializable functions.
if (isAnySerialized()) {
assert(
witnessFunction->hasValidLinkageForFragileRef(getSerializedKind()) &&
"Fragile witness tables should not reference "
"less visible functions.");
}

assert(F->getLoweredFunctionType()->getRepresentation() ==
assert(witnessFunction->getLoweredFunctionType()->getRepresentation() ==
SILFunctionTypeRepresentation::WitnessMethod &&
"Witnesses must have witness_method representation.");
"Witnesses must have witness_method representation.");

if (mod.getStage() != SILStage::Lowered &&
!mod.getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
// Note the direction of the compatibility check: the witness
// function must be compatible with being used as the requirement
// type.
auto baseInfo = witnessFunction->getModule().Types.getConstantInfo(
TypeExpansionContext::minimal(),
entry.getMethodWitness().Requirement);
SmallString<32> baseName;
{
llvm::raw_svector_ostream os(baseName);
entry.getMethodWitness().Requirement.print(os);
}

SILVerifier(*witnessFunction, /*calleeCache=*/nullptr,
/*SingleFunction=*/true,
/*checkLinearLifetime=*/false)
.requireABICompatibleFunctionTypes(
witnessFunction->getLoweredFunctionType(),
baseInfo.getSILType().castTo<SILFunctionType>(),
"witness table entry for " + baseName + " must be ABI-compatible",
*witnessFunction);
}
}
}

/// Verify that a default witness table follows invariants.
void SILDefaultWitnessTable::verify(const SILModule &M) const {
#ifndef NDEBUG
for (const Entry &E : getEntries()) {
void SILDefaultWitnessTable::verify(const SILModule &mod) const {
if (!verificationEnabled(mod))
return;

for (const Entry &entry : getEntries()) {
// FIXME: associated type witnesses.
if (!E.isValid() || E.getKind() != SILWitnessTable::Method)
if (!entry.isValid() || entry.getKind() != SILWitnessTable::Method)
continue;

SILFunction *F = E.getMethodWitness().Witness;
if (!F)
auto *witnessFunction = entry.getMethodWitness().Witness;
if (!witnessFunction)
continue;

#if 0
// FIXME: For now, all default witnesses are private.
assert(F->hasValidLinkageForFragileRef(IsSerialized) &&
assert(witnessFunction->hasValidLinkageForFragileRef(IsSerialized) &&
"Default witness tables should not reference "
"less visible functions.");
#endif

assert(F->getLoweredFunctionType()->getRepresentation() ==
SILFunctionTypeRepresentation::WitnessMethod &&
assert(witnessFunction->getLoweredFunctionType()->getRepresentation() ==
SILFunctionTypeRepresentation::WitnessMethod &&
"Default witnesses must have witness_method representation.");

if (mod.getStage() != SILStage::Lowered &&
!mod.getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
// Note the direction of the compatibility check: the witness
// function must be compatible with being used as the requirement
// type.
auto baseInfo = witnessFunction->getModule().Types.getConstantInfo(
TypeExpansionContext::minimal(),
entry.getMethodWitness().Requirement);
SmallString<32> baseName;
{
llvm::raw_svector_ostream os(baseName);
entry.getMethodWitness().Requirement.print(os);
}

SILVerifier(*witnessFunction, /*calleeCache=*/nullptr,
/*SingleFunction=*/true,
/*checkLinearLifetime=*/false)
.requireABICompatibleFunctionTypes(
witnessFunction->getLoweredFunctionType(),
baseInfo.getSILType().castTo<SILFunctionType>(),
"default witness table entry for " + baseName +
" must be ABI-compatible",
*witnessFunction);
}
}
#endif
}

/// Verify that a global variable follows invariants.
Expand Down
3 changes: 1 addition & 2 deletions lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -2624,8 +2624,7 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
void collectThunkParams(
SILLocation loc, SmallVectorImpl<ManagedValue> &params,
SmallVectorImpl<ManagedValue> *indirectResultParams = nullptr,
SmallVectorImpl<ManagedValue> *indirectErrorParams = nullptr,
ThunkGenOptions options = {});
SmallVectorImpl<ManagedValue> *indirectErrorParams = nullptr);

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