code wiki / _hdl_build / nx_regalloc_race_kernel_test.nx

nx_regalloc_race_kernel_test.nx source

↩ module page · 83 lines · 4151 B

1// nx_regalloc_race_kernel_test.nx -- point the proven allocator at the EXACT hot 2// loop we lose ~1.9x to C on (nx_race_kernel), to quantify the lever concretely. 3// 4// The race kernel's loop body, as a straight-line IR (each line defines one vreg): 5// c_in, acc_in, i_in live-ins (already in registers) 6// t0 = c_in * K (3) 7// c1 = t0 + A (4) 8// t1 = c1 >> 31 (5) 9// c2 = c1 ^ t1 (6) <- next iteration's c (live-out) 10// t2 = c2 & 65535 (7) 11// acc1 = acc_in + t2 (8) <- next iteration's acc (live-out) 12// i1 = i_in + 1 (9) <- next iteration's i (live-out) 13// cmp = i1 < K (10) 14// 15// Pressure stays low (~4-5 live), so on x86-64's ~8+ usable GP registers the WHOLE 16// body fits in registers: ZERO memory traffic. The stack-resident codegen instead 17// loads each operand + stores each result every iteration -- run 20,000,000 times, 18// that is the ~1.9x. This shows the allocator turns the hot loop memory-free, the 19// direct path to closing the gap. 20// 21// Known answer: 0 spills (fits in registers) AND regalloc memory traffic = 0 AND 22// the stack baseline is large -> exit 0. 23 24import "nx_regalloc_linscan.nx" 25 26func rk_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 27func rk_emit(name: *u8, v: i64) -> i64 { 28 rk_puts(name) 29 let b: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m } 30 let t: *u8 = sys_mmap(28); var k: i64 = 0 31 if m == 0 { t[0] = 48; k = 1 } 32 while m > 0 { t[k] = 48 + (m % 10); m = m / 10; k = k + 1 } 33 var i: i64 = 0; while i < k { b[i] = t[k - 1 - i]; i = i + 1 } 34 b[k] = 10; sys_write(1, b, k + 1); return 0 35} 36 37const RKB_N: i64 = 11 38const RKB_NREG: i64 = 8 // x86-64 has ~14 GP regs; 8 is conservative 39const RKB_ITERS: i64 = 20000000 // the race kernel's iteration count 40 41func main() -> i64 { 42 rk_puts("=== register allocation applied to the race kernel's hot loop ===\n" as *u8) 43 let u0: *i64 = sys_mmap(8 * RKB_N) 44 let u1: *i64 = sys_mmap(8 * RKB_N) 45 // live-ins (no operands) 46 u0[0] = 0 - 1; u1[0] = 0 - 1 // c_in 47 u0[1] = 0 - 1; u1[1] = 0 - 1 // acc_in 48 u0[2] = 0 - 1; u1[2] = 0 - 1 // i_in 49 u0[3] = 0; u1[3] = 0 - 1 // t0 = c_in * K 50 u0[4] = 3; u1[4] = 0 - 1 // c1 = t0 + A 51 u0[5] = 4; u1[5] = 0 - 1 // t1 = c1 >> 31 52 u0[6] = 4; u1[6] = 5 // c2 = c1 ^ t1 53 u0[7] = 6; u1[7] = 0 - 1 // t2 = c2 & M 54 u0[8] = 1; u1[8] = 7 // acc1 = acc_in + t2 55 u0[9] = 2; u1[9] = 0 - 1 // i1 = i_in + 1 56 u0[10] = 9; u1[10] = 0 - 1 // cmp = i1 < K 57 58 let last_use: *i64 = sys_mmap(8 * RKB_N) 59 let alloc: *i64 = sys_mmap(8 * RKB_N) 60 ra_compute_last_use(RKB_N, u0, u1, last_use) 61 let spills: i64 = ra_linscan(RKB_N, RKB_NREG, last_use, alloc) 62 let valid: i64 = ra_validate(RKB_N, last_use, alloc, RKB_NREG) 63 let base: i64 = ra_baseline_mem(RKB_N, u0, u1) 64 let ra: i64 = ra_regalloc_mem(RKB_N, u0, u1, alloc) 65 66 rk_emit(" instructions in body : " as *u8, RKB_N) 67 rk_emit(" spills (0 = fits in regs) : " as *u8, spills) 68 rk_emit(" sound (0 = valid) : " as *u8, valid) 69 rk_emit(" stack mem ops / iteration : " as *u8, base) 70 rk_emit(" regalloc mem ops / iteration : " as *u8, ra) 71 rk_emit(" stack mem ops over the run : " as *u8, base * RKB_ITERS) 72 rk_emit(" regalloc mem ops over the run: " as *u8, ra * RKB_ITERS) 73 rk_puts(" -> the hot loop becomes MEMORY-FREE; that erased traffic is the ~1.9x.\n" as *u8) 74 75 // GATE: the body fits in registers (0 spills), allocation is sound, it is 76 // memory-free, and the stack baseline it replaces is real (large). 77 if valid != 0 { sys_exit(1); return 1 } 78 if spills != 0 { sys_exit(2); return 2 } // low-pressure body must fit 79 if ra != 0 { sys_exit(3); return 3 } // memory-free under regalloc 80 if base < 10 { sys_exit(4); return 4 } // the stack tax it removes is real 81 sys_exit(0) 82 return 0 83}