← board

A pxx routine called from C reads 64-bit parameters at the internal positions

The asymmetry

That bug fixed the caller side: pxx now marshals a 64-bit argument to an external C function the way the target's C ABI wants it — consecutive registers on riscv32, an even-aligned pair on xtensa (measured from each target's gcc).

The callee side did not change. parser.inc's xtensa/riscv32 parameter spill assigns word indices with no padding, because that is the internal convention and both halves of an internal call agree on it. So a routine that C calls with a 64-bit argument reads:

C caller puts it at pxx callee reads it at
riscv32 consecutive words consecutive words — agree
xtensa next EVEN word next word — disagree when the index is odd

Only xtensa, and only when a 64-bit parameter follows an odd number of preceding words.

Why it is not urgent

Nothing crosses that boundary today. app_main takes no arguments; the esp_timer callback signature is void (*)(void*); the PAL's exported entry points are called from Pascal. It becomes real the moment a pxx routine is registered as an SDK callback with a 64-bit parameter — a gptimer alarm callback carrying a uint64_t count, say.

The fix, and why it should wait for a decision

Guessing from ProcExternal is not available here: the callee is a pxx routine, so nothing marks it as "C calls this". The honest signal is an explicit cdecl; (or export;) marker on the declaration, which would then drive:

That is a small language-surface decision rather than a code change, so it wants a [[decide-cdecl-marker-drives-abi]] answer first if it is not obvious.

Acceptance

FIXED (2026-08-03) — no marker; xtensa just uses the xtensa C ABI

The decision, from the user

The ticket wanted [[decide-cdecl-marker-drives-abi]] answered first. It is answered, and it removes the marker from the design rather than adding it:

"we just treat any calling definition as pure decoration. so on windows — stdcall, on linux cdecl as calling convention, no questions asked, invalid decorators just ignored. so, any calling convention is host specific by definition."

So cdecl/stdcall on a pxx routine are DECORATION, and a target has exactly one convention: its own. The fix follows directly — apply the xtensa C ABI's even-word rule unconditionally, on both sides, instead of keeping a packed internal layout and a C layout that can disagree. No flag, no per-routine divergence, and the asymmetry cannot come back.

The marker-driven alternative was measured and is worse than it looks: setting ProcCdecl on a bodied Pascal routine is already enough to make x86-64 miscompile, because that flag switches the CALLER to SysV while the callee prologue has no C-ABI spill. A 9-parameter cdecl routine printed 1398329043 0.00 140721706815082 SHELL=/bin/bash … — it was reading the process environment. Honouring the keyword for real is a multi-backend feature, not this ticket. (cdecl on a routine with a body stays inert, as it is today.)

Measured — the gcc oracle

xtensa-esp32s3-elf-gcc, both ABIs, on f(int a, long long b):

a b
gcc call0 a2 a4:a5 (a3 skipped)
gcc windowed a10 a12:a13 (a11 skipped)
pxx callee, before a2 a3:a4 — one register early
pxx callee, after a2 a4:a5 — agrees

Verified by RUNNING it, not just disassembling

test/test_esp_bare_arg64.pas puts a 64-bit argument at word indices 1, 3 and 5 (index 5 straddles a7 into the caller's stack area) plus an even control case, and runs on real xtensa and riscv32 under qemu via the existing bare-boot harness: UART output byte-identical to the x86-64 oracle on both chips. The whole test-esp-bare suite stays green, which is what proves the internal calls moved in lockstep (both sides changed together — they must always be changed together). Wired into test-esp-bare.

That closes acceptance line 2 (internal calls unaffected) and gets line 1 as far as it can go without hardware: the callee now reads exactly the registers gcc writes, verified against the IDF's own compiler.

Not covered, deliberately

An actual C→pxx call still cannot be written: pxx exports only app_main from an object (every other routine is LOCAL), so an SDK callback with a 64-bit parameter has no way to name a pxx routine yet. That export mechanism is a separate gap — worth its own ticket if the callback story is picked up. What this ticket was about, the register disagreement, is gone.

Log