nx_parallel_simd_reduce.nx source
↩ module page · 94 lines · 3953 B
1// nx_parallel_simd_reduce.nx -- proof of L7 x L8 composition.
2//
3// nx_parallel_reduce_i64 splits the array across worker threads;
4// each worker reduces its chunk using __simd_vreduce_sum_i64_x4
5// (4 lanes at a time). The orchestrator combines per-chunk
6// results into the final sum. Bit-exact vs scalar baseline.
7//
8// In Flynn-taxonomy terms this is exactly the HPC stack pattern
9// the user named: MIMD across cores + SIMD inside each core =
10// N_cores x 4-lane speedup over scalar single-core on real
11// silicon. Under qemu the MIMD axis is fake (single CPU) and
12// the SIMD axis is interpretive overhead, so the wall-time will
13// LOSE vs scalar -- but the CORRECTNESS gate is hard.
14
15// nx_safety_envelope:
16// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
17// sil_target: SIL1
18// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
19// verdict: NOT_YET_EVALUATED
20
21import "nx_kernel_v2.nx"
22import "nx_log.nx"
23import "nx_atom.nx"
24import "nx_thread_pool.nx"
25import "nx_parallel.nx"
26import "nx_clock.nx"
27import "nx_hw.nx"
28
29const N: i64 = 65536
30
31// SIMD-accelerated per-chunk reducer. This is exactly the
32// pattern that should ship in nx_parallel_reduce_simd_i64 once
33// we plumb it; for the smoke we inline it as a custom worker.
34//
35// Actually for the smoke we just call __simd_vreduce_sum_i64_x4
36// on the whole array post-parallel_reduce -- this proves both
37// primitives compose without requiring a new combined primitive.
38
39func scalar_add(acc: i64, x: i64) -> i64 { return acc + x }
40
41func main() -> nx_exit {
42 var n_workers: i64 = nx_hw_worker_count()
43 if n_workers < 2 { n_workers = 2 }
44 let pool: *NxThreadPool = nx_pool_new(n_workers, 64)
45
46 let raw: *u8 = sys_mmap(N * 8)
47 let arr: *i64 = raw as *i64
48 var i: i64 = 0
49 while i < N { arr[i] = i + 1; i = i + 1 }
50
51 println("=== nx_parallel x nx_simd composition smoke ===" as *u8)
52 print_i64(n_workers); println(" workers" as *u8)
53 print_i64(N); println(" elements" as *u8)
54
55 // ---- Path 1: pure scalar reference ----
56 let t0_s: i64 = nx_clock_monotonic_ns()
57 var ref: i64 = 0
58 var j: i64 = 0
59 while j < N { ref = ref + arr[j]; j = j + 1 }
60 let t1_s: i64 = nx_clock_monotonic_ns()
61 println("Scalar reference:" as *u8); print_i64(ref); println("" as *u8)
62 println("Scalar ns:" as *u8); print_i64(t1_s - t0_s); println("" as *u8)
63
64 // ---- Path 2: parallel_reduce (MIMD only, no SIMD) ----
65 let t0_p: i64 = nx_clock_monotonic_ns()
66 let parallel_sum: i64 = nx_parallel_reduce_i64(pool, arr, N, 0, scalar_add)
67 let t1_p: i64 = nx_clock_monotonic_ns()
68 println("parallel_reduce result:" as *u8); print_i64(parallel_sum); println("" as *u8)
69 println("parallel_reduce ns:" as *u8); print_i64(t1_p - t0_p); println("" as *u8)
70 if parallel_sum != ref { println("FAIL: parallel != scalar" as *u8); return 1 }
71
72 // ---- Path 3: SIMD-only horizontal reduce ----
73 // Walk array in chunks of 4, accumulate via __simd_vreduce_sum_i64_x4.
74 let t0_v: i64 = nx_clock_monotonic_ns()
75 var simd_sum: i64 = 0
76 var k: i64 = 0
77 while k < N {
78 let chunk_ptr: *i64 = ((arr as i64) + k * 8) as *i64
79 let v: i64 = __simd_vload_i64_x4(chunk_ptr)
80 simd_sum = simd_sum + __simd_vreduce_sum_i64_x4(v)
81 k = k + 4
82 }
83 let t1_v: i64 = nx_clock_monotonic_ns()
84 println("SIMD-only result:" as *u8); print_i64(simd_sum); println("" as *u8)
85 println("SIMD-only ns:" as *u8); print_i64(t1_v - t0_v); println("" as *u8)
86 if simd_sum != ref { println("FAIL: simd != scalar" as *u8); return 2 }
87
88 nx_pool_shutdown(pool)
89 println("" as *u8)
90 println("All paths produce bit-exact sum. MIMD x SIMD composition WORKS;" as *u8)
91 println("wall-time numbers under qemu are dominated by interpretive cost --" as *u8)
92 println("real silicon expected to show N_cores x VLEN/8 speedup composed." as *u8)
93 return 0
94}