← board

NilPy cannot target xtensa at all — an empty .npy file refuses

The floor case

$ : > empty.npy
$ ./compiler/pascal26 --target=xtensa --platform=esp empty.npy empty.o
pascal26:3380: error: target xtensa: addi immediate displacement 128 is
  outside the encodable range -128..127; the code is too large for this
  branch form

A zero-byte source. There is no user construct to reduce, and no program a user could write that avoids it: the failure is in the NilPy runtime the driver appends, not in anything the .npy contains.

Scope, one row each

input target / flags result
empty .npy --target=xtensa FAIL
empty .npy --target=xtensa --platform=esp FAIL
empty .npy --target=xtensa --esp-profile=bare FAIL
x = 1 (no print) xtensa FAIL
print("hi"), a list, a dict, a class, a def xtensa FAIL, all
the same files riscv32 --platform=esp ok
program p; begin end. --target=xtensa --platform=esp ok
int main(void){return 0;} --target=xtensa --platform=esp ok

So: NilPy-specific, every xtensa spelling, and not a general xtensa breakage — Pascal and C both compile on the identical flags.

Not today's work — and that is ALL that is established

HEAD    3854783c605a / 5f986d67044a : FAIL
PINNED  stable_pinned               : FAIL, same line, same message

The pinned compiler refuses identically, so this is not attributable to the --dce default, 523833fde, or 324d668b8.

This section said "pre-existing, not a regression" when filed, and the second half was an overstatement I corrected the same day. What the pinned control shows is that the failure is older than today. It says nothing about whether xtensa NilPy ever worked, and the two are not the same claim — a pin is days old, not months. Treating "the pin refuses too" as "it was always broken" is the quantifier riding on the verb's credibility again, in a ticket whose author had just written that phrase into a memory file.

See the cross-reference at the end: there is a concrete lead that this may be a two-day regression, which would make it bisectable and a much better ticket than this one.

Why the priority is 70 rather than lower

xtensa is the PRIMARY ESP target — the S-lane rule says so in as many words ("Primary target xtensa; riscv32 works"). So the whole NilPy-on-ESP32-S3 path is closed, while esp32c3 is fine.

That asymmetry is structurally easy to miss: tools/esp_run_bare.sh defaults to --chip esp32c3, which is riscv32, so anyone exercising NilPy on ESP by the usual route measures the working half and sees nothing. This is the "measured on the default target" class — the same reason a width bug hides on x86-64.

What is NOT diagnosed

The diagnostic is honest and names its own cause, which is why this is filed as a bounded backend gap rather than an investigation.

Provenance

Found while checking a remark from frankb-8e that a ticket's subject "cannot be re-measured today" on xtensa. Reproduced independently rather than filed from the report, and the scope turned out wider than the remark: it is not that one measurement is blocked, it is that the frontend does not build for the target at all. Existing mentions of the same message live in done/bug-a-riscv32-dce-keeps-135-more-bodies-than-xtensa-on-one-program and in working/feature-a-unreferenced-class-rtti-keeps-every-method-alive, neither of which is a ticket for it.

Cross-reference, and it runs both ways

working/feature-a-unreferenced-class-rtti-keeps-every-method-alive (frankb-8e) tells its reader to re-measure its headline number before quoting it — and if that number is a NilPy-on-xtensa measurement, it currently asks for something that cannot be done at all. So whatever closes this ticket also unblocks that one, and until then that instruction is unsatisfiable rather than merely expensive.

Worth stating the direction plainly, because it is the useful half: the RTTI ticket is evidence about THIS ticket, not the other way round. If its number was taken on xtensa at 857dcdaac, then xtensa NilPy built two days ago and this is a two-day regression with a 1233-commit range to bisect. That is a different and much better ticket than the one filed here. One person building 857dcdaac settles it.

What would retire this section: either a confirmation that the RTTI number was taken on riscv32 (in which case this is a standing gap and the lead is dead), or a build of 857dcdaac that compiles an empty .npy for xtensa (in which case reopen this at a higher priority as a regression).

REJECTED BY ITS OWN AUTHOR, 2026-09-22 — the premise is false

empty .npy  --target=xtensa --platform=esp                     FAIL (addi displacement)
empty .npy  + --xtensa-abi=windowed --xtensa-long-calls        FAIL (in the WRITER, not codegen)
empty .npy  + those two + --emit-obj                           BUILDS, 975,588 B
print(1)    + those two + --emit-obj                           BUILDS, 975,740 B

Measured at 6fb91c73e88e; byte-identical to frankb-8e's independent run, which is why this is stated flatly. The middle row matters: windowed + long-calls gets past the codegen limit, and what then fails is the IDF profile being asked for a complete executable when its documented output is an OBJECT for the IDF link. --emit-obj is the right form and it builds.

Everything above this section is retained and is wrong. It is left in place rather than deleted because the measurements in it are real — they are just measurements of one ABI, presented as measurements of a target.

The axis error, which is the only thing worth keeping

Three "xtensa spellings" were tested and all three share the default Call0 ABI. The platform axis was varied; the ABI axis — the one that decides the outcome — was held fixed and never listed. A table with three rows that all fix the deciding axis is a table with one row.

The floor case is what produced the confidence, and it was answering a different question. An empty .npy proves the failure is not in the user's program. It says nothing about whether the cause is the TARGET or the FLAGS. Excluding one axis rigorously got read as locating the cause on another, and because the exclusion was genuinely rigorous, the location inherited its credibility. That is the hedge-the-premise failure with premise and inference on different axes.

Worse, the author had been pleased with the floor case — it was the one quantifier checked carefully all day, after three earlier failures of exactly that kind. Checking the quantifier on the axis you thought of does not protect the axis you did not.

The diagnostic named the right axis the whole time. "The code is too large for this branch form" is a statement about an ENCODING CHOICE. This ticket quoted that sentence twice while concluding the target was broken. A message naming a form, mode, encoding or model is pointing at a flag, not at a capability.

Before calling a target or subsystem broken

List the axes varied and the axes held fixed, and name the ones you have no reason to trust. For a compiler invocation that is at minimum: target, platform/profile, ABI, output form (--emit-obj vs executable), and optimisation level.

What survives

Nothing about a compiler defect. One coverage gap, split out at its real size: chore-t-the-bare-esp-runner-exercises-riscv32-only-so-xtensa-nilpy-has-no-routine-coverage.