SWI 0x06 — Div¶
- Entry:
0x000003B4(ARM) - Status: verified (hardware-checked 2026-07-08: cycle counts and return values measured on real GBA via the worker ROM)
Summary¶
Signed 32-bit integer division. Computes r0 / r1 (numerator / denominator),
returning the truncated-toward-zero quotient, the remainder, and the absolute
value of the quotient. The algorithm is a classic restoring binary long
division (shift-and-subtract), not Newton iteration and not repeated linear
subtraction.
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | s32 | Numerator (dividend) |
| r1 | s32 | Denominator (divisor) |
Returns¶
| Reg | Out | Meaning |
|---|---|---|
| r0 | s32 | Quotient r0 / r1, rounded toward zero |
| r1 | s32 | Remainder r0 MOD r1; sign follows the numerator |
| r3 | s32 | Absolute value of the quotient, abs(r0 / r1) |
Clobbered registers¶
r2, r12 (ip) are used as scratch and destroyed. r0/r1/r3 hold results.
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0 = quotient, r1 = remainder, r3 = |quotient|. r2 was preserved (canary intact — the "r2 destroyed" static note is not caller-visible; the dispatcher does not save r2, so the routine itself must leave it alone). r2, r4–r12, r13, and CPSR (flags and mode) came back bit-identical on every tested path. r11/r12 are explained by the SWI dispatcher (it pushes {r11, r12, lr} — see 10_irq_boot_and_iwram.md) and CPSR by the SPSR restore on return; r2 and r4–r10 must be preserved or restored by the routine itself — where a static note above claims r2 is destroyed, the hardware disagrees at the caller level. Raw data: results/clobber_audit_pass2.csv.
Algorithm¶
- Extract signs.
r3 = r1 & 0x80000000captures the denominator sign; the denominator is then made positive (rsbmi). A combined sign word is built inip = (denominator_sign_bit) XOR (numerator sign-extended to 32 bits), so that bit31 ofip= quotient sign (num_sign XOR den_sign) and bit30 = the numerator's sign (used later for the remainder). The numerator is made positive (rsbcs, carry = numerator sign from the precedingEORS). - Divisor alignment: starting from
r2 = |denominator|, doubler2whiler2 < (|numerator| >> 1), sor2becomes the divisor shifted up to just below the numerator's top set bit. - Restoring loop: repeatedly compare
numerator >= r2; shift a bit into the quotient accumulatorr3withadc r3, r3, r3(carry = comparison result), conditionally subtractr2, then halver2. The loop ends whenr2has been halved back down to the original|denominator|. Quotient lands inr3, remainder in the working numerator register. - Sign fix-up:
lsls ip,#1puts the quotient sign into carry (negate quotient if needed) and the numerator sign into N (negate remainder if the numerator was negative).r3keeps the unsigned quotient magnitude.
Note: r3 is seeded with the denominator's sign bit (0x80000000 or 0) rather
than being explicitly zeroed; that stray top bit is harmlessly shifted out by the
first adc and never affects the result.
SWI 0x07 DivArm (0x000003A8)¶
DivArm is a 3-instruction shim that swaps r0 and r1 and falls straight into Div.
It is identical to Div except the register roles are reversed:
r0 = denominator, r1 = numerator on entry. Outputs are the same as Div.
Edge cases & known bugs¶
- Division by zero (
r1 == 0): The divisor-alignment loop doublesr2starting from 0; since0 < (|numerator| >> 1)stays true forever,r2remains 0 and the loop never terminates for|numerator| >= 2(effectively hangs; static analysis — deliberately not run on hardware). Degenerate small numerators do return, hardware-verified:1/0 → (1,1,1),-1/0 → (-1,-1,1),0/0 → (1,0,1)(r0, r1, r3; 69 cycles each). The BIOS performs no divide-by-zero check.INT_MIN/-1returns0x80000000(hardware-verified, no hang). - INT_MIN / -1 (
0x80000000 / -1): overflow case. Returnsr0 = 0x80000000(still negative — the true value+2^31is unrepresentable),r1 = 0,r3 = 0x80000000. No crash. - Remainder always takes the numerator's sign, matching C
%semantics (truncation toward zero). Verified examples:-10/3 → q=-3, rem=-1;10/-3 → q=-3, rem=+1;-10/-3 → q=3, rem=-1.
Cycle count¶
Hardware-measured net CPU cycles (worker-ROM harness, TM0/TM1 cascade at F/1,
13-cycle baseline subtracted; identical across 3 runs — see
02_hardware_verification_checklist.md § Measurement setup).
| Input | Cycles |
|---|---|
1/1, 0/1, 1/0x7FFFFFFF |
69 (minimum path) |
degenerate 1/0, 0/0, -1/0 |
69 |
100/7 — all four sign combinations |
108 |
0x7FFFFFFF/3 |
446 |
0x7FFFFFFF/1 |
459 |
INT_MIN/-1 |
467 (largest observed) |
Cost scales with the bit-length gap between numerator and denominator (one
alignment-loop and one restoring-loop pass per bit); sign handling adds no
measurable cost — all four sign combinations of 100/7 measure identically.
Open questions (need hardware verification)¶
- Confirm real hardware truly hangs (vs. some undocumented watchdog behavior) on
divide-by-zero with
|numerator| >= 2. - Confirm the exact returned words for
1/0,-1/0,0/0on hardware.
GBATEK cross-reference¶
Agrees with GBATEK (r0=quotient, r1=remainder, r3=abs(quotient)). Adds: the exact algorithm class (restoring binary division), the precise remainder sign rule, and the concrete divide-by-zero hang boundary and INT_MIN/-1 overflow result, which GBATEK leaves as "undefined".