DECIDE: unify integer div/mod-by-zero behavior across targets
- Type: decision (low priority) — Track A
- Status: done the signal/exception stack ([[feature-signal-handlers]]) matures.
- Opened: 2026-07-02, capturing the user design discussion so the eventual decision starts from the recorded trade-offs.
Current state (after the x86-64 slice landed, v135)
| target | i div 0 today |
|---|---|
| x86-64 | pre-divide check → Runtime error 200, exit 200 (--no-div-check restores raw SIGFPE) |
| i386 | raw SIGFPE core dump |
| arm32 / aarch64 | hardware yields 0, silently (ARM spec) |
| riscv32 | hardware yields -1 (all ones), silently (RISC-V spec) |
| xtensa | hardware divide traps; software divide (LX6) undefined-ish |
Positions recorded (2026-07-02 discussion)
- User: "returning zero is a sane result" — perspective from real-time
measurement data where inputs go out of bounds inside complex math;
abort-on-math-error is a theorist's default, not a practitioner's. Ints are
acknowledged as different from floats, and riscv's -1 is the real outlier
(neither a sane value nor an error). Halting should at minimum be behind a
compiler switch (that exists now: check is default-on,
--no-div-checkopts out — whether that polarity is right is part of this decision). - FPC parity (we want to be able to emulate closely, defaults may differ):
FPC checks unconditionally (no switch at all) → RE 200; with sysutils in the
uses closure its initialization installs hooks converting RE→ raised
EDivByZero. The PXXDivZeroHook in builtinheap mirrors exactly that design and is ready for a future exception-providing unit to install into.
Options on the table
- RE 200 everywhere (extend the pre-check to the other 5 backends, ~9 sites incl the 64-bit soft-div helpers on 32-bit targets): FPC-parity, consistent, kills the ARM silent-0 / riscv -1 divergence.
- Defined result everywhere (e.g.
x div 0 = 0by decree, check emits a cmov/select instead of a call): user's "sane result" position; cheap; diverges from FPC unless behind the emulation switch. - Switchable semantics:
--div-zero=error|zero|trap(or directive) — both camps served; more surface to test per target. - Catchable exception: needs an exception-class home that is not sysutils (user: prefer builtins) — i.e. move/define a minimal Exception/EDivByZero in builtin, or wait for [[feature-emission-size-dce]] so carrying it is free. Hook mechanism already in place either way.
Also unguarded today (fold into whichever option wins)
Low(Int64) div -1/mod -1: x86 idiv still SIGFPEs (overflow trap, not zero divisor). FPC raises RE 215 (overflow). Either a second compare in the check or the signal handler catches it.
Acceptance (of the decision, not code)
A written choice among the options (or a hybrid), with default + switch polarity + FPC-emulation story fixed; implementation tickets then follow per target.
DECIDED 2026-07-20 — Option 1, RE 200 everywhere
User's call: 1. Integer division by zero raises RE 200 on every target. Extend the existing x86 pre-check to the other five backends (~9 sites, including the 64-bit soft-div helpers on 32-bit targets).
This kills the divergence that made the current state indefensible: x86 raises, ARM silently yields 0, riscv yields -1. A silent wrong answer that differs per target is exactly the bug class this project exists to hunt, and it is also FPC-parity, so it costs nothing in compatibility.
Fold in the unguarded overflow case: Low(Int64) div -1 / mod -1 still
SIGFPEs on x86 (overflow trap, not zero divisor); FPC raises RE 215. Handle it
in the same pass — either a second compare in the check or via the signal
handler — rather than leaving a second silent divergence behind.
Rejected: 2 (defined result) diverges from FPC for no gain now that the check is being written anyway; 3 (switchable) is surface to test per target for a question with one right answer; 4 (catchable) needs an exception-class home outside sysutils and can be layered on later — the hook mechanism already exists.
Log
- 2026-07-20 — DECIDED by the user; see the DECISION section above. Implementation follows in its own tickets.
2026-08-04 — measured data for the PROMO layer, and the position restated
Still postponed by the user; recorded only so the eventual decision starts from measurements rather than recollection. Nothing was changed.
PromoInt div/mod by zero, measured today
| target | tier | x div 0 |
|---|---|---|
| x86-64 | inline | Runtime error 200, exit 200 |
| x86-64 | inline, 0 div 0 |
Runtime error 200 |
| x86-64 | heap (2^70) | Runtime error 200 |
| i386 | inline | 4294967295, exit 0 — SILENT |
So the promotable-int layer inherits the same cross-target split the integer table above documents, and i386 adds a fourth flavour to the list: not a trap, not 0, not -1, but 2^32-1. Worth noting when option 1 ("RE 200 everywhere") is costed — the promo runtime is ordinary Pascal compiled by pxx, so it follows whatever the backends do rather than having a policy of its own.
NilPy is already decided and correct: 7 // 0 raises ZeroDivisionError,
byte-identical to CPython, because the frontend checks before promocore is
reached. Whatever this ticket settles for Pascal, the Python frontend keeps
Python's answer.
The user's position, restated 2026-08-04
0/0 == 1by definition. The confusion the position is aimed at is people reading a LIMIT where there is only a zero:lim x→0of something is not the value at zero, and the two get conflated.- The float cases are already settled and correct:
1/0 = +Inf,1/-0 = -Inf,0/0 = NaN— verified today at both targets, IEEE throughout. - The blocker remains that CPU behaviour differs and there is no pragmatic forcing case yet. Postponed until one appears, deliberately.
(The float VALUES are right; printing one is not — writeln of a non-finite
Double hangs. That is a plain bug, not a policy question, and is filed as
[[bug-a-writeln-of-a-non-finite-double-hangs]].)