← board

bug: arm32 writeln(LongWord) mangles a value with the high bit set

Symptom

On arm32, writeln of a LongWord/Cardinal (32-bit unsigned) whose value has bit 31 set prints garbage; small values are fine. x86-64 / i386 / aarch64 are all correct.

var c: LongWord;
begin
  c := 4294967295; writeln(c);   { arm32 prints `/`   ; want 4294967295 }
  c := 100;        writeln(c);   { arm32 prints 100   (OK)             }
end.

Independent of not — a plain assignment + writeln reproduces it. The bitwise not work only surfaced it (not LongWord(0) = 4294967295).

Likely cause

arm32's unsigned-32 write path (the writeln/Str integer formatter for tyUInt32) probably treats the value as signed, or the high-bit value collapses to a wrong digit/char — / is ASCII 47, suggesting a single mis-derived digit rather than a full number. Compare with the i386/aarch64 unsigned-write paths, which handle the same value correctly (so the fix is arm32-local).

Workaround

test_not_int64_expr.pas checks the LongWord complement by value comparison (if c = 4294967295) instead of writeln(c), so it stays portable across targets. Remove the workaround once this is fixed.

Acceptance

Resolution (2026-06-26, Track A)

arm32's write/Str integer loops divided with SDIV; the unsigned writers mangled LongWord >= 2^31, and the signed writers also mangled INT_MIN (negation overflows to 0x80000000, SDIV mis-divides). Every loop divides a NON-NEGATIVE magnitude (unsigned value, or post-negation magnitude), so switched all four sdiv (0xE714F210) -> udiv (0xE734F210) in emit.inc. Fixes both LongWord-high-bit AND INT_MIN as a bonus. Regression: test/test_uint32_write.pas, cross-checked arm32 vs x86-64 in make test. Self-host byte-identical. (test_not_int64_expr's value-comparison workaround can stay; the new test covers the writeln path.)