Skip to content

Commit fedd5e2

Browse files
authored
Merge pull request #74864 from kavon/ncgenerics-runtime-demangling-fix
2 parents 6a7be95 + a5c8a62 commit fedd5e2

File tree

4 files changed

+331
-18
lines changed

4 files changed

+331
-18
lines changed

lib/IRGen/GenReflection.cpp

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,25 @@ getRuntimeVersionThatSupportsDemanglingType(CanType type) {
226226
// involving them.
227227
}
228228

229+
/// Any nominal type that has an inverse requirement in its generic
230+
/// signature uses NoncopyableGenerics. Since inverses are mangled into
231+
/// symbols, a Swift 6.0+ runtime is generally needed to demangle them.
232+
///
233+
/// We make an exception for types in the stdlib, like Optional, since the
234+
/// runtime should still be able to demangle them, based on the availability
235+
/// of the type.
236+
if (auto nominalTy = dyn_cast<NominalOrBoundGenericNominalType>(t)) {
237+
auto *nom = nominalTy->getDecl();
238+
if (auto sig = nom->getGenericSignature()) {
239+
SmallVector<InverseRequirement, 2> inverses;
240+
SmallVector<Requirement, 2> reqs;
241+
sig->getRequirementsWithInverses(reqs, inverses);
242+
if (!inverses.empty() && !nom->getModuleContext()->isStdlibModule()) {
243+
return addRequirement(Swift_6_0);
244+
}
245+
}
246+
}
247+
229248
return false;
230249
});
231250

