Rework runtime entrypoints for isolated conformance checking

Replace the pair of global actor type/conformance we are passing around with
a general "conformance execution context" that could grow new functionality
over time. Add three external symbols to the runtime:

* swift_conformsToProtocolWithExecutionContext: a conforms-to-protocol check
  that also captures the execution context that should be checked before
  using the conformance for anything. The only execution context right now
  is for an isolated conformance.
* swift_isInConformanceExecutionContext: checks whether the function is
  being executed in the given execution context, i.e., running on the
  executor for the given global actor.
* swift_ConformanceExecutionContextSize: the size of the conformance
  execution context. Client code outside of the Swift runtime can allocate
  a pointer-aligned region of memory of this size to use with the runtime
  functions above.
This commit is contained in:
Doug Gregor
2025-03-07 23:34:18 -08:00
parent 48aa75d86f
commit 296e14662a
9 changed files with 166 additions and 117 deletions

View File

@@ -2745,6 +2745,10 @@ struct TargetGlobalActorReference {
TargetRelativeProtocolConformanceDescriptorPointer<Runtime> conformance;
};
/// Describes the context of a protocol conformance that is relevant when
/// the conformance is used, such as global actor isolation.
struct ConformanceExecutionContext;
/// The structure of a protocol conformance.
///
/// This contains enough static information to recover the witness table for a
@@ -2885,15 +2889,12 @@ public:
/// necessary, or return null if the conformance does not apply to the
/// type.
///
/// The globalActorIsolationType will be populated with the type of the global
/// actor to which this conformance is isolated, or NULL if this is a
/// nonisolated conformances. When it is isolated,
/// globalActorIsolationConformance is the conformance of
/// globalActorIsolationType to the GlobalActor protocol.
/// The context will be populated with any information that needs to be
/// checked before this witness table can be used within a given execution
/// context.
const swift::TargetWitnessTable<Runtime> *
getWitnessTable(const TargetMetadata<Runtime> *type,
const Metadata *&globalActorIsolationType,
const WitnessTable *&globalActorIsolationConformance) const;
ConformanceExecutionContext &context) const;
/// Retrieve the resilient witnesses.
llvm::ArrayRef<ResilientWitness> getResilientWitnesses() const {

View File

@@ -260,17 +260,31 @@ const WitnessTable *
swift_conformsToProtocolCommon(const Metadata *type,
const ProtocolDescriptor *protocol);
/// The size of the ConformanceExecutionContext structure.
SWIFT_RUNTIME_EXPORT
size_t swift_ConformanceExecutionContextSize;
/// Check whether a type conforms to a given native Swift protocol. This
/// is similar to swift_conformsToProtocolCommon, but allows the caller to
/// capture the global actor isolation of the conformance rather than
/// checking that the code is currently executing on that global actor.
/// either capture the execution context (in *context).
SWIFT_RUNTIME_EXPORT
const WitnessTable *
swift_conformsToProtocolCommonIsolated(
swift_conformsToProtocolWithExecutionContext(
const Metadata *type,
const ProtocolDescriptor *protocol,
const Metadata **globalActorIsolationType,
const WitnessTable **globalActorIsolationWitnessTable);
ConformanceExecutionContext *context);
/// Determine whether this function is being executed within the execution
/// context for a conformance. For example, if the conformance is
/// isolated to a given global actor, checks whether this code is running on
/// that global actor's executor.
///
/// The context should have been filled in by
/// swift_conformsToProtocolWithExecutionContext.
SWIFT_RUNTIME_EXPORT
bool swift_isInConformanceExecutionContext(
const Metadata *type,
const ConformanceExecutionContext *context);
} // end namespace swift

View File

