code wiki / _hdl_build / nx_clbg_fannkuch.nx
nx_clbg_fannkuch.nx source
↩ module page · 150 lines · 5292 B
1// nx_clbg_fannkuch.nx -- struct-free, pure-integer fannkuch-redux(N).
2//
3// Canonical Computer Language Benchmarks Game integer hot loop. Enumerates all
4// N! permutations of {0..N-1} via the "rotations" odometer (NOT lexicographic),
5// reversing the perm[0]+1 prefix until perm[0]==0 (counting flips) for each.
6//
7// Outputs (written into a caller-owned 2-slot i64 buffer):
8// out[0] = checksum (sum of flips with alternating sign +,-,+,- in gen order)
9// out[1] = maxflips (max flip count over all permutations)
10//
11// Golden (verified vs gcc -O2 C ref, 2026-06-13):
12// N=7 -> checksum 228 maxflips 16
13// N=10 -> checksum 73196 maxflips 38 (X-PERF-003a fixed N)
14// N=11 -> checksum 556355 maxflips 51
15//
16// Sovereign-compiled (nx_cc_sovereign -> nxasm_x86_main, NO gcc); this is the
17// runtime measured by _xperf003a_gate. spec: knowledge/specs/2026-06-13-clbg-fannkuch-throughput.md
18// genealogy_id: clbg_fannkuch_redux
19// lineage_id: function
20
21import "nx_clock.nx"
22import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
23
24const FK_MAXN: i64 = 32 // perm arrays sized for N up to 32 (golden uses 10)
25const FK_INTSZ: i64 = 8 // i64 per array slot
26const FK_DEFAULT_N: i64 = 10 // X-PERF-003a fixed N (golden checksum 73196 / maxflips 38)
27const FK_REPEAT: i64 = 5 // run the enumeration this many times -> stable timing floor
28
29// fannkuch_redux(n, out): runs the full N! enumeration.
30// out is a caller-mmapped i64[2]; on return out[0]=checksum, out[1]=maxflips.
31// Returns 0 on success, -1 if n is out of the supported [1,FK_MAXN] range.
32func fannkuch_redux(n: i64, out: *i64) -> i64 {
33 if n < 1 { return 0 - 1 }
34 if n > FK_MAXN { return 0 - 1 }
35
36 let perm: *i64 = sys_mmap(FK_MAXN * FK_INTSZ) as *i64
37 let perm1: *i64 = sys_mmap(FK_MAXN * FK_INTSZ) as *i64
38 let count: *i64 = sys_mmap(FK_MAXN * FK_INTSZ) as *i64
39
40 var i: i64 = 0
41 while i < n {
42 perm1[i] = i
43 i = i + 1
44 }
45
46 var checksum: i64 = 0
47 var maxflips: i64 = 0
48 var sign: i64 = 1
49 var r: i64 = n
50
51 var done: i64 = 0
52 while done == 0 {
53 // refill the odometer to the right of position r
54 while r != 1 {
55 count[r - 1] = r
56 r = r - 1
57 }
58
59 // copy perm1 -> perm
60 var c: i64 = 0
61 while c < n {
62 perm[c] = perm1[c]
63 c = c + 1
64 }
65
66 // flip loop: reverse the perm[0]+1 prefix until perm[0]==0
67 var flips: i64 = 0
68 var k: i64 = perm[0]
69 while k != 0 {
70 var lo: i64 = 0
71 var hi: i64 = k
72 while lo < hi {
73 let t: i64 = perm[lo]
74 perm[lo] = perm[hi]
75 perm[hi] = t
76 lo = lo + 1
77 hi = hi - 1
78 }
79 flips = flips + 1
80 k = perm[0]
81 }
82
83 if flips > maxflips { maxflips = flips }
84 if sign > 0 { checksum = checksum + flips }
85 if sign < 0 { checksum = checksum - flips }
86 sign = 0 - sign
87
88 // advance odometer: left-rotate growing prefixes of perm1
89 var adv: i64 = 0
90 while adv == 0 {
91 if r == n {
92 out[0] = checksum
93 out[1] = maxflips
94 return 0
95 }
96 let perm0: i64 = perm1[0]
97 var m: i64 = 0
98 while m < r {
99 perm1[m] = perm1[m + 1]
100 m = m + 1
101 }
102 perm1[r] = perm0
103 count[r] = count[r] - 1
104 if count[r] > 0 { adv = 1 }
105 if adv == 0 { r = r + 1 }
106 }
107 }
108 out[0] = checksum
109 out[1] = maxflips
110 return 0
111}
112
113// --- standalone smoke / sovereign timing harness ---
114// Brackets the sovereign permutation hot loop with nx_clock_monotonic_ns() and
115// prints a single machine-parseable line the gate reads back:
116// FANNKUCH_SOV n=<N> checksum=<..> maxflips=<..> sov_us=<..> reps=<..>
117// sov_us = wall time of FK_REPEAT enumerations / FK_REPEAT (per-run floor).
118
119func fk_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
120// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
121// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
122// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
123// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
124func fk_putn(v: i64) -> i64 { nxi_out(v); return 0 }
125
126func main() -> i64 {
127 let out: *i64 = sys_mmap(2 * FK_INTSZ) as *i64
128 out[0] = 0
129 out[1] = 0
130
131 // SOVEREIGN HOT LOOP -- bracketed by the monotonic clock.
132 let t0: i64 = nx_clock_monotonic_ns()
133 var rep: i64 = 0
134 while rep < FK_REPEAT {
135 fannkuch_redux(FK_DEFAULT_N, out)
136 rep = rep + 1
137 }
138 let t1: i64 = nx_clock_monotonic_ns()
139
140 let total_us: i64 = (t1 - t0) / 1000
141 let sov_us: i64 = total_us / FK_REPEAT
142
143 fk_puts("FANNKUCH_SOV n=" as *u8); fk_putn(FK_DEFAULT_N)
144 fk_puts(" checksum=" as *u8); fk_putn(out[0])
145 fk_puts(" maxflips=" as *u8); fk_putn(out[1])
146 fk_puts(" sov_us=" as *u8); fk_putn(sov_us)
147 fk_puts(" reps=" as *u8); fk_putn(FK_REPEAT)
148 fk_puts("\n" as *u8)
149 return 0
150}