← board

rv32 has no timerfd_settime, and the skip was hiding a hang

Measured (this box, qemu-riscv32)

Direct probe — the same fd, two numbers:

timerfd_create(85)     = 3        { takes no timespec, works }
timerfd_settime(86)    = -38      { ENOSYS: rv32 never got the time32 calls }
timerfd_settime64(411) = 0

test/test_timer.pas, built by compiler/pascal26:

target before after
x86-64 / i386 / aarch64 / arm32 woke 50 / woke 100 / woke 150 / done unchanged
riscv32 rc=124, NO OUTPUT — hangs forever woke 50 / woke 100 / woke 150 / done

Why it hangs rather than fails

ArmOneShotTimer assigns the syscall result to rc and never reads it. So the ENOSYS is discarded, the function returns a perfectly valid timerfd, and CoSleep parks on WaitReadable(tfd) for a timer that was never armed. Nothing in the path can observe the failure. WaitIOTimeout has the same shape, so a timeout on rv32 never fires either.

Two defects, not one — the struct as well as the number

timerfd_settime64 wants a 64-bit itimerspec, so it_value starts at +16, not +8. The existing code selects that layout on {$ifdef CPU64}, and rv32 is a 32-bit machine with a 64-bit timespec — the one combination CPU64 cannot express. The patch adds SCHED_TIME64 (riscv32 alone), mirroring PAL_TIME64 in platform_backend.pas, and writes the fields through an explicit ^Int64 rather than relying on spec having been cleared.

THE SKIP IS THE REAL FINDING

Makefile carried five riscv32 skips, all reading backend feature gap (see bug-test-riscv32-thin-coverage notes). Measured, they are three different things:

row truth today before this fix
test_timer was a live HANG hung — a red the skip hid
test_reactor passes already passed — pure lost coverage
test_asyncecho passes already passed — pure lost coverage
test_extern_c genuinely refused real skip, keep
test_extern_c_float genuinely refused real skip, keep

The reason was TRUE for all five when written and stopped being true for three of them at different moments — and because a skip is silent, nothing said so. The two extern_c rows refuse loudly with a diagnostic (external (dynamic) symbols are not supported on this target), which is what a correct skip looks like: it names a reason you can re-test cheaply.

frankZ's rule, arrived at independently the same evening: a false skip is worse than a false red. A red is loud. This one cost a hang on a target the release advertises, plus two rows of coverage nobody knew were missing.

Land

devdocs/progress/patches/rv32-timerfd-time64-and-three-unskips.patchlib/rtl/scheduler.pas + the three Makefile rows, with the un-skip comment explaining which reasons were stale and which are real. All three rows verified through tools/expect_same.sh exactly as the Makefile invokes them, with a negative control (a wrong expectation gives MISMATCH, rc=1).

Held ONLY for the bounded landing quiet period. This is a red fix, not tidiness.