← board

FPC-compatible Get/SetExceptionMask in lib/rtl/math.pas

What to write

type
  TFPUException = (exInvalidOp, exDenormalized, exZeroDivide,
                   exOverflow, exUnderflow, exPrecision);
  TFPUExceptionMask = set of TFPUException;

function GetExceptionMask: TFPUExceptionMask;
function SetExceptionMask(const m: TFPUExceptionMask): TFPUExceptionMask;

__pxxGetFPUMask / __pxxSetFPUMask(m) already trade in a 6-bit integer in exactly this enum's order, with 1 = masked — the compiler chose that encoding so this wrapper is a set<->bitmask conversion and nothing else. SetExceptionMask returns the previous mask (FPC returns the new one; check FPC's actual signature before matching it — its Math unit returns the mask that ended up in effect).

FPC's enum has the same six members in the same order, which is not a coincidence: it is x87/MXCSR's mask-bit order.

Constraints

Also worth knowing

A handler for the resulting SIGFPE must not re-mask and return — sigreturn restores the FP state from the ucontext and the instruction re-traps forever. Recover through __pxxSigPCPtr or halt. The parent ticket records the measured si_code per cause (FLTDIV 3 / FLTOVF 4 / FLTUND 5 / FLTRES 6 / FLTINV 7).

Gate

make lib-test green + a demo/test that round-trips a mask and shows the default is untouched.

Resolution (2026-08-15)

Landed in lib/rtl/math.pas behind {$ifdef CPUX86_64} as the ticket specified, with test/fpu_exception_mask_x64.pas (15 rows) wired into lib-test.

The blocker was already clear. The pinned stable carries __pxxGetFPUMask/__pxxSetFPUMask — checked before writing a line (__pxxGetFPUMask answers 63, i.e. all six masked, which is also the quiet-IEEE default this ticket must not change).

Two things the ticket predicted wrongly, both measured

The non-x86-64 arm refuses by being ABSENT

No stub. The intrinsics are a compile-time Error on every other target, so a non-x86-64 build fails on the call site — the same refusal the compiler makes, and the same shape lib/rtl/coroutine.pas already uses. A stub answering "all masked" would be a lie the caller cannot detect, which is exactly what the ticket said to avoid.

The test is named outside the lib-test glob, deliberately

tools/lib_cross_sweep.sh builds every lib test source for i386, arm32, aarch64 and riscv32. This API cannot build on any of them by design, so a lib_-named test would read as four sweep failures instead of as a refusal. Hence test/fpu_exception_mask_x64.pas, wired into lib-test directly.

Not covered here: that clearing a bit actually traps. It does, but a test whose success condition is a fatal signal belongs in the compiler suite, where test/test_float_exception_mask.pas already carries it.

The default is untouched — default-all-masked pins that, and FPC's differing default (invalid/zero-divide/overflow unmasked there) is left alone on purpose.

Log