← board

LoadFile's runtime syscall wrappers have no riscv32 or xtensa arm

PXXStrLoadFile in compiler/builtin/builtinheap.pas calls four per-target syscall wrappers. Three of them cover four architectures and not the two 32-bit generic ones:

wrapper has arms for missing
PXXSysOpenRO x86-64, i386, arm32, aarch64 riscv32, xtensa
PXXSysLseek x86-64, i386, arm32, aarch64 riscv32, xtensa
PXXSysClose x86-64, i386, arm32, aarch64 riscv32, xtensa
PXXSysRead x86-64, i386, arm32, aarch64, riscv32, xtensa

PXXSysRead already having both arms is the tell that this is drift, not a decision: one of the four was extended and its three siblings were not.

The {$else} returns -1, and its comment says exactly why that is right: "Returning the POSIX failure value is the whole point" — an armless target that left Result unassigned made if fd < 0 then Exit read the return slot's leftover bytes. So the failure here is honest, but it is silent: PXXStrLoadFile returns nil and LoadFile publishes an EMPTY string.

Why this is filed and not fixed, and why the codegen was HELD

Found while adding the SysOpen family and LoadFile to ir_codegen_xtensa.inc and ir_codegen_riscv32.inc (a bounded grant covering those two files). The SysOpen family landed and is green on both backends against the x86-64 oracle.

The LoadFile codegen arm was written, measured, and deliberately not landed. It is correct — the refcount dance is all a backend owns there, the file I/O is entirely in the helper — but with the wrappers missing it turns

error: this builtin has no arm in the xtensa backend (builtin -100)

into a program that compiles and prints nothing. The compile error is the safety property here, and trading a diagnostic for a silent empty string is a regression even though it closes a feature gap. The arm is banked at scratchpad/rw/loadfile-xtensa.arm and should land in the SAME change as the wrappers, never before them.

The numbers, already measured

Both tables were probed for the SysOpen family that landed alongside this, so whoever picks this up does not need to re-derive them:

riscv32 (asm-generic) xtensa (its own table)
openat 56 288
read 63 12
write 64 13
close 57 9
lseek 62 — but see below 15
fchmod 52 52

riscv32 has NO plain open, so PXXSysOpenRO must use openat(AT_FDCWD = -100, path, 0, 0) there; xtensa still carries a legacy open (8) but should use openat too, so the two read the same. lseek on rv32 is the _llseek split-offset question the existing riscv32 block in lib/rtl/platform/posix/platform_backend.pas already documents — for source loads the plain form is what qemu-user tolerates, which is the same call i386/arm32 already make.

Gate

make compiler/pascal26 to fixedpoint, then test_cross_loadfile against the x86-64 oracle on riscv32 and on xtensa in BOTH ABIs, plus the cross differential for regressions. Un-SKIP the test_cross_loadfile rows in test-riscv32 and add the xtensa one — the riscv32 SKIP comment in the Makefile points here by slug.

Same family as bug-a-xtensa-cannot-read-a-managed-string-out-of-a-record-field- or-array-element: a rule most targets carry and the two without a working oracle were skipped for.

RESOLVED

Landed with the codegen arms in one change, as the ticket required. All three wrappers gained riscv32 and xtensa arms; test_cross_loadfile goes CFAIL -> MATCH on riscv32 and on xtensa in BOTH ABIs, and is wired into test-riscv32 (its SKIP removed) and test-xtensa.

The measured numbers in the table above were right except for rv32 lseek, and that one mattered. The ticket said 62 "is the _llseek split-offset question the existing riscv32 block already documents -- for source loads the plain form is what qemu-user tolerates". That was copied from a comment in platform_backend.pas and it is false:

llseek(3,0,2,NULL,UNKNOWN) = -1 errno=22 (Invalid argument)
read(3,0x2b2ad050,-22)     = -1 errno=14 (Bad address)

rv32 has no plain lseek at all; 62 is _llseek(fd, hi, lo, loff_t *result, whence) and the 3-arg form leaves the result pointer NULL. The size comes back -1 and LoadFile publishes an empty string — the same silent-wrong-value shape this ticket exists to prevent, reached through the ticket's own guidance. Fixed by mirroring PalBackendSeek, which had the correct split in that same file the whole time. The stale comment is filed separately as [[bug-b-platform-backend-rv32-comment-claims-plain-lseek-is-tolerated]].

Worth keeping: a syscall number is two facts, the number and the SIGNATURE, and this ticket carried a careful table of the first while getting the second from prose. PXXSysRead's existing arms are what made the numbers look sufficient.

Measured, set difference both directions with the totals cross-checked against the row sets: xtensa call0 102 -> 103, windowed 52 -> 53, riscv32 110 -> 111, zero matches lost on any target. x86-64 emitted output verified byte-identical across 11 programs, because builtinheap.pas is compiled into every emitted program and the self-host fixedpoint cannot see that.

Log