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:
mov/movk chain is unavoidable for a "random-looking" 64-bit immediate on Aarch64. But 0x2492492492492493 has structure (the repeating 001 pattern). In principle you could form it as (x << 3) - x style expressions of a smaller seed, but that trades instruction count for latency and dependency chain length — rarely a win.d/7 is inside a hot loop, the magic constant should be hoisted so the per-iteration cost drops to just umulh + sub + add + lsr. Confirm with your compiler's loop output — sometimes LLVM re-materializes.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.
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.
