Skip to content

Commit 4fa0855

Browse files
committed
[Distributed] RemoteCallTarget now pretty prints and hides mangled names
1 parent 8925f9d commit 4fa0855

19 files changed

+376
-104
lines changed

include/swift/Runtime/HeapObject.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,15 @@ swift_getTypeName(const Metadata *type, bool qualified);
11011101
/// -> (UnsafePointer<UInt8>, Int)
11021102
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
11031103
TypeNamePair
1104+
swift_getFunctionFullNameFromMangledName(
1105+
const char *mangledNameStart, uintptr_t mangledNameLength);
1106+
1107+
/// Return the human-readable full name of the mangled function name passed in.
1108+
/// func _getMangledTypeName(_ mangledName: UnsafePointer<UInt8>,
1109+
/// mangledNameLength: UInt)
1110+
/// -> (UnsafePointer<UInt8>, Int)
1111+
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
1112+
TypeNamePair
11041113
swift_getMangledTypeName(const Metadata *type);
11051114

11061115
} // end namespace swift

lib/Sema/TypeCheckDistributed.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ bool swift::checkDistributedActorSystemAdHocProtocolRequirements(
310310
}
311311
if (checkAdHocRequirementAccessControl(decl, Proto, recordArgumentDecl))
312312
anyMissingAdHocRequirements = true;
313-
313+
314314
// - recordReturnType
315315
auto recordReturnTypeDecl = C.getRecordReturnTypeOnDistributedInvocationEncoder(decl);
316316
if (!recordReturnTypeDecl) {

stdlib/public/Distributed/DistributedActorSystem.swift

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -172,23 +172,18 @@ extension DistributedActorSystem {
172172
/// latency sensitive-use-cases.
173173
public func executeDistributedTarget<Act, ResultHandler>(
174174
on actor: Act,
175-
mangledTargetName: String,
175+
target: RemoteCallTarget,
176176
invocationDecoder: inout InvocationDecoder,
177177
handler: ResultHandler
178178
) async throws where Act: DistributedActor,
179179
// Act.ID == ActorID, // FIXME(distributed): can we bring this back?
180180
ResultHandler: DistributedTargetInvocationResultHandler {
181-
// NOTE: this implementation is not the most efficient, nor final, version of this func
182-
// we end up demangling the name multiple times, perform more heap allocations than
183-
// we truly need to etc. We'll eventually move this implementation to a specialized one
184-
// avoiding these issues.
185-
guard mangledTargetName.count > 0 && mangledTargetName.first == "$" else {
181+
// Get the expected parameter count of the func
182+
guard let targetName = target.identifier else {
186183
throw ExecuteDistributedTargetError(
187-
message: "Illegal mangledTargetName detected, must start with '$'")
184+
message: "Unable to extract target identifier from remote call target: \(target)")
188185
}
189-
190-
// Get the expected parameter count of the func
191-
let nameUTF8 = Array(mangledTargetName.utf8)
186+
let nameUTF8 = Array(targetName.utf8)
192187

193188
// Gen the generic environment (if any) associated with the target.
194189
let genericEnv = nameUTF8.withUnsafeBufferPointer { nameUTF8 in
@@ -207,7 +202,9 @@ extension DistributedActorSystem {
207202

208203
if let genericEnv = genericEnv {
209204
let subs = try invocationDecoder.decodeGenericSubstitutions()
210-
205+
for item in subs {
206+
print("SUB: \(item)")
207+
}
211208
if subs.isEmpty {
212209
throw ExecuteDistributedTargetError(
213210
message: "Cannot call generic method without generic argument substitutions")
@@ -224,7 +221,7 @@ extension DistributedActorSystem {
224221
genericArguments: substitutionsBuffer!)
225222
if numWitnessTables < 0 {
226223
throw ExecuteDistributedTargetError(
227-
message: "Generic substitutions \(subs) do not satisfy generic requirements of \(mangledTargetName)")
224+
message: "Generic substitutions \(subs) do not satisfy generic requirements of \(target) (\(targetName))")
228225
}
229226
}
230227

@@ -237,7 +234,7 @@ extension DistributedActorSystem {
237234
message: """
238235
Failed to decode distributed invocation target expected parameter count,
239236
error code: \(paramCount)
240-
mangled name: \(mangledTargetName)
237+
mangled name: \(targetName)
241238
""")
242239
}
243240

@@ -262,7 +259,7 @@ extension DistributedActorSystem {
262259
message: """
263260
Failed to decode the expected number of params of distributed invocation target, error code: \(decodedNum)
264261
(decoded: \(decodedNum), expected params: \(paramCount)
265-
mangled name: \(mangledTargetName)
262+
mangled name: \(targetName)
266263
""")
267264
}
268265

@@ -280,7 +277,7 @@ extension DistributedActorSystem {
280277
return UnsafeRawPointer(UnsafeMutablePointer<R>.allocate(capacity: 1))
281278
}
282279

283-
guard let returnTypeFromTypeInfo: Any.Type = _getReturnTypeInfo(mangledMethodName: mangledTargetName,
280+
guard let returnTypeFromTypeInfo: Any.Type = _getReturnTypeInfo(mangledMethodName: targetName,
284281
genericEnv: genericEnv,
285282
genericArguments: substitutionsBuffer) else {
286283
throw ExecuteDistributedTargetError(
@@ -307,7 +304,7 @@ extension DistributedActorSystem {
307304
// Execute the target!
308305
try await _executeDistributedTarget(
309306
on: actor,
310-
mangledTargetName, UInt(mangledTargetName.count),
307+
targetName, UInt(targetName.count),
311308
argumentDecoder: &invocationDecoder,
312309
argumentTypes: argumentTypesBuffer.baseAddress!._rawValue,
313310
resultBuffer: resultBuffer._rawValue,
@@ -326,6 +323,52 @@ extension DistributedActorSystem {
326323
}
327324
}
328325

326+
/// Represents a 'target' of a distributed call, such as a `distributed func` or
327+
/// `distributed` computed property. Identification schemes may vary between
328+
/// systems, and are subject to evolution.
329+
///
330+
/// Actor systems generally should treat the `identifier` as an opaque string,
331+
/// and pass it along to the remote system for in their `remoteCall`
332+
/// implementation. Alternative approaches are possible, where the identifiers
333+
/// are either compressed, cached, or represented in other ways, as long as the
334+
/// recipient system is able to determine which target was intended to be
335+
/// invoked.
336+
///
337+
/// The string representation will attempt to pretty print the target identifier,
338+
/// however its exact format is not specified and may change in future versions.
339+
@available(SwiftStdlib 5.7, *)
340+
public struct RemoteCallTarget: CustomStringConvertible {
341+
private let _storage: _Storage
342+
private enum _Storage {
343+
case mangledName(String)
344+
}
345+
346+
// Only intended to be created by the _Distributed library.
347+
// TODO(distributed): make this internal and only allow calling by the synthesized code?
348+
public init(_mangledName: String) {
349+
self._storage = .mangledName(_mangledName)
350+
}
351+
352+
/// The underlying identifier of the target, returned as-is.
353+
public var identifier: String? {
354+
switch self._storage {
355+
case .mangledName(let name):
356+
return name
357+
}
358+
}
359+
360+
public var description: String {
361+
switch self._storage {
362+
case .mangledName(let mangledName):
363+
if let name = _getFunctionFullNameFromMangledName(mangledName: mangledName) {
364+
return name
365+
} else {
366+
return "\(mangledName)"
367+
}
368+
}
369+
}
370+
}
371+
329372
@available(SwiftStdlib 5.7, *)
330373
@_silgen_name("swift_distributed_execute_target")
331374
func _executeDistributedTarget<D: DistributedTargetInvocationDecoder>(
@@ -339,29 +382,6 @@ func _executeDistributedTarget<D: DistributedTargetInvocationDecoder>(
339382
numWitnessTables: UInt
340383
) async throws
341384

342-
// ==== ----------------------------------------------------------------------------------------------------------------
343-
// MARK: Support types
344-
/// A distributed 'target' can be a `distributed func` or `distributed` computed property.
345-
@available(SwiftStdlib 5.7, *)
346-
public struct RemoteCallTarget { // TODO: ship this around always; make printing nice
347-
let _mangledName: String // TODO: StaticString would be better here; no arc, codesize of cleanups
348-
349-
// Only intended to be created by the _Distributed library.
350-
// TODO(distributed): make this internal and only allow calling by the synthesized code?
351-
public init(_mangledName: String) {
352-
self._mangledName = _mangledName
353-
}
354-
355-
public var mangledName: String {
356-
_mangledName
357-
}
358-
359-
// <module>.Base.hello(hi:)
360-
public var fullName: String { // TODO: make description
361-
fatalError("NOT IMPLEMENTED YET: \(#function)")
362-
}
363-
}
364-
365385
/// Used to encode an invocation of a distributed target (method or computed property).
366386
///
367387
/// ## Forming an invocation

stdlib/public/core/Misc.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,35 @@ public func _autorelease(_ x: AnyObject) {
4646
}
4747
#endif
4848

49+
50+
@available(SwiftStdlib 5.7, *)
51+
@_silgen_name("swift_getFunctionFullNameFromMangledName")
52+
public // SPI (Distributed)
53+
func _getFunctionFullNameFromMangledNameImpl(
54+
_ mangledName: UnsafePointer<UInt8>, _ mangledNameLength: UInt
55+
) -> (UnsafePointer<UInt8>, UInt)
56+
57+
/// Given a function's mangled name, return a human readable name.
58+
/// Used e.g. by Distributed.RemoteCallTarget to hide mangled names.
59+
@available(SwiftStdlib 5.7, *)
60+
public // SPI (Distributed)
61+
func _getFunctionFullNameFromMangledName(mangledName: String) -> String? {
62+
let mangledNameUTF8 = Array(mangledName.utf8)
63+
let (stringPtr, count) =
64+
mangledNameUTF8.withUnsafeBufferPointer { (mangledNameUTF8) in
65+
return _getFunctionFullNameFromMangledNameImpl(
66+
mangledNameUTF8.baseAddress!,
67+
UInt(mangledNameUTF8.endIndex))
68+
}
69+
70+
guard count > 0 else {
71+
return nil
72+
}
73+
74+
return String._fromUTF8Repairing(
75+
UnsafeBufferPointer(start: stringPtr, count: Int(count))).0
76+
}
77+
4978
// FIXME(ABI)#51 : this API should allow controlling different kinds of
5079
// qualification separately: qualification with module names and qualification
5180
// with type names that we are nested in.

stdlib/public/runtime/Casting.cpp

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,19 @@ using TypeNameCacheKey = llvm::PointerIntPair<const Metadata *, 2, TypeNameKind>
146146

147147
#if SWIFT_CASTING_SUPPORTS_MUTEX
148148
static StaticReadWriteLock TypeNameCacheLock;
149+
static StaticReadWriteLock MangledToPrettyFunctionNameCacheLock;
149150
#endif
150151

151152
/// Cache containing rendered names for Metadata.
152153
/// Access MUST be protected using `TypeNameCacheLock`.
153154
static Lazy<llvm::DenseMap<TypeNameCacheKey, std::pair<const char *, size_t>>>
154155
TypeNameCache;
155156

157+
/// Cache containing rendered human-readable names for incoming mangled names.
158+
static Lazy<llvm::DenseMap<llvm::StringRef, std::pair<const char *, size_t>>>
159+
/// Access MUST be protected using `MangledToPrettyFunctionNameCache`.
160+
MangledToPrettyFunctionNameCache;
161+
156162
TypeNamePair
157163
swift::swift_getTypeName(const Metadata *type, bool qualified) {
158164
TypeNameCacheKey key = TypeNameCacheKey(type, qualified ? TypeNameKind::Qualified: TypeNameKind::NotQualified);
@@ -255,6 +261,141 @@ swift::swift_getMangledTypeName(const Metadata *type) {
255261
}
256262
}
257263

264+
265+
TypeNamePair
266+
swift::swift_getFunctionFullNameFromMangledName(
267+
const char *mangledNameStart, uintptr_t mangledNameLength) {
268+
llvm::StringRef mangledName(mangledNameStart, mangledNameLength);
269+
270+
auto &cache = MangledToPrettyFunctionNameCache.get();
271+
// Attempt read-only lookup of cache entry.
272+
{
273+
#if SWIFT_CASTING_SUPPORTS_MUTEX
274+
StaticScopedReadLock guard(MangledToPrettyFunctionNameCacheLock);
275+
#endif
276+
277+
auto found = cache.find(mangledName);
278+
if (found != cache.end()) {
279+
auto result = found->second;
280+
return TypeNamePair{result.first, result.second};
281+
}
282+
}
283+
284+
for (char c : mangledName) {
285+
if (c >= '\x01' && c <= '\x1F')
286+
return TypeNamePair{nullptr, 0};
287+
}
288+
289+
// Read-only lookup failed, we may need to demangle and cache the entry.
290+
// We have to copy the string to be able to refer to it "forever":
291+
auto copy = (char *)malloc(mangledNameLength);
292+
memcpy(copy, mangledNameStart, mangledNameLength);
293+
mangledName = StringRef(copy, mangledNameLength);
294+
295+
std::string demangled;
296+
StackAllocatedDemangler<1024> Dem;
297+
NodePointer node = Dem.demangleSymbol(mangledName);
298+
if (!node) {
299+
return TypeNamePair{nullptr, 0};
300+
}
301+
302+
// Form the demangled string from the node tree.
303+
node = node->findByKind(Demangle::Node::Kind::Function, /*maxDepth=*/3);
304+
if (!node || node->getNumChildren() < 3) {
305+
// we normally expect Class/Identifier/Type, but don't need `Type`
306+
return TypeNamePair{nullptr, 0};
307+
}
308+
309+
// Class identifier:
310+
auto clazz = node->findByKind(Demangle::Node::Kind::Class, 1);
311+
if (clazz) {
312+
if (auto module = clazz->findByKind(Demangle::Node::Kind::Module, 1)) {
313+
demangled += module->getText();
314+
demangled += ".";
315+
}
316+
if (auto clazzIdent = clazz->findByKind(Demangle::Node::Kind::Identifier, 1)) {
317+
demangled += clazzIdent->getText();
318+
demangled += ".";
319+
}
320+
}
321+
322+
// Function identifier:
323+
NodePointer funcIdent = nullptr; // node == Function
324+
for (size_t i = 0; i < node->getNumChildren(); ++i) {
325+
if (node->getChild(i)->getKind() == Demangle::Node::Kind::Identifier) {
326+
funcIdent = node->getChild(i);
327+
}
328+
}
329+
330+
// We always expect to work with functions here and they must have idents
331+
if (!funcIdent) {
332+
return TypeNamePair{nullptr, 0};
333+
}
334+
assert(funcIdent->getKind() == Demangle::Node::Kind::Identifier);
335+
demangled += funcIdent->getText();
336+
demangled += "(";
337+
338+
if (auto labelList = node->findByKind(Demangle::Node::Kind::LabelList, /*maxDepth=*/1)) {
339+
if (labelList->getNumChildren()) {
340+
size_t paramIdx = 0;
341+
while (paramIdx < labelList->getNumChildren()) {
342+
auto labelIdentifier = labelList->getChild(paramIdx++);
343+
if (labelIdentifier) {
344+
if (labelIdentifier->getKind() == Demangle::Node::Kind::Identifier) {
345+
demangled += labelIdentifier->getText();
346+
demangled += ":";
347+
} else if (labelIdentifier->getKind() ==
348+
Demangle::Node::Kind::FirstElementMarker) {
349+
demangled += "_:";
350+
}
351+
}
352+
}
353+
} else if (auto argumentTuple = node->findByKind(
354+
Demangle::Node::Kind::ArgumentTuple, /*maxDepth=*/5)) {
355+
// LabelList was empty.
356+
//
357+
// The function has no labels at all, but could have some parameters...
358+
// we need to check for their count, and render it as e.g. (::) for two
359+
// anonymous parameters.
360+
auto params = argumentTuple->getFirstChild();
361+
if (auto paramsType = params->getFirstChild()) {
362+
if (paramsType->getKind() != Demangle::Node::Kind::Tuple) {
363+
// was a single, unnamed, parameter
364+
demangled += "_:";
365+
} else {
366+
// there are a few parameters; find out how many
367+
while (params && params->getFirstChild() &&
368+
params->getFirstChild()->getKind() !=
369+
Demangle::Node::Kind::TupleElement) {
370+
params = params->getFirstChild();
371+
}
372+
if (params) {
373+
for (size_t i = 0; i < params->getNumChildren(); ++i) {
374+
demangled += "_:";
375+
}
376+
}
377+
}
378+
}
379+
}
380+
}
381+
demangled += ")";
382+
383+
// We have to copy the string to be able to refer to it;
384+
auto size = demangled.size();
385+
auto result = (char *)malloc(size + 1);
386+
memcpy(result, demangled.data(), size);
387+
result[size] = 0; // 0-terminated string
388+
389+
{
390+
#if SWIFT_CASTING_SUPPORTS_MUTEX
391+
StaticScopedWriteLock guard(MangledToPrettyFunctionNameCacheLock);
392+
#endif
393+
394+
cache.insert({mangledName, {result, size}});
395+
return TypeNamePair{result, size};
396+
}
397+
}
398+
258399
/// Report a dynamic cast failure.
259400
// This is noinline to preserve this frame in stack traces.
260401
// We want "dynamicCastFailure" to appear in crash logs even we crash

0 commit comments

Comments
 (0)