Skip to content

Commit 1a7647a

Browse files
committed
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.
1 parent 13b5db9 commit 1a7647a

File tree

8 files changed

+270
-3
lines changed

8 files changed

+270
-3
lines changed

include/swift/ABI/Metadata.h

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2734,6 +2734,17 @@ struct TargetResilientWitnessesHeader {
27342734
};
27352735
using ResilientWitnessesHeader = TargetResilientWitnessesHeader<InProcess>;
27362736

2737+
/// Describes a reference to a global actor type and its conformance to the
2738+
/// global actor protocol.
2739+
template<typename Runtime>
2740+
struct TargetGlobalActorReference {
2741+
/// The type of the global actor.
2742+
RelativeDirectPointer<const char, /*nullable*/ false> type;
2743+
2744+
/// The conformance of the global actor to the GlobalActor protocol.
2745+
TargetRelativeProtocolConformanceDescriptorPointer<Runtime> conformance;
2746+
};
2747+
27372748
/// The structure of a protocol conformance.
27382749
///
27392750
/// This contains enough static information to recover the witness table for a
@@ -2747,7 +2758,8 @@ struct TargetProtocolConformanceDescriptor final
27472758
GenericPackShapeDescriptor,
27482759
TargetResilientWitnessesHeader<Runtime>,
27492760
TargetResilientWitness<Runtime>,
2750-
TargetGenericWitnessTable<Runtime>> {
2761+
TargetGenericWitnessTable<Runtime>,
2762+
TargetGlobalActorReference<Runtime>> {
27512763

27522764
using TrailingObjects = swift::ABI::TrailingObjects<
27532765
TargetProtocolConformanceDescriptor<Runtime>,
@@ -2756,7 +2768,8 @@ struct TargetProtocolConformanceDescriptor final
27562768
GenericPackShapeDescriptor,
27572769
TargetResilientWitnessesHeader<Runtime>,
27582770
TargetResilientWitness<Runtime>,
2759-
TargetGenericWitnessTable<Runtime>>;
2771+
TargetGenericWitnessTable<Runtime>,
2772+
TargetGlobalActorReference<Runtime>>;
27602773
friend TrailingObjects;
27612774

27622775
template<typename T>
@@ -2892,6 +2905,32 @@ struct TargetProtocolConformanceDescriptor final
28922905
return this->template getTrailingObjects<GenericWitnessTable>();
28932906
}
28942907

2908+
/// Whether this conformance has any conditional requirements that need to
2909+
/// be evaluated.
2910+
bool hasGlobalActorIsolation() const {
2911+
return Flags.hasGlobalActorIsolation();
2912+
}
2913+
2914+
/// Retrieve the global actor type to which this conformance is isolated, if
2915+
/// any.
2916+
llvm::StringRef
2917+
getGlobalActorType() const {
2918+
if (!Flags.hasGlobalActorIsolation())
2919+
return llvm::StringRef();
2920+
2921+
return Demangle::makeSymbolicMangledNameStringRef(this->template getTrailingObjects<TargetGlobalActorReference<Runtime>>()->type);
2922+
}
2923+
2924+
/// Retrieve the protocol conformance of the global actor type to the
2925+
/// GlobalActor protocol.
2926+
const TargetProtocolConformanceDescriptor<Runtime> *
2927+
getGlobalActorConformance() const {
2928+
if (!Flags.hasGlobalActorIsolation())
2929+
return nullptr;
2930+
2931+
return this->template getTrailingObjects<TargetGlobalActorReference<Runtime>>()->conformance;
2932+
}
2933+
28952934
#if !defined(NDEBUG) && SWIFT_OBJC_INTEROP
28962935
void dump() const;
28972936
#endif
@@ -2934,6 +2973,10 @@ struct TargetProtocolConformanceDescriptor final
29342973
size_t numTrailingObjects(OverloadToken<GenericWitnessTable>) const {
29352974
return Flags.hasGenericWitnessTable() ? 1 : 0;
29362975
}
2976+
2977+
size_t numTrailingObjects(OverloadToken<RelativeDirectPointer<const char, /*nullable*/ true>>) const {
2978+
return Flags.hasGlobalActorIsolation() ? 1 : 0;
2979+
}
29372980
};
29382981
using ProtocolConformanceDescriptor
29392982
= TargetProtocolConformanceDescriptor<InProcess>;

