← board

The ESP object writer exports only app_main

Measured

test/c_obj_data_pascal.pas — two cvar/public globals and four cdecl routines — built with --emit-obj at d402147d6:

target writer OBJECT GLOBAL FUNC GLOBAL
x86-64 writeELFRelX64General 2 the 4 cdecl routines
i386 writeELFRel386General 2 the 4 cdecl routines
riscv32 writeELF32Rel 0 1app_main, and nothing else

xtensa could not be measured on the same source: the fixture fails to compile for that target inside the RTL (near: := 0 to High ( Separators ) do), which is a separate pre-existing gap and does not bear on the symbol model.

Not an oversight in the data work — a different model

writeELF32Rel's own comment states it: "every proc as LOCAL FUNC (debug visibility without link-time name collisions), the program entry exported GLOBAL as app_main". That is the ESP-IDF component shape — one entry point the SDK calls — and it predates data symbols entirely. So the object does not merely lack the DATA groups the two general writers gained; it has never exported a cdecl routine either, and the data gap is the smaller half of that.

This is why the parent ticket's "every target --emit-obj supports" row is not satisfied by the x86-64/i386 work and cannot be closed by extending it: the group layout the general writers share (4 + localProcs + localData, extSym0, impSym0) has no counterpart here.

The question to answer first, in Track U if it is a fork

Is an ESP object supposed to be linkable by name at all? The SDK calls app_main; a component that also exports helpers to other components is a different use than the one this writer was built for. Do not widen the export surface before that is answered — every symbol made GLOBAL is a name that can collide inside an IDF build, and the LOCAL-FUNC choice above is a deliberate guard against exactly that, not an omission.

Acceptance

Resolved

frankA, 2026-09-01. Compiler 03130e1067d4. Regression rows: test-emit-obj block 4b-octies over test/c_obj_esp_export.c and test/esp_obj_export.pas, riscv32 and xtensa, both writers.

The question this ticket said to answer first turned out to be answerable by measurement rather than by asking. The fork was "is an ESP object supposed to be linkable by name at all", and the reason it looked like a fork is that widening an export surface can collide inside an IDF build. But the rule the x86-64 and i386 writers use is DIRECTIVE-GATED: only cdecl and only cvar/public. Nothing joins the global group that the programmer did not mark, so the LOCAL-FUNC collision guard is kept rather than traded away, and the actual divergence being removed is that the same marker meant something on four targets and nothing on two.

Asserted, not argued: an unmarked ESP program's object is BYTE-IDENTICAL across the change, on riscv32 and xtensa, built with the two compilers either side of it (69eeb1efd71e and 03130e1067d4). The regression row carries the one-run form of the same property — an unmarked program exports exactly one defined global.

What works. Data exports through both writers, both frontends, both targets. Routine exports through the C frontend, both targets. app_main stays exactly as it was.

What does not, and it is not the writer. A Pascal cdecl routine still does not export on xtensa/riscv32, because pasparser_proc.inc only sets ProcCdecl for x86-64, aarch64, arm32 and i386 — the C convention is not claimed for those targets at all, so exporting such a routine would export something callable and wrong. A Pascal ESP object can therefore export DATA and not routines until [[bug-a-the-cdecl-soundness-reject-still-has-its-argument-shaped-door-on-four-targets]] lands. The writer half is target-neutral and needs no further work then.

Two things the work found.

One unrelated defect fell out of the sweep and is filed separately: [[bug-a-c-a-global-initialised-with-a-function-address-is-not-exported]] — of seven C file-scope forms, fp_t F = helper; is the only one that gets no symbol at all, on every target, which makes it a frontend gap rather than a writer one.

Log