Skip to content

Commit 37d71f3

Browse files
committed
Add StrictSendableMetatypes to require Sendable requirements on metatypes
Introduce a new experimental feature StrictSendableMetatypes that stops treating all metatypes as `Sendable`. Instead, metatypes of generic parameters and existentials are only considered Sendable if their corresponding instance types are guaranteed to be Sendable. Start with enforcing this property within region isolation. Track metatype creation instructions and put them in the task's isolation domain, so that transferring them into another isolation domain produces a diagnostic. As an example: func f<T: P>(_: T.Type) { let x: P.Type = T.self Task.detached { x.someStaticMethod() // oops, T.Type is not Sendable } }
1 parent dff88f9 commit 37d71f3

File tree

7 files changed

+156
-14
lines changed

7 files changed

+156
-14
lines changed

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,9 @@ EXPERIMENTAL_FEATURE(NonIsolatedAsyncInheritsIsolationFromContext, false)
446446
/// Allow custom availability domains to be defined and referenced.
447447
SUPPRESSIBLE_EXPERIMENTAL_FEATURE(CustomAvailability, true)
448448

449+
/// Be strict about the Sendable conformance of metatypes.
450+
EXPERIMENTAL_FEATURE(StrictSendableMetatypes, true)
451+
449452
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
450453
#undef EXPERIMENTAL_FEATURE
451454
#undef UPCOMING_FEATURE

