Hello. A crash log through swiftfilt --simplified:
0 MyApp 0x…104abc123 $s10AppIntents0aB8XPCErrorO9errorCodeSivg + 12
2 MyApp 0x…104abe789 $s4main3fooyyFSi_Tg5 + 4
3 MyApp 0x…104abf012 _$s3foo3barC3bas3zimyAaEC_tFTo + 40
4 libswift 0x…1904aa000 _T013call_protocol1CCAA1PA2aDP3fooSiyFTW + 20
becomes
0 MyApp 0x…104abc123 AppIntentsXPCError.errorCode.getter + 12
2 MyApp 0x…104abe789 specialized foo() + 4
3 MyApp 0x…104abf012 @objc bar.bas(zim:) + 40
4 libswift 0x…1904aa000 protocol witness for P.foo() in conformance C + 20
Every byte that isn't a validated Swift mangling passes through untouched, invalid UTF-8 included, so it's safe on anything a pipe carries. Every era demangles, including the _T grammar in frame 4 that predates most people's crash archives
swift build 2>&1 | swiftfilt # linker errors become readable
tail -f build.log | swiftfilt --simplified
nm MyApp | swiftfilt census # what shipped
The parts that aren't just "demangle a string":
identityKey — one canonical key per logical function, computed on the tree rather than by regexing printed names. Specializations, async partials, @objc thunks, and outlined copies all collapse to the same key. Crash-group a log in one line:
swiftfilt --json --slim < crash.log | jq -r .identityKey | sort | uniq -c | sort -rn
explain — every demangler is silent about the symbol that doesn't work. This one tells you which era it claims, which byte the parse died on, and whether it's truncated or corrupt:
$ swiftfilt explain '$s4main9foo'
status malformed — carries a Swift prefix but does not parse
parse stopped at byte 8 of 11
reason an identifier declares 9 bytes but only 3 remain
likely a name cut short — a frame truncated in a fixed-width log column
census — pipe it an Xcode LinkMap and get your Swift symbol population: by kind, by module, which generic origins specialized how many times and at what byte cost, how much is compiler-generated machinery. It's the analysis teams hand-roll with nm | grep | sort | uniq -c, except every input line is accounted for and it refuses to print if its tables don't tile to its totals
"Isn't this just Runtime.demangle now?" SE-0498 is good, and I benchmark against it, like per call it's faster than my pure-Swift engine. It's also a different tool, it hands you one String, from inside a running Swift process, on a stdlib that's @available(OS 27). Most demangling isn't there. It's a crash log from a user's phone, an nm dump, a build log. Text on disk, no process to be inside, and often full of _T0/_Tt names the installed runtime no longer speaks
In-process, the difference is structure: a string, versus the node tree, kind, module, path, and specialization/thunk flags, which is what makes identityKey and census possible at all. And SE-0498 is explicit that its output "may change without any warning, during even patch releases of Swift". Right for its purpose, and the reason you can't hang a CI gate or a persisted crash-group key on it
Underneath it's a 0-import library, so it builds anywhere Swift compiles, on-device iOS included. Correctness is defined by external oracles. Acceptance swept 13m real symbols
githuh
brew install mi11ione/tap/swiftfilt