← board

platform_backend's rv32 block says qemu tolerates plain lseek; strace says no

lib/rtl/platform/posix/platform_backend.pas, the CPU_RISCV32 constant block:

"lseek is llseek(62) with a split 64-bit offset — PalSeek below only passes small offsets, and qemu-user tolerates the plain form for them"

Measured under qemu-riscv32 -strace on 2026-08-30, calling the 3-arg form:

openat(AT_FDCWD,"test/hello.pas",O_RDONLY) = 3
llseek(3,0,2,NULL,UNKNOWN)                 = -1 errno=22 (Invalid argument)
read(3,0x2b2ad050,-22)                     = -1 errno=14 (Bad address)

It is not tolerated. _llseek is (fd, off_hi, off_lo, loff_t *result, whence) — the 3-arg form leaves the result pointer NULL, the kernel returns EINVAL, and the -1 flows onward as a size.

The comment is stale, not wrong-headed — and the code below it already knows

PalBackendSeek in the same file has carried the correct split for some time:

{$ifdef CPU_RISCV32}
  { rv32 syscall 62 is _llseek(fd, off_hi, off_lo, loff_t *result, whence), NOT
    plain lseek — the 3-arg form left the result pointer NULL and the kernel
    faulted (EFAULT). }
  res := 0;
  r := __pxxrawsyscall(SYS_lseek, handle, (offset shr 32) and $FFFFFFFF,
                       offset and $FFFFFFFF, Int64(@res), whence);

So the implementation was fixed and the constant block's comment was not. Two statements in one file that contradict each other, and the wrong one is the one a reader meets first — it sits beside the SYS_lseek = 62 definition, which is exactly where someone goes to write a new caller.

Why it is filed rather than shrugged at

It cost a debugging cycle today. PXXSysLseek in compiler/builtin/builtinheap.pas was given a riscv32 arm using the plain 3-arg form on the strength of this comment, and produced a LoadFile that returned an empty string with no error anywhere — the "plausible wrong value far from the cause" shape. The strace above is what settled it. The arm now mirrors PalBackendSeek.

A comment that asserts a runtime behaviour is a claim, and this one is testable in one command. devdocs/dev/debugging-playbook.md's rule applies to prose too: the comment was reasoning, the strace was measurement.

Fix

Correct the sentence in the CPU_RISCV32 block to say 62 is _llseek and that callers must pass the split offset and a result pointer, pointing at PalBackendSeek as the reference. Grep for any other 3-arg SYS_lseek caller on rv32 while there — PalSeek's own path is the one the comment was excusing, and whether IT is correct today was not checked as part of this finding.

Trivial change; filed because the file is Track B's and the finding came out of a Track A/S grant.

Resolved 2026-08-30 (frankB)

Comment corrected in the CPU_RISCV32 block of lib/rtl/platform/posix/platform_backend.pas. It now states that 62 is _llseek(fd, off_hi, off_lo, loff_t *result, whence) and that rv32 has no plain lseek at all, says what a 3-arg call actually does (NULL result pointer → EINVAL → -1 flowing onward as a size), points at both correct implementations — PalBackendSeek below it and PXXSysLseek in compiler/builtin/builtinheap.pas — and carries the qemu-strace that settles it, dated. The old sentence asserted a runtime behaviour with nothing that re-checked it; the replacement carries its own evidence, which is the only form that does not rot silently.

The grep the ticket asked for: clean

Every SYS_lseek call site in the repo, both of them:

lib/rtl/platform/posix/platform_backend.pas:417   split 5-arg form, rv32 arm
lib/rtl/platform/posix/platform_backend.pas:421   plain 3-arg form, {$else} arm

plus PXXSysLseek in compiler/builtin/builtinheap.pas, which already carries the identical rv32 split (it was fixed by the Track A/S session that filed this). No third caller exists, and no 3-arg rv32 path survives anywhere.

PalSeek's own path — the question this ticket left open — is CORRECT

The filing noted that PalSeek was what the stale comment was excusing and that nobody had checked whether it is right today. It is, and this is measured rather than reasoned. PalSeekPalBackendSeek, which takes the rv32 split arm. Cross-built test/lib_platform.pas for riscv32 and ran it under qemu:

llseek(3,0,0,0x2b2aabb8,SEEK_CUR) = 0
llseek(3,0,0,0x2b2aabd8,SEEK_END) = 0
llseek(3,0,0,0x2b2aabd8,SEEK_SET) = 0

Five arguments, a real result pointer rather than NULL, all three returning 0 — and the program's own output agrees end to end (tell=2, file=io:2:2, exit 0). Compare the failing form in the filing: llseek(3,0,2,NULL,UNKNOWN) = -1 errno=22.

Verification

No compiler was rebuilt; everything was built with $(PXX_STABLE).

One line this creates for Track A

PXXSysLseek's comment in compiler/builtin/builtinheap.pas ends with "NOTE the sibling comment in that same file's rv32 block still says the plain form is tolerated by qemu-user". That clause is now false — it was true when written this morning and this ticket is what falsified it. compiler/builtin/** is Track A's ground so it is not touched here; filed as [[chore-a-trim-the-stale-cross-reference-in-pxxsyslseek-s-rv32-comment]]. It is a three-line deletion, and it is filed rather than shrugged at for precisely the reason this ticket exists.

Log