Skip to content

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

  1. Extract signs. r3 = r1 & 0x80000000 captures the denominator sign; the denominator is then made positive (rsbmi). A combined sign word is built in ip = (denominator_sign_bit) XOR (numerator sign-extended to 32 bits), so that bit31 of ip = 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 preceding EORS).
  2. Divisor alignment: starting from r2 = |denominator|, double r2 while r2 < (|numerator| >> 1), so r2 becomes the divisor shifted up to just below the numerator's top set bit.
  3. Restoring loop: repeatedly compare numerator >= r2; shift a bit into the quotient accumulator r3 with adc r3, r3, r3 (carry = comparison result), conditionally subtract r2, then halve r2. The loop ends when r2 has been halved back down to the original |denominator|. Quotient lands in r3, remainder in the working numerator register.
  4. Sign fix-up: lsls ip,#1 puts the quotient sign into carry (negate quotient if needed) and the numerator sign into N (negate remainder if the numerator was negative). r3 keeps 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 doubles r2 starting from 0; since 0 < (|numerator| >> 1) stays true forever, r2 remains 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/-1 returns 0x80000000 (hardware-verified, no hang).
  • INT_MIN / -1 (0x80000000 / -1): overflow case. Returns r0 = 0x80000000 (still negative — the true value +2^31 is 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/0 on 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".