← board

Every ESP test guards its IDF arm on a symbol the compiler never defines

Measured 2026-09-24 at 0adefbd789, compiler af40370a8a91d298.

The probe, and it is a compile-time probe for a reason

A deliberate syntax error inside the guarded arm, so the answer is a compiler diagnostic and not an inference:

{$ifdef PXX_ESP}
  this_symbol_is_defined_deliberate_error;
{$endif}
invocation PXX_ESP PXX_ESP_BARE PXX_ESP_IDF
--target=esp32c3 --esp-profile=bare not defined DEFINED not defined
--target=esp32c3 --emit-obj not defined not defined not defined
--target=esp32c3 --emit-obj --platform=esp not defined not defined DEFINED

PXX_ESP is never defined, on any profile. The middle row is the second finding: the Mode 2 invocation in docs/targets/esp32.md defines neither symbol.

Where PXX_ESP does exist

compiler/builtin/builtinheap.pas:18:

{$ifdef PXX_ESP_BARE}{$define PXX_ESP}{$endif}

A unit-local define, and its header is explicit that within that unit the name means bare ("Only a BARE boot (PXX_ESP_BARE) is ESP for this") — it was added to FIX an earlier version that defined it unconditionally on xtensa. So the name is correct where it lives and means the opposite of what the tests want: the tests reach for it in the {$else} of PXX_ESP_BARE, i.e. to mean IDF.

Two compiler comments (pasparser_prog.inc:61, :1627) and one in dce.inc refer to {$ifndef PXX_ESP} bodies without saying the symbol is unit-local, which is how the name reads as ambient.

The shape in the tests

{$ifdef PXX_ESP_BARE}
  ... UART MMIO ...          { live on bare }
{$else}
{$ifdef PXX_ESP}
  ... esp_rom_printf ...     { DEAD -- never compiled, on any profile }
{$else}
  ... Write(Chr(code)) ...   { what an IDF build actually gets }
{$endif}
{$endif}

The bare arm is live and correct, which is why the bare rows in test-esp-bare genuinely test the ESP ISAs. The IDF arm has never been compiled.

An instrument that answered this BACKWARDS

Worth recording, because it is the house failure mode and it nearly landed: strings -a <object> | grep esp_rom_printf answered YES for the unmodified file and NO after renaming the guard to PXX_ESP_IDF — the exact opposite of the truth, which would have been written up as "the arm is live and the rename breaks it". strings answers about byte sequences anywhere in a file, which is a different question from "was this arm compiled". It was caught only because the compile-time probe contradicted it sixty seconds later. Ask the compiler which arm it took; do not ask the output what words it contains.

Why this is not the one-line rename it looks like

