Skip to content

Commit f812dc3

Browse files
committed
Add _mangledTypeName to allow round trips T->mangledName->T
1 parent 33150e3 commit f812dc3

File tree

4 files changed

+132
-9
lines changed

4 files changed

+132
-9
lines changed

include/swift/Runtime/HeapObject.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,14 @@ struct TypeNamePair {
10821082
/// -> (UnsafePointer<UInt8>, Int)
10831083
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
10841084
TypeNamePair
1085-
swift_getTypeName(const Metadata *type, bool qualified);
1085+
swift_getTypeName(const Metadata *type, bool qualified);
1086+
1087+
/// Return the mangled name of a Swift type represented by a metadata object.
1088+
/// func _getMangledTypeName(_ type: Any.Type)
1089+
/// -> (UnsafePointer<UInt8>, Int)
1090+
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
1091+
TypeNamePair
1092+
swift_getMangledTypeName(const Metadata *type);
10861093

10871094
} // end namespace swift
10881095

stdlib/public/core/Misc.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,28 @@ func _typeName(_ type: Any.Type, qualified: Bool = true) -> String {
7878
UnsafeBufferPointer(start: stringPtr, count: count)).0
7979
}
8080

81+
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
82+
@_silgen_name("swift_getMangledTypeName")
83+
public func _getMangledTypeName(_ type: Any.Type)
84+
-> (UnsafePointer<UInt8>, Int)
85+
86+
/// Returns the mangled name for a given type.
87+
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
88+
public // SPI
89+
func _mangledTypeName<T>(_ type: T.Type) -> String? {
90+
let (stringPtr, count) = _getMangledTypeName(type)
91+
guard count > 0 else {
92+
return nil
93+
}
94+
95+
let (result, repairsMade) = String._fromUTF8Repairing(
96+
UnsafeBufferPointer(start: stringPtr, count: count))
97+
98+
precondition(!repairsMade, "repairs made to _mangledTypeName, this is not expected since names should always valid UTF-8")
99+
100+
return result
101+
}
102+
81103
/// Lookup a class given a name. Until the demangled encoding of type
82104
/// names is stabilized, this is limited to top-level class names (Foo.bar).
83105
public // SPI(Foundation)

stdlib/public/runtime/Casting.cpp

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,27 @@ std::string swift::nameForMetadata(const Metadata *type,
121121
return result;
122122
}
123123