@@ -193,15 +193,18 @@ OVERRIDE_PROTOCOLCONFORMANCE(conformsToProtocolCommon, const WitnessTable *, , ,
const ProtocolDescriptor *protocol),
(type, protocol))
OVERRIDE_PROTOCOLCONFORMANCE(conformsToProtocolCommonIsolated,
OVERRIDE_PROTOCOLCONFORMANCE(conformsToProtocolWithExecutionContext,
const WitnessTable *, , , swift::,
(const Metadata * const type,
const ProtocolDescriptor *protocol,
const Metadata **globalActorIsolationType,
const WitnessTable **
globalActorIsolationWitnessTable),
(type, protocol, globalActorIsolationType,
globalActorIsolationWitnessTable))
ConformanceExecutionContext *context),
(type, protocol, context))
OVERRIDE_PROTOCOLCONFORMANCE(isInConformanceExecutionContext,
bool, , , swift::,
(const Metadata * const type,
const ConformanceExecutionContext *context),
(type, context))
OVERRIDE_KEYPATH(getKeyPath, const HeapObject *, , , swift::,
(const void *pattern, const void *arguments),

View File

@@ -534,13 +534,11 @@ bool swift::_conformsToProtocol(
const Metadata *type,
ProtocolDescriptorRef protocol,
const WitnessTable **conformance,
const Metadata **globalActorIsolationType,
const WitnessTable **globalActorIsolationWitnessTable) {
ConformanceExecutionContext *context) {
// Look up the witness table for protocols that need them.
if (protocol.needsWitnessTable()) {
auto witness = swift_conformsToProtocolCommonIsolated(
type, protocol.getSwiftProtocol(), globalActorIsolationType,
globalActorIsolationWitnessTable);
auto witness = swift_conformsToProtocolWithExecutionContext(
type, protocol.getSwiftProtocol(), context);
if (!witness)
return false;
if (conformance)
@@ -612,6 +610,22 @@ bool swift::_conformsToProtocol(
return false;
}
bool swift::_conformsToProtocolInContext(
const OpaqueValue *value,
const Metadata *type,
ProtocolDescriptorRef protocol,
const WitnessTable **conformance) {
ConformanceExecutionContext context;
if (!_conformsToProtocol(value, type, protocol, conformance, &context))
return false;
if (!swift_isInConformanceExecutionContext(type, &context))
return false;
return true;
}
/// Check whether a type conforms to the given protocols, filling in a
/// list of conformances.
static bool _conformsToProtocols(const OpaqueValue *value,
@@ -629,8 +643,8 @@ static bool _conformsToProtocols(const OpaqueValue *value,
}
for (auto protocol : existentialType->getProtocols()) {
if (!_conformsToProtocol(
value, type, protocol, conformances, nullptr, nullptr))
if (!_conformsToProtocolInContext(
value, type, protocol, conformances))
return false;
if (conformances != nullptr && protocol.needsWitnessTable()) {
assert(*conformances != nullptr);

View File

@@ -1384,8 +1384,8 @@ static bool _conformsToProtocols(const OpaqueValue *value,
}
for (auto protocol : existentialType->getProtocols()) {
if (!swift::_conformsToProtocol(value, type, protocol, conformances,
nullptr, nullptr))
if (!swift::_conformsToProtocolInContext(
value, type, protocol, conformances))
return false;
if (conformances != nullptr && protocol.needsWitnessTable()) {
assert(*conformances != nullptr);
@@ -1867,6 +1867,7 @@ static DynamicCastResult tryCastToExtendedExistential(
allGenericArgsVec.data());
// Verify the requirements in the requirement signature against the
// arguments from the source value.
ConformanceExecutionContext context;
auto requirementSig = destExistentialShape->getRequirementSignature();
auto error = swift::_checkGenericRequirements(
requirementSig.getParams(),
@@ -1881,9 +1882,12 @@ static DynamicCastResult tryCastToExtendedExistential(
[](const Metadata *type, unsigned index) -> const WitnessTable * {
swift_unreachable("Resolution of witness tables is not supported");
},
nullptr, nullptr);
&context);
if (error)
return DynamicCastResult::Failure;
if (!swift_isInConformanceExecutionContext(selfType, &context))
return DynamicCastResult::Failure;
}
OpaqueValue *destBox = nullptr;

View File

@@ -1603,7 +1603,7 @@ _gatherGenericParameters(const ContextDescriptor *context,
[&substitutions](const Metadata *type, unsigned index) {
return substitutions.getWitnessTable(type, index);
},
nullptr, nullptr);
nullptr);
if (error)
return *error;
@@ -2045,7 +2045,7 @@ public:
[](const Metadata *type, unsigned index) -> const WitnessTable * {
swift_unreachable("never called");
},
nullptr, nullptr);
nullptr);
if (error)
return *error;
@@ -3059,7 +3059,7 @@ swift_distributed_getWitnessTables(GenericEnvironmentDescriptor *genericEnv,
[&substFn](const Metadata *type, unsigned index) {
return substFn.getWitnessTable(type, index);
},
nullptr, nullptr);
nullptr);
if (error) {
return {/*ptr=*/nullptr, -1};

View File

@@ -567,10 +567,9 @@ public:
/// generic requirements (e.g., those that need to be
/// passed to an instantiation function) will be added to this vector.
///
/// \param globalActorIsolationType When non-NULL, the global actor isolation
/// of these requirements will be reported through this OUT parameter.
/// When NULL, any global actor isolation will be checked dynamically.
///
/// \param context When non-NULL, receives any information about the
/// execution context that is required to use this conformance.
///
/// \returns the error if an error occurred, None otherwise.
std::optional<TypeLookupError> _checkGenericRequirements(
llvm::ArrayRef<GenericParamDescriptor> genericParams,
@@ -579,8 +578,7 @@ public:
SubstGenericParameterFn substGenericParam,
SubstGenericParameterOrdinalFn substGenericParamOrdinal,
SubstDependentWitnessTableFn substWitnessTable,
const Metadata **globalActorIsolationType,
const WitnessTable **globalActorIsolationWitnessTable);
ConformanceExecutionContext *context);
/// A helper function which avoids performing a store if the destination
/// address already contains the source value. This is useful when
@@ -685,6 +683,19 @@ public:
bool _isCImportedTagType(const TypeContextDescriptor *type,
const ParsedTypeIdentity &identity);
/// The execution context for a conformance, containing any additional
/// checking that has to be done in context to determine whether a given
/// conformance is available.
struct ConformanceExecutionContext {
/// The global actor to which this conformance is isolated, or NULL for
/// a nonisolated conformances.
const Metadata *globalActorIsolationType = nullptr;
/// When the conformance is global-actor-isolated, this is the conformance
/// of globalActorIsolationType to GlobalActor.
const WitnessTable *globalActorIsolationWitnessTable = nullptr;
};
/// Check whether a type conforms to a protocol.
///
/// \param value - can be null, in which case the question should
@@ -692,19 +703,25 @@ public:
/// \param conformance - if non-null, and the protocol requires a
/// witness table, and the type implements the protocol, the witness
/// table will be placed here
/// \param globalActorIsolationType - when non-NULL and the conformance is
/// global-actor-isolated, capture the global actor isolation type in this
/// out variable rather than checking when we are executing on that global
/// actor.
/// \param globalActorIsolationWitnessTable - receives the witness table for
/// *globalActorIsolationType's conformance to GlobalActor.
/// \param context - when non-NULL, receives any information about the
/// required execution context for this conformance.
bool _conformsToProtocol(
const OpaqueValue *value,
const Metadata *type,
ProtocolDescriptorRef protocol,
const WitnessTable **conformance,
const Metadata **globalActorIsolationType,
const WitnessTable **globalActorIsolationWitnessTable);
ConformanceExecutionContext *context);
/// Check whether a type conforms to a value within the currently-executing
/// context.
///
/// This is equivalent to a _conformsToProtocol check followed by runtime
/// checking for global actor isolation, if needed.
bool _conformsToProtocolInContext(
const OpaqueValue *value,
const Metadata *type,
ProtocolDescriptorRef protocol,
const WitnessTable **conformance);
/// Construct type metadata for the given protocol.
const Metadata *

View File

@@ -370,16 +370,14 @@ static bool _checkWitnessTableIsolation(
const Metadata *type,
const WitnessTable *wtable,
llvm::ArrayRef<const void *> conditionalArgs,
const Metadata *&globalActorIsolationType,
const WitnessTable *&globalActorIsolationWitnessTable
ConformanceExecutionContext &context
);
template<>
const WitnessTable *
ProtocolConformanceDescriptor::getWitnessTable(
const Metadata *type,
const Metadata *&globalActorIsolationType,
const WitnessTable *&globalActorIsolationWitnessTable
ConformanceExecutionContext &context
) const {
// If needed, check the conditional requirements.
llvm::SmallVector<const void *, 8> conditionalArgs;
@@ -401,8 +399,7 @@ ProtocolConformanceDescriptor::getWitnessTable(
[&substitutions](const Metadata *type, unsigned index) {
return substitutions.getWitnessTable(type, index);
},
&globalActorIsolationType,
&globalActorIsolationWitnessTable);
&context);
if (error)
return nullptr;
}
@@ -419,9 +416,7 @@ ProtocolConformanceDescriptor::getWitnessTable(
// Check the global-actor isolation for this conformance, combining it with
// any global-actor isolation determined based on the conditional
// requirements above.
if (_checkWitnessTableIsolation(
type, wtable, conditionalArgs, globalActorIsolationType,
globalActorIsolationWitnessTable))
if (_checkWitnessTableIsolation(type, wtable, conditionalArgs, context))
return nullptr;
return wtable;
@@ -430,14 +425,12 @@ ProtocolConformanceDescriptor::getWitnessTable(
ConformanceLookupResult ConformanceLookupResult::fromConformance(
const Metadata *type,
const ProtocolConformanceDescriptor *conformanceDescriptor) {
const Metadata *globalActorIsolationType = nullptr;
const WitnessTable *globalActorIsolationWitnessTable = nullptr;
auto wtable = conformanceDescriptor->getWitnessTable(
type, globalActorIsolationType, globalActorIsolationWitnessTable);
ConformanceExecutionContext context;
auto wtable = conformanceDescriptor->getWitnessTable(type, context);
return {
wtable,
globalActorIsolationType,
globalActorIsolationWitnessTable
context.globalActorIsolationType,
context.globalActorIsolationWitnessTable
};
}
@@ -449,8 +442,7 @@ static bool _checkWitnessTableIsolation(
const Metadata *type,
const WitnessTable *wtable,
llvm::ArrayRef<const void *> conditionalArgs,
const Metadata *&globalActorIsolationType,
const WitnessTable *&globalActorIsolationWitnessTable
ConformanceExecutionContext &context
) {
// If there's no protocol conformance descriptor, do nothing.
auto description = wtable->getDescription();
@@ -481,8 +473,8 @@ static bool _checkWitnessTableIsolation(
// If the global actor isolation from this conformance conflicts with
// the one we already have, fail.
if (globalActorIsolationType &&
globalActorIsolationType != myGlobalActorIsolationType)
if (context.globalActorIsolationType &&
context.globalActorIsolationType != myGlobalActorIsolationType)
return true;
// Dig out the witness table.
@@ -495,8 +487,8 @@ static bool _checkWitnessTableIsolation(
if (!myWitnessTable)
return true;
globalActorIsolationType = myGlobalActorIsolationType;
globalActorIsolationWitnessTable = myWitnessTable.witnessTable;
context.globalActorIsolationType = myGlobalActorIsolationType;
context.globalActorIsolationWitnessTable = myWitnessTable.witnessTable;
return false;
}
@@ -1323,11 +1315,10 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
}
static const WitnessTable *
swift_conformsToProtocolCommonIsolatedImpl(
swift_conformsToProtocolWithExecutionContextImpl(
const Metadata *const type,
const ProtocolDescriptor *protocol,
const Metadata **globalActorIsolationType,
const WitnessTable **globalActorIsolationWitnessTable) {
ConformanceExecutionContext *context) {
ConformanceLookupResult found;
bool hasUninstantiatedSuperclass;
@@ -1350,33 +1341,17 @@ swift_conformsToProtocolCommonIsolatedImpl(
type, protocol, true /*instantiateSuperclassMetadata*/);
// Check for isolated conformances.
if (found.globalActorIsolationType) {
// If we were asked to report the global actor isolation, do so.
if (globalActorIsolationType) {
// If the existing global actor isolation differs from the one we
// computed, it's a conflict. Fail.
if (*globalActorIsolationType &&
*globalActorIsolationType != found.globalActorIsolationType)
return nullptr;
if (found.globalActorIsolationType && context) {
// If the existing global actor isolation differs from the one we
// computed, it's a conflict. Fail.
if (context->globalActorIsolationType &&
context->globalActorIsolationType != found.globalActorIsolationType)
return nullptr;
// Report the global actor isolation.
*globalActorIsolationType = found.globalActorIsolationType;
if (globalActorIsolationWitnessTable) {
*globalActorIsolationWitnessTable =
found.globalActorIsolationWitnessTable;
}
} else {
// The concurrency library provides a function to check whether we
// are executing on the given global actor.
if (!_swift_task_isCurrentGlobalActorHook)
return nullptr;
// Check whether we are running on this global actor.
if (!_swift_task_isCurrentGlobalActorHook(
found.globalActorIsolationType,
found.globalActorIsolationWitnessTable))
return nullptr;
}
// Report the global actor isolation.
context->globalActorIsolationType = found.globalActorIsolationType;
context->globalActorIsolationWitnessTable =
found.globalActorIsolationWitnessTable;
}
return found.witnessTable;
@@ -1386,8 +1361,8 @@ static const WitnessTable *
swift_conformsToProtocolCommonImpl(
const Metadata *const type,
const ProtocolDescriptor *protocol) {
return swift_conformsToProtocolCommonIsolatedImpl(
type, protocol, nullptr, nullptr);
return swift_conformsToProtocolWithExecutionContextImpl(
type, protocol, nullptr);
}
static const WitnessTable *
@@ -1410,6 +1385,26 @@ swift_conformsToProtocolImpl(const Metadata *const type,
type, static_cast<const ProtocolDescriptor *>(protocol));
}
static bool swift_isInConformanceExecutionContextImpl(
const Metadata *type,
const ConformanceExecutionContext *context) {
if (!context)
return true;
if (context->globalActorIsolationType) {
if (!_swift_task_isCurrentGlobalActorHook)
return false;
// Check whether we are running on this global actor.
if (!_swift_task_isCurrentGlobalActorHook(
context->globalActorIsolationType,
context->globalActorIsolationWitnessTable))
return false;
}
return true;
}
const ContextDescriptor *
swift::_searchConformancesByMangledTypeName(Demangle::NodePointer node) {
auto traceState = runtime::trace::protocol_conformance_scan_begin(node);
@@ -1569,8 +1564,7 @@ checkGenericRequirement(
SubstGenericParameterFn substGenericParam,
SubstDependentWitnessTableFn substWitnessTable,
llvm::SmallVectorImpl<InvertibleProtocolSet> &suppressed,
const Metadata **globalActorIsolationType,
const WitnessTable **globalActorIsolationWitnessTable) {
ConformanceExecutionContext *context) {
assert(!req.getFlags().isPackRequirement());
// Make sure we understand the requirement we're dealing with.
@@ -1590,8 +1584,7 @@ checkGenericRequirement(
case GenericRequirementKind::Protocol: {
const WitnessTable *witnessTable = nullptr;
if (!_conformsToProtocol(nullptr, subjectType, req.getProtocol(),
&witnessTable, globalActorIsolationType,
globalActorIsolationWitnessTable)) {
&witnessTable, context)) {
const char *protoName =
req.getProtocol() ? req.getProtocol().getName() : "<null>";
return TYPE_LOOKUP_ERROR_FMT(
@@ -1689,8 +1682,7 @@ checkGenericPackRequirement(
SubstGenericParameterFn substGenericParam,
SubstDependentWitnessTableFn substWitnessTable,
llvm::SmallVectorImpl<InvertibleProtocolSet> &suppressed,
const Metadata **globalActorIsolationType,
const WitnessTable **globalActorIsolationWitnessTable) {
ConformanceExecutionContext *context) {
assert(req.getFlags().isPackRequirement());
// Make sure we understand the requirement we're dealing with.
@@ -1717,8 +1709,7 @@ checkGenericPackRequirement(
const WitnessTable *witnessTable = nullptr;
if (!_conformsToProtocol(nullptr, elt, req.getProtocol(),
&witnessTable, globalActorIsolationType,
globalActorIsolationWitnessTable)) {
&witnessTable, context)) {
const char *protoName =
req.getProtocol() ? req.getProtocol().getName() : "<null>";
return TYPE_LOOKUP_ERROR_FMT(
@@ -2121,7 +2112,7 @@ checkInvertibleRequirements(const Metadata *type,
[&substFn](const Metadata *type, unsigned index) {
return substFn.getWitnessTable(type, index);
},
nullptr, nullptr);
nullptr);
if (error)
return error;
}
@@ -2136,20 +2127,17 @@ std::optional<TypeLookupError> swift::_checkGenericRequirements(
SubstGenericParameterFn substGenericParam,
SubstGenericParameterOrdinalFn substGenericParamOrdinal,
SubstDependentWitnessTableFn substWitnessTable,
const Metadata **globalActorIsolationType,
const WitnessTable **globalActorIsolationWitnessTable) {
ConformanceExecutionContext *context) {
// The suppressed conformances for each generic parameter.
llvm::SmallVector<InvertibleProtocolSet, 4> allSuppressed;
for (const auto &req : requirements) {
if (req.getFlags().isPackRequirement()) {
auto error = checkGenericPackRequirement(
req, extraArguments,
substGenericParam,
substWitnessTable,
allSuppressed,
globalActorIsolationType,
globalActorIsolationWitnessTable);
auto error = checkGenericPackRequirement(req, extraArguments,
substGenericParam,
substWitnessTable,
allSuppressed,
context);
if (error)
return error;
} else if (req.getFlags().isValueRequirement()) {
@@ -2164,8 +2152,7 @@ std::optional<TypeLookupError> swift::_checkGenericRequirements(
substGenericParam,
substWitnessTable,
allSuppressed,
globalActorIsolationType,
globalActorIsolationWitnessTable);
context);
if (error)
return error;
}
@@ -2249,5 +2236,8 @@ const Metadata *swift::findConformingSuperclass(
return conformingType;
}
size_t swift::swift_ConformanceExecutionContextSize =
sizeof(ConformanceExecutionContext);
#define OVERRIDE_PROTOCOLCONFORMANCE COMPATIBILITY_OVERRIDE
#include "../CompatibilityOverride/CompatibilityOverrideIncludePath.h"

View File

@@ -895,6 +895,12 @@ Added: _swift_cvw_initializeBufferWithCopyOfBufferMultiPayloadEnumFN
// SE-0457 Expose attosecond representation of Duration
Added: _$ss8DurationV11attosecondss6Int128VvpMV
// add callee-allocated coro entrypoints
Added: _$ss48swift_deletedCalleeAllocatedCoroutineMethodErrorytvg
Added: _swift_deletedCalleeAllocatedCoroutineMethodError
// Isolated conformances
Added: _swift_ConformanceExecutionContextSize
Added: _swift_conformsToProtocolWithExecutionContext
Added: _swift_isInConformanceExecutionContext