Optimised 64 bit constant division on Aarch64

2026-08-20

Stack Overflow: View Question

Tags: best-practices, optimization, compiler-construction, arm64

Score: 0 | Views: 137

The asker shows Clang's Aarch64 output for unsigned long div(unsigned long d) { return d/7; }. Instead of an actual udiv, Clang emits a four-instruction constant load (mov + three movks), a umulh, and then a curious sub/add-with-lsr/lsr tail. The implicit question: is this the tightest sequence, and why the extra correction dance?

This is the classic Granlund–Montgomery "divide by invariant integer using multiplication" trick. For divisor d, you'd like a magic multiplier m ≈ 264/d such that (x * m) >> 64 equals x / d. For d = 7 the exact ceiling is m = ⌈265/7⌉ = 0x2492492492492493 × 2 = a 65-bit value. It doesn't fit in a 64-bit register, so the compiler falls back to the "add-correction" variant using a truncated 64-bit m = 0x2492492492492493 (which satisfies 7m = 264 + 5).

With that smaller m, q = umulh(d, m) is almost right but shy by roughly d/2. The fix-up is:

t = d - q          ; sub  x9, x0, x8
q = q + (t >> 1)   ; add  x8, x8, x9, lsr #1
r = q >> 2         ; lsr  x0, x8, #2

Aarch64's free shifted-register operand folds the (t >> 1) into the add, so the correction only costs two extra integer ops. Total: 4 constant-materialization + 4 arithmetic = 8 instructions, no divide.

Is it optimal? Broadly, yes — this is what libdivide and GCC also produce. Two directions to poke at:

Gotchas: the correction sequence exists precisely because a naive q + d) >> 1 could overflow past 264. Writing (d - q) >> 1 first, then adding back, sidesteps that carry. For signed division the recipe changes (sign-extending smulh, extra add/lsr #63 for the sign bit). And the specific magic differs on 32-bit — don't cargo-cult the constant.

The challenge: Understanding why an 8-instruction sequence beats a hardware udiv requires grokking that division-by-constant becomes fixed-point multiplication, and that a too-large magic number forces a subtle overflow-avoiding correction step.

All newsletters