Skip to content

Commit 951b535

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 7214717 commit 951b535

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
@@ -746,6 +746,7 @@ class ConformanceFlags {
746746
HasResilientWitnessesMask = 0x01u << 16,
747747
HasGenericWitnessTableMask = 0x01u << 17,
748748
IsConformanceOfProtocolMask = 0x01u << 18,
749+
HasGlobalActorIsolation = 0x01u << 19,
749750

750751
NumConditionalPackDescriptorsMask = 0xFFu << 24,
751752
NumConditionalPackDescriptorsShift = 24
@@ -805,6 +806,14 @@ class ConformanceFlags {
805806
: 0));
806807
}
807808

809+
ConformanceFlags withHasGlobalActorIsolation(
810+
bool hasGlobalActorIsolation) const {
811+
return ConformanceFlags((Value & ~HasGlobalActorIsolation)
812+
| (hasGlobalActorIsolation
813+
? HasGlobalActorIsolation
814+
: 0));
815+
}
816+
808817
/// Retrieve the type reference kind kind.
809818
TypeReferenceKind getTypeReferenceKind() const {
810819
return TypeReferenceKind(
@@ -843,7 +852,12 @@ class ConformanceFlags {
843852
bool isConformanceOfProtocol() const {
844853
return Value & IsConformanceOfProtocolMask;
845854
}
846-
855+
856+
/// Does this conformance have a global actor to which it is isolated?
857+
bool hasGlobalActorIsolation() const {
858+
return Value & HasGlobalActorIsolation;
859+
}
860+
847861
/// Retrieve the # of conditional requirements.
848862
unsigned getNumConditionalRequirements() const {
849863
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
@@ -2180,6 +2186,7 @@ namespace {
21802186
addConditionalRequirements();
21812187
addResilientWitnesses();
21822188
addGenericWitnessTable();
2189+
addGlobalActorIsolation();
21832190

21842191
// We fill the flags last, since we continue filling them in
21852192
// after the call to addFlags() deposits the placeholder.
@@ -2216,6 +2223,7 @@ namespace {
22162223
Flags = Flags.withIsRetroactive(conf->isRetroactive());
22172224
Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique());
22182225
Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol());
2226+
Flags = Flags.withHasGlobalActorIsolation(conf->isIsolated());
22192227
} else {
22202228
Flags = Flags.withIsRetroactive(false)
22212229
.withIsSynthesizedNonUnique(false);
@@ -2403,6 +2411,42 @@ namespace {
24032411
B.addRelativeAddress(privateData);
24042412
}
24052413
}
2414+
2415+
void addGlobalActorIsolation() {
2416+
if (!Flags.hasGlobalActorIsolation())
2417+
return;
2418+
2419+
auto normal = cast<NormalProtocolConformance>(Conformance);
2420+
assert(normal->isIsolated());
2421+
auto nominal = normal->getDeclContext()->getSelfNominalTypeDecl();
2422+
2423+
// Add global actor type.
2424+
auto sig = nominal->getGenericSignatureOfContext();
2425+
auto isolation = getConformanceIsolation(
2426+
const_cast<RootProtocolConformance *>(Conformance));
2427+
assert(isolation.isGlobalActor());
2428+
Type globalActorType = isolation.getGlobalActor();
2429+
auto globalActorTypeName = IGM.getTypeRef(
2430+
globalActorType, sig, MangledTypeRefRole::Metadata).first;
2431+
B.addRelativeAddress(globalActorTypeName);
2432+
2433+
// Add conformance of the global actor type to the GlobalActor protocol.
2434+
SmallVector<ProtocolConformance *, 1> globalActorConformances;
2435+
auto globalActorProtocol =
2436+
IGM.Context.getProtocol(KnownProtocolKind::GlobalActor);
2437+
auto globalActorConformance = lookupConformance(
2438+
globalActorType, globalActorProtocol);
2439+
2440+
auto rootGlobalActorConformance = globalActorConformance.getConcrete()
2441+
->getRootConformance();
2442+
IGM.IRGen.addLazyWitnessTable(rootGlobalActorConformance);
2443+
2444+
auto globalActorConformanceDescriptor =
2445+
IGM.getAddrOfLLVMVariableOrGOTEquivalent(
2446+
LinkEntity::forProtocolConformanceDescriptor(
2447+
rootGlobalActorConformance));
2448+
B.addRelativeAddress(globalActorConformanceDescriptor);
2449+
}
24062450
};
24072451
}
24082452

stdlib/public/Concurrency/Task.swift

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

1550+
extension GlobalActor {
1551+
@available(SwiftStdlib 6.2, *)
1552+
@_silgen_name("swift_task_isCurrentGlobalActor")
1553+
@usableFromInline
1554+
internal static func _taskIsCurrentGlobalActor() -> Bool {
1555+
let executor = unsafe sharedUnownedExecutor
1556+
return unsafe _taskIsCurrentExecutor(executor: executor.executor, flags: 0)
1557+
}
1558+
}
15501559
#endif
15511560

15521561
@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)