Controls for whoever takes it

  1. A row that proves an arm COMPILED, not that a test passed. Every affected test is green today; greenness is what hid this. Assert the symbol reference (esp_rom_printf in the object's symbol table) or assert a deliberate error inside the arm, as the probe above does.
  2. The bare arm must not move. PXX_ESP_BARE is correct and live; a whole-file rename that touches it would break the rows that work.
  3. builtinheap must keep meaning bare. Its unconditional-define bug is already in its header as a fixed defect; re-introducing it is the regression this ticket could most easily cause.

Umbrella

[[meta-a-pxx-produces-linkable-code]]

2026-09-24 (frank, frankb-8e) — REJECTED, and the measurements that reject it

franks-5b re-ran my own probe on the real files and got the opposite answer within minutes of the filing. I reproduced it independently before accepting it.

The probe, aimed at the RIGHT population this time

A deliberate error injected into the real file's first in-body arm (test/test_esp_dynarray.pas), and into test/test_esp_bare_float.pas's esp_rom_printf arm:

invocation in-body arm esp_rom_printf arm
--target=xtensa --esp-profile=bare TAKEN not taken (MMIO arm wins, correctly)
--target=riscv32 --esp-profile=bare TAKEN —
--target=esp32c3 --esp-profile=bare TAKEN not taken
--target=esp32c3 --emit-obj — TAKEN
--target=esp32c3 --emit-obj --platform=esp — TAKEN
x86-64 not taken not taken

Every arm resolves correctly on every profile. All 36 files carry {$ifdef CPU_XTENSA}{$define PXX_ESP}{$endif} and the CPU_RISCV32 twin at the top — 36 of 36, checked — and both CPU symbols are defined per target, verified with the same probe.

The error, which is this file's own population rule

My probe was a synthetic five-line program. It did not contain the self-define lines that every real file carries, so it was correct about the compiler's built-in defines and structurally silent about the question I was asking. A population that cannot contain the subject returns a clean, confident, wrong answer — and I had spent the evening writing that sentence about other people's measurements.

It is also the second time in one night I made the same shape of error: the stale binary was the right instrument aimed at the wrong subject too. 5b's phrasing is the one to keep — "the right instrument, the wrong subject" — and the thing that caught both was somebody re-measuring rather than re-reading.

The instrument lesson is withdrawn IN THE OPPOSITE DIRECTION

I told two peers that strings -a <object> | grep esp_rom_printf had inverted the answer, and offered "ask the compiler which arm it took; do not ask the output what words it contains" as the generalisable form.

strings was right both times. esp_rom_printf genuinely is in the --emit-obj object, because that arm is genuinely taken. And it genuinely disappeared when I renamed the guard to PXX_ESP_IDF, because PXX_ESP_IDF requires --platform=esp and that invocation lacks it — so the rename really did break it. The artefact grep was the reliable instrument and my synthetic compile-time probe was the unreliable one, which is the exact reverse of what I reported.

What survives is narrower and worth less than what I claimed: a deliberate error inside a guarded arm is a good way to ask which arm compiled, provided the file you run it on is the file in question. It is not a reason to distrust a grep of the artefact. Both peers have been told; 5b was about to bank the wrong version in the playbook.

Census correction, since three counts now exist for one symbol

count how what it actually measured
130 occurrences mine, grep 'PXX_ESP}' no -w; missed occurrences not followed by }
44 files / 161 5b's first pass substring-matched PXX_ESP_BARE, a different symbol
36 files / 144 grep -rnw ... . minus builtinheap WITHDRAWN — population . not test/, so it included compiler/builtin/builtin.pas AND its pin snapshot (the same file twice), minus one real hit the -v builtinheap filter ate from a comment
36 files / 131 grep -row PXX_ESP test/ the figure, franks-5b's, reproduced at 37655f4bb5

Two grep hazards, one symbol, two seats, inside an hour. Use -w and say that you did.

2026-09-24 — the census correction was itself wrong, and the mechanism is worth more than the number

franks-5b could not reproduce my 144 and said so with their population stated, which is what made it falsifiable in one command. They are right: it is 131.

grep -row PXX_ESP test/ | wc -l        ->  131   (36 files, 131 lines)

My grep -rnw 'PXX_ESP' --include=*.pas . | grep -v builtinheap | wc -l gave 144, and it decomposes exactly:

term count why it was in there
test/ — the actual subject 131 correct
compiler/builtin/builtin.pas +7 population was ., not test/
stable_linux_amd64/default/builtin/builtin.pas +7 the pin snapshot of the line above — the same file counted twice
the -v builtinheap filter −1 ate test_esp_bare_variant.pas:6, a comment mentioning builtinheap.pas
144

The two general points, which are why this is recorded at all

A repo-wide census of a compiler-source symbol DOUBLE-COUNTS, by construction. stable_linux_amd64/ is a snapshot of compiler/builtin/, so any grep -r ... . over this tree sees builtin.pas twice — and the two copies agree until a pin is cut, which is exactly when a re-run silently changes. Nothing about the command looks wrong, and the duplicate is a correct file in a correct location.

A grep -v <name> filter matches the LINE, not the PATH. I meant "exclude builtinheap.pas" and wrote something that also excludes any line whose prose mentions it — in a tree where tickets and test headers cite filenames constantly. The filter removed a real hit from the population it was supposed to be measuring, and removed it silently, in the direction that makes the number smaller.

And the lesson I attached was the wrong one

I wrote "use -w and say so". That was the fix for 5b's error — a substring match on PXX_ESP_BARE. It was not the fix for mine: my errors were the population and the filter, and -w was already present in my command. Sitting directly above a number, the wrong lesson makes a reader adopt that number without re-running it, which is 5b's point and the reason they flagged it.

The durable form is the one already in this ticket twice over, now for the third time in the same investigation: state the population beside the number. -w is a detail; the set is the claim.