code wiki / (root) / nx_ir_dump.nx

nx_ir_dump.nx source

↩ module page · 155 lines · 5829 B

1// nx_ir_dump.nx -- bits-up IR-dump primitive for self-host 2// bootstrap-divergence diagnosis. 3// 4// Writes one stderr line per IR instruction in a function: 5// "IR f=<name> op=<N> n=<N> r=<VID> kind=<K> ci=<CI> o0=<V> o1=<V>" 6// 7// Each line is fixed-width hex-ish so RV64-qemu and native-x86_64 8// traces can be byte-compared via `diff` to pinpoint the exact 9// transformation that differs. 10// 11// Usage: invoke once per function between parse_module and 12// x86ctx_emit_module, then run the same source through both lanes 13// and `diff -u rv64.trace native.trace`. The first diverging line 14// names the buggy instruction. 15// 16// 3-step usage: 17// 1. cat repro.nx | qemu-riscv64-static _offc/nx_compile_x86.elf 2>rv64.trace >/dev/null 18// 2. cat repro.nx | _offc/nx_compile_x86_native.elf 2>native.trace >/dev/null 19// 3. diff -u rv64.trace native.trace | head -20 20 21import "nx_syscalls.nx" 22import "nx_types.nx" 23 24// Helper: write one decimal i64 to stderr followed by a space. 25// Bounded 5-digit signed range to keep lines tight. 26func _ird_write_i64(v: i64) -> i64 { 27 let buf: *u8 = sys_mmap(16) 28 var x: i64 = v 29 var pos: i64 = 0 30 if x < 0 { buf[pos] = 0x2D; pos = pos + 1; x = 0 - x } 31 if x == 0 { buf[pos] = 0x30; pos = pos + 1 } 32 else { 33 let tmp: *u8 = sys_mmap(16) 34 var tp: i64 = 0 35 while x > 0 { tmp[tp] = (0x30 + (x % 10)) as u8; tp = tp + 1; x = x / 10 } 36 while tp > 0 { tp = tp - 1; buf[pos] = tmp[tp]; pos = pos + 1 } 37 } 38 buf[pos] = 0x20; pos = pos + 1 39 sys_write(2, buf, pos) 40 return 0 41} 42 43func _ird_write_str(s: *u8, n: i64) -> i64 { 44 return sys_write(2, s, n) 45} 46 47// Dump one Instr to stderr. Caller-allocates nothing; the function 48// uses scratch sys_mmap buffers freed implicitly at process exit. 49func _ird_dump_instr(f: *Function, i: *Instr, idx: i64) -> i64 { 50 let lbl_idx: *u8 = sys_mmap(8); lbl_idx[0]=0x69; lbl_idx[1]=0x3D // "i=" 51 sys_write(2, lbl_idx, 2) 52 _ird_write_i64(idx) 53 54 let lbl_op: *u8 = sys_mmap(8); lbl_op[0]=0x6F; lbl_op[1]=0x70; lbl_op[2]=0x3D 55 sys_write(2, lbl_op, 3) 56 _ird_write_i64(i.op) 57 58 let lbl_n: *u8 = sys_mmap(8); lbl_n[0]=0x6E; lbl_n[1]=0x3D 59 sys_write(2, lbl_n, 2) 60 _ird_write_i64(i.n_operands) 61 62 let lbl_r: *u8 = sys_mmap(8); lbl_r[0]=0x72; lbl_r[1]=0x3D 63 sys_write(2, lbl_r, 2) 64 _ird_write_i64(i.result) 65 66 // Result Value's kind + const_int -- the diagnostic surface 67 // for the bootstrap-divergence Heisenbug. 68 if i.result >= 0 { 69 if i.result < f.n_values { 70 let base: i64 = f.values as i64 71 let v: *Value = (base + i.result * 48) as *Value 72 let lbl_k: *u8 = sys_mmap(8); lbl_k[0]=0x6B; lbl_k[1]=0x3D 73 sys_write(2, lbl_k, 2) 74 _ird_write_i64(v.kind) 75 let lbl_c: *u8 = sys_mmap(8); lbl_c[0]=0x63; lbl_c[1]=0x69; lbl_c[2]=0x3D 76 sys_write(2, lbl_c, 3) 77 _ird_write_i64(v.const_int) 78 } 79 } 80 81 // First 4 operands (most ops use <=4). AC RLE callers can 82 // raise this if needed. 83 let lbl_o0: *u8 = sys_mmap(8); lbl_o0[0]=0x6F; lbl_o0[1]=0x30; lbl_o0[2]=0x3D 84 sys_write(2, lbl_o0, 3) 85 _ird_write_i64(i.op0) 86 let lbl_o1: *u8 = sys_mmap(8); lbl_o1[0]=0x6F; lbl_o1[1]=0x31; lbl_o1[2]=0x3D 87 sys_write(2, lbl_o1, 3) 88 _ird_write_i64(i.op1) 89 90 // For CALL (op=33), dump callee pointer and callee.name_start so 91 // we can diff "is the callee pointer the same" vs "does the name 92 // read give the same bytes" between lanes. 93 if i.op == 33 { 94 let lbl_cp: *u8 = sys_mmap(8); lbl_cp[0]=0x63; lbl_cp[1]=0x70; lbl_cp[2]=0x3D 95 sys_write(2, lbl_cp, 3) 96 _ird_write_i64(i.callee as i64) 97 if (i.callee as i64) != 0 { 98 let lbl_cn: *u8 = sys_mmap(8); lbl_cn[0]=0x63; lbl_cn[1]=0x6E; lbl_cn[2]=0x3D 99 sys_write(2, lbl_cn, 3) 100 _ird_write_i64(i.callee.name_start) 101 let lbl_cl: *u8 = sys_mmap(8); lbl_cl[0]=0x63; lbl_cl[1]=0x6C; lbl_cl[2]=0x3D 102 sys_write(2, lbl_cl, 3) 103 _ird_write_i64(i.callee.name_len) 104 } 105 } 106 107 let nl: *u8 = sys_mmap(4); nl[0]=0x0A 108 sys_write(2, nl, 1) 109 return 0 110} 111 112// Walk every block + every instruction of a function, dumping one 113// stderr line per instruction. 114// 115// f -- the function to dump 116// label -- short cstring tag prefix (e.g. "post-parse", "pre-emit") 117func nx_ir_dump_function(f: *Function, label: *u8) -> i64 { 118 // Header line: "== IR DUMP <label> name_off=N n_values=N n_blocks=N ==" 119 let hdr: *u8 = sys_mmap(64) 120 hdr[0]=0x3D; hdr[1]=0x3D; hdr[2]=0x20 // "== " 121 hdr[3]=0x49; hdr[4]=0x52; hdr[5]=0x20 // "IR " 122 hdr[6]=0x44; hdr[7]=0x55; hdr[8]=0x4D; hdr[9]=0x50; hdr[10]=0x20 // "DUMP " 123 sys_write(2, hdr, 11) 124 var ln: i64 = 0 125 while label[ln] != 0 { ln = ln + 1 } 126 sys_write(2, label, ln) 127 let sp: *u8 = sys_mmap(4); sp[0] = 0x20 128 sys_write(2, sp, 1) 129 let lbl_nv: *u8 = sys_mmap(8); lbl_nv[0]=0x6E; lbl_nv[1]=0x76; lbl_nv[2]=0x3D 130 sys_write(2, lbl_nv, 3) 131 _ird_write_i64(f.n_values) 132 let lbl_nb: *u8 = sys_mmap(8); lbl_nb[0]=0x6E; lbl_nb[1]=0x62; lbl_nb[2]=0x3D 133 sys_write(2, lbl_nb, 3) 134 _ird_write_i64(f.n_blocks) 135 let lbl_ni: *u8 = sys_mmap(8); lbl_ni[0]=0x6E; lbl_ni[1]=0x69; lbl_ni[2]=0x3D 136 sys_write(2, lbl_ni, 3) 137 _ird_write_i64(f.n_instrs) 138 let nl: *u8 = sys_mmap(4); nl[0]=0x0A 139 sys_write(2, nl, 1) 140 141 var bi: i64 = 0 142 var inst_idx: i64 = 0 143 while bi < f.n_blocks { 144 let base: i64 = f.blocks as i64 145 let b: *BasicBlock = (base + bi * 96) as *BasicBlock 146 var inst: *Instr = b.head 147 while inst != (0 as *Instr) { 148 _ird_dump_instr(f, inst, inst_idx) 149 inst_idx = inst_idx + 1 150 inst = inst.next 151 } 152 bi = bi + 1 153 } 154 return 0 155}