nx_divprobe.nx source
↩ module page · 95 lines · 4760 B
1// nx_divprobe.nx -- DOES nx_cc STRENGTH-REDUCE DIVISION BY A CONSTANT POWER OF TWO?
2//
3// WHY THIS PROBE EXISTS. A render-performance lane measured gs_resolve at 12.3 ns/pixel while doing
4// six divisions by 256 -- a power of two -- and concluded from an OUTLINE GREP that the x86 backend
5// has no strength-reduction pass. That is an absence claimed from a FILTERED READ, which this estate
6// does not accept as evidence: a grep that finds no function named "shr" cannot see a shift emitted
7// inline, and would report the same nothing either way. Before any compiler is edited on the strength
8// of that claim, the claim itself gets an instrument.
9//
10// THE EXPERIMENT DISCRIMINATES, which is the only reason to run it. Three loops, identical in every
11// respect except the arithmetic in the hot line:
12// A: / DP_POW2 -- divisor IS a power of two, so a strength-reducing compiler emits a shift
13// B: / DP_NOTPOW2 -- divisor is one less, NOT a power of two, so no compiler can avoid the idiv
14// C: * DP_MUL -- a multiply, the cost floor for "arithmetic that is not division"
15//
16// If A is much faster than B and close to C -> strength reduction EXISTS; the rung is REFUTED.
17// If A and B are alike and both far above C -> no strength reduction; the rung is CONFIRMED.
18// Either outcome is a result. The probe is written to be unable to flatter the answer it was built
19// hoping for: the two divisors differ by one, so nothing but the power-of-two property distinguishes
20// them, and the accumulator is PRINTED so no loop can be optimised away as dead.
21//
22// DP_ITERS is DERIVED, not picked: the clock is microsecond-resolution, and a measurement worth
23// trusting needs to exceed that by a wide margin, so the loop runs long enough that each arm takes
24// order-milliseconds. The three arms share one iteration count so their times are directly comparable.
25
26import "nx_syscalls.nx"
27import "nx_gate_verdict.nx"
28
29const DP_ITERS: i64 = 20000000
30const DP_POW2: i64 = 256 // the divisor gs_resolve and gs_blend_rect actually use (GFXA)
31const DP_NOTPOW2: i64 = 255 // one less: identical magnitude, NOT a power of two
32const DP_MUL: i64 = 3 // the non-division cost floor
33const DP_SEED: i64 = 1103515245 // an odd starting value so the accumulator does not degenerate
34
35func main() -> i64 {
36 gv_head("nx_divprobe -- is division by a constant power of two strength-reduced?" as *u8)
37
38 // ---- arm A: divide by a power of two ------------------------------------------------------
39 var a: i64 = 0
40 var x: i64 = DP_SEED
41 let ta0: i64 = sys_clock_now_us()
42 var i: i64 = 0
43 while i < DP_ITERS {
44 a = a + x / DP_POW2
45 x = x + 1
46 i = i + 1
47 }
48 let ta1: i64 = sys_clock_now_us()
49
50 // ---- arm B: divide by a NON power of two --------------------------------------------------
51 var b: i64 = 0
52 x = DP_SEED
53 let tb0: i64 = sys_clock_now_us()
54 i = 0
55 while i < DP_ITERS {
56 b = b + x / DP_NOTPOW2
57 x = x + 1
58 i = i + 1
59 }
60 let tb1: i64 = sys_clock_now_us()
61
62 // ---- arm C: multiply, the cost floor ------------------------------------------------------
63 var c: i64 = 0
64 x = DP_SEED
65 let tc0: i64 = sys_clock_now_us()
66 i = 0
67 while i < DP_ITERS {
68 c = c + x * DP_MUL
69 x = x + 1
70 i = i + 1
71 }
72 let tc1: i64 = sys_clock_now_us()
73
74 let ua: i64 = ta1 - ta0
75 let ub: i64 = tb1 - tb0
76 let uc: i64 = tc1 - tc0
77
78 gv_puts(" iterations per arm = " as *u8); gv_num(DP_ITERS); gv_puts("\n" as *u8)
79 gv_puts(" A x / " as *u8); gv_num(DP_POW2); gv_puts(" (power of two) us=" as *u8); gv_num(ua); gv_puts("\n" as *u8)
80 gv_puts(" B x / " as *u8); gv_num(DP_NOTPOW2); gv_puts(" (NOT power of two) us=" as *u8); gv_num(ub); gv_puts("\n" as *u8)
81 gv_puts(" C x * " as *u8); gv_num(DP_MUL); gv_puts(" (cost floor) us=" as *u8); gv_num(uc); gv_puts("\n" as *u8)
82
83 // Accumulators are printed so that no arm can be eliminated as dead code -- an optimised-away
84 // loop would time as zero and read as an infinitely fast divide.
85 gv_puts(" accumulators (printed so no arm is dead code): " as *u8)
86 gv_num(a); gv_puts(" " as *u8); gv_num(b); gv_puts(" " as *u8); gv_num(c); gv_puts("\n\n" as *u8)
87
88 if uc > 0 {
89 gv_puts(" A/C ratio (x1000) = " as *u8); gv_num(ua * 1000 / uc); gv_puts("\n" as *u8)
90 gv_puts(" B/C ratio (x1000) = " as *u8); gv_num(ub * 1000 / uc); gv_puts("\n" as *u8)
91 }
92 gv_puts("\n READING: A close to C and far below B => strength reduction EXISTS (rung refuted).\n" as *u8)
93 gv_puts(" A close to B and both above C => NO strength reduction (rung confirmed).\n" as *u8)
94 return 0
95}