← board

The measurement

program fo;
begin
  writeln('openro=', PXXSysOpenRO(nil));
  writeln('close=',  PXXSysClose(7));
  writeln('lseek=',  PXXSysLseek(7, 0, 0));
end.
native x86-64 --target=wasm32
PXXSysOpenRO(nil) -14 (EFAULT) 7
PXXSysClose(7) -9 (EBADF) 6
PXXSysLseek(7,0,0) -9 (EBADF) 6

The wasm module reports 123 of 123 bodies lowered — op coverage is complete for this program. Nothing warns.

Why

compiler/builtin/builtinheap.pas:1861, :1877, :1893 are the same shape:

function PXXSysOpenRO(path: Pointer): Int64;
begin
{$ifdef CPUX86_64} Result := __pxxrawsyscall(2, Int64(path), 0, 0); {$endif}
{$ifdef CPU_I386}  ... {$endif}
{$ifdef CPU_ARM32} ... {$endif}
{$ifdef CPUAARCH64} ... {$endif}
end;

Four arms, no CPU_WASM32, no else. On a seventh target the body is empty and Result is never assigned.

The severity is in the VALUE, not the absence. An unassigned result that came back as 0 or a large negative would be caught by the first sanity check. 7 is in the range a real open returns, and it is stable across runs, so it survives every plausible caller guard. PXXSysClose returning 6 reads as success.

Relation to the family

PXXSysWrite in the same file had exactly this hole and was fixed (bug-a-pxxsyswrite-has-no-wasm32-arm, done) by adding a CPU_WASM32 arm over __wasi_fd_write. These three were not swept at the same time. HeapMmap is the third member and is still ungranted.

That makes this the fourth instance of one pattern in one file: a per-target ifdef chain with no terminal else, where a new target gets silence instead of a diagnostic. Worth fixing as a class -- an {$else} Result := PAL_ERR_UNSUPPORTED; {$endif} on every chain in the file would have made all four fail closed on the day wasm32 was registered.

The fix, and what it is NOT

Minimal and correct: return an explicit error on wasm32. That converts fail open to fail closed, which is this project's stated preferred direction -- bug-a-emitzeroframeslot-has-no-wasm32-arm is prio 55 rather than 70 precisely because it fails loud.

It is deliberately NOT "implement these over WASI". A real PXXSysOpenRO needs WASI's preopen resolution, rights computation and errno mapping, which already exist once in lib/rtl/platform/wasi/platform_backend.pas and which compiler.pas cannot reach -- it links no PAL by design. That is a separate design fork, filed as decide-how-the-sys-intrinsics-reach-wasi-when-the-compiler-links-no-pal. Fixing the failure DIRECTION here does not depend on settling it, and should not wait for it.

Gate

Track A's: make compiler/pascal26 plus the repro above on both targets. The wasm32 column must stop reporting plausible success.

Log

Resolved 2026-08-29 — with the sibling, as one defect

Fixed by the chain restructure in [[bug-a-per-cpu-ifdef-chains-in-builtinheap-fail-open]]; see that ticket for the measurements, the three corrections it needed, and what was deliberately left.

The severity argument in this ticket is the one that carried the fix, and it should be quoted forward: the severity is in the VALUE, not the absence. 7 is a plausible file descriptor and it is stable across runs, so if fd < 0 then error passes and the caller reads a file it never opened. A 0 or a large negative would have been caught by the first sanity check. That is why this outranked "a target is missing an arm" as a framing.

PXXSysOpenRO/Close/Lseek now return -1 on any target with no arm — verified on riscv32 with this ticket's own reproducer (-1/-1/-1, native unchanged at -14/-9/-9) and by IR dump (IR count=1, an empty body, becoming Result := -1).

Not a wasm32 fix. wasm32 cannot codegen this unit here at all ("wasm32: code generation not implemented"), so the measurement was taken on hosted riscv32, an armless target that does build. The fix is target-shaped rather than wasm-shaped, so it covers wasm32, riscv32 and xtensa/IDF at once — and the next target too, which was the point.

Log

CORRECTION 2026-08-29 — {$error} exists; see the sibling ticket

This ticket's proposed compile-time refusal was available all along. It is still not the right terminal here, but for a reachability reason rather than an availability one: {$error} fires when builtinheap.pas is COMPILED, and that file is compiled into every program on every target, so it would refuse every wasm32/xtensa build including programs that never open a file. Full correction, and how the wrong answer was produced, in [[bug-a-per-cpu-ifdef-chains-in-builtinheap-fail-open]].