From 951b535608008061328915d57bb3d0d010a17522 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 4 Mar 2025 23:06:48 -0800 Subject: [PATCH 01/13] Metadata and runtime support for checking isolated conformances at runtime Extend the metadata representation of protocol conformance descriptors to include information about the global actor to which the conformance is isolated (when there is one), as well as the conformance of that type to the GlobalActor protocol. Emit this metadata whenever a conformance is isolated. When performing a conforms-to-protocol check at runtime, check whether the conformance that was found is isolated. If so, extract the serial executor for the global actor and check whether we are running on that executor. If not, the conformance fails. --- include/swift/ABI/Metadata.h | 47 ++++++++++- include/swift/ABI/MetadataValues.h | 16 +++- lib/IRGen/GenProto.cpp | 44 +++++++++++ stdlib/public/Concurrency/Task.swift | 9 +++ stdlib/public/runtime/ProtocolConformance.cpp | 56 +++++++++++++ .../Runtime/isolated_conformance.swift | 79 +++++++++++++++++++ test/Concurrency/isolated_conformance.swift | 1 + test/IRGen/isolated_conformance.swift | 21 +++++ 8 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 test/Concurrency/Runtime/isolated_conformance.swift create mode 100644 test/IRGen/isolated_conformance.swift diff --git a/include/swift/ABI/Metadata.h b/include/swift/ABI/Metadata.h index 622e2bc6600eb..a893d028e5053 100644 --- a/include/swift/ABI/Metadata.h +++ b/include/swift/ABI/Metadata.h @@ -2734,6 +2734,17 @@ struct TargetResilientWitnessesHeader { }; using ResilientWitnessesHeader = TargetResilientWitnessesHeader; +/// Describes a reference to a global actor type and its conformance to the +/// global actor protocol. +template +struct TargetGlobalActorReference { + /// The type of the global actor. + RelativeDirectPointer type; + + /// The conformance of the global actor to the GlobalActor protocol. + TargetRelativeProtocolConformanceDescriptorPointer conformance; +}; + /// The structure of a protocol conformance. /// /// This contains enough static information to recover the witness table for a @@ -2747,7 +2758,8 @@ struct TargetProtocolConformanceDescriptor final GenericPackShapeDescriptor, TargetResilientWitnessesHeader, TargetResilientWitness, - TargetGenericWitnessTable> { + TargetGenericWitnessTable, + TargetGlobalActorReference> { using TrailingObjects = swift::ABI::TrailingObjects< TargetProtocolConformanceDescriptor, @@ -2756,7 +2768,8 @@ struct TargetProtocolConformanceDescriptor final GenericPackShapeDescriptor, TargetResilientWitnessesHeader, TargetResilientWitness, - TargetGenericWitnessTable>; + TargetGenericWitnessTable, + TargetGlobalActorReference>; friend TrailingObjects; template @@ -2892,6 +2905,32 @@ struct TargetProtocolConformanceDescriptor final return this->template getTrailingObjects(); } + /// Whether this conformance has any conditional requirements that need to + /// be evaluated. + bool hasGlobalActorIsolation() const { + return Flags.hasGlobalActorIsolation(); + } + + /// Retrieve the global actor type to which this conformance is isolated, if + /// any. + llvm::StringRef + getGlobalActorType() const { + if (!Flags.hasGlobalActorIsolation()) + return llvm::StringRef(); + + return Demangle::makeSymbolicMangledNameStringRef(this->template getTrailingObjects>()->type); + } + + /// Retrieve the protocol conformance of the global actor type to the + /// GlobalActor protocol. + const TargetProtocolConformanceDescriptor * + getGlobalActorConformance() const { + if (!Flags.hasGlobalActorIsolation()) + return nullptr; + + return this->template getTrailingObjects>()->conformance; + } + #if !defined(NDEBUG) && SWIFT_OBJC_INTEROP void dump() const; #endif @@ -2934,6 +2973,10 @@ struct TargetProtocolConformanceDescriptor final size_t numTrailingObjects(OverloadToken) const { return Flags.hasGenericWitnessTable() ? 1 : 0; } + + size_t numTrailingObjects(OverloadToken>) const { + return Flags.hasGlobalActorIsolation() ? 1 : 0; + } }; using ProtocolConformanceDescriptor = TargetProtocolConformanceDescriptor; diff --git a/include/swift/ABI/MetadataValues.h b/include/swift/ABI/MetadataValues.h index c7579236417cf..a2f3ef06913ca 100644 --- a/include/swift/ABI/MetadataValues.h +++ b/include/swift/ABI/MetadataValues.h @@ -746,6 +746,7 @@ class ConformanceFlags { HasResilientWitnessesMask = 0x01u << 16, HasGenericWitnessTableMask = 0x01u << 17, IsConformanceOfProtocolMask = 0x01u << 18, + HasGlobalActorIsolation = 0x01u << 19, NumConditionalPackDescriptorsMask = 0xFFu << 24, NumConditionalPackDescriptorsShift = 24 @@ -805,6 +806,14 @@ class ConformanceFlags { : 0)); } + ConformanceFlags withHasGlobalActorIsolation( + bool hasGlobalActorIsolation) const { + return ConformanceFlags((Value & ~HasGlobalActorIsolation) + | (hasGlobalActorIsolation + ? HasGlobalActorIsolation + : 0)); + } + /// Retrieve the type reference kind kind. TypeReferenceKind getTypeReferenceKind() const { return TypeReferenceKind( @@ -843,7 +852,12 @@ class ConformanceFlags { bool isConformanceOfProtocol() const { return Value & IsConformanceOfProtocolMask; } - + + /// Does this conformance have a global actor to which it is isolated? + bool hasGlobalActorIsolation() const { + return Value & HasGlobalActorIsolation; + } + /// Retrieve the # of conditional requirements. unsigned getNumConditionalRequirements() const { return (Value & NumConditionalRequirementsMask) diff --git a/lib/IRGen/GenProto.cpp b/lib/IRGen/GenProto.cpp index e5163e9d19246..cb4def2aa85ba 100644 --- a/lib/IRGen/GenProto.cpp +++ b/lib/IRGen/GenProto.cpp @@ -30,6 +30,7 @@ #include "swift/AST/ASTContext.h" #include "swift/AST/CanTypeVisitor.h" #include "swift/AST/Types.h" +#include "swift/AST/ConformanceLookup.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsIRGen.h" #include "swift/AST/GenericEnvironment.h" @@ -93,6 +94,11 @@ using namespace swift; using namespace irgen; +namespace swift { + // FIXME: Move this on to ProtocolConformance? + ActorIsolation getConformanceIsolation(ProtocolConformance *conformance); +} + namespace { /// A class for computing how to pass arguments to a polymorphic @@ -2180,6 +2186,7 @@ namespace { addConditionalRequirements(); addResilientWitnesses(); addGenericWitnessTable(); + addGlobalActorIsolation(); // We fill the flags last, since we continue filling them in // after the call to addFlags() deposits the placeholder. @@ -2216,6 +2223,7 @@ namespace { Flags = Flags.withIsRetroactive(conf->isRetroactive()); Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique()); Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol()); + Flags = Flags.withHasGlobalActorIsolation(conf->isIsolated()); } else { Flags = Flags.withIsRetroactive(false) .withIsSynthesizedNonUnique(false); @@ -2403,6 +2411,42 @@ namespace { B.addRelativeAddress(privateData); } } + + void addGlobalActorIsolation() { + if (!Flags.hasGlobalActorIsolation()) + return; + + auto normal = cast(Conformance); + assert(normal->isIsolated()); + auto nominal = normal->getDeclContext()->getSelfNominalTypeDecl(); + + // Add global actor type. + auto sig = nominal->getGenericSignatureOfContext(); + auto isolation = getConformanceIsolation( + const_cast(Conformance)); + assert(isolation.isGlobalActor()); + Type globalActorType = isolation.getGlobalActor(); + auto globalActorTypeName = IGM.getTypeRef( + globalActorType, sig, MangledTypeRefRole::Metadata).first; + B.addRelativeAddress(globalActorTypeName); + + // Add conformance of the global actor type to the GlobalActor protocol. + SmallVector globalActorConformances; + auto globalActorProtocol = + IGM.Context.getProtocol(KnownProtocolKind::GlobalActor); + auto globalActorConformance = lookupConformance( + globalActorType, globalActorProtocol); + + auto rootGlobalActorConformance = globalActorConformance.getConcrete() + ->getRootConformance(); + IGM.IRGen.addLazyWitnessTable(rootGlobalActorConformance); + + auto globalActorConformanceDescriptor = + IGM.getAddrOfLLVMVariableOrGOTEquivalent( + LinkEntity::forProtocolConformanceDescriptor( + rootGlobalActorConformance)); + B.addRelativeAddress(globalActorConformanceDescriptor); + } }; } diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index b07c66f60a51a..378f409d58095 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -1547,6 +1547,15 @@ func _taskIsCurrentExecutor(_ executor: Builtin.Executor) -> Bool internal func _taskIsCurrentExecutor( executor: Builtin.Executor, flags: UInt64) -> Bool +extension GlobalActor { + @available(SwiftStdlib 6.2, *) + @_silgen_name("swift_task_isCurrentGlobalActor") + @usableFromInline + internal static func _taskIsCurrentGlobalActor() -> Bool { + let executor = unsafe sharedUnownedExecutor + return unsafe _taskIsCurrentExecutor(executor: executor.executor, flags: 0) + } +} #endif @available(SwiftStdlib 5.1, *) diff --git a/stdlib/public/runtime/ProtocolConformance.cpp b/stdlib/public/runtime/ProtocolConformance.cpp index ca7849a34d5b5..603343fd071ea 100644 --- a/stdlib/public/runtime/ProtocolConformance.cpp +++ b/stdlib/public/runtime/ProtocolConformance.cpp @@ -1154,6 +1154,50 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( return {foundWitness, hasUninstantiatedSuperclass}; } +/// Determine if +static bool isExecutingInIsolationOfConformance( + const Metadata *const type, + const ProtocolConformanceDescriptor *description, + const WitnessTable *table +) { + // Resolve the global actor type. + SubstGenericParametersFromMetadata substitutions(type); + auto result = swift_getTypeByMangledName( + MetadataState::Abstract, description->getGlobalActorType(), + /*FIXME:conditionalArgs.data()*/{ }, + [&substitutions](unsigned depth, unsigned index) { + return substitutions.getMetadata(depth, index).Ptr; + }, + [&substitutions](const Metadata *type, unsigned index) { + return substitutions.getWitnessTable(type, index); + }); + if (result.isError()) + return false; + + const Metadata *globalActorType = result.getType().getMetadata(); + if (!globalActorType) + return false; + + auto globalActorConformance = description->getGlobalActorConformance(); + if (!globalActorConformance) + return false; + + auto globalActorWitnessTable = + globalActorConformance->getWitnessTable(globalActorType); + if (!globalActorWitnessTable) + return false; + + // The concurrency library provides a function to check whether we + // are executing on the given global actor. + auto isCurrentGlobalActor = SWIFT_LAZY_CONSTANT(reinterpret_cast( + dlsym(RTLD_DEFAULT, "swift_task_isCurrentGlobalActor"))); + if (!isCurrentGlobalActor) + return false; + + // Check whether we are running on this global actor. + return isCurrentGlobalActor(globalActorType, globalActorWitnessTable); +} + static const WitnessTable * swift_conformsToProtocolCommonImpl(const Metadata *const type, const ProtocolDescriptor *protocol) { @@ -1178,6 +1222,18 @@ swift_conformsToProtocolCommonImpl(const Metadata *const type, swift_conformsToProtocolMaybeInstantiateSuperclasses( type, protocol, true /*instantiateSuperclassMetadata*/); + // If the conformance is isolated to a global actor, check whether we are + // currently executing on that global actor. Otherwise, the type does not + // conform. + if (table) { + if (auto description = table->getDescription()) { + if (description->hasGlobalActorIsolation() && + !isExecutingInIsolationOfConformance(type, description, table)) { + return nullptr; + } + } + } + return table; } diff --git a/test/Concurrency/Runtime/isolated_conformance.swift b/test/Concurrency/Runtime/isolated_conformance.swift new file mode 100644 index 0000000000000..a1ca3dba9d2bf --- /dev/null +++ b/test/Concurrency/Runtime/isolated_conformance.swift @@ -0,0 +1,79 @@ +// RUN: %target-run-simple-swift(-enable-experimental-feature IsolatedConformances -target %target-swift-5.1-abi-triple) | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: concurrency +// REQUIRES: concurrency_runtime +// REQUIRES: swift_feature_IsolatedConformances +// UNSUPPORTED: back_deployment_runtime + +protocol P { + func f() +} + +@MainActor +class MyClass: isolated P { + func f() { + print("MyClass.f()") + + // Make sure we're on the main actor. + MainActor.assumeIsolated { } + } +} + +struct Wrapper { + var wrapped: T +} + +extension Wrapper: P where T: P { + func f() { + print("Wrapper for ", terminator: "") + wrapped.f() + } +} + +func tryCastToP(_ value: any Sendable) -> Bool { + if let p = value as? any P { + p.f() + return true + } + + print("Conformance did not match") + return false +} + +// CHECK: Testing on the main actor +// CHECK-NEXT: MyClass.f() +// CHECK-NEXT: Wrapper for MyClass.f() +print("Testing on the main actor") +let mc = MyClass() +let wrappedMC = Wrapper(wrapped: mc) +precondition(tryCastToP(mc)) +precondition(tryCastToP(wrappedMC)) + +// CHECK: Testing a separate task on the main actor +// CHECK-NEXT: MyClass.f() +// CHECK-NEXT: Wrapper for MyClass.f() +print("Testing a separate task on the main actor") +await Task.detached { @MainActor in + precondition(tryCastToP(mc)) + precondition(tryCastToP(wrappedMC)) +}.value + +// FIXME: Currently not handling the wrapper case appropriately, because +// we don't track whether we used an isolated conformance to satisfy another +// conformance at runtime. + +// CHECK: Testing a separate task off the main actor +print("Testing a separate task off the main actor") +await Task.detached { + if #available(SwiftStdlib 6.2, *) { + precondition(!tryCastToP(mc)) + // precondition(!tryCastToP(wrappedMC)) + } else { + print("Cast succeeds, but shouldn't") + precondition(tryCastToP(mc)) + } +}.value + +// Ensure that we access mc later +print(mc) diff --git a/test/Concurrency/isolated_conformance.swift b/test/Concurrency/isolated_conformance.swift index 872fb4c02a5e7..2db33ffcc5183 100644 --- a/test/Concurrency/isolated_conformance.swift +++ b/test/Concurrency/isolated_conformance.swift @@ -1,6 +1,7 @@ // RUN: %target-swift-frontend -typecheck -verify -target %target-swift-5.1-abi-triple -swift-version 6 -enable-experimental-feature IsolatedConformances %s // REQUIRES: swift_feature_IsolatedConformances +// REQUIRES: concurrency protocol P { func f() // expected-note 2{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}} diff --git a/test/IRGen/isolated_conformance.swift b/test/IRGen/isolated_conformance.swift new file mode 100644 index 0000000000000..c135d960e754e --- /dev/null +++ b/test/IRGen/isolated_conformance.swift @@ -0,0 +1,21 @@ +// RUN: %target-swift-frontend -primary-file %s -emit-ir -swift-version 6 -enable-experimental-feature IsolatedConformances | %FileCheck %s -DINT=i%target-ptrsize + +// REQUIRES: PTRSIZE=64 +// REQUIRES: concurrency +// UNSUPPORTED: CPU=arm64e + +protocol P { + func f() +} + +// CHECK-LABEL: @"$s20isolated_conformance1XVyxGAA1PAAMc" = +// CHECK-SAME: ptr @"$s20isolated_conformance1PMp" +// CHECK-SAME: ptr @"$s20isolated_conformance1XVMn" +// CHECK-SAME: ptr @"$s20isolated_conformance1XVyxGAA1PAAWP +// CHECK-SAME: i32 524288 +// CHECK-SAME: @"symbolic ScM" +// CHECK-SAME: ptr @"$sScMs11GlobalActorsMc" +@MainActor +struct X: isolated P { + func f() { } +} From 5b6aff77abd076403f52adbdfae0f72af91376b9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 5 Mar 2025 09:22:54 -0800 Subject: [PATCH 02/13] Test case fixes for isolated conformances --- test/IRGen/isolated_conformance.swift | 3 ++- test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/IRGen/isolated_conformance.swift b/test/IRGen/isolated_conformance.swift index c135d960e754e..72f8121f17ef2 100644 --- a/test/IRGen/isolated_conformance.swift +++ b/test/IRGen/isolated_conformance.swift @@ -2,6 +2,7 @@ // REQUIRES: PTRSIZE=64 // REQUIRES: concurrency +// REQUIRES: swift_feature_IsolatedConformances // UNSUPPORTED: CPU=arm64e protocol P { @@ -14,7 +15,7 @@ protocol P { // CHECK-SAME: ptr @"$s20isolated_conformance1XVyxGAA1PAAWP // CHECK-SAME: i32 524288 // CHECK-SAME: @"symbolic ScM" -// CHECK-SAME: ptr @"$sScMs11GlobalActorsMc" +// CHECK-SAME: ptr @"got.$sScMs11GlobalActorsMc" @MainActor struct X: isolated P { func f() { } diff --git a/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts b/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts index 38037f92cc94b..e5c4d46a6ebbf 100644 --- a/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts +++ b/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts @@ -997,6 +997,7 @@ _swift_task_getMainExecutor _swift_task_hasTaskGroupStatusRecord _swift_task_isCancelled _swift_task_isCurrentExecutor +_swift_task_isCurrentGlobalActor _swift_task_isOnExecutor _swift_task_isOnExecutor_hook _swift_task_localValueGet From c266a0965ff554a82f666038b4fc0721fcc23bb1 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 5 Mar 2025 13:48:37 -0800 Subject: [PATCH 03/13] [Isolated conformances] Check witness tables for conditional requirements When establishing whether a given conformance is isolated, look through the witness tables used to satisfy conditional requirements as well. This is because an otherwise-nonisolated conditional conformance can become isolated if one of its associated conformance requirements is satisfied by an isolated conformance. While here, make sure this code works with variadic generics, too. --- stdlib/public/runtime/ProtocolConformance.cpp | 177 ++++++++++++++++-- .../Runtime/isolated_conformance.swift | 46 ++++- 2 files changed, 205 insertions(+), 18 deletions(-) diff --git a/stdlib/public/runtime/ProtocolConformance.cpp b/stdlib/public/runtime/ProtocolConformance.cpp index 603343fd071ea..18056689b963d 100644 --- a/stdlib/public/runtime/ProtocolConformance.cpp +++ b/stdlib/public/runtime/ProtocolConformance.cpp @@ -1154,8 +1154,9 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( return {foundWitness, hasUninstantiatedSuperclass}; } -/// Determine if -static bool isExecutingInIsolationOfConformance( +/// Determine if we are executing within the isolation domain of the global +/// actor to which the given conformance is isolated. +static bool _isExecutingInIsolationOfConformance( const Metadata *const type, const ProtocolConformanceDescriptor *description, const WitnessTable *table @@ -1198,6 +1199,164 @@ static bool isExecutingInIsolationOfConformance( return isCurrentGlobalActor(globalActorType, globalActorWitnessTable); } +static bool _checkConformanceIsolation( + const Metadata *const type, const WitnessTable *table); + +/// Check for conformance isolation for a protocol requirement within the +/// conditional requirements of a witness table. +static bool _checkConformanceIsolationOfProtocolRequirement( + const Metadata *const type, const WitnessTable *table, + const GenericRequirementDescriptor &requirement, + const WitnessTable *conditionalWitnessTable +) { + assert(requirement.Flags.getKind() == GenericRequirementKind::Protocol); + assert(!requirement.Flags.isPackRequirement()); + + // If the conditional witness table has neither a global actor nor conditional + // requirements, there's nothing to check. + auto conditionalDescription = conditionalWitnessTable->getDescription(); + if (!conditionalDescription || + !(conditionalDescription->hasConditionalRequirements() || + conditionalDescription->hasGlobalActorIsolation())) + return true; + + // Recurse into this conformance. + + // Resolve the conforming type. + SubstGenericParametersFromMetadata substitutions(type); + auto result = swift_getTypeByMangledName( + MetadataState::Abstract, requirement.getParam(), + /*FIXME:conditionalArgs.data()*/{ }, + [&substitutions](unsigned depth, unsigned index) { + return substitutions.getMetadata(depth, index).Ptr; + }, + [&substitutions](const Metadata *type, unsigned index) { + return substitutions.getWitnessTable(type, index); + }); + if (result.isError()) + return false; + + return _checkConformanceIsolation( + result.getType().getMetadata(), conditionalWitnessTable); +} + +/// Check for conformance isolation for a protocol requirement pack within the +/// conditional requirements of a witness table. +static bool _checkConformanceIsolationOfProtocolRequirementPack( + const Metadata *const type, const WitnessTable *table, + const GenericRequirementDescriptor &requirement, + WitnessTablePackPointer conditionalWitnessTables +) { + assert(requirement.Flags.getKind() == GenericRequirementKind::Protocol); + assert(requirement.Flags.isPackRequirement()); + + // Check each of the conditional witness tables. If any has neither a global + // actor nor conditional requirements, there's nothing to check for that one. + MetadataPackPointer conformingTypes; + unsigned count = conditionalWitnessTables.getNumElements(); + for (unsigned index = 0; index != count; ++index) { + auto conditionalWitnessTable = + conditionalWitnessTables.getElements()[index]; + + // If the conditional witness table has neither a global actor nor conditional + // requirements, there's nothing to check. + auto conditionalDescription = conditionalWitnessTable->getDescription(); + if (!conditionalDescription || + !(conditionalDescription->hasConditionalRequirements() || + conditionalDescription->hasGlobalActorIsolation())) + continue; + + // If we don't have it already, get the parameter pack for the + // conforming types. + if (!conformingTypes) { + // Resolve the conforming type. + SubstGenericParametersFromMetadata substitutions(type); + auto result = swift::getTypePackByMangledName( + requirement.getParam(), + /*FIXME:conditionalArgs.data()*/{ }, + [&substitutions](unsigned depth, unsigned index) { + return substitutions.getMetadata(depth, index).Ptr; + }, + [&substitutions](const Metadata *type, unsigned index) { + return substitutions.getWitnessTable(type, index); + }); + if (result.isError()) + return false; + + conformingTypes = result.getType(); + assert(conformingTypes.getNumElements() == count); + } + + if (!_checkConformanceIsolation( + conformingTypes.getElements()[index], conditionalWitnessTable)) + return false; + } + + return true; +} + +/// Check whether all isolated conformances in the given witness table (and +/// any witness tables it depends on) are satisfied by the current execution +/// context. +/// +/// Returns false if there is an isolated conformance but we are not executing +/// in that isolation domain. +static bool _checkConformanceIsolation(const Metadata *const type, const WitnessTable *table) { + if (!table) + return true; + + auto description = table->getDescription(); + if (!description) + return true; + + // If this conformance has global actor isolation, check that we are + // running in that isolation domain. + if (description->hasGlobalActorIsolation() && + !_isExecutingInIsolationOfConformance(type, description, table)) { + return false; + } + + // Check any witness tables that are part of a conditional conformance. + unsigned instantiationArgIndex = 0; + for (const auto &requirement: description->getConditionalRequirements()) { + if (!requirement.Flags.hasKeyArgument()) + continue; + + switch (requirement.Flags.getKind()) { + case GenericRequirementKind::Protocol: { + auto instantiationArg = + ((void* const *)table)[-1 - (int)instantiationArgIndex]; + if (requirement.Flags.isPackRequirement()) { + if (!_checkConformanceIsolationOfProtocolRequirementPack( + type, table, requirement, + WitnessTablePackPointer(instantiationArg))) + return false; + } else { + if (!_checkConformanceIsolationOfProtocolRequirement( + type, table, requirement, + (const WitnessTable *)instantiationArg)) { + return false; + } + } + + break; + } + + case GenericRequirementKind::SameType: + case GenericRequirementKind::BaseClass: + case GenericRequirementKind::SameConformance: + case GenericRequirementKind::SameShape: + case GenericRequirementKind::InvertedProtocols: + case GenericRequirementKind::Layout: + break; + } + + ++instantiationArgIndex; + } + + return true; +} + static const WitnessTable * swift_conformsToProtocolCommonImpl(const Metadata *const type, const ProtocolDescriptor *protocol) { @@ -1222,17 +1381,9 @@ swift_conformsToProtocolCommonImpl(const Metadata *const type, swift_conformsToProtocolMaybeInstantiateSuperclasses( type, protocol, true /*instantiateSuperclassMetadata*/); - // If the conformance is isolated to a global actor, check whether we are - // currently executing on that global actor. Otherwise, the type does not - // conform. - if (table) { - if (auto description = table->getDescription()) { - if (description->hasGlobalActorIsolation() && - !isExecutingInIsolationOfConformance(type, description, table)) { - return nullptr; - } - } - } + // Check for isolated conformances. + if (!_checkConformanceIsolation(type, table)) + return nullptr; return table; } diff --git a/test/Concurrency/Runtime/isolated_conformance.swift b/test/Concurrency/Runtime/isolated_conformance.swift index a1ca3dba9d2bf..13c3d1a9738f4 100644 --- a/test/Concurrency/Runtime/isolated_conformance.swift +++ b/test/Concurrency/Runtime/isolated_conformance.swift @@ -31,6 +31,26 @@ extension Wrapper: P where T: P { } } +@available(SwiftStdlib 5.9, *) +struct WrapMany { + var wrapped: (repeat each T) +} + +@available(SwiftStdlib 5.9, *) +extension WrapMany: P where repeat each T: P { + func f() { + print("Wrapper for many") + } +} + +extension Int: P { + func f() { } +} + +extension String: P { + func f() { } +} + func tryCastToP(_ value: any Sendable) -> Bool { if let p = value as? any P { p.f() @@ -50,6 +70,11 @@ let wrappedMC = Wrapper(wrapped: mc) precondition(tryCastToP(mc)) precondition(tryCastToP(wrappedMC)) +if #available(SwiftStdlib 5.9, *) { + let wrappedMany = WrapMany(wrapped: (17, mc, "Pack")) + precondition(tryCastToP(wrappedMany)) +} + // CHECK: Testing a separate task on the main actor // CHECK-NEXT: MyClass.f() // CHECK-NEXT: Wrapper for MyClass.f() @@ -57,21 +82,32 @@ print("Testing a separate task on the main actor") await Task.detached { @MainActor in precondition(tryCastToP(mc)) precondition(tryCastToP(wrappedMC)) -}.value -// FIXME: Currently not handling the wrapper case appropriately, because -// we don't track whether we used an isolated conformance to satisfy another -// conformance at runtime. + if #available(SwiftStdlib 5.9, *) { + let wrappedMany = WrapMany(wrapped: (17, mc, "Pack")) + precondition(tryCastToP(wrappedMany)) + } + +}.value // CHECK: Testing a separate task off the main actor print("Testing a separate task off the main actor") await Task.detached { if #available(SwiftStdlib 6.2, *) { precondition(!tryCastToP(mc)) - // precondition(!tryCastToP(wrappedMC)) + precondition(!tryCastToP(wrappedMC)) + + let wrappedMany = WrapMany(wrapped: (17, mc, "Pack")) + precondition(!tryCastToP(wrappedMany)) } else { print("Cast succeeds, but shouldn't") precondition(tryCastToP(mc)) + precondition(tryCastToP(wrappedMC)) + + if #available(SwiftStdlib 5.9, *) { + let wrappedMany = WrapMany(wrapped: (17, mc, "Pack")) + precondition(tryCastToP(wrappedMany)) + } } }.value From 6dd141ad54177a362894a6b1dc707075857bb780 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 6 Mar 2025 15:03:26 -0800 Subject: [PATCH 04/13] Replace dlsym of swift_task_isCurrentGlobalActor with a constructor hook Following the approach taken with the concurrency-specific type descriptors, register a hook function for the "is current global actor" check used for isolated conformances. --- include/swift/ABI/MetadataValues.h | 4 ++++ include/swift/Runtime/Config.h | 4 ++++ include/swift/Runtime/Metadata.h | 17 +++++++++++++---- stdlib/public/Concurrency/Setup.cpp | 9 ++++++++- stdlib/public/Concurrency/Task.swift | 3 +-- stdlib/public/runtime/MetadataLookup.cpp | 12 ++++++++++-- stdlib/public/runtime/Private.h | 7 +++++++ stdlib/public/runtime/ProtocolConformance.cpp | 6 ++---- .../macOS/arm64/concurrency/baseline-asserts | 1 - test/abi/Inputs/macOS/arm64/stdlib/baseline | 2 +- .../Inputs/macOS/arm64/stdlib/baseline-asserts | 2 +- test/abi/Inputs/macOS/x86_64/stdlib/baseline | 2 +- .../Inputs/macOS/x86_64/stdlib/baseline-asserts | 2 +- 13 files changed, 53 insertions(+), 18 deletions(-) diff --git a/include/swift/ABI/MetadataValues.h b/include/swift/ABI/MetadataValues.h index a2f3ef06913ca..71a7e1a40c2cd 100644 --- a/include/swift/ABI/MetadataValues.h +++ b/include/swift/ABI/MetadataValues.h @@ -1769,6 +1769,10 @@ namespace SpecialPointerAuthDiscriminators { /// Isolated deinit body function pointer const uint16_t DeinitWorkFunction = 0x8438; // = 33848 + + /// IsCurrentGlobalActor function used between the Swift runtime and + /// concurrency runtime. + const uint16_t IsCurrentGlobalActorFunction = 0xd1b8; // = 53688 } /// The number of arguments that will be passed directly to a generic diff --git a/include/swift/Runtime/Config.h b/include/swift/Runtime/Config.h index 8e92c925af0c2..2b50bf4429443 100644 --- a/include/swift/Runtime/Config.h +++ b/include/swift/Runtime/Config.h @@ -327,6 +327,9 @@ extern uintptr_t __COMPATIBILITY_LIBRARIES_CANNOT_CHECK_THE_IS_SWIFT_BIT_DIRECTL #define __ptrauth_swift_deinit_work_function \ __ptrauth(ptrauth_key_function_pointer, 1, \ SpecialPointerAuthDiscriminators::DeinitWorkFunction) +#define __ptrauth_swift_is_global_actor_function \ + __ptrauth(ptrauth_key_function_pointer, 1, \ + SpecialPointerAuthDiscriminators::IsCurrentGlobalActorFunction) #if __has_attribute(ptrauth_struct) #define swift_ptrauth_struct(key, discriminator) \ @@ -368,6 +371,7 @@ extern uintptr_t __COMPATIBILITY_LIBRARIES_CANNOT_CHECK_THE_IS_SWIFT_BIT_DIRECTL #define swift_ptrauth_sign_opaque_modify_resume_function(__fn, __buffer) (__fn) #define __ptrauth_swift_type_layout_string #define __ptrauth_swift_deinit_work_function +#define __ptrauth_swift_is_global_actor_function #define swift_ptrauth_struct(key, discriminator) #define swift_ptrauth_struct_derived(from) #endif diff --git a/include/swift/Runtime/Metadata.h b/include/swift/Runtime/Metadata.h index d26d625d269cf..dd5c7ae036ff9 100644 --- a/include/swift/Runtime/Metadata.h +++ b/include/swift/Runtime/Metadata.h @@ -1057,11 +1057,20 @@ struct ConcurrencyStandardTypeDescriptors { #include "swift/Demangling/StandardTypesMangling.def" }; -/// Register the type descriptors with standard manglings from the Concurrency -/// runtime. The passed-in struct must be immortal. +/// Function that determines whether we are executing on the given global +/// actor. The metadata is for the global actor type, and the witness table +/// is the conformance of that type to the GlobalActor protocol. +typedef bool (* SWIFT_CC(swift) IsCurrentGlobalActor)(const Metadata *, const WitnessTable *); + +/// Register various concurrency-related data and hooks needed in the Swift +/// standard library / runtime. This includes type descriptors with standard +/// manglings from the Concurrency runtime as well as a hook to check whether +/// we are running on a specific global actor. Any pointers passed in here must +/// be immortal. SWIFT_RUNTIME_STDLIB_SPI -void _swift_registerConcurrencyStandardTypeDescriptors( - const ConcurrencyStandardTypeDescriptors *descriptors); +void _swift_registerConcurrencyRuntime( + const ConcurrencyStandardTypeDescriptors *descriptors, + IsCurrentGlobalActor isCurrentGlobalActor); /// Initialize the value witness table for a struct using the provided like type /// as the basis for the layout. diff --git a/stdlib/public/Concurrency/Setup.cpp b/stdlib/public/Concurrency/Setup.cpp index c5be7abe0871f..a8b8666f25f8f 100644 --- a/stdlib/public/Concurrency/Setup.cpp +++ b/stdlib/public/Concurrency/Setup.cpp @@ -33,6 +33,11 @@ MANGLING, DESCRIPTOR_MANGLING_SUFFIX(KIND)); #include "swift/Demangling/StandardTypesMangling.def" +// Defined in Swift, redeclared here so we can register it with the runtime. +extern "C" SWIFT_CC(swift) +bool _swift_task_isCurrentGlobalActor( + const swift::Metadata *, const swift::WitnessTable *); + // Register our type descriptors with standard manglings when the concurrency // runtime is loaded. This allows the runtime to quickly resolve those standard // manglings. @@ -43,5 +48,7 @@ __attribute__((constructor)) static void setupStandardConcurrencyDescriptors() { &DESCRIPTOR_MANGLING(MANGLING, DESCRIPTOR_MANGLING_SUFFIX(KIND)), #include "swift/Demangling/StandardTypesMangling.def" }; - _swift_registerConcurrencyStandardTypeDescriptors(&descriptors); + _swift_registerConcurrencyRuntime( + &descriptors, + &_swift_task_isCurrentGlobalActor); } diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 378f409d58095..996b5a12d2937 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -1549,8 +1549,7 @@ internal func _taskIsCurrentExecutor( extension GlobalActor { @available(SwiftStdlib 6.2, *) - @_silgen_name("swift_task_isCurrentGlobalActor") - @usableFromInline + @_silgen_name("_swift_task_isCurrentGlobalActor") internal static func _taskIsCurrentGlobalActor() -> Bool { let executor = unsafe sharedUnownedExecutor return unsafe _taskIsCurrentExecutor(executor: executor.executor, flags: 0) diff --git a/stdlib/public/runtime/MetadataLookup.cpp b/stdlib/public/runtime/MetadataLookup.cpp index 315bd8c5a8496..7bf54ba38f683 100644 --- a/stdlib/public/runtime/MetadataLookup.cpp +++ b/stdlib/public/runtime/MetadataLookup.cpp @@ -1053,9 +1053,17 @@ _findContextDescriptor(Demangle::NodePointer node, return foundContext; } -void swift::_swift_registerConcurrencyStandardTypeDescriptors( - const ConcurrencyStandardTypeDescriptors *descriptors) { +/// Function to check whether we're currently running on the given global +/// actor. +bool (* __ptrauth_swift_is_global_actor_function SWIFT_CC(swift) + swift::_swift_task_isCurrentGlobalActorHook)( + const Metadata *, const WitnessTable *); + +void swift::_swift_registerConcurrencyRuntime( + const ConcurrencyStandardTypeDescriptors *descriptors, + IsCurrentGlobalActor isCurrentGlobalActor) { concurrencyDescriptors = descriptors; + _swift_task_isCurrentGlobalActorHook = isCurrentGlobalActor; } #pragma mark Protocol descriptor cache diff --git a/stdlib/public/runtime/Private.h b/stdlib/public/runtime/Private.h index c2d5036ca184f..7918e168b6b05 100644 --- a/stdlib/public/runtime/Private.h +++ b/stdlib/public/runtime/Private.h @@ -751,6 +751,13 @@ class TypeInfo { id _quickLookObjectForPointer(void *value); #endif + /// Hook function that calls into the concurrency library to check whether + /// we are currently executing the given global actor. + SWIFT_RUNTIME_LIBRARY_VISIBILITY + extern bool (* __ptrauth_swift_is_global_actor_function SWIFT_CC(swift) + _swift_task_isCurrentGlobalActorHook)( + const Metadata *, const WitnessTable *); + } // end namespace swift #endif /* SWIFT_RUNTIME_PRIVATE_H */ diff --git a/stdlib/public/runtime/ProtocolConformance.cpp b/stdlib/public/runtime/ProtocolConformance.cpp index 18056689b963d..73bcf0aaafbca 100644 --- a/stdlib/public/runtime/ProtocolConformance.cpp +++ b/stdlib/public/runtime/ProtocolConformance.cpp @@ -1190,13 +1190,11 @@ static bool _isExecutingInIsolationOfConformance( // The concurrency library provides a function to check whether we // are executing on the given global actor. - auto isCurrentGlobalActor = SWIFT_LAZY_CONSTANT(reinterpret_cast( - dlsym(RTLD_DEFAULT, "swift_task_isCurrentGlobalActor"))); - if (!isCurrentGlobalActor) + if (!_swift_task_isCurrentGlobalActorHook) return false; // Check whether we are running on this global actor. - return isCurrentGlobalActor(globalActorType, globalActorWitnessTable); + return _swift_task_isCurrentGlobalActorHook(globalActorType, globalActorWitnessTable); } static bool _checkConformanceIsolation( diff --git a/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts b/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts index e5c4d46a6ebbf..38037f92cc94b 100644 --- a/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts +++ b/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts @@ -997,7 +997,6 @@ _swift_task_getMainExecutor _swift_task_hasTaskGroupStatusRecord _swift_task_isCancelled _swift_task_isCurrentExecutor -_swift_task_isCurrentGlobalActor _swift_task_isOnExecutor _swift_task_isOnExecutor_hook _swift_task_localValueGet diff --git a/test/abi/Inputs/macOS/arm64/stdlib/baseline b/test/abi/Inputs/macOS/arm64/stdlib/baseline index 521a019a242b8..7fa9a942d9065 100644 --- a/test/abi/Inputs/macOS/arm64/stdlib/baseline +++ b/test/abi/Inputs/macOS/arm64/stdlib/baseline @@ -13415,7 +13415,7 @@ __swift_makeAnyHashableUpcastingToHashableBaseType __swift_modifyAtReferenceWritableKeyPath_impl __swift_modifyAtWritableKeyPath_impl __swift_objcClassUsesNativeSwiftReferenceCounting -__swift_registerConcurrencyStandardTypeDescriptors +__swift_registerConcurrencyRuntime __swift_release __swift_release_n __swift_reportFatalErrorsToDebugger diff --git a/test/abi/Inputs/macOS/arm64/stdlib/baseline-asserts b/test/abi/Inputs/macOS/arm64/stdlib/baseline-asserts index cf4c5867e8d6a..4807b19909ef1 100644 --- a/test/abi/Inputs/macOS/arm64/stdlib/baseline-asserts +++ b/test/abi/Inputs/macOS/arm64/stdlib/baseline-asserts @@ -13504,7 +13504,7 @@ __swift_makeAnyHashableUpcastingToHashableBaseType __swift_modifyAtReferenceWritableKeyPath_impl __swift_modifyAtWritableKeyPath_impl __swift_objcClassUsesNativeSwiftReferenceCounting -__swift_registerConcurrencyStandardTypeDescriptors +__swift_registerConcurrencyRuntime __swift_release __swift_release_n __swift_reportFatalErrorsToDebugger diff --git a/test/abi/Inputs/macOS/x86_64/stdlib/baseline b/test/abi/Inputs/macOS/x86_64/stdlib/baseline index a59c1ca9ea70e..bc79fe5f89879 100644 --- a/test/abi/Inputs/macOS/x86_64/stdlib/baseline +++ b/test/abi/Inputs/macOS/x86_64/stdlib/baseline @@ -13373,7 +13373,7 @@ __swift_makeAnyHashableUpcastingToHashableBaseType __swift_modifyAtReferenceWritableKeyPath_impl __swift_modifyAtWritableKeyPath_impl __swift_objcClassUsesNativeSwiftReferenceCounting -__swift_registerConcurrencyStandardTypeDescriptors +__swift_registerConcurrencyRuntime __swift_release __swift_release_n __swift_reportFatalErrorsToDebugger diff --git a/test/abi/Inputs/macOS/x86_64/stdlib/baseline-asserts b/test/abi/Inputs/macOS/x86_64/stdlib/baseline-asserts index 1c5a7171f9994..ba9fb06fe7c5a 100644 --- a/test/abi/Inputs/macOS/x86_64/stdlib/baseline-asserts +++ b/test/abi/Inputs/macOS/x86_64/stdlib/baseline-asserts @@ -13462,7 +13462,7 @@ __swift_makeAnyHashableUpcastingToHashableBaseType __swift_modifyAtReferenceWritableKeyPath_impl __swift_modifyAtWritableKeyPath_impl __swift_objcClassUsesNativeSwiftReferenceCounting -__swift_registerConcurrencyStandardTypeDescriptors +__swift_registerConcurrencyRuntime __swift_release __swift_release_n __swift_reportFatalErrorsToDebugger From 48aa75d86ffbf69cf208ffb28f9756686b21cd64 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Mar 2025 15:51:08 -0800 Subject: [PATCH 05/13] [Isolated conformances] Cache resolved global actor for conformances In the prior implementation of runtime resolution of isolated conformances, the runtime had to look in both the protocol conformance descriptor and in all conditional conformance requirements (recursively) to find any isolated conformances. If it found one, it had to demangle the global actor type to metadata. Since swift_conformsToProtocol is a hot path through the runtime, we can't afford this non-constant-time work in the common case. Instead, cache the resolved global actor and witness table as part of the conformance cache, so that we have access to this information every time we look up a witness table for a conformance. Propagate this up through various callers (e.g., generic requirement checking) to the point where we either stash it in the cache or check it at runtime. This gets us down to a very quick check (basically, NULL-or-not) for nonisolated conformances, and just one check for isolated conformances. --- include/swift/ABI/Metadata.h | 10 +- include/swift/Runtime/Casting.h | 12 + .../CompatibilityOverrideRuntime.def | 10 + stdlib/public/runtime/Casting.cpp | 18 +- stdlib/public/runtime/DynamicCast.cpp | 6 +- stdlib/public/runtime/MetadataLookup.cpp | 9 +- stdlib/public/runtime/Private.h | 25 +- stdlib/public/runtime/ProtocolConformance.cpp | 528 +++++++++--------- 8 files changed, 346 insertions(+), 272 deletions(-) diff --git a/include/swift/ABI/Metadata.h b/include/swift/ABI/Metadata.h index a893d028e5053..31351e26b11b0 100644 --- a/include/swift/ABI/Metadata.h +++ b/include/swift/ABI/Metadata.h @@ -2884,8 +2884,16 @@ struct TargetProtocolConformanceDescriptor final /// Get the witness table for the specified type, realizing it if /// 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. const swift::TargetWitnessTable * - getWitnessTable(const TargetMetadata *type) const; + getWitnessTable(const TargetMetadata *type, + const Metadata *&globalActorIsolationType, + const WitnessTable *&globalActorIsolationConformance) const; /// Retrieve the resilient witnesses. llvm::ArrayRef getResilientWitnesses() const { diff --git a/include/swift/Runtime/Casting.h b/include/swift/Runtime/Casting.h index f2801d0e160dc..84699b7f4c418 100644 --- a/include/swift/Runtime/Casting.h +++ b/include/swift/Runtime/Casting.h @@ -260,6 +260,18 @@ const WitnessTable * swift_conformsToProtocolCommon(const Metadata *type, const ProtocolDescriptor *protocol); +/// 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. +SWIFT_RUNTIME_EXPORT +const WitnessTable * +swift_conformsToProtocolCommonIsolated( + const Metadata *type, + const ProtocolDescriptor *protocol, + const Metadata **globalActorIsolationType, + const WitnessTable **globalActorIsolationWitnessTable); + } // end namespace swift #endif // SWIFT_RUNTIME_CASTING_H diff --git a/stdlib/public/CompatibilityOverride/CompatibilityOverrideRuntime.def b/stdlib/public/CompatibilityOverride/CompatibilityOverrideRuntime.def index d197674f75ded..47af031cb7165 100644 --- a/stdlib/public/CompatibilityOverride/CompatibilityOverrideRuntime.def +++ b/stdlib/public/CompatibilityOverride/CompatibilityOverrideRuntime.def @@ -193,6 +193,16 @@ OVERRIDE_PROTOCOLCONFORMANCE(conformsToProtocolCommon, const WitnessTable *, , , const ProtocolDescriptor *protocol), (type, protocol)) +OVERRIDE_PROTOCOLCONFORMANCE(conformsToProtocolCommonIsolated, + const WitnessTable *, , , swift::, + (const Metadata * const type, + const ProtocolDescriptor *protocol, + const Metadata **globalActorIsolationType, + const WitnessTable ** + globalActorIsolationWitnessTable), + (type, protocol, globalActorIsolationType, + globalActorIsolationWitnessTable)) + OVERRIDE_KEYPATH(getKeyPath, const HeapObject *, , , swift::, (const void *pattern, const void *arguments), (pattern, arguments)) diff --git a/stdlib/public/runtime/Casting.cpp b/stdlib/public/runtime/Casting.cpp index 18d13b7117f3f..35c6a428baa95 100644 --- a/stdlib/public/runtime/Casting.cpp +++ b/stdlib/public/runtime/Casting.cpp @@ -529,13 +529,18 @@ static bool _unknownClassConformsToObjCProtocol(const OpaqueValue *value, } #endif -bool swift::_conformsToProtocol(const OpaqueValue *value, - const Metadata *type, - ProtocolDescriptorRef protocol, - const WitnessTable **conformance) { +bool swift::_conformsToProtocol( + const OpaqueValue *value, + const Metadata *type, + ProtocolDescriptorRef protocol, + const WitnessTable **conformance, + const Metadata **globalActorIsolationType, + const WitnessTable **globalActorIsolationWitnessTable) { // Look up the witness table for protocols that need them. if (protocol.needsWitnessTable()) { - auto witness = swift_conformsToProtocolCommon(type, protocol.getSwiftProtocol()); + auto witness = swift_conformsToProtocolCommonIsolated( + type, protocol.getSwiftProtocol(), globalActorIsolationType, + globalActorIsolationWitnessTable); if (!witness) return false; if (conformance) @@ -624,7 +629,8 @@ static bool _conformsToProtocols(const OpaqueValue *value, } for (auto protocol : existentialType->getProtocols()) { - if (!_conformsToProtocol(value, type, protocol, conformances)) + if (!_conformsToProtocol( + value, type, protocol, conformances, nullptr, nullptr)) return false; if (conformances != nullptr && protocol.needsWitnessTable()) { assert(*conformances != nullptr); diff --git a/stdlib/public/runtime/DynamicCast.cpp b/stdlib/public/runtime/DynamicCast.cpp index 2805e371ae2c2..954d2925dd6f5 100644 --- a/stdlib/public/runtime/DynamicCast.cpp +++ b/stdlib/public/runtime/DynamicCast.cpp @@ -1384,7 +1384,8 @@ static bool _conformsToProtocols(const OpaqueValue *value, } for (auto protocol : existentialType->getProtocols()) { - if (!swift::_conformsToProtocol(value, type, protocol, conformances)) + if (!swift::_conformsToProtocol(value, type, protocol, conformances, + nullptr, nullptr)) return false; if (conformances != nullptr && protocol.needsWitnessTable()) { assert(*conformances != nullptr); @@ -1879,7 +1880,8 @@ static DynamicCastResult tryCastToExtendedExistential( }, [](const Metadata *type, unsigned index) -> const WitnessTable * { swift_unreachable("Resolution of witness tables is not supported"); - }); + }, + nullptr, nullptr); if (error) return DynamicCastResult::Failure; } diff --git a/stdlib/public/runtime/MetadataLookup.cpp b/stdlib/public/runtime/MetadataLookup.cpp index 7bf54ba38f683..2b538174df795 100644 --- a/stdlib/public/runtime/MetadataLookup.cpp +++ b/stdlib/public/runtime/MetadataLookup.cpp @@ -1602,7 +1602,8 @@ _gatherGenericParameters(const ContextDescriptor *context, }, [&substitutions](const Metadata *type, unsigned index) { return substitutions.getWitnessTable(type, index); - }); + }, + nullptr, nullptr); if (error) return *error; @@ -2043,7 +2044,8 @@ class DecodedMetadataBuilder { }, [](const Metadata *type, unsigned index) -> const WitnessTable * { swift_unreachable("never called"); - }); + }, + nullptr, nullptr); if (error) return *error; @@ -3056,7 +3058,8 @@ swift_distributed_getWitnessTables(GenericEnvironmentDescriptor *genericEnv, }, [&substFn](const Metadata *type, unsigned index) { return substFn.getWitnessTable(type, index); - }); + }, + nullptr, nullptr); if (error) { return {/*ptr=*/nullptr, -1}; diff --git a/stdlib/public/runtime/Private.h b/stdlib/public/runtime/Private.h index 7918e168b6b05..8377a5b17f606 100644 --- a/stdlib/public/runtime/Private.h +++ b/stdlib/public/runtime/Private.h @@ -567,6 +567,10 @@ class TypeInfo { /// 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. + /// /// \returns the error if an error occurred, None otherwise. std::optional _checkGenericRequirements( llvm::ArrayRef genericParams, @@ -574,7 +578,9 @@ class TypeInfo { llvm::SmallVectorImpl &extraArguments, SubstGenericParameterFn substGenericParam, SubstGenericParameterOrdinalFn substGenericParamOrdinal, - SubstDependentWitnessTableFn substWitnessTable); + SubstDependentWitnessTableFn substWitnessTable, + const Metadata **globalActorIsolationType, + const WitnessTable **globalActorIsolationWitnessTable); /// A helper function which avoids performing a store if the destination /// address already contains the source value. This is useful when @@ -686,10 +692,19 @@ class TypeInfo { /// \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 - bool _conformsToProtocol(const OpaqueValue *value, - const Metadata *type, - ProtocolDescriptorRef protocol, - const WitnessTable **conformance); + /// \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. + bool _conformsToProtocol( + const OpaqueValue *value, + const Metadata *type, + ProtocolDescriptorRef protocol, + const WitnessTable **conformance, + const Metadata **globalActorIsolationType, + const WitnessTable **globalActorIsolationWitnessTable); /// Construct type metadata for the given protocol. const Metadata * diff --git a/stdlib/public/runtime/ProtocolConformance.cpp b/stdlib/public/runtime/ProtocolConformance.cpp index 73bcf0aaafbca..e2d027eb73e44 100644 --- a/stdlib/public/runtime/ProtocolConformance.cpp +++ b/stdlib/public/runtime/ProtocolConformance.cpp @@ -27,6 +27,7 @@ #include "swift/Runtime/Metadata.h" #include "swift/Basic/Unreachable.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/PointerUnion.h" #include "../CompatibilityOverride/CompatibilityOverride.h" #include "ImageInspection.h" #include "Private.h" @@ -323,9 +324,63 @@ ProtocolConformanceDescriptor::getCanonicalTypeMetadata() const { swift_unreachable("Unhandled TypeReferenceKind in switch."); } +namespace { + +/// Describes the result of looking in the conformance cache. +struct ConformanceLookupResult { + /// The actual witness table, which will be NULL if the type does not + /// conform. + const WitnessTable *witnessTable = nullptr; + + /// 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; + + ConformanceLookupResult() { } + + ConformanceLookupResult(std::nullptr_t) { } + + ConformanceLookupResult(const WitnessTable *witnessTable, + const Metadata *globalActorIsolationType, + const WitnessTable *globalActorIsolationWitnessTable) + : witnessTable(witnessTable), + globalActorIsolationType(globalActorIsolationType), + globalActorIsolationWitnessTable(globalActorIsolationWitnessTable) { } + + explicit operator bool() const { return witnessTable != nullptr; } + + /// Given a type and conformance descriptor, form a conformance lookup + /// result. + static ConformanceLookupResult fromConformance( + const Metadata *type, + const ProtocolConformanceDescriptor *conformanceDescriptor); +}; + +} + +/// Determine the global actor isolation for the given witness table. +/// +/// Returns true if an error occurred, false if global actor isolation was +/// successfully computed (which can mean "not isolated"). +static bool _checkWitnessTableIsolation( + const Metadata *type, + const WitnessTable *wtable, + llvm::ArrayRef conditionalArgs, + const Metadata *&globalActorIsolationType, + const WitnessTable *&globalActorIsolationWitnessTable +); + template<> const WitnessTable * -ProtocolConformanceDescriptor::getWitnessTable(const Metadata *type) const { +ProtocolConformanceDescriptor::getWitnessTable( + const Metadata *type, + const Metadata *&globalActorIsolationType, + const WitnessTable *&globalActorIsolationWitnessTable +) const { // If needed, check the conditional requirements. llvm::SmallVector conditionalArgs; @@ -345,16 +400,104 @@ ProtocolConformanceDescriptor::getWitnessTable(const Metadata *type) const { }, [&substitutions](const Metadata *type, unsigned index) { return substitutions.getWitnessTable(type, index); - }); + }, + &globalActorIsolationType, + &globalActorIsolationWitnessTable); if (error) return nullptr; } #if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES - return (const WitnessTable *) + auto wtable = (const WitnessTable *) swift_getWitnessTableRelative(this, type, conditionalArgs.data()); #else - return swift_getWitnessTable(this, type, conditionalArgs.data()); + auto wtable = swift_getWitnessTable(this, type, conditionalArgs.data()); #endif + + if (!wtable) + return nullptr; + + // 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)) + return nullptr; + + return wtable; +} + +ConformanceLookupResult ConformanceLookupResult::fromConformance( + const Metadata *type, + const ProtocolConformanceDescriptor *conformanceDescriptor) { + const Metadata *globalActorIsolationType = nullptr; + const WitnessTable *globalActorIsolationWitnessTable = nullptr; + auto wtable = conformanceDescriptor->getWitnessTable( + type, globalActorIsolationType, globalActorIsolationWitnessTable); + return { + wtable, + globalActorIsolationType, + globalActorIsolationWitnessTable + }; +} + +/// Determine the global actor isolation for the given witness table. +/// +/// Returns true if an error occurred, false if global actor isolation was +/// successfully computed (which can mean "not isolated"). +static bool _checkWitnessTableIsolation( + const Metadata *type, + const WitnessTable *wtable, + llvm::ArrayRef conditionalArgs, + const Metadata *&globalActorIsolationType, + const WitnessTable *&globalActorIsolationWitnessTable +) { + // If there's no protocol conformance descriptor, do nothing. + auto description = wtable->getDescription(); + if (!description) + return false; + + // If this conformance doesn't have global actor isolation, we're done. + if (!description->hasGlobalActorIsolation()) + return false; + + // Resolve the global actor type. + SubstGenericParametersFromMetadata substitutions(type); + auto result = swift_getTypeByMangledName( + MetadataState::Abstract, description->getGlobalActorType(), + conditionalArgs.data(), + [&substitutions](unsigned depth, unsigned index) { + return substitutions.getMetadata(depth, index).Ptr; + }, + [&substitutions](const Metadata *type, unsigned index) { + return substitutions.getWitnessTable(type, index); + }); + if (result.isError()) + return true; + + auto myGlobalActorIsolationType = result.getType().getMetadata(); + if (!myGlobalActorIsolationType) + return true; + + // If the global actor isolation from this conformance conflicts with + // the one we already have, fail. + if (globalActorIsolationType && + globalActorIsolationType != myGlobalActorIsolationType) + return true; + + // Dig out the witness table. + auto myConformance = description->getGlobalActorConformance(); + if (!myConformance) + return true; + + auto myWitnessTable = ConformanceLookupResult::fromConformance( + myGlobalActorIsolationType, myConformance); + if (!myWitnessTable) + return true; + + globalActorIsolationType = myGlobalActorIsolationType; + globalActorIsolationWitnessTable = myWitnessTable.witnessTable; + return false; } namespace { @@ -396,11 +539,22 @@ namespace { struct ConformanceCacheEntry { private: ConformanceCacheKey Key; - const WitnessTable *Witness; + + /// The witness table or along with a bit that indicates whether the + /// conformance is isolated to a global actor. + llvm::PointerUnion + WitnessTableOrLookupResult; public: - ConformanceCacheEntry(ConformanceCacheKey key, const WitnessTable *witness) - : Key(key), Witness(witness) {} + ConformanceCacheEntry(ConformanceCacheKey key, + ConformanceLookupResult result) + : Key(key) { + if (result.globalActorIsolationType) { + WitnessTableOrLookupResult = new ConformanceLookupResult(result); + } else { + WitnessTableOrLookupResult = result.witnessTable; + } + } bool matchesKey(const ConformanceCacheKey &key) const { return Key.Type == key.Type && Key.Proto == key.Proto; @@ -410,14 +564,23 @@ namespace { return hash_value(entry.Key); } - template - static size_t getExtraAllocationSize(Args &&... ignored) { - return 0; - } - /// Get the cached witness table, or null if we cached failure. const WitnessTable *getWitnessTable() const { - return Witness; + if (auto witnessTable = WitnessTableOrLookupResult.dyn_cast()) + return witnessTable; + + return WitnessTableOrLookupResult.get() + ->witnessTable; + } + + ConformanceLookupResult getResult() const { + if (auto witnessTable = WitnessTableOrLookupResult.dyn_cast()) + return ConformanceLookupResult { witnessTable, nullptr, nullptr }; + + if (auto lookupResult = WitnessTableOrLookupResult.dyn_cast()) + return *lookupResult; + + return nullptr; } }; } // end anonymous namespace @@ -486,7 +649,7 @@ struct ConformanceState { } void cacheResult(const Metadata *type, const ProtocolDescriptor *proto, - const WitnessTable *witness, size_t sectionsCount) { + ConformanceLookupResult result, size_t sectionsCount) { Cache.getOrInsert(ConformanceCacheKey(type, proto), [&](ConformanceCacheEntry *entry, bool created) { // Create the entry if needed. If it already exists, @@ -514,7 +677,7 @@ struct ConformanceState { return false; // abandon the new entry ::new (entry) ConformanceCacheEntry( - ConformanceCacheKey(type, proto), witness); + ConformanceCacheKey(type, proto), result); return true; // keep the new entry }); } @@ -628,8 +791,8 @@ swift::swift_registerProtocolConformances(const ProtocolConformanceRecord *begin /// First element of the return value is `true` if the result is authoritative /// i.e. the result is for the type itself and not a superclass. If `false` /// then we cached a conformance on a superclass, but that may be overridden. -/// A return value of `{ false, nullptr }` indicates nothing was cached. -static std::pair +/// A return value of `{ false, { } }` indicates nothing was cached. +static std::pair searchInConformanceCache(const Metadata *type, const ProtocolDescriptor *protocol, bool instantiateSuperclassMetadata) { @@ -641,12 +804,12 @@ searchInConformanceCache(const Metadata *type, type, instantiateSuperclassMetadata}; for (; auto type = superclassIterator.metadata; ++superclassIterator) { if (auto *Value = snapshot.find(ConformanceCacheKey(type, protocol))) { - return {type == origType, Value->getWitnessTable()}; + return { type == origType, Value->getResult() }; } } // We did not find a cache entry. - return {false, nullptr}; + return { false, ConformanceLookupResult{} }; } /// Get the appropriate context descriptor for a type. If the descriptor is a @@ -747,7 +910,7 @@ namespace { static void validateDyldResults( ConformanceState &C, const Metadata *type, const ProtocolDescriptor *protocol, - const WitnessTable *dyldCachedWitnessTable, + ConformanceLookupResult dyldCachedWitnessTable, const ProtocolConformanceDescriptor *dyldCachedConformanceDescriptor, bool instantiateSuperclassMetadata) { #if USE_DYLD_SHARED_CACHE_CONFORMANCE_TABLES @@ -879,7 +1042,8 @@ static _dyld_protocol_conformance_result getDyldOnDiskConformance( /// value is a tuple consisting of the found witness table (if any), the found /// conformance descriptor (if any), and a bool that's true if a failure is /// definitive. -static std::tuple findConformanceWithDyld(ConformanceState &C, const Metadata *type, const ProtocolDescriptor *protocol, @@ -937,8 +1101,9 @@ findConformanceWithDyld(ConformanceState &C, const Metadata *type, // so do it up front. DYLD_CONFORMANCES_LOG("DYLD Found conformance descriptor %p for %s", conformanceDescriptor, protocol->Name.get()); - auto *witnessTable = conformanceDescriptor->getWitnessTable(type); - return std::make_tuple(witnessTable, conformanceDescriptor, false); + auto result = ConformanceLookupResult::fromConformance( + type, conformanceDescriptor); + return std::make_tuple(result, conformanceDescriptor, false); } break; } @@ -976,16 +1141,16 @@ findConformanceWithDyld(ConformanceState &C, const Metadata *type, /// Check if a type conforms to a protocol, possibly instantiating superclasses /// that have not yet been instantiated. The return value is a pair consisting -/// of the witness table for the conformance (or NULL if no conformance was +/// of the the result of the lookup (which evaluates false if no conformance was /// found), and a boolean indicating whether there are uninstantiated /// superclasses that were not searched. -static std::pair +static std::pair swift_conformsToProtocolMaybeInstantiateSuperclasses( const Metadata *const type, const ProtocolDescriptor *protocol, bool instantiateSuperclassMetadata) { auto &C = Conformances.get(); - const WitnessTable *dyldCachedWitnessTable = nullptr; + ConformanceLookupResult dyldCachedWitnessTable; const ProtocolConformanceDescriptor *dyldCachedConformanceDescriptor = nullptr; @@ -1019,7 +1184,7 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( instantiateSuperclassMetadata); if (definitiveFailure) - return {nullptr, false}; + return {ConformanceLookupResult{}, false}; if (dyldCachedWitnessTable || dyldCachedConformanceDescriptor) break; @@ -1044,10 +1209,11 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( searchInConformanceCache(type, protocol, instantiateSuperclassMetadata); if (found.first) { // An authoritative negative result can be overridden by a result from dyld. - if (!found.second) { + if (!found.second.witnessTable) { if (dyldCachedWitnessTable) return {dyldCachedWitnessTable, false}; } + return {found.second, false}; } @@ -1056,7 +1222,8 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( auto *matchingType = std::get( candidate.getMatchingType(type, instantiateSuperclassMetadata)); assert(matchingType); - auto witness = dyldCachedConformanceDescriptor->getWitnessTable(matchingType); + auto witness = ConformanceLookupResult::fromConformance( + matchingType, dyldCachedConformanceDescriptor); C.cacheResult(type, protocol, witness, /*always cache*/ 0); DYLD_CONFORMANCES_LOG("Caching generic conformance to %s found by DYLD", protocol->Name.get()); @@ -1064,7 +1231,7 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( } // Scan conformance records. - llvm::SmallDenseMap foundWitnesses; + llvm::SmallDenseMap foundWitnesses; auto processSection = [&](const ConformanceSection §ion) { // Eagerly pull records for nondependent witnesses into our cache. auto processDescriptor = [&](const ProtocolConformanceDescriptor &descriptor) { @@ -1082,7 +1249,8 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( candidate.getMatchingType(type, instantiateSuperclassMetadata); noteFinalMetadataState(finalState); if (matchingType) { - auto witness = descriptor.getWitnessTable(matchingType); + auto witness = ConformanceLookupResult::fromConformance( + matchingType, &descriptor); C.cacheResult(matchingType, protocol, witness, /*always cache*/ 0); foundWitnesses.insert({matchingType, witness}); } @@ -1110,13 +1278,13 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( } // Find the most specific conformance that was scanned. - const WitnessTable *foundWitness = nullptr; + ConformanceLookupResult foundWitness = nullptr; const Metadata *foundType = nullptr; MaybeIncompleteSuperclassIterator superclassIterator{ type, instantiateSuperclassMetadata}; for (; auto searchType = superclassIterator.metadata; ++superclassIterator) { - const WitnessTable *witness = foundWitnesses.lookup(searchType); + auto witness = foundWitnesses.lookup(searchType); if (witness) { if (!foundType) { foundWitness = witness; @@ -1136,7 +1304,7 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( } noteFinalMetadataState(superclassIterator.state); - traceState.end(foundWitness); + traceState.end(foundWitness.witnessTable); // If it's for a superclass or if we didn't find anything, then add an // authoritative entry for this type. @@ -1154,211 +1322,13 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses( return {foundWitness, hasUninstantiatedSuperclass}; } -/// Determine if we are executing within the isolation domain of the global -/// actor to which the given conformance is isolated. -static bool _isExecutingInIsolationOfConformance( - const Metadata *const type, - const ProtocolConformanceDescriptor *description, - const WitnessTable *table -) { - // Resolve the global actor type. - SubstGenericParametersFromMetadata substitutions(type); - auto result = swift_getTypeByMangledName( - MetadataState::Abstract, description->getGlobalActorType(), - /*FIXME:conditionalArgs.data()*/{ }, - [&substitutions](unsigned depth, unsigned index) { - return substitutions.getMetadata(depth, index).Ptr; - }, - [&substitutions](const Metadata *type, unsigned index) { - return substitutions.getWitnessTable(type, index); - }); - if (result.isError()) - return false; - - const Metadata *globalActorType = result.getType().getMetadata(); - if (!globalActorType) - return false; - - auto globalActorConformance = description->getGlobalActorConformance(); - if (!globalActorConformance) - return false; - - auto globalActorWitnessTable = - globalActorConformance->getWitnessTable(globalActorType); - if (!globalActorWitnessTable) - return false; - - // The concurrency library provides a function to check whether we - // are executing on the given global actor. - if (!_swift_task_isCurrentGlobalActorHook) - return false; - - // Check whether we are running on this global actor. - return _swift_task_isCurrentGlobalActorHook(globalActorType, globalActorWitnessTable); -} - -static bool _checkConformanceIsolation( - const Metadata *const type, const WitnessTable *table); - -/// Check for conformance isolation for a protocol requirement within the -/// conditional requirements of a witness table. -static bool _checkConformanceIsolationOfProtocolRequirement( - const Metadata *const type, const WitnessTable *table, - const GenericRequirementDescriptor &requirement, - const WitnessTable *conditionalWitnessTable -) { - assert(requirement.Flags.getKind() == GenericRequirementKind::Protocol); - assert(!requirement.Flags.isPackRequirement()); - - // If the conditional witness table has neither a global actor nor conditional - // requirements, there's nothing to check. - auto conditionalDescription = conditionalWitnessTable->getDescription(); - if (!conditionalDescription || - !(conditionalDescription->hasConditionalRequirements() || - conditionalDescription->hasGlobalActorIsolation())) - return true; - - // Recurse into this conformance. - - // Resolve the conforming type. - SubstGenericParametersFromMetadata substitutions(type); - auto result = swift_getTypeByMangledName( - MetadataState::Abstract, requirement.getParam(), - /*FIXME:conditionalArgs.data()*/{ }, - [&substitutions](unsigned depth, unsigned index) { - return substitutions.getMetadata(depth, index).Ptr; - }, - [&substitutions](const Metadata *type, unsigned index) { - return substitutions.getWitnessTable(type, index); - }); - if (result.isError()) - return false; - - return _checkConformanceIsolation( - result.getType().getMetadata(), conditionalWitnessTable); -} - -/// Check for conformance isolation for a protocol requirement pack within the -/// conditional requirements of a witness table. -static bool _checkConformanceIsolationOfProtocolRequirementPack( - const Metadata *const type, const WitnessTable *table, - const GenericRequirementDescriptor &requirement, - WitnessTablePackPointer conditionalWitnessTables -) { - assert(requirement.Flags.getKind() == GenericRequirementKind::Protocol); - assert(requirement.Flags.isPackRequirement()); - - // Check each of the conditional witness tables. If any has neither a global - // actor nor conditional requirements, there's nothing to check for that one. - MetadataPackPointer conformingTypes; - unsigned count = conditionalWitnessTables.getNumElements(); - for (unsigned index = 0; index != count; ++index) { - auto conditionalWitnessTable = - conditionalWitnessTables.getElements()[index]; - - // If the conditional witness table has neither a global actor nor conditional - // requirements, there's nothing to check. - auto conditionalDescription = conditionalWitnessTable->getDescription(); - if (!conditionalDescription || - !(conditionalDescription->hasConditionalRequirements() || - conditionalDescription->hasGlobalActorIsolation())) - continue; - - // If we don't have it already, get the parameter pack for the - // conforming types. - if (!conformingTypes) { - // Resolve the conforming type. - SubstGenericParametersFromMetadata substitutions(type); - auto result = swift::getTypePackByMangledName( - requirement.getParam(), - /*FIXME:conditionalArgs.data()*/{ }, - [&substitutions](unsigned depth, unsigned index) { - return substitutions.getMetadata(depth, index).Ptr; - }, - [&substitutions](const Metadata *type, unsigned index) { - return substitutions.getWitnessTable(type, index); - }); - if (result.isError()) - return false; - - conformingTypes = result.getType(); - assert(conformingTypes.getNumElements() == count); - } - - if (!_checkConformanceIsolation( - conformingTypes.getElements()[index], conditionalWitnessTable)) - return false; - } - - return true; -} - -/// Check whether all isolated conformances in the given witness table (and -/// any witness tables it depends on) are satisfied by the current execution -/// context. -/// -/// Returns false if there is an isolated conformance but we are not executing -/// in that isolation domain. -static bool _checkConformanceIsolation(const Metadata *const type, const WitnessTable *table) { - if (!table) - return true; - - auto description = table->getDescription(); - if (!description) - return true; - - // If this conformance has global actor isolation, check that we are - // running in that isolation domain. - if (description->hasGlobalActorIsolation() && - !_isExecutingInIsolationOfConformance(type, description, table)) { - return false; - } - - // Check any witness tables that are part of a conditional conformance. - unsigned instantiationArgIndex = 0; - for (const auto &requirement: description->getConditionalRequirements()) { - if (!requirement.Flags.hasKeyArgument()) - continue; - - switch (requirement.Flags.getKind()) { - case GenericRequirementKind::Protocol: { - auto instantiationArg = - ((void* const *)table)[-1 - (int)instantiationArgIndex]; - if (requirement.Flags.isPackRequirement()) { - if (!_checkConformanceIsolationOfProtocolRequirementPack( - type, table, requirement, - WitnessTablePackPointer(instantiationArg))) - return false; - } else { - if (!_checkConformanceIsolationOfProtocolRequirement( - type, table, requirement, - (const WitnessTable *)instantiationArg)) { - return false; - } - } - - break; - } - - case GenericRequirementKind::SameType: - case GenericRequirementKind::BaseClass: - case GenericRequirementKind::SameConformance: - case GenericRequirementKind::SameShape: - case GenericRequirementKind::InvertedProtocols: - case GenericRequirementKind::Layout: - break; - } - - ++instantiationArgIndex; - } - - return true; -} - static const WitnessTable * -swift_conformsToProtocolCommonImpl(const Metadata *const type, - const ProtocolDescriptor *protocol) { - const WitnessTable *table; +swift_conformsToProtocolCommonIsolatedImpl( + const Metadata *const type, + const ProtocolDescriptor *protocol, + const Metadata **globalActorIsolationType, + const WitnessTable **globalActorIsolationWitnessTable) { + ConformanceLookupResult found; bool hasUninstantiatedSuperclass; // First, try without instantiating any new superclasses. This avoids @@ -1367,23 +1337,57 @@ swift_conformsToProtocolCommonImpl(const Metadata *const type, // in the chain before we get to an uninstantiated superclass) so this search // will succeed without trying to instantiate Super while it's already being // instantiated.= - std::tie(table, hasUninstantiatedSuperclass) = + std::tie(found, hasUninstantiatedSuperclass) = swift_conformsToProtocolMaybeInstantiateSuperclasses( type, protocol, false /*instantiateSuperclassMetadata*/); // If no conformance was found, and there is an uninstantiated superclass that // was not searched, then try the search again and instantiate all // superclasses. - if (!table && hasUninstantiatedSuperclass) - std::tie(table, hasUninstantiatedSuperclass) = + if (!found && hasUninstantiatedSuperclass) + std::tie(found, hasUninstantiatedSuperclass) = swift_conformsToProtocolMaybeInstantiateSuperclasses( type, protocol, true /*instantiateSuperclassMetadata*/); // Check for isolated conformances. - if (!_checkConformanceIsolation(type, table)) - return nullptr; + 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; + + // 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; + } + } + + return found.witnessTable; +} - return table; +static const WitnessTable * +swift_conformsToProtocolCommonImpl( + const Metadata *const type, + const ProtocolDescriptor *protocol) { + return swift_conformsToProtocolCommonIsolatedImpl( + type, protocol, nullptr, nullptr); } static const WitnessTable * @@ -1564,7 +1568,9 @@ checkGenericRequirement( llvm::SmallVectorImpl &extraArguments, SubstGenericParameterFn substGenericParam, SubstDependentWitnessTableFn substWitnessTable, - llvm::SmallVectorImpl &suppressed) { + llvm::SmallVectorImpl &suppressed, + const Metadata **globalActorIsolationType, + const WitnessTable **globalActorIsolationWitnessTable) { assert(!req.getFlags().isPackRequirement()); // Make sure we understand the requirement we're dealing with. @@ -1584,7 +1590,8 @@ checkGenericRequirement( case GenericRequirementKind::Protocol: { const WitnessTable *witnessTable = nullptr; if (!_conformsToProtocol(nullptr, subjectType, req.getProtocol(), - &witnessTable)) { + &witnessTable, globalActorIsolationType, + globalActorIsolationWitnessTable)) { const char *protoName = req.getProtocol() ? req.getProtocol().getName() : ""; return TYPE_LOOKUP_ERROR_FMT( @@ -1681,7 +1688,9 @@ checkGenericPackRequirement( llvm::SmallVectorImpl &extraArguments, SubstGenericParameterFn substGenericParam, SubstDependentWitnessTableFn substWitnessTable, - llvm::SmallVectorImpl &suppressed) { + llvm::SmallVectorImpl &suppressed, + const Metadata **globalActorIsolationType, + const WitnessTable **globalActorIsolationWitnessTable) { assert(req.getFlags().isPackRequirement()); // Make sure we understand the requirement we're dealing with. @@ -1708,7 +1717,8 @@ checkGenericPackRequirement( const WitnessTable *witnessTable = nullptr; if (!_conformsToProtocol(nullptr, elt, req.getProtocol(), - &witnessTable)) { + &witnessTable, globalActorIsolationType, + globalActorIsolationWitnessTable)) { const char *protoName = req.getProtocol() ? req.getProtocol().getName() : ""; return TYPE_LOOKUP_ERROR_FMT( @@ -2110,7 +2120,8 @@ checkInvertibleRequirements(const Metadata *type, }, [&substFn](const Metadata *type, unsigned index) { return substFn.getWitnessTable(type, index); - }); + }, + nullptr, nullptr); if (error) return error; } @@ -2124,16 +2135,21 @@ std::optional swift::_checkGenericRequirements( llvm::SmallVectorImpl &extraArguments, SubstGenericParameterFn substGenericParam, SubstGenericParameterOrdinalFn substGenericParamOrdinal, - SubstDependentWitnessTableFn substWitnessTable) { + SubstDependentWitnessTableFn substWitnessTable, + const Metadata **globalActorIsolationType, + const WitnessTable **globalActorIsolationWitnessTable) { // The suppressed conformances for each generic parameter. llvm::SmallVector allSuppressed; for (const auto &req : requirements) { if (req.getFlags().isPackRequirement()) { - auto error = checkGenericPackRequirement(req, extraArguments, - substGenericParam, - substWitnessTable, - allSuppressed); + auto error = checkGenericPackRequirement( + req, extraArguments, + substGenericParam, + substWitnessTable, + allSuppressed, + globalActorIsolationType, + globalActorIsolationWitnessTable); if (error) return error; } else if (req.getFlags().isValueRequirement()) { @@ -2147,7 +2163,9 @@ std::optional swift::_checkGenericRequirements( auto error = checkGenericRequirement(req, extraArguments, substGenericParam, substWitnessTable, - allSuppressed); + allSuppressed, + globalActorIsolationType, + globalActorIsolationWitnessTable); if (error) return error; } From 296e14662a2478ba2ec119ddeaafd7af46016e29 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Mar 2025 23:34:18 -0800 Subject: [PATCH 06/13] 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. --- include/swift/ABI/Metadata.h | 15 +- include/swift/Runtime/Casting.h | 24 +++- .../CompatibilityOverrideRuntime.def | 15 +- stdlib/public/runtime/Casting.cpp | 28 +++- stdlib/public/runtime/DynamicCast.cpp | 10 +- stdlib/public/runtime/MetadataLookup.cpp | 6 +- stdlib/public/runtime/Private.h | 45 ++++-- stdlib/public/runtime/ProtocolConformance.cpp | 136 ++++++++---------- test/abi/macOS/arm64/stdlib.swift | 6 + 9 files changed, 167 insertions(+), 118 deletions(-) diff --git a/include/swift/ABI/Metadata.h b/include/swift/ABI/Metadata.h index 31351e26b11b0..eb336ac50abd4 100644 --- a/include/swift/ABI/Metadata.h +++ b/include/swift/ABI/Metadata.h @@ -2745,6 +2745,10 @@ struct TargetGlobalActorReference { TargetRelativeProtocolConformanceDescriptorPointer 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 @@ struct TargetProtocolConformanceDescriptor final /// 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 * getWitnessTable(const TargetMetadata *type, - const Metadata *&globalActorIsolationType, - const WitnessTable *&globalActorIsolationConformance) const; + ConformanceExecutionContext &context) const; /// Retrieve the resilient witnesses. llvm::ArrayRef getResilientWitnesses() const { diff --git a/include/swift/Runtime/Casting.h b/include/swift/Runtime/Casting.h index 84699b7f4c418..9950bb4ba9e71 100644 --- a/include/swift/Runtime/Casting.h +++ b/include/swift/Runtime/Casting.h @@ -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 diff --git a/stdlib/public/CompatibilityOverride/CompatibilityOverrideRuntime.def b/stdlib/public/CompatibilityOverride/CompatibilityOverrideRuntime.def index 47af031cb7165..ba750a965786c 100644 --- a/stdlib/public/CompatibilityOverride/CompatibilityOverrideRuntime.def +++ b/stdlib/public/CompatibilityOverride/CompatibilityOverrideRuntime.def @@ -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), diff --git a/stdlib/public/runtime/Casting.cpp b/stdlib/public/runtime/Casting.cpp index 35c6a428baa95..8125662ebada5 100644 --- a/stdlib/public/runtime/Casting.cpp +++ b/stdlib/public/runtime/Casting.cpp @@ -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); diff --git a/stdlib/public/runtime/DynamicCast.cpp b/stdlib/public/runtime/DynamicCast.cpp index 954d2925dd6f5..237fc7e006d71 100644 --- a/stdlib/public/runtime/DynamicCast.cpp +++ b/stdlib/public/runtime/DynamicCast.cpp @@ -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; diff --git a/stdlib/public/runtime/MetadataLookup.cpp b/stdlib/public/runtime/MetadataLookup.cpp index 2b538174df795..eeb5b6ed0c273 100644 --- a/stdlib/public/runtime/MetadataLookup.cpp +++ b/stdlib/public/runtime/MetadataLookup.cpp @@ -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 @@ class DecodedMetadataBuilder { [](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}; diff --git a/stdlib/public/runtime/Private.h b/stdlib/public/runtime/Private.h index 8377a5b17f606..2ef15f450a292 100644 --- a/stdlib/public/runtime/Private.h +++ b/stdlib/public/runtime/Private.h @@ -567,10 +567,9 @@ class TypeInfo { /// 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 _checkGenericRequirements( llvm::ArrayRef genericParams, @@ -579,8 +578,7 @@ class TypeInfo { 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 @@ class TypeInfo { 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 @@ class TypeInfo { /// \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 * diff --git a/stdlib/public/runtime/ProtocolConformance.cpp b/stdlib/public/runtime/ProtocolConformance.cpp index e2d027eb73e44..acf5d7e8de69a 100644 --- a/stdlib/public/runtime/ProtocolConformance.cpp +++ b/stdlib/public/runtime/ProtocolConformance.cpp @@ -370,16 +370,14 @@ static bool _checkWitnessTableIsolation( const Metadata *type, const WitnessTable *wtable, llvm::ArrayRef 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 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 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; - - // 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; - } + 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. + 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(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 &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() : ""; return TYPE_LOOKUP_ERROR_FMT( @@ -1689,8 +1682,7 @@ checkGenericPackRequirement( SubstGenericParameterFn substGenericParam, SubstDependentWitnessTableFn substWitnessTable, llvm::SmallVectorImpl &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() : ""; 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 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 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 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" diff --git a/test/abi/macOS/arm64/stdlib.swift b/test/abi/macOS/arm64/stdlib.swift index 9ded241f8b5a0..8eccd6a67d9ea 100644 --- a/test/abi/macOS/arm64/stdlib.swift +++ b/test/abi/macOS/arm64/stdlib.swift @@ -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 From 8ef9f7fcad73d9d6968bd8e3c7f99e5e56541da5 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Mar 2025 23:42:29 -0800 Subject: [PATCH 07/13] [WebAssembly] Temporarily work around lack of __attribute__((constructor)) We don't have a great way to ensure that the current-global-actor hook will get installed by the concurrency library with WebAssembly, so temporarily work around the issue by relying on the fact that we also aren't doing actual concurrency with WebAssembly. --- stdlib/public/runtime/ProtocolConformance.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/stdlib/public/runtime/ProtocolConformance.cpp b/stdlib/public/runtime/ProtocolConformance.cpp index acf5d7e8de69a..2af0862047d19 100644 --- a/stdlib/public/runtime/ProtocolConformance.cpp +++ b/stdlib/public/runtime/ProtocolConformance.cpp @@ -1392,8 +1392,17 @@ static bool swift_isInConformanceExecutionContextImpl( return true; if (context->globalActorIsolationType) { - if (!_swift_task_isCurrentGlobalActorHook) + if (!_swift_task_isCurrentGlobalActorHook) { +#if defined(__wasm__) + // FIXME: We don't currently support a concurrency model for WebAssembly, + // and the global actor hook isn't getting initialized due to the + // lack of __attribute__((constructor)) support. Therefore, we + // treat everything "as if" it were on the correct global actor. + return true; +#else return false; +#endif + } // Check whether we are running on this global actor. if (!_swift_task_isCurrentGlobalActorHook( From 904d0cffb2ae816dddd5445c6002f9dc1d75b9f6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Mar 2025 23:49:40 -0800 Subject: [PATCH 08/13] Generalize FileCheck'ing of IRGen testcase slightly --- test/IRGen/isolated_conformance.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/IRGen/isolated_conformance.swift b/test/IRGen/isolated_conformance.swift index 72f8121f17ef2..2e5e50d453fbb 100644 --- a/test/IRGen/isolated_conformance.swift +++ b/test/IRGen/isolated_conformance.swift @@ -14,8 +14,8 @@ protocol P { // CHECK-SAME: ptr @"$s20isolated_conformance1XVMn" // CHECK-SAME: ptr @"$s20isolated_conformance1XVyxGAA1PAAWP // CHECK-SAME: i32 524288 -// CHECK-SAME: @"symbolic ScM" -// CHECK-SAME: ptr @"got.$sScMs11GlobalActorsMc" +// CHECK-SAME: symbolic ScM" +// CHECK-SAME: $sScMs11GlobalActorsMc" @MainActor struct X: isolated P { func f() { } From d7f687619e249e68c8cae8a12c699d30faf9d1b4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 8 Mar 2025 08:06:28 -0800 Subject: [PATCH 09/13] Revert "[WebAssembly] Temporarily work around lack of __attribute__((constructor))" This reverts commit 8ef9f7fcad73d9d6968bd8e3c7f99e5e56541da5. --- stdlib/public/runtime/ProtocolConformance.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/stdlib/public/runtime/ProtocolConformance.cpp b/stdlib/public/runtime/ProtocolConformance.cpp index 2af0862047d19..acf5d7e8de69a 100644 --- a/stdlib/public/runtime/ProtocolConformance.cpp +++ b/stdlib/public/runtime/ProtocolConformance.cpp @@ -1392,17 +1392,8 @@ static bool swift_isInConformanceExecutionContextImpl( return true; if (context->globalActorIsolationType) { - if (!_swift_task_isCurrentGlobalActorHook) { -#if defined(__wasm__) - // FIXME: We don't currently support a concurrency model for WebAssembly, - // and the global actor hook isn't getting initialized due to the - // lack of __attribute__((constructor)) support. Therefore, we - // treat everything "as if" it were on the correct global actor. - return true; -#else + if (!_swift_task_isCurrentGlobalActorHook) return false; -#endif - } // Check whether we are running on this global actor. if (!_swift_task_isCurrentGlobalActorHook( From f54791b56b50316ae6e23ec55dbf140a3f747cdf Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 8 Mar 2025 08:08:15 -0800 Subject: [PATCH 10/13] [WebAssembly] Disable the runtime test for isolated conformances WebAssembly doesn't currently have a good way to install the "isCurrentGlobalActor" hook on which this checking depends. Disable the test for the moment. --- test/Concurrency/Runtime/isolated_conformance.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/Concurrency/Runtime/isolated_conformance.swift b/test/Concurrency/Runtime/isolated_conformance.swift index 13c3d1a9738f4..20032b5f36f24 100644 --- a/test/Concurrency/Runtime/isolated_conformance.swift +++ b/test/Concurrency/Runtime/isolated_conformance.swift @@ -6,6 +6,11 @@ // REQUIRES: swift_feature_IsolatedConformances // UNSUPPORTED: back_deployment_runtime +// FIXME: WebAssembly doesn't currently have a good way to install the +// "isCurrentGlobalActor" hook on which this checking depends. Disable +// the test for the moment. +// UNSUPPORTED: wasm + protocol P { func f() } From 313a099499ecc4cb1d3295037a11a48a561038bc Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 8 Mar 2025 08:10:38 -0800 Subject: [PATCH 11/13] Fixup ABI test --- test/abi/macOS/x86_64/stdlib.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/abi/macOS/x86_64/stdlib.swift b/test/abi/macOS/x86_64/stdlib.swift index e701107edd48a..76e2772e56939 100644 --- a/test/abi/macOS/x86_64/stdlib.swift +++ b/test/abi/macOS/x86_64/stdlib.swift @@ -899,3 +899,8 @@ Added: _$ss8DurationV11attosecondss6Int128VvpMV // add callee-allocated coro entrypoints Added: _$ss48swift_deletedCalleeAllocatedCoroutineMethodErrorytvg Added: _swift_deletedCalleeAllocatedCoroutineMethodError + +// Isolated conformances +Added: _swift_ConformanceExecutionContextSize +Added: _swift_conformsToProtocolWithExecutionContext +Added: _swift_isInConformanceExecutionContext From a099c69e667d2c6f34d280a3d06c7157751ce857 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 8 Mar 2025 10:39:19 -0800 Subject: [PATCH 12/13] Disable test on WebAssembly harder --- test/Concurrency/Runtime/isolated_conformance.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Concurrency/Runtime/isolated_conformance.swift b/test/Concurrency/Runtime/isolated_conformance.swift index 20032b5f36f24..490c2b6186301 100644 --- a/test/Concurrency/Runtime/isolated_conformance.swift +++ b/test/Concurrency/Runtime/isolated_conformance.swift @@ -10,6 +10,7 @@ // "isCurrentGlobalActor" hook on which this checking depends. Disable // the test for the moment. // UNSUPPORTED: wasm +// UNSUPPORTED: CPU=wasm32 protocol P { func f() From d9ed5d9a3d3cd38119aecab78440058c046a9c26 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 8 Mar 2025 15:54:27 -0800 Subject: [PATCH 13/13] Generalize IRGen test yet more --- test/IRGen/isolated_conformance.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/IRGen/isolated_conformance.swift b/test/IRGen/isolated_conformance.swift index 2e5e50d453fbb..f6e614c19c426 100644 --- a/test/IRGen/isolated_conformance.swift +++ b/test/IRGen/isolated_conformance.swift @@ -14,7 +14,7 @@ protocol P { // CHECK-SAME: ptr @"$s20isolated_conformance1XVMn" // CHECK-SAME: ptr @"$s20isolated_conformance1XVyxGAA1PAAWP // CHECK-SAME: i32 524288 -// CHECK-SAME: symbolic ScM" +// CHECK-SAME: ScM // CHECK-SAME: $sScMs11GlobalActorsMc" @MainActor struct X: isolated P {