@@ -418,21 +437,31 @@ getTypeRefImpl(IRGenModule &IGM,
418437

419438
bool isAlwaysNoncopyable = false;
420439
if (contextualTy->isNoncopyable()) {
440+
isAlwaysNoncopyable = true;
441+
421442
// If the contextual type has any archetypes in it, it's plausible that
422-
// we could end up with a copyable type in some instances. Look for those.
443+
// we could end up with a copyable type in some instances. Look for those
444+
// so we can permit unsafe reflection of the field, by assuming it could
445+
// be Copyable.
423446
if (contextualTy->hasArchetype()) {
424447
// If this is a nominal type, check whether it can ever be copyable.
425448
if (auto nominal = contextualTy->getAnyNominal()) {
426-
if (!nominal->canBeCopyable())
427-
isAlwaysNoncopyable = true;
449+
// If it's a nominal that can ever be Copyable _and_ it's defined in
450+
// the stdlib, assume that we could end up with a Copyable type.
451+
if (nominal->canBeCopyable()
452+
&& nominal->getModuleContext()->isStdlibModule())
453+
isAlwaysNoncopyable = false;
428454
} else {
429-
// Assume that we could end up with a copyable type somehow.
455+
// Assume that we could end up with a Copyable type somehow.
456+
// This allows you to reflect a 'T: ~Copyable' stored in a type.
457+
isAlwaysNoncopyable = false;
430458
}
431-
} else {
432-
isAlwaysNoncopyable = true;
433459
}
434460
}
435461

462+
// The getTypeRefByFunction strategy will emit a forward-compatible runtime
463+
// check to see if the runtime can safely reflect such fields. Otherwise,
464+
// the field will be artificially hidden to reflectors.
436465
if (isAlwaysNoncopyable) {
437466
IGM.IRGen.noteUseOfTypeMetadata(type);
438467
return getTypeRefByFunction(IGM, sig, type);
Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: %target-swift-frontend -emit-ir -o - %s -module-name test \
2+
// RUN: %swift-frontend -emit-ir -o - %s -module-name test \
33
// RUN: -enable-experimental-feature NonescapableTypes \
44
// RUN: -parse-as-library \
55
// RUN: -enable-library-evolution \
6-
// RUN: > %t/test.irgen
6+
// RUN: -target %target-cpu-apple-macosx15 \
7+
// RUN: > %t/test_new.irgen
78

8-
// RUN: %FileCheck %s < %t/test.irgen
9+
// RUN: %swift-frontend -emit-ir -o - %s -module-name test \
10+
// RUN: -enable-experimental-feature NonescapableTypes \
11+
// RUN: -parse-as-library \
12+
// RUN: -enable-library-evolution \
13+
// RUN: -target %target-cpu-apple-macosx14 \
14+
// RUN: > %t/test_old.irgen
15+
16+
// RUN: %FileCheck --check-prefix=NEW %s < %t/test_new.irgen
17+
// RUN: %FileCheck --check-prefix=OLD %s < %t/test_old.irgen
918

1019
// rdar://124401253
11-
// REQUIRES: OS=macosx || OS=linux || OS=windows-msvc
20+
// REQUIRES: OS=macosx
1221
// UNSUPPORTED: CPU=arm64e
1322

1423
@frozen
@@ -28,14 +37,104 @@ public enum NeverCopyable<Wrapped: ~Copyable>: ~Copyable {
2837
@frozen
2938
public struct NonCopyable: ~Copyable { }
3039

31-
// CHECK: @"$s4test1CCMF" =
32-
// CHECK-SAME: @"symbolic _____yxG 4test21ConditionallyCopyableOAARi_zrlE"
33-
// CHECK-SAME: @"get_type_metadata Ri_zl4test21ConditionallyCopyableOyAA03NonC0VG.3"
34-
// CHECK-SAME: @"symbolic _____yxG 4test21ConditionallyCopyableOAARi_zrlE"
35-
// CHECK-SAME: @"get_type_metadata Ri_zl4test21ConditionallyCopyableOyAA03NonC0VG.3"
36-
public class C<T: ~Copyable> {
40+
// HINT: when debugging this test, you can look for an `i32 2` field in the
41+
// 'MF' constant as a separator that precedes each field descriptor.
42+
43+
// NEW: @"$s4test8CC_TestsCMF" =
44+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyxG.3"
45+
// NEW-SAME: @"symbolic _____yq_G 4test21ConditionallyCopyableOAARi_zrlE"
46+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyAA03NonC0VG.4"
47+
// NEW-SAME: @"symbolic _____ySSG 4test21ConditionallyCopyableOAARi_zrlE"
48+
49+
// OLD: @"$s4test8CC_TestsCMF" =
50+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyxG.3"
51+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyq_G.4"
52+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOyAA03NonC0VG.5"
53+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test21ConditionallyCopyableOySSG.6"
54+
public class CC_Tests<NCG: ~Copyable, T> {
55+
var ccNCG: ConditionallyCopyable<NCG> = .none
3756
var ccT: ConditionallyCopyable<T> = .none
3857
var ccNC: ConditionallyCopyable<NonCopyable> = .none
39-
var ncT: ConditionallyCopyable<T> = .none
40-
var ncNC: ConditionallyCopyable<NonCopyable> = .none
58+
var ccC: ConditionallyCopyable<String> = .none
59+
}
60+
61+
62+
/// For the "never copyable" fields, we expect to always go through the
63+
/// type metadata accessor strategy, which is designed to hide these
64+
/// fields until a future runtime says they're safe to reflect.
65+
66+
// NEW: @"$s4test8NC_TestsCMF" =
67+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyxG.5"
68+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyq_G.6"
69+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyAA03NonC0VG.7"
70+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOySSG.8"
71+
72+
// OLD: @"$s4test8NC_TestsCMF" =
73+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyxG.7"
74+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyq_G.8"
75+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOyAA03NonC0VG.9"
76+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test13NeverCopyableOySSG.10"
77+
public class NC_Tests<NCG: ~Copyable, T> {
78+
var ncNCG: NeverCopyable<NCG> = .none
79+
var ncT: NeverCopyable<T> = .none
80+
var ncNC: NeverCopyable<NonCopyable> = .none
81+
var ncC: NeverCopyable<String> = .none
82+
}
83+
84+
85+
// NEW: @"$s4test17StdlibTypes_TestsCMF" =
86+
// NEW-SAME: @"symbolic xSg"
87+
// NEW-SAME: @"symbolic q_Sg"
88+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableVSg.9"
89+
// NEW-SAME: @"symbolic SSSg"
90+
// NEW-SAME: @"symbolic SPyxG"
91+
// NEW-SAME: @"symbolic SPyq_G"
92+
// NEW-SAME: @"symbolic SPy_____G 4test11NonCopyableV"
93+
// NEW-SAME: @"symbolic SPySSG"
94+
95+
// OLD: @"$s4test17StdlibTypes_TestsCMF" =
96+
// OLD-SAME: @"symbolic xSg"
97+
// OLD-SAME: @"symbolic q_Sg"
98+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableVSg.11"
99+
// OLD-SAME: @"symbolic SSSg"
100+
// OLD-SAME: @"symbolic SPyxG"
101+
// OLD-SAME: @"symbolic SPyq_G"
102+
// OLD-SAME: @"symbolic SPy_____G 4test11NonCopyableV"
103+
// OLD-SAME: @"symbolic SPySSG"
104+
public class StdlibTypes_Tests<NCG: ~Copyable, T> {
105+
var optNCG: Optional<NCG> = .none
106+
var optT: Optional<T> = .none
107+
var optNC: Optional<NonCopyable> = .none
108+
var optC: Optional<String> = .none
109+
110+
var upNCG: UnsafePointer<NCG> = .init(bitPattern: 16)!
111+
var upT: UnsafePointer<T> = .init(bitPattern: 32)!
112+
var upNC: UnsafePointer<NonCopyable> = .init(bitPattern: 64)!
113+
var upC: UnsafePointer<String> = .init(bitPattern: 128)!
114+
}
115+
116+
117+
// NEW: @"$s4test19PlainlyStored_TestsCMF" =
118+
// NEW-SAME: @"symbolic x"
119+
// NEW-SAME: @"symbolic q_"
120+
// NEW-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableV.10"
121+
// NEW-SAME: @"symbolic SS"
122+
123+
// OLD: @"$s4test19PlainlyStored_TestsCMF" =
124+
// OLD-SAME: @"symbolic x"
125+
// OLD-SAME: @"symbolic q_"
126+
// OLD-SAME: @"get_type_metadata Ri_zr0_l4test11NonCopyableV.12"
127+
// OLD-SAME: @"symbolic SS"
128+
public class PlainlyStored_Tests<NCG: ~Copyable, T> {
129+
var ncg: NCG
130+
var t: T
131+
var concreteNC: NonCopyable
132+
var str: String
133+
134+
public init(_ ncg: consuming NCG, _ t: T) {
135+
self.ncg = ncg
136+
self.t = t
137+
self.concreteNC = NonCopyable()
138+
self.str = ""
139+
}
41140
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %swift-frontend %s -target %target-cpu-apple-macosx15 -module-name main -emit-ir -o %t/new.ir
3+
// RUN: %swift-frontend %s -target %target-cpu-apple-macosx14 -module-name main -emit-ir -o %t/old.ir
4+
5+
// RUN: %FileCheck %s --check-prefix=NEW < %t/new.ir
6+
// RUN: %FileCheck %s --check-prefix=OLD < %t/old.ir
7+
8+
// Check that we add extra type metadata accessors for types with generic
9+
// parameters that have an inverse. These are used instead of using demangling
10+
// cache variables since old runtimes cannot synthesize type metadata based on
11+
// the new mangling.
12+
13+
// RUN: %target-build-swift %s -o %t/test_mangling
14+
// RUN: %target-run %t/test_mangling | %FileCheck %s
15+
16+
// REQUIRES: OS=macosx
17+
// REQUIRES: executable_test
18+
19+
20+
// This type's generic parameter is noncopyable, so older runtimes can't
21+
// demangle the type's name to build the metadata.
22+
struct Foo<T: ~Copyable>: ~Copyable {
23+
mutating func bar(_ i: Int) { print("Foo.bar(\(i))") }
24+
}
25+
26+
func test() {
27+
var foo = Foo<Int>()
28+
foo.bar(1)
29+
}
30+
test()
31+
// CHECK: Foo.bar(1)
32+
33+
// NEW: define hidden swiftcc void @"$s4main4testyyF"()
34+
// NEW: call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$s4main3FooVySiGMD")
35+
// NEW: }
36+
37+
// OLD: define hidden swiftcc void @"$s4main4testyyF"()
38+
// OLD: call swiftcc %swift.metadata_response @"$s4main3FooVySiGMa"(i64 0)
39+
// OLD: }
40+
41+
struct NC: ~Copyable {}
42+
43+
// NEW: define hidden swiftcc void @"$s4main10testWithNCyyF"()
44+
// NEW: call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$s4main3FooVyAA2NCVGMD")
45+
// NEW: }
46+
47+
// OLD: define hidden swiftcc void @"$s4main10testWithNCyyF"()
48+
// OLD: call swiftcc %swift.metadata_response @"$s4main3FooVyAA2NCVGMa"
49+
// OLD: }
50+
func testWithNC() {
51+
var foo = Foo<NC>()
52+
foo.bar(2)
53+
}
54+
testWithNC()
55+
// CHECK: Foo.bar(2)
56+
57+
58+
// NEW: define hidden swiftcc void @"$s4main17testWithNCGenericyyxnRi_zlF"
59+
// NEW: call swiftcc %swift.metadata_response @"$s4main3FooVMa"
60+
// NEW: }
61+
62+
// OLD: define hidden swiftcc void @"$s4main17testWithNCGenericyyxnRi_zlF"
63+
// OLD: call swiftcc %swift.metadata_response @"$s4main3FooVMa"
64+
// OLD: }
65+
func testWithNCGeneric<T: ~Copyable>(_ t: consuming T) {
66+
var foo = Foo<T>()
67+
foo.bar(3)
68+
}
69+
testWithNCGeneric(Foo<NC>())
70+
// CHECK: Foo.bar(3)
71+
72+
73+
// This type does not need a Swift 6.0 runtime, despite being noncopyable,
74+
// because it doesn't have a noncopyable generic parameter.
75+
struct JustNoncopyable<T>: ~Copyable {
76+
mutating func bar() { print("JustNoncopyable.bar") }
77+
}
78+
79+
func testNonGeneric() {
80+
var ng = JustNoncopyable<Int>()
81+
ng.bar()
82+
}
83+
testNonGeneric()
84+
// CHECK: JustNoncopyable.bar
85+
86+
// NEW: define hidden swiftcc void @"$s4main14testNonGenericyyF"()
87+
// NEW: call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$s4main15JustNoncopyableVySiGMD")
88+
// NEW: }
89+
90+
// OLD: define hidden swiftcc void @"$s4main14testNonGenericyyF"()
91+
// OLD: call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$s4main15JustNoncopyableVySiGMD")
92+
// OLD: }
93+
94+
95+
/// Check that Optional still uses `__swift_instantiateConcreteTypeFromMangledName`
96+
/// even when calling a method available to a noncopyable Optional.
97+
extension Optional where Wrapped: ~Copyable {
98+
mutating func bar(_ i: Int) { print("Optional.bar(\(i))") }
99+
}
100+
101+
// NEW: define hidden swiftcc void @"$s4main20testCopyableOptionalyyF"()
102+
// NEW: call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$sSiSgMD")
103+
// NEW: }
104+
105+
// OLD: define hidden swiftcc void @"$s4main20testCopyableOptionalyyF"()
106+
// OLD: call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$sSiSgMD")
107+
// OLD: }
108+
func testCopyableOptional() {
109+
var opt = Optional<Int>(94103)
110+
opt.bar(1)
111+
}
112+
testCopyableOptional()
113+
// CHECK: Optional.bar(1)
114+
115+
116+
// NEW: define hidden swiftcc void @"$s4main23testNOTCopyableOptionalyyF"()
117+
// NEW: call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$s4main2NCVSgMD")
118+
// NEW: }
119+
120+
// OLD: define hidden swiftcc void @"$s4main23testNOTCopyableOptionalyyF"()
121+
// OLD: call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$s4main2NCVSgMD")
122+
// OLD: }
123+
func testNOTCopyableOptional() {
124+
var opt = Optional<NC>(NC())
125+
opt.bar(2)
126+
}
127+
testNOTCopyableOptional()
128+
// CHECK: Optional.bar(2)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-build-swift -D CRASH %s -o %t/crash
4+
// RUN: %target-codesign %t/crash
5+
6+
// RUN: %target-build-swift %s -o %t/exe
7+
// RUN: %target-codesign %t/exe
8+
9+
// RUN: %target-run %t/exe | %FileCheck %s
10+
// RUN: %target-run not --crash %t/crash
11+
12+
// REQUIRES: executable_test
13+
14+
// Simulators and devices don't appear to have the 'not' binary in their PATH
15+
// to handle tests that intentionally crash such as this.
16+
// UNSUPPORTED: DARWIN_SIMULATOR={{.*}}
17+
// UNSUPPORTED: remote_run || device_run
18+
19+
enum Maybe<Wrapped: ~Copyable>: ~Copyable {
20+
case some(Wrapped)
21+
case none
22+
}
23+
extension Maybe: Copyable where Wrapped: Copyable {}
24+
25+
struct NC: ~Copyable {
26+
let data: Int
27+
}
28+
29+
class Protected<T: ~Copyable> {
30+
var field: Maybe<T>
31+
init(_ t: consuming T) {
32+
self.field = .some(t)
33+
}
34+
}
35+
36+
class Dangerous<T: ~Copyable> {
37+
var field: Optional<T>
38+
init(_ t: consuming T) {
39+
self.field = .some(t)
40+
}
41+
}
42+
43+
func printFields<T: Copyable>(_ val: T) {
44+
let mirror = Mirror.init(reflecting: val)
45+
mirror.children.forEach { print($0.label ?? "", $0.value) }
46+
}
47+
48+
defer { test() }
49+
func test() {
50+
#if CRASH
51+
printFields(Dangerous(NC(data: 22)))
52+
#else
53+
printFields(Protected("oreo")) // CHECK: field ()
54+
printFields(Protected(NC(data: 11))) // CHECK: field ()
55+
printFields(Dangerous("spots")) // CHECK: field Optional("spots")
56+
#endif
57+
}

0 commit comments

Comments
 (0)