← board

C on the ESP profile cannot reach crtl

The 2×2, which is the whole diagnosis

Source is #include <stdio.h> plus int main(void) { printf("hello\n"); return 0; }.

--platform=posix ESP profile (default and --esp-profile=bare)
--emit-obj BUILDS pascal26:11: error: compiler error: PXXMemZero not found
executable BUILDS refuses earlier, at the C entry stub (correct, by design)

The discriminator is the PROFILE, not the output mode. That matters because the obvious reading — "the object path is less complete than the executable path" — is wrong, and it is the reading someone will reach for.

A freestanding C source is fine on both ESP profiles: int main(void) with no includes builds to an object exporting GLOBAL app_main. So the entry machinery works and it is specifically the crtl reach that does not.

Why "not found" is misleading, and what it is not

PXXMemZero is declared at compiler/builtin/builtinheap.pas:468 and defined at :4561. The body is unconditional — only its fast paths carry {$ifdef CPUX86_64} — and it sits after the {$ifndef PXX_ESP} block (3671–4356) closes, so it is not guarded out on ESP.

So the symbol exists and the lookup is not reaching it. This is not a missing implementation; it is the builtin heap unit not being pulled into a C compilation on PLATFORM_ESP. The compiler error: spelling is an internal assertion — one of fifteen identical PXXMemZero not found sites across six backends (symtab.inc plus five ir_codegen_*), so the message says which symbol and nothing about which of the six asked.

Do not "fix" this by defining PXXMemZero somewhere. It is defined. The question is unit reachability on this profile, and a second definition would make the symptom go away while leaving every other builtin in the same unit just as unreachable — the next one would surface as a different name and read as an unrelated bug.

Where this came from, and what it bounds

decide-should-a-c-main-exist-on-the-esp-profile-at-all (decided today) established that --emit-obj is how C ships to an ESP32 and that the standalone refusal is correct. That is true for freestanding C and I wrote it without the bound, which frankC caught by noticing its own c_va_arg_every_target.sh --emit-obj row builds only a trivial main and a stdarg program and never reaches printf. The diagnostic in cparser.inc now carries the bound and names this ticket.

Next step for whoever takes it

The cheap first measurement is which of the six backends raises it here — PXXDBG or a temporary distinct string per site — because symtab.inc raising it means the symbol is absent from the table entirely, while an ir_codegen_* site raising it means the table was built and the unit was still not in it. Those are different bugs with the same message.