lib/AST/ConformanceLookup.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,10 +381,36 @@ static ProtocolConformanceRef getBuiltinMetaTypeTypeConformance(
381381
Type type, const AnyMetatypeType *metatypeType, ProtocolDecl *protocol) {
382382
ASTContext &ctx = protocol->getASTContext();
383383

384-
// All metatypes are Sendable, Copyable, Escapable, and BitwiseCopyable.
384+
// All metatypes are Copyable, Escapable, and BitwiseCopyable.
385385
if (auto kp = protocol->getKnownProtocolKind()) {
386386
switch (*kp) {
387387
case KnownProtocolKind::Sendable:
388+
// Metatypes are generally Sendable, but under StrictSendableMetatypes we
389+
// cannot assume that metatypes based on type parameters are Sendable.
390+
if (ctx.LangOpts.hasFeature(Feature::StrictSendableMetatypes)) {
391+
Type instanceType = metatypeType->getInstanceType();
392+
393+
// If the instance type is a type parameter, it is not necessarily
394+
// Sendable. There will need to be a Sendable requirement.
395+
if (instanceType->isTypeParameter())
396+
break;
397+
398+
// If the instance type is an archetype or existential, check whether
399+
// it conforms to Sendable.
400+
// FIXME: If the requirement machine were to infer T.Type: Sendable
401+
// from T: Sendable, we wouldn't need this for archetypes.
402+
if (instanceType->is<ArchetypeType>() ||
403+
instanceType->isAnyExistentialType()) {
404+
auto instanceConformance = lookupConformance(instanceType, protocol);
405+
if (instanceConformance.isInvalid() ||
406+
instanceConformance.hasMissingConformance())
407+
break;
408+
}
409+
410+
// Every other metatype is Sendable.
411+
}
412+
LLVM_FALLTHROUGH;
413+
388414
case KnownProtocolKind::Copyable:
389415
case KnownProtocolKind::Escapable:
390416
case KnownProtocolKind::BitwiseCopyable:

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ UNINTERESTING_FEATURE(NonfrozenEnumExhaustivity)
269269
UNINTERESTING_FEATURE(ClosureIsolation)
270270
UNINTERESTING_FEATURE(Extern)
271271
UNINTERESTING_FEATURE(ConsumeSelfInDeinit)
272+
UNINTERESTING_FEATURE(StrictSendableMetatypes)
272273

273274
static bool usesFeatureBitwiseCopyable2(Decl *decl) {
274275
if (!decl->getModuleContext()->isStdlibModule()) {

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2952,9 +2952,10 @@ CONSTANT_TRANSLATION(WitnessMethodInst, AssignFresh)
29522952
CONSTANT_TRANSLATION(IntegerLiteralInst, AssignFresh)
29532953
CONSTANT_TRANSLATION(FloatLiteralInst, AssignFresh)
29542954
CONSTANT_TRANSLATION(StringLiteralInst, AssignFresh)
2955-
// Metatypes are Sendable, but AnyObject isn't
2956-
CONSTANT_TRANSLATION(ObjCMetatypeToObjectInst, AssignFresh)
2957-
CONSTANT_TRANSLATION(ObjCExistentialMetatypeToObjectInst, AssignFresh)
2955+
// Metatypes are often, but not always, Sendable, but AnyObject isn't
2956+
CONSTANT_TRANSLATION(ObjCMetatypeToObjectInst, Assign)
2957+
CONSTANT_TRANSLATION(ObjCExistentialMetatypeToObjectInst, Assign)
2958+
CONSTANT_TRANSLATION(MetatypeInst, AssignFresh)
29582959

29592960
//===---
29602961
// Assign
@@ -2998,6 +2999,10 @@ CONSTANT_TRANSLATION(UncheckedEnumDataInst, Assign)
29982999
CONSTANT_TRANSLATION(UncheckedOwnershipConversionInst, Assign)
29993000
CONSTANT_TRANSLATION(IndexRawPointerInst, Assign)
30003001

3002+
CONSTANT_TRANSLATION(InitExistentialMetatypeInst, Assign)
3003+
CONSTANT_TRANSLATION(OpenExistentialMetatypeInst, Assign)
3004+
CONSTANT_TRANSLATION(ObjCToThickMetatypeInst, Assign)
3005+
30013006
// These are used by SIL to aggregate values together in a gep like way. We
30023007
// want to look at uses of structs, not the struct uses itself. So just
30033008
// propagate.
@@ -3095,7 +3100,6 @@ CONSTANT_TRANSLATION(EndUnpairedAccessInst, Ignored)
30953100
CONSTANT_TRANSLATION(HopToExecutorInst, Ignored)
30963101
CONSTANT_TRANSLATION(InjectEnumAddrInst, Ignored)
30973102
CONSTANT_TRANSLATION(DestroyNotEscapedClosureInst, Ignored)
3098-
CONSTANT_TRANSLATION(MetatypeInst, Ignored)
30993103
CONSTANT_TRANSLATION(EndApplyInst, Ignored)
31003104
CONSTANT_TRANSLATION(AbortApplyInst, Ignored)
31013105
CONSTANT_TRANSLATION(DebugStepInst, Ignored)
@@ -3127,15 +3131,6 @@ CONSTANT_TRANSLATION(ExistentialMetatypeInst, Require)
31273131
// These can take a parameter. If it is non-Sendable, use a require.
31283132
CONSTANT_TRANSLATION(GetAsyncContinuationAddrInst, Require)
31293133

3130-
//===---
3131-
// Asserting If Non Sendable Parameter
3132-
//
3133-
3134-
// Takes metatypes as parameters and metatypes today are always sendable.
3135-
CONSTANT_TRANSLATION(InitExistentialMetatypeInst, AssertingIfNonSendable)
3136-
CONSTANT_TRANSLATION(OpenExistentialMetatypeInst, AssertingIfNonSendable)
3137-
CONSTANT_TRANSLATION(ObjCToThickMetatypeInst, AssertingIfNonSendable)
3138-
31393134
//===---
31403135
// Terminators
31413136
//

lib/SILOptimizer/Utils/SILIsolationInfo.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,12 @@ SILIsolationInfo SILIsolationInfo::get(SILInstruction *inst) {
805805
}
806806
}
807807

808+
/// Consider non-Sendable metatypes to be task-isolated, so they cannot cross
809+
/// into another isolation domain.
810+
if (auto *mi = dyn_cast<MetatypeInst>(inst)) {
811+
return SILIsolationInfo::getTaskIsolated(mi);
812+
}
813+
808814
// Check if we have an ApplyInst with nonisolated.
809815
//
810816
// NOTE: We purposely avoid using other isolation info from an ApplyExpr since
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// RUN: %target-typecheck-verify-swift -swift-version 6 -enable-experimental-feature StrictSendableMetatypes -emit-sil -o /dev/null
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: swift_feature_StrictSendableMetatypes
5+
6+
7+
protocol Q {
8+
static func g()
9+
}
10+
11+
nonisolated func acceptMeta<T>(_: T.Type) { }
12+
13+
// -------------------------------------------------------------------------
14+
// Non-Sendable metatype instances that cross into other isolation domains.
15+
// -------------------------------------------------------------------------
16+
nonisolated func staticCallThroughMeta<T: Q>(_: T.Type) {
17+
let x = T.self
18+
Task.detached { // expected-error{{risks causing data races}}
19+
x.g() // expected-note{{closure captures 'x' which is accessible to code in the current task}}
20+
}
21+
}
22+
23+
nonisolated func passMeta<T: Q>(_: T.Type) {
24+
let x = T.self
25+
Task.detached { // expected-error{{risks causing data races}}
26+
acceptMeta(x) // expected-note{{closure captures 'x' which is accessible to code in the current task}}
27+
}
28+
}
29+
30+
nonisolated func staticCallThroughMetaSmuggled<T: Q>(_: T.Type) {
31+
let x: Q.Type = T.self
32+
Task.detached { // expected-error{{risks causing data races}}
33+
x.g() // expected-note{{closure captures 'x' which is accessible to code in the current task}}
34+
}
35+
}
36+
37+
nonisolated func passMetaSmuggled<T: Q>(_: T.Type) {
38+
let x: Q.Type = T.self
39+
Task.detached { // expected-error{{risks causing data races}}
40+
acceptMeta(x) // expected-note{{closure captures 'x' which is accessible to code in the current task}}
41+
}
42+
}
43+
44+
nonisolated func passMetaSmuggledAny<T: Q>(_: T.Type) {
45+
let x: Any.Type = T.self
46+
Task.detached { // expected-error{{risks causing data races}}
47+
acceptMeta(x) // expected-note{{closure captures 'x' which is accessible to code in the current task}}
48+
}
49+
}
50+
51+
// -------------------------------------------------------------------------
52+
// Sendable metatype instances that cross into other isolation domains.
53+
// -------------------------------------------------------------------------
54+
nonisolated func passMetaWithSendable<T: Sendable & Q>(_: T.Type) {
55+
let x = T.self
56+
Task.detached {
57+
acceptMeta(x) // okay, because T is Sendable implies T.Type: Sendable
58+
x.g() // okay, because T is Sendable implies T.Type: Sendable
59+
}
60+
}
61+
62+
nonisolated func passMetaWithSendableSmuggled<T: Sendable & Q>(_: T.Type) {
63+
let x: any (Q & Sendable).Type = T.self
64+
Task.detached {
65+
acceptMeta(x) // okay, because T is Sendable implies T.Type: Sendable
66+
x.g() // okay, because T is Sendable implies T.Type: Sendable
67+
}
68+
}
69+
70+
// -------------------------------------------------------------------------
71+
// Sendable requirements on metatypes
72+
// -------------------------------------------------------------------------
73+
74+
nonisolated func passMetaWithMetaSendable<T: Q>(_: T.Type) where T.Type: Sendable {
75+
// FIXME: Bogus errors below because we don't currently handle constraints on
76+
// metatypes.
77+
let x = T.self
78+
Task.detached { // expected-error{{risks causing data races}}
79+
acceptMeta(x) // expected-note{{closure captures 'x' which is accessible to code in the current task}}
80+
// should be okay, because T is Sendable implies T.Type: Sendable
81+
x.g() // okay, because T is Sendable implies T.Type: Sendable
82+
}
83+
}
84+
85+
// TODO: Cannot currently express an existential or opaque type where the
86+
// metatype is Sendable.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %target-typecheck-verify-swift -swift-version 6 -enable-experimental-feature StrictSendableMetatypes
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: swift_feature_StrictSendableMetatypes
5+
6+
// This test checks for typecheck-only diagnostics involving non-sendable
7+
// metatypes.
8+
9+
protocol Q {
10+
static func g()
11+
}
12+
13+
nonisolated func acceptMeta<T>(_: T.Type) { }
14+
15+
nonisolated func staticCallThroughMeta<T: Q>(_: T.Type) {
16+
Task.detached {
17+
T.g()
18+
}
19+
}
20+
21+
nonisolated func passMeta<T: Q>(_: T.Type) {
22+
Task.detached {
23+
acceptMeta(T.self)
24+
}
25+
}

0 commit comments

Comments
 (0)