← board

A Variant div by zero SIGFPEs on x86-64 and answers garbage elsewhere

Found 2026-08-23 by the Variant differential family (fpc 3.2.2 -Mobjfpc -O1 vs pxx 1bfd5e9e9).

uses variants, sysutils;
var a, b, c: Variant;
begin
  a := 1; b := 0;
  try c := a div b; writeln(c) except on e: Exception do writeln('raised ', e.ClassName) end;
end.
target before fpc
x86-64 SIGFPE, core dumped, exit 136 raised EDivByZero
i386 -1 raised EDivByZero
arm32 -1 raised EDivByZero
aarch64 0 raised EDivByZero

mod behaved the same way. Four targets, three different wrong answers, and the worst of them is uncatchable: a hardware trap with no message, in a program whose except block was right there.

The check already existed and the variant path did not call it

The identical program on plain Integer variables raises EDivByZero and runs its handler on x86-64. So this is not a missing feature — it is one operator's worth of divide that never reached the guard:

That is the whole fix: call the existing check from the two places that skipped it. Nothing new was written.

Why the results differed per target

Nothing was checking, so each target's divide instruction did whatever it does with a zero divisor: x86 idiv traps, ARM sdiv yields 0, and the 32-bit paths came back with -1. The divergence is diagnostic, not meaningful — it is what "no check" looks like across four ISAs.

Scope

Found alongside, and BIGGER than this ticket

The claim "plain Integers were already right" holds on x86-64 and nowhere else. Measured against the PINNED binary, so pre-existing and unrelated to this change:

ia := 1; ib := 0; ic := ia div ib;
  x86-64  raised EDivByZero          i386  got -1
  arm32   got -1                     aarch64  got 0

grep confirms it: EmitDivZeroCheckX64 appears seven times in ir_codegen.inc and there is no equivalent in ir_codegen_i386.inc, _arm32, _aarch64, _riscv32 or _xtensa. So ORDINARY integer division by zero silently answers garbage on five of six targets. Filed as [[bug-a-the-div-by-zero-check-is-emitted-on-x86-64-only]] at prio 60 — it is a wider defect than this one and wants its own gate.

This ticket's test therefore does NOT assert plain-Integer div-by-zero: doing so would pass natively while staying wrong on four targets, which is exactly the shape of claim this repo keeps getting caught by. That assertion belongs to the new ticket.

Verified

test/test_variant_div_by_zero_raises.pas, wired into test-core: div and mod by a zero Variant, by a zero-valued Double variant, and with the zero on a plain Integer operand; plus non-zero rows proving ordinary division still works and a plain-Integer row proving that path is unchanged. ALL OK under pxx x86-64, i386, aarch64 (qemu), arm32 (qemu) and fpc 3.2.2.

Gate

make compiler/pascal26 converged + the four-target differential + tools/gate.sh quick.

Log