← board

Integer div / mod by zero aborts with SIGFPE (uncatchable)

Symptom

Integer division or modulo by a zero divisor terminates the process with a hardware SIGFPE ("Floating point exception (core dumped)", exit 136). A surrounding try ... except does not catch it:

var i, z: integer;
begin
  z := 0; i := 7;
  try i := i div z;            { SIGFPE — process aborts here }
  except writeln('caught div'); end;
  writeln('after div');        { never reached }
end.

i mod z behaves identically. Both abort before any handler runs.

Expected

FPC raises a catchable EDivByZero (integer div) / EZeroDivide, so robust code can try ... except around user-supplied divisors. At minimum the abort should be a clean runtime error, not a raw core-dumping SIGFPE.

Likely cause

The backend emits a bare idiv with no zero-divisor guard, and no SIGFPE handler is installed to convert the trap into a language-level exception. Either (a) emit a pre-divide zero check that raises EDivByZero, or (b) install a SIGFPE handler in the runtime that raises into the active exception frame. Depends on the exception machinery; note also [[bug-except-base-handler-misses-derived]] (a base on E: Exception handler must be able to catch the raised EDivByZero).

Track B impact

Libraries/demos must hand-guard every divisor (the calc demo already checks for a zero denominator before dividing rather than catching). Acceptable workaround, but it means no idiomatic try/except around arithmetic.

Acceptance

Scoping note (2026-07-01, investigated but not attempted)

Looked at what "emit a pre-divide zero check that raises EDivByZero" would actually take before writing any code, since the surface area turned out bigger than it looks:

Scoping note 2 (2026-07-01, a smaller fallback considered, also not attempted)

Reconsidered the ticket's own "at minimum" fallback goal — a clean abort (print a message, exit(1)) instead of a raw core-dumping SIGFPE, WITHOUT full catchable-exception integration. This sidesteps the architecture question above entirely (no Exception/uses sysutils dependency needed — just a portable PXXDivZeroAbort-style runtime helper, the same shape as existing helpers like PXXDynSetLen), so it looked promising at first.

Sized it before writing code: div/mod codegen isn't one call site per target, it's roughly 10+ across all 6 backends —

That's core arithmetic — the single most heavily-exercised operation in any program — on every target, each needing a zero-check correctly inserted into already-delicate assembly-emission code (getting a register/flag subtly wrong here wouldn't just miss the ticket's goal, it could silently corrupt ordinary division results). Wide blast radius for a solo overnight pass with no one to review; parking this narrower framing for whoever picks it up next rather than rushing ~10 site edits across 6 backends at this hour. The good news: this fallback version genuinely doesn't need the architecture decision above, so it's a smaller, well-defined, mechanical (if wide) task once someone has a clear multi-hour block for it.

Scoping note 3 (2026-07-02, re-examined the x86-64-only slice specifically)

Reconsidered once more with fresh eyes, this time actually counting the real idiv call sites in ir_codegen.inc (x86-64) rather than estimating: there are only 3 total (grep -c idiv), and 2 of those are inside a rare Variant-arithmetic helper (dynamic-typed div/mod on a Variant value, not the common path). The ordinary integer div/mod codegen most programs actually hit is really one code site (ir_codegen.inc ~2598-2612, the tkDiv/tkMod cases of the main binop dispatch). So "x86-64 only" is much narrower than the "~10+ sites across 6 backends" framing above suggested when scoped to just this one target — worth recording precisely rather than leaving the wide estimate as the only data point.

That narrower count does NOT change the parking decision, though, for two reasons independent of line count:

  1. This would still be the first codegen-inserted runtime safety check of any kind in the whole compiler (confirmed again this pass: grepped for any existing array-bounds-check / nil-deref-check / similar precedent anywhere in ir_codegen.inc/ir.inc — genuinely none exist; every runtime-detected failure today is a raw hardware trap). Even the "no message, just exit(1)" minimal version is a new architectural category, not a bug-fix-shaped change — the kind of precedent worth the user weighing in on (does this open the door to bounds-checking, etc., or should it stay a one-off?), not something to decide by fiat overnight.
  2. A single-target fix creates a real inconsistency: x86-64 would get a clean abort while i386/arm32/aarch64/riscv32/xtensa still raw-SIGFPE-crash on the exact same source program — an asymmetry across targets that itself deserves a decision (ship it now for the primary target and file the rest as follow-ups? or hold for parity?) rather than silently introducing target-dependent behavior for the same Pascal semantics.

Still parked; recording the narrower, precisely-counted x86-64 scope so a future pass (or the user) can weigh the real tradeoff (small code footprint vs. real precedent-setting/consistency questions) instead of over- or under-estimating the effort.

Resolution — 2026-07-02, Track A (x86-64 slice landed after user discussion)

Discussed live with the user (the architecture questions the two parking notes raised are now answered):

Gate: test/test_div_zero_re200.pas (div + mod paths, exit-code 200 + message oracle, non-zero divisors sanity) wired into make test; full suite green; self-host converged (3-gen, codegen change) byte-identical.