Files
ppsspp/docs/CodeWarriorMangling.md
T
Henrik RydgårdandClaude Opus 5 febccfc857 docs: clarify that the destructor flag 0 vs -1 is a caller-side convention
Both mean "do not free" as far as the callee is concerned - it only tests the
sign - but 0 is passed specifically for base-subobject destruction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SF5eS5QDNexLksRDeDZvwY
2026-08-30 00:02:14 +02:00

252 lines
11 KiB
Markdown

# Metrowerks CodeWarrior C++ symbol mangling (PSP)
Some PSP titles were built with Metrowerks CodeWarrior rather than the SDK's GCC, so their
symbols aren't Itanium-mangled and `_Z`-based demanglers do nothing with them. PPSSPP
demangles them in `Common/Data/Text/Demangle.cpp` (`DemangleCodeWarrior`), which is what makes
the symbol map readable for those games.
The scheme is a descendant of the AT&T cfront mangling, and the basics match the Macintosh C++
ABI. It deviates in enough places that following that document alone produces wrong answers, so
this is a description of what PSP binaries actually contain.
## How this was worked out
From two PSP executables that shipped with intact symbol tables - one small C++ test program,
one large application of about 10,000 symbols - by demangling every symbol and checking the
result against the disassembly and against the bytes each data symbol points at.
Constructs the corpus did not contain are marked **(inferred)** below. They come from the cfront
scheme, are implemented, and are believed right, but nothing here proves them.
## Overall shape
A mangled symbol is:
```
<basename> __ [<class>] [<cv>] F <parameters> [_ <return type>]
```
with the class, the cv-qualifiers, the `F` and everything after it all optional. A few examples,
building up:
| Mangled | Demangled |
| --- | --- |
| `run_tests__Fv` | `run_tests()` |
| `getDistance__6KzUtilFP7st_unitP7st_unit` | `KzUtil::getDistance(st_unit *, st_unit *)` |
| `what__Q23std9exceptionCFv` | `std::exception::what() const` |
| `count__Q23foo3bar` | `foo::bar::count` |
The last one has no `F`, so it isn't a function at all - it's a static data member, and there is
no parameter list to print. A name with neither a class nor an `F` is not mangled.
### Finding the separator
The `__` separator is genuinely ambiguous. A base name can itself start with underscores
(`__SetupFrameInfo__FP12ThrowContext...`), contain a `__` of its own
(`__TableUnit__SetValue<i,1>__5shTbbF...`), and plain C code in the same binary is full of names
like `I3dCacheManager__GarbageCollect` that are not mangled at all.
There is no way to resolve this from the grammar; PPSSPP tries every `__` in the symbol and keeps
the first split whose right-hand side parses completely. Only if none does will it accept a split
whose parameters didn't decode - and then only when a class qualifier is present, because
otherwise every C name with a double underscore in it decodes as a garbage function signature.
## Names
An identifier is written as its length in decimal followed by its characters: `6KzUtil`.
A qualified name is `Q<count>` followed by that many identifiers: `Q23std9exception` is
`std::exception` (2 components, `3std` and `9exception`). The count is a single digit; ten or
more components are written `Q_<count>_` **(inferred)**.
### Template arguments
Template arguments are written literally between `<` and `>`, *inside* the length-prefixed name -
the length covers the whole thing, brackets included. (The Macintosh ABI document describes a
`__PT` prefix instead. PSP binaries do not use it.)
```
39CList<Q38hlScreen5Brwsr13CContentsUnit> shList::CList<hlScreen::Brwsr::CContentsUnit>
```
Here `39` is the length of `CList<Q38hlScreen5Brwsr13CContentsUnit>`. Each argument is either a
mangled type, or a plain integer for a non-type parameter, separated by commas:
| Mangled | Demangled |
| --- | --- |
| `15CSimpleChar<24>` | `CSimpleChar<24>` |
| `61ForwardIterator<16TABLE_VAR_MEMBER,21TABLE_VAR_SECT_HEADER,v>` | `ForwardIterator<TABLE_VAR_MEMBER, TABLE_VAR_SECT_HEADER, void>` |
| `47CList<Q26ssTool29tag_<Q26shFont12SysCmdString>>` | `CList<ssTool::tag_<shFont::SysCmdString>>` |
They nest, so a parser must match brackets rather than scanning for the next `>`, and split
arguments only on top-level commas.
A *function* template puts its arguments in the base name instead, and - unlike a normal function -
encodes its return type after a trailing `_`:
```
sort<Pf>__3stdFPfPf_v void std::sort<float *>(float *, float *)
```
### Special base names
| Base name | Meaning |
| --- | --- |
| `__ct` | Constructor; the name is taken from the class |
| `__dt` | Destructor |
| `__op<type>` | Conversion operator, e.g. `__opCi` is `operator const int()` |
| `__<code>` | Overloaded operator, see below |
Operator codes are the cfront set: `nw` `dl` `nwa` `dla` for `new`/`delete`, `pl` `mi` `ml` `dv`
`md` for arithmetic, `apl` `ami` `amu` `adv` `amd` `aad` `aor` `aer` `als` `ars` for the compound
assignments, `eq` `ne` `lt` `gt` `le` `ge` for comparisons, `aa` `oo` `nt` for logic, `ad` `or`
`er` `co` `ls` `rs` for bitwise, and `as` `pp` `mm` `cl` `vc` `rf` `rm` `cm` for
`=` `++` `--` `()` `[]` `->` `->*` `,`.
So `__as__9ANIMEDataFRC9ANIMEData` is `ANIMEData::operator=(const ANIMEData &)`.
## Types
Type codes are read left to right, each one modifying what follows.
| Code | Type | |
| --- | --- | --- |
| `v` | `void` | |
| `b` | `bool` | |
| `c` | `char` | |
| `s` | `short` | |
| `i` | `int` | |
| `l` | `long` | |
| `x` | `long long` | |
| `f` | `float` | |
| `d` | `double` | |
| `r` | `long double` | **(inferred)** |
| `w` | `wchar_t` | **(inferred)** - a Metrowerks addition to the cfront set |
| `e` | `...` | varargs, always last |
| `P` | pointer to | |
| `R` | reference to | |
| `C` | `const` | |
| `V` | `volatile` | |
| `U` | `unsigned` | |
| `S` | `signed` | **(inferred)** |
| `A<n>_` | array of `n` | **(inferred)** |
| `F<params>_<ret>` | function | |
| `<len><name>` | class | |
| `Q<n>...` | qualified class | |
`PCc` is `const char *`; `CPc` is `char * const`. A pointer to a function or an array needs the
declarator wrapped rather than a `*` appended - `PFi_i` is `int (*)(int)`, and `PPFv_v` is
`void (**)()`.
Two forms back-reference an earlier parameter of the same function **(inferred)**:
| Form | Meaning |
| --- | --- |
| `T<index>` | Same type as parameter `<index>`, 1-based |
| `N<count><index>` | `<count>` more parameters, each the type of parameter `<index>` |
So `foo__FPCcUiN21` is `foo(const char *, unsigned int, const char *, const char *)`.
### cv-qualifiers on the function
A `C` or `V` sits between the class and the `F`, and qualifies the member function rather than a
parameter: `InitRun__Q28shCamera11TStillParamVFv` is
`shCamera::TStillParam::InitRun() volatile`.
## Compiler-generated symbols
CodeWarrior emits a family of symbols for things with no C++ name of their own. These wrap a
mangled name rather than being one, and none of them are described in the ABI document.
| Form | Meaning | Example |
| --- | --- | --- |
| `__vt__<class>` | Vtable | `__vt__Q23std9exception` |
| `__RTTI__<class>` | Typeinfo record | `__RTTI__Q23std9exception` |
| `__sinit_<file>` | Static initializers for a translation unit | `__sinit_hl_app.cpp` |
| `__sterm_<file>` | Static destructors | |
| `@<n>@<symbol>` | `this`-adjusting thunk, `n` bytes | `@12@__dt__3SonFv` |
| `@STRING@<symbol>[@<n>]` | A string literal used inside that function | `@STRING@Get_BGM__Q25hlBHC4SBhcFi@0` |
| `@LOCAL@<symbol>@<var>[@<n>]` | A function-local static | `@LOCAL@sort<Pf>__3stdFPfPf_v@shuffle@0` |
| `@GUARD@<var>$<n>` | Its "already constructed" flag | `@GUARD@app$16079` |
| `@<n>` | An anonymous string constant | `@10046` |
The trailing `@<n>` on `@STRING@` and `@LOCAL@` is a discriminator, present only when a function
has more than one of them - so a parser has to treat it as optional, and must not mistake the
`@` before a `@LOCAL@` variable name for it.
`@<n>@` thunks really are thunks: the one above is 8 bytes of code that does `addiu a0, a0, -12`
and jumps, i.e. adjusts `this` for a secondary base. `@STRING@` symbols point at ordinary string
data - usually the `__FILE__` an assertion expanded to.
## Other decorations
Two more show up inside otherwise ordinary names. Neither needs decoding, but both are worth
recognising:
- `<name>$<digits><file>` - a class or variable with internal linkage, tagged with where it was
declared: `ClutScreen$11229hl_cplayer_effect_renderer_cpp`. These make for very long symbols,
since the tag repeats everywhere the type appears.
- `<len>@unnamed@<file>@` - used as a qualifier for file-scope statics, i.e. an unnamed namespace.
`ARWMENU_MODEL_NAME__34@unnamed@hl_editor_arrow_menu_cpp@` is a data symbol in one.
## The hidden destructor parameter
**The mangling describes the source-level signature, not the generated one.** `__dt__3SonFv`
demangles to `Son::~Son()`, and that is the right answer for a demangler - but the function the
compiler emitted takes a second argument, and anything that turns a demangled name into a
function signature (a decompiler, a debugger's parameter view) will be wrong about it.
CodeWarrior emits one destructor per class rather than the two or three the Itanium ABI clones,
and distinguishes the cases with a flag. On PSP that flag is a **`short` in `a1`**, and the
destructor **returns `this` in `v0`**. So the real signature is:
```c
Son *__dt__3SonFv(Son *this, short flag);
```
Decompiled from a real one, the body is:
```c
Son *__dt__3SonFv(Son *this, short flag) {
if (this) { // destructors are null-safe
... restore vtable pointers, destroy members ...
__dt__3DadFv(this + 12, 0); // base subobjects get 0
__dt__3MomFv(this, 0);
if (flag > 0) // "blez" - only a positive flag frees
__dl__FPv(this); // operator delete
}
return this;
}
```
The values that actually get passed, counted over the call sites in a retail binary:
| Flag | Meaning | Frequency |
| --- | --- | --- |
| `-1` | Destroy a complete object without freeing it - a stack object, a member, an explicit `~T()` call | Most calls |
| `1` | Destroy and free - what `delete` compiles to | A handful |
| `0` | Destroy a base-class subobject | A handful |
`0` and `-1` are a caller-side distinction: the destructors examined here only test the sign, so
both simply mean "don't free", but the compiler still passes `0` specifically when destroying a
base subobject and `-1` everywhere else. Treat `0` as "this call is destroying me as somebody's
base" when reading a call site, even though the callee can't tell.
Constructors in these binaries take only `this` (and also return it).
Note that the vtable slot holds this same function, flag and all, which is how CodeWarrior gets
away without separate deleting and non-deleting destructors: a virtual `delete` passes 1 through
the vtable.
## Hazards
- **A truncated name is unrecoverable.** Some symbol tables cap names (127 characters is a common
limit), and templates blow past that easily. Once a name is cut, the length prefixes no longer
match what's left and nothing after the cut can be trusted - better to reject the symbol than
to print a plausible-looking guess.
- **Not everything with a `__` is mangled.** C code linked into the same binary uses `__` as a
word separator freely. Requiring a class qualifier before accepting a partial parse is what
keeps `I3dClut__FlushCache` from turning into `I3dClut(long, ...)`.
- **`e` is a real parameter, not a parse failure.** `Printf__Q26shFont4FontFiiPCce` ends in
varargs; `(int, int, const char *, ...)` is the correct answer, not a partial one.