← board

DECIDE: unify integer div/mod-by-zero behavior across targets

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)

Options on the table

  1. 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.
  2. Defined result everywhere (e.g. x div 0 = 0 by decree, check emits a cmov/select instead of a call): user's "sane result" position; cheap; diverges from FPC unless behind the emulation switch.
  3. Switchable semantics: --div-zero=error|zero|trap (or directive) — both camps served; more surface to test per target.
  4. 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)

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-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

(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]].)