The futex helpers are trapped behind __pxxclone
- Type: bug (structural) — Track B (
lib/rtl) - Status: backlog
- Opened: 2026-08-05
- Found by: implementing
TCriticalSectionfor real ([[bug-b-criticalsection-was-a-no-op-stub]]) and, before that,lib/rtl/palatomic.pas.
The shape
PalFutexWait, PalFutexWaitTimeout and PalFutexWake are three-line
__pxxrawsyscall(SYS_futex, ...) wrappers. They live in lib/rtl/palthread.pas
— which also contains __pxxclone. Reaching that unit at all fails the compile:
error: __pxxclone (thread creation) requires --threadsafe or {$threadsafe on}:
the default heap/ARC/console-I/O runtime is not thread-safe
That gate is right for thread creation. It is wrong for waiting on a word, which needs no thread-safe heap and no threads at all to compile.
What it has already cost
syncobjs.TCriticalSectionis a spinlock, not a blocking mutex. It cannot usepalsync's futexTMutexbecausepalsync uses palthread, anduses syncobjsmust not start requiring--threadsafe— Synapse'sssfpc.incis exactly the caller that would break, and it does no threading. So a contended waiter burns CPU instead of sleeping.palatomichad to be its own unit rather than living inpalsyncwith the other users of the same intrinsics, for the identical reason: anInterLockedIncrementon a refcount must not drag in thread creation.
The fix
Move the three futex wrappers (plus the SYS_futex/FUTEX_WAIT/FUTEX_WAKE
constants and whatever per-arch {$ifdef} block they need) into a new
dependency-free lib/rtl/palfutex.pas, the way palatomic is dependency-free.
Then:
palthreadusespalfutex(its own callers keep working, but note Pascalusesis not transitive — every caller ofPalFutexWaitneeds its ownuses palfutex);palsyncusespalfutexinstead ofpalthread— check whether it needs anything else from there first;syncobjscan then usepalsyncandTCriticalSectionbecomes a real blocking mutex, a body-only change (FLockis already the futex-word shape).
Current PalFutex* callers: palthread, palthreadobj, palpthread,
palsync, palparallel.
Gate
Track B: tools/gate.sh lib. Also rebuild the threading demos and re-run
tools/fpc_diff_probe.sh's thread-* cases — thread-critical-section is the
one that proves the lock still excludes.