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}