include/swift/ABI/MetadataValues.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,7 @@ class ConformanceFlags {
709709
HasResilientWitnessesMask = 0x01u << 16,
710710
HasGenericWitnessTableMask = 0x01u << 17,
711711
IsConformanceOfProtocolMask = 0x01u << 18,
712+
HasGlobalActorIsolation = 0x01u << 19,
712713

713714
NumConditionalPackDescriptorsMask = 0xFFu << 24,
714715
NumConditionalPackDescriptorsShift = 24
@@ -768,6 +769,14 @@ class ConformanceFlags {
768769
: 0));
769770
}
770771

772+
ConformanceFlags withHasGlobalActorIsolation(
773+
bool hasGlobalActorIsolation) const {
774+
return ConformanceFlags((Value & ~HasGlobalActorIsolation)
775+
| (hasGlobalActorIsolation
776+
? HasGlobalActorIsolation
777+
: 0));
778+
}
779+
771780
/// Retrieve the type reference kind kind.
772781
TypeReferenceKind getTypeReferenceKind() const {
773782
return TypeReferenceKind(
@@ -806,7 +815,12 @@ class ConformanceFlags {
806815
bool isConformanceOfProtocol() const {
807816
return Value & IsConformanceOfProtocolMask;
808817
}
809-
818+
819+
/// Does this conformance have a global actor to which it is isolated?
820+
bool hasGlobalActorIsolation() const {
821+
return Value & HasGlobalActorIsolation;
822+
}
823+
810824
/// Retrieve the # of conditional requirements.
811825
unsigned getNumConditionalRequirements() const {
812826
return (Value & NumConditionalRequirementsMask)

lib/IRGen/GenProto.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "swift/AST/ASTContext.h"
3131
#include "swift/AST/CanTypeVisitor.h"
3232
#include "swift/AST/Types.h"
33+
#include "swift/AST/ConformanceLookup.h"
3334
#include "swift/AST/Decl.h"
3435
#include "swift/AST/DiagnosticsIRGen.h"
3536
#include "swift/AST/GenericEnvironment.h"
@@ -93,6 +94,11 @@
9394
using namespace swift;
9495
using namespace irgen;
9596

97+
namespace swift {
98+
// FIXME: Move this on to ProtocolConformance?
99+
ActorIsolation getConformanceIsolation(ProtocolConformance *conformance);
100+
}
101+
96102
namespace {
97103

98104
/// A class for computing how to pass arguments to a polymorphic
@@ -2164,6 +2170,7 @@ namespace {
21642170
addConditionalRequirements();
21652171
addResilientWitnesses();
21662172
addGenericWitnessTable();
2173+
addGlobalActorIsolation();
21672174

21682175
// We fill the flags last, since we continue filling them in
21692176
// after the call to addFlags() deposits the placeholder.
@@ -2200,6 +2207,7 @@ namespace {
22002207
Flags = Flags.withIsRetroactive(conf->isRetroactive());
22012208
Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique());
22022209
Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol());
2210+
Flags = Flags.withHasGlobalActorIsolation(conf->isIsolated());
22032211
} else {
22042212
Flags = Flags.withIsRetroactive(false)
22052213
.withIsSynthesizedNonUnique(false);
@@ -2387,6 +2395,42 @@ namespace {
23872395
B.addRelativeAddress(privateData);
23882396
}
23892397
}
2398+
2399+
void addGlobalActorIsolation() {
2400+
if (!Flags.hasGlobalActorIsolation())
2401+
return;
2402+
2403+
auto normal = cast<NormalProtocolConformance>(Conformance);
2404+
assert(normal->isIsolated());
2405+
auto nominal = normal->getDeclContext()->getSelfNominalTypeDecl();
2406+
2407+
// Add global actor type.
2408+
auto sig = nominal->getGenericSignatureOfContext();
2409+
auto isolation = getConformanceIsolation(
2410+
const_cast<RootProtocolConformance *>(Conformance));
2411+
assert(isolation.isGlobalActor());
2412+
Type globalActorType = isolation.getGlobalActor();
2413+
auto globalActorTypeName = IGM.getTypeRef(
2414+
globalActorType, sig, MangledTypeRefRole::Metadata).first;
2415+
B.addRelativeAddress(globalActorTypeName);
2416+
2417+
// Add conformance of the global actor type to the GlobalActor protocol.
2418+
SmallVector<ProtocolConformance *, 1> globalActorConformances;
2419+
auto globalActorProtocol =
2420+
IGM.Context.getProtocol(KnownProtocolKind::GlobalActor);
2421+
auto globalActorConformance = lookupConformance(
2422+
globalActorType, globalActorProtocol);
2423+
2424+
auto rootGlobalActorConformance = globalActorConformance.getConcrete()
2425+
->getRootConformance();
2426+
IGM.IRGen.addLazyWitnessTable(rootGlobalActorConformance);
2427+
2428+
auto globalActorConformanceDescriptor =
2429+
IGM.getAddrOfLLVMVariableOrGOTEquivalent(
2430+
LinkEntity::forProtocolConformanceDescriptor(
2431+
rootGlobalActorConformance));
2432+
B.addRelativeAddress(globalActorConformanceDescriptor);
2433+
}
23902434
};
23912435
}
23922436

stdlib/public/Concurrency/Task.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,15 @@ func _taskIsCurrentExecutor(_ executor: Builtin.Executor) -> Bool
15181518
internal func _taskIsCurrentExecutor(
15191519
executor: Builtin.Executor, flags: UInt64) -> Bool
15201520

1521+
extension GlobalActor {
1522+
@available(SwiftStdlib 6.2, *)
1523+
@_silgen_name("swift_task_isCurrentGlobalActor")
1524+
@usableFromInline
1525+
internal static func _taskIsCurrentGlobalActor() -> Bool {
1526+
let executor = unsafe sharedUnownedExecutor
1527+
return unsafe _taskIsCurrentExecutor(executor: executor.executor, flags: 0)
1528+
}
1529+
}
15211530
#endif
15221531

15231532
@available(SwiftStdlib 5.1, *)

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,50 @@ swift_conformsToProtocolMaybeInstantiateSuperclasses(
11541154
return {foundWitness, hasUninstantiatedSuperclass};
11551155
}
11561156

1157+
/// Determine if
1158+
static bool isExecutingInIsolationOfConformance(
1159+
const Metadata *const type,
1160+
const ProtocolConformanceDescriptor *description,
1161+
const WitnessTable *table
1162+
) {
1163+
// Resolve the global actor type.
1164+
SubstGenericParametersFromMetadata substitutions(type);
1165+
auto result = swift_getTypeByMangledName(
1166+
MetadataState::Abstract, description->getGlobalActorType(),
1167+
/*FIXME:conditionalArgs.data()*/{ },
1168+
[&substitutions](unsigned depth, unsigned index) {
1169+
return substitutions.getMetadata(depth, index).Ptr;
1170+
},
1171+
[&substitutions](const Metadata *type, unsigned index) {
1172+
return substitutions.getWitnessTable(type, index);
1173+
});
1174+
if (result.isError())
1175+
return false;
1176+
1177+
const Metadata *globalActorType = result.getType().getMetadata();
1178+
if (!globalActorType)
1179+
return false;
1180+
1181+
auto globalActorConformance = description->getGlobalActorConformance();
1182+
if (!globalActorConformance)
1183+
return false;
1184+
1185+
auto globalActorWitnessTable =
1186+
globalActorConformance->getWitnessTable(globalActorType);
1187+
if (!globalActorWitnessTable)
1188+
return false;
1189+
1190+
// The concurrency library provides a function to check whether we
1191+
// are executing on the given global actor.
1192+
auto isCurrentGlobalActor = SWIFT_LAZY_CONSTANT(reinterpret_cast<bool (*)(const Metadata *, const WitnessTable *)>(
1193+
dlsym(RTLD_DEFAULT, "swift_task_isCurrentGlobalActor")));
1194+
if (!isCurrentGlobalActor)
1195+
return false;
1196+
1197+
// Check whether we are running on this global actor.
1198+
return isCurrentGlobalActor(globalActorType, globalActorWitnessTable);
1199+
}
1200+
11571201
static const WitnessTable *
11581202
swift_conformsToProtocolCommonImpl(const Metadata *const type,
11591203
const ProtocolDescriptor *protocol) {
@@ -1178,6 +1222,18 @@ swift_conformsToProtocolCommonImpl(const Metadata *const type,
11781222
swift_conformsToProtocolMaybeInstantiateSuperclasses(
11791223
type, protocol, true /*instantiateSuperclassMetadata*/);
11801224

1225+
// If the conformance is isolated to a global actor, check whether we are
1226+
// currently executing on that global actor. Otherwise, the type does not
1227+
// conform.
1228+
if (table) {
1229+
if (auto description = table->getDescription()) {
1230+
if (description->hasGlobalActorIsolation() &&
1231+
!isExecutingInIsolationOfConformance(type, description, table)) {
1232+
return nullptr;
1233+
}
1234+
}
1235+
}
1236+
11811237
return table;
11821238
}
11831239

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// RUN: %target-run-simple-swift(-enable-experimental-feature IsolatedConformances -target %target-swift-5.1-abi-triple) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: concurrency_runtime
6+
// REQUIRES: swift_feature_IsolatedConformances
7+
// UNSUPPORTED: back_deployment_runtime
8+
9+
protocol P {
10+
func f()
11+
}
12+
13+
@MainActor
14+
class MyClass: isolated P {
15+
func f() {
16+
print("MyClass.f()")
17+
18+
// Make sure we're on the main actor.
19+
MainActor.assumeIsolated { }
20+
}
21+
}
22+
23+
struct Wrapper<T> {
24+
var wrapped: T
25+
}
26+
27+
extension Wrapper: P where T: P {
28+
func f() {
29+
print("Wrapper for ", terminator: "")
30+
wrapped.f()
31+
}
32+
}
33+
34+
func tryCastToP(_ value: any Sendable) -> Bool {
35+
if let p = value as? any P {
36+
p.f()
37+
return true
38+
}
39+
40+
print("Conformance did not match")
41+
return false
42+
}
43+
44+
// CHECK: Testing on the main actor
45+
// CHECK-NEXT: MyClass.f()
46+
// CHECK-NEXT: Wrapper for MyClass.f()
47+
print("Testing on the main actor")
48+
let mc = MyClass()
49+
let wrappedMC = Wrapper(wrapped: mc)
50+
precondition(tryCastToP(mc))
51+
precondition(tryCastToP(wrappedMC))
52+
53+
// CHECK: Testing a separate task on the main actor
54+
// CHECK-NEXT: MyClass.f()
55+
// CHECK-NEXT: Wrapper for MyClass.f()
56+
print("Testing a separate task on the main actor")
57+
await Task.detached { @MainActor in
58+
precondition(tryCastToP(mc))
59+
precondition(tryCastToP(wrappedMC))
60+
}.value
61+
62+
// FIXME: Currently not handling the wrapper case appropriately, because
63+
// we don't track whether we used an isolated conformance to satisfy another
64+
// conformance at runtime.
65+
66+
// CHECK: Testing a separate task off the main actor
67+
print("Testing a separate task off the main actor")
68+
await Task.detached {
69+
if #available(SwiftStdlib 6.2, *) {
70+
precondition(!tryCastToP(mc))
71+
// precondition(!tryCastToP(wrappedMC))
72+
} else {
73+
print("Cast succeeds, but shouldn't")
74+
precondition(tryCastToP(mc))
75+
}
76+
}.value
77+
78+
// Ensure that we access mc later
79+
print(mc)

test/Concurrency/isolated_conformance.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// RUN: %target-swift-frontend -typecheck -verify -target %target-swift-5.1-abi-triple -swift-version 6 -enable-experimental-feature IsolatedConformances %s
22

33
// REQUIRES: swift_feature_IsolatedConformances
4+
// REQUIRES: concurrency
45

56
protocol P {
67
func f() // expected-note 2{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}}

test/IRGen/isolated_conformance.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-swift-frontend -primary-file %s -emit-ir -swift-version 6 -enable-experimental-feature IsolatedConformances | %FileCheck %s -DINT=i%target-ptrsize
2+
3+
// REQUIRES: PTRSIZE=64
4+
// REQUIRES: concurrency
5+
// UNSUPPORTED: CPU=arm64e
6+
7+
protocol P {
8+
func f()
9+
}
10+
11+
// CHECK-LABEL: @"$s20isolated_conformance1XVyxGAA1PAAMc" =
12+
// CHECK-SAME: ptr @"$s20isolated_conformance1PMp"
13+
// CHECK-SAME: ptr @"$s20isolated_conformance1XVMn"
14+
// CHECK-SAME: ptr @"$s20isolated_conformance1XVyxGAA1PAAWP
15+
// CHECK-SAME: i32 524288
16+
// CHECK-SAME: @"symbolic ScM"
17+
// CHECK-SAME: ptr @"$sScMs11GlobalActorsMc"
18+
@MainActor
19+
struct X<T>: isolated P {
20+
func f() { }
21+
}

0 commit comments

Comments
 (0)