Division Clock
Games & Classics
A sweeping analog clock and a rolling odometer on a Super Nintendo. To split the frame counter
into hours:minutes:seconds and the odometer into base-10 digits, it divides by compile-time
constants (/60, /10, /12) — the textbook case a
compiler turns into a magic-reciprocal
multiply. The interesting part is what happens when it doesn't (see the notes
below). Runs automatically; no joypad needed. Written in C and compiled with the
llvm-mos-based 65816 toolchain
(+mos-a16, 16-bit accumulator mode).
Self-running demo — the clock sweeps and the odometer rolls on their own.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
Dividing by a constant is one of the oldest compiler optimisations: x / 10 becomes
a multiply by a carefully chosen "magic" number followed by a shift, because multiplying is far
cheaper than dividing — on a CPU with a fast multiplier.
sec = (tick / 60) % 60; // divide by a COMPILE-TIME constant
min = (tick / 3600) % 60; // → magic reciprocal on most CPUs...
for (i=0;i<6;i++) { // → but on this soft-multiply target,
d[i] = v % 10; v /= 10; // it stays a __udiv libcall (see below)
} This demo was built to show that transform. Instead it measured the opposite: on the 65816, where even multiplication is a software routine, the magic-reciprocal form would need the high half of a wide multiply — itself a library call — so the compiler correctly keeps the division routine. The clock and odometer are exact all the same; the point is the honest result, not the assumption.
Compiler stress-test
| Item | What it exercises |
|---|---|
| constant divide | Splitting a counter into h:m:s and base-10 digits divides by compile-time constants (/60, /10, /12). On CPUs with a fast multiply, the compiler turns these into a "magic reciprocal" — a multiply-high by 0xCCCC… plus a shift — avoiding the divide entirely. |
| the finding | llvm-mos does NOT do that here — even uint16 x/10 lowers to a __udivhi3 call. Measured, not assumed: the strength reduction needs the high half of a wide multiply (MULHU), which is itself a libcall on this 8-bit soft-multiply target — so it cannot beat the divide. |
| correct, not a bug | The LLVM cost model gates the magic-reciprocal transform on a cheap MULHU. With none available it correctly keeps the division libcall — swapping one libcall for an equally-expensive one would be no win. The differential proves the retained division is bit-exact. |
| vs #27 | Distinct from a runtime-divisor modulo (#27): here the divisor is a compile-time constant, the exact case that "should" optimise away — and the interesting result is that, on this target, it does not. |
Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg
(and MAME where the SPC700 IPL is present). Hit Verify fidelity to reproduce the build
gate's WRAM assert (gate CRC 0xF72E — a rotate-XOR fold of the clock fields + hand
angles + odometer digits) live in this tab. No far pointers — the same source passes every way:
host == default == +mos-a16 == +mos-xy16 == bsnes-jg,
-verify clean.