RTTI is emitted unconditionally (every class, even a classless program) — dead weight on ESP32/embedded
- Track: A (RTTI emission is core:
compiler/rtti_emit.inc, the emit driver inparser.inc). Tag: O (size/codegen). Cross-cutting toward the ESP/xtensa + riscv32 targets. - Found: 2026-07-16, dissecting the frozen
hellodemo's size (280 B remembered vs 785 B now). See [[project_rtti_streaming_plan]], [[project_rtti_reflection_and_overload_landmines]].
What we measured (frozen, -uPXX_MANAGED_STRING)
| build | file | code | data |
|---|---|---|---|
bare begin end. x86-64 |
679 B | 351 | 208 |
bare begin end. x86-64 --no-signals |
407 B | 79 | 208 |
bare begin end. xtensa/ESP |
~168 B data | 6 | 168 |
Breakdown of the x86-64 baseline growth vs the old ~280 B:
- Signal-handler runtime ~272 B of code — x86-64 default-on,
--no-signalsopts out. NOT on ESP (x86-64-gated). Working as intended; leave it. - Div-by-zero abort stub + "Runtime error 200…" string (~40 B) — x86-64 ONLY
(
if TargetArch = TARGET_X86_64 then EmitDiv0Stub, parser.inc ~24210). NOT on ESP (uses hardware/trap behavior). Fine. - RTTI — emitted on ALL targets, including ESP. This is the only dead weight on a
micro. The bare xtensa binary is code=6 B but still carries a
TObjectremnant in.data.
The two RTTI wastes
- Every user class gets a full RTTI header even when never reflected. Deliberate
today —
EmitRTTI(rtti_emit.inc ~319-338) emits oneRTTI_CLS_SIZEheader + interned name per class soClassNameanswers for any class (the published-only gate used to makeClassNamea coin flip). Correct, but on ESP32 with classes and nois/as/ClassName/TypeInfo/streaming it is pure size cost. - A classless program still emits a
TObjectremnant (~8 B interned name). The "plain wrong" case: zero classes, zero reflection, yet RTTI bytes ship.
Goal
Opt OUT of RTTI to compress embedded size, keep it available (opt-in / on-demand) for debugging, reflection, and advanced software (streaming, fpcunit, dynamic dispatch).
Approaches (ranked)
- Usage-driven emission (north-star). Emit a class's RTTI only if the program
actually reflects on it —
ClassName/ClassType/InheritsFrom,is/asagainst it,TypeInfo, published-member access, LFM streaming. Track a per-class "RTTI-touched" bit during parse/lower;EmitRTTIskips untouched classes. Zero cost when unused, no flag, no silent breakage. Hard part: cases the compiler can't see statically (LFMFindClass-by-name, dynamic streaming) need a force-on escape. --rtti=auto|full|noneflag (pragmatic first step).auto= usage-driven (or, interim, the current all-classes behavior);fullforces every class (for dynamic/streaming reflection the compiler can't prove);nonehard-off — a reflection op undernoneis a compile-time error, never a silent wrong value. ESP/bare default leans (auto, and skip the classlessTObjectremnant).- Minimum quick win, independent of the above: when
UClsCount = 0and no reflection op appears, emit NO RTTI at all (drop theTObjectremnant).
Guard rails
- Never silently break reflection.
is/as,ClassName, LFM streaming ([[project_rtti_streaming_plan]]), fpcunit ([[project_fpcunit_green_metaclass_self]]) all consume RTTI. Gating must be usage-aware or explicit; a stripped build that hits a reflection op must error at compile time, not miscompute. - ESP already skips signals/div0 by target; RTTI gating should follow the same "lean-by-default on embedded, full on host" instinct.
Not in scope (separate, minor)
-
x86-64
EmitDiv0Stubis unconditional even for division-free programs (~40 B). Could gate on "a div/mod site exists." Small host-only win; file separately if wanted. -
2026-07-18 night (fable-O): claimed, reconned, PARKED on a design fork —
EmitRTTIalso emits FUNCTIONAL data (class-field finalize layouts at [VMT-16], ClassName backlink at [VMT-8]), so--rtti=nonesemantics need a user call. Filed [[decide-rtti-none-semantics]] (options + recommendation). Unclaimed; back to backlog until decided.
Elevated 2026-07-20 — this IS the --rtti=none answer
[[decide-rtti-none-semantics]] resolved as option C: usage-driven emission, and
no --rtti=none flag ships at all. So this ticket is no longer an
optimization sitting alongside a user-facing switch — it is the entire
mechanism by which RTTI shrinks, and the ESP/size-constrained audience has
nothing else to reach for. Any remaining text describing --rtti=none as a
planned switch is superseded.