124-
TypeNamePair
125-
swift::swift_getTypeName(const Metadata *type, bool qualified) {
126-
using Key = llvm::PointerIntPair<const Metadata *, 1, bool>;
124+
/// Used as part of cache key for `TypeNameCache`.
125+
enum class TypeNameKind {
126+
NotQualified,
127+
Qualified,
128+
Mangled,
129+
};
130+
131+
using TypeNameCacheKey = llvm::PointerIntPair<const Metadata *, 2, TypeNameKind>;
127132

128-
static StaticReadWriteLock TypeNameCacheLock;
129-
static Lazy<llvm::DenseMap<Key, std::pair<const char *, size_t>>>
133+
#if SWIFT_CASTING_SUPPORTS_MUTEX
134+
static StaticReadWriteLock TypeNameCacheLock;
135+
#endif
136+
137+
/// Cache containing rendered names for Metadata.
138+
/// Access MUST be protected using `TypeNameCacheLock`.
139+
static Lazy<llvm::DenseMap<TypeNameCacheKey, std::pair<const char *, size_t>>>
130140
TypeNameCache;
131-
132-
Key key(type, qualified);
141+
142+
TypeNamePair
143+
swift::swift_getTypeName(const Metadata *type, bool qualified) {
144+
TypeNameCacheKey key = TypeNameCacheKey(type, qualified ? TypeNameKind::Qualified: TypeNameKind::NotQualified);
133145
auto &cache = TypeNameCache.get();
134146

135147
// Attempt read-only lookup of cache entry.
@@ -168,6 +180,60 @@ swift::swift_getTypeName(const Metadata *type, bool qualified) {
168180
}
169181
}
170182

183+
/// Return mangled name for the given type.
184+
TypeNamePair
185+
swift::swift_getMangledTypeName(const Metadata *type) {
186+
TypeNameCacheKey key(type, TypeNameKind::Mangled);
187+
auto &cache = TypeNameCache.get();
188+
189+
// Attempt read-only lookup of cache entry.
190+
{
191+
#if SWIFT_CASTING_SUPPORTS_MUTEX
192+
StaticScopedReadLock guard(TypeNameCacheLock);
193+
#endif
194+
195+
auto found = cache.find(key);
196+
if (found != cache.end()) {
197+
auto result = found->second;
198+
return TypeNamePair{result.first, result.second};
199+
}
200+
}
201+
202+
// Read-only cache lookup failed, we may need to create it.
203+
{
204+
#if SWIFT_CASTING_SUPPORTS_MUTEX
205+
StaticScopedWriteLock guard(TypeNameCacheLock);
206+
#endif
207+
208+
// Do lookup again just to make sure it wasn't created by another
209+
// thread before we acquired the write lock.
210+
auto found = cache.find(key);
211+
if (found != cache.end()) {
212+
auto result = found->second;
213+
return TypeNamePair{result.first, result.second};
214+
}
215+
216+
// Build the mangled name.
217+
Demangle::Demangler Dem;
218+
auto demangling = _swift_buildDemanglingForMetadata(type, Dem);
219+
220+
if (demangling == nullptr) {
221+
return TypeNamePair{NULL, 0};
222+
}
223+
auto name = Demangle::mangleNode(demangling);
224+
225+
// Copy it to memory we can reference forever.
226+
auto size = name.size();
227+
auto result = (char *)malloc(size + 1);
228+
memcpy(result, name.data(), size);
229+
result[size] = 0;
230+
231+
cache.insert({key, {result, size}});
232+
233+
return TypeNamePair{result, size};
234+
}
235+
}
236+
171237
/// Report a dynamic cast failure.
172238
// This is noinline to preserve this frame in stack traces.
173239
// We want "dynamicCastFailure" to appear in crash logs even we crash

test/Runtime/demangleToMetadata.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,5 +423,33 @@ DemangleToMetadataTests.test("Nested types in same-type-constrained extensions")
423423
// V !: P3 in InnerTEqualsConformsToP1
424424
}
425425

426-
runAllTests()
426+
if #available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) {
427+
DemangleToMetadataTests.test("Round-trip with _mangledTypeName and _typeByName") {
428+
func roundTrip<T>(_ type: T.Type) {
429+
let mangledName: String? = _mangledTypeName(type)
430+
let recoveredType: Any.Type? = _typeByName(mangledName!)
431+
expectEqual(String(reflecting: type), String(reflecting: recoveredType!))
432+
expectEqual(type, recoveredType! as! T.Type)
433+
}
434+
435+
roundTrip(Int.self)
436+
roundTrip(String.self)
437+
roundTrip(ConformsToP2AndP3.self)
438+
roundTrip(SG12<ConformsToP1AndP2, ConformsToP1AndP2>.self)
439+
roundTrip(SG12<ConformsToP1AndP2, ConformsToP1AndP2>.InnerTEqualsU<ConformsToP3>.self)
440+
roundTrip(SG12<ConformsToP1, ConformsToP2>.InnerTEqualsConformsToP1<ConformsToP3>.self)
441+
roundTrip(SG12<ConformsToP1, ConformsToP2>.InnerUEqualsConformsToP2<ConformsToP3>.self)
442+
}
443+
444+
DemangleToMetadataTests.test("Check _mangledTypeName, _typeName use appropriate cache keys") {
445+
// sanity check that name functions use the right keys to store cached names:
446+
for _ in 1...2 {
447+
expectEqual("Si", _mangledTypeName(Int.self)!)
448+
expectEqual("Swift.Int", _typeName(Int.self, qualified: true))
449+
expectEqual("Int", _typeName(Int.self, qualified: false))
450+
}
451+
}
452+
}
427453

454+
455+
runAllTests()

0 commit comments

Comments
 (0)