nx_diff.nx source
↩ module page · 182 lines · 5596 B
1// nx_diff.nx -- byte-level binary diff (sovereign cmp / xxd | diff).
2//
3// The F6 reproducibility gate compares the SHA-256 of compiled
4// outputs. When a build fails the gate, you get "hashes differ"
5// and nothing else -- you have to dig in to find WHERE. This
6// module implements the digging:
7//
8// - cmp-style: first differing byte offset (or -1 if equal).
9// - hex-window: dump 16-byte windows around each difference so
10// you can eyeball whether the diff is endian, padding, a
11// symbol-name string, or a relocation patch.
12// - diff stats: total differing bytes + % similarity.
13//
14// Pairs with bench/f6_manifest.txt + bootstrap_proof.sh. When a
15// stage-N hash drifts, run nx_diff against the stage-1 baseline to
16// find what changed.
17//
18// Not a textual diff -- this is for binaries. For source-level
19// diffs use git diff.
20
21// nx_safety_envelope:
22// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
23// sil_target: SIL1
24// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
25// verdict: NOT_YET_EVALUATED
26
27import "syscalls.nx"
28
29// Returns the offset of the first differing byte, or -1 if a == b
30// over the first `len` bytes. Bails as soon as it finds one --
31// O(len) worst case, O(1) common case for early divergence.
32func nx_diff_first(a: *u8, b: *u8, len: i64) -> i64 {
33 var i: i64 = 0
34 while i < len {
35 if a[i] != b[i] { return i }
36 i = i + 1
37 }
38 return -1
39}
40
41// Total count of differing bytes over `len`. Use for a similarity
42// percentage, not for early-exit.
43func nx_diff_count(a: *u8, b: *u8, len: i64) -> i64 {
44 var i: i64 = 0
45 var n: i64 = 0
46 while i < len {
47 if a[i] != b[i] { n = n + 1 }
48 i = i + 1
49 }
50 return n
51}
52
53// Print one byte as two hex chars to fd.
54func nx_diff_hex_byte(fd: i64, v: i64) -> i64 {
55 let buf: *u8 = sys_mmap(8)
56 let hi: i64 = (v >> 4) & 0xF
57 let lo: i64 = v & 0xF
58 if hi < 10 { buf[0] = 0x30 + hi } else { buf[0] = 0x57 + hi }
59 if lo < 10 { buf[1] = 0x30 + lo } else { buf[1] = 0x57 + lo }
60 sys_write(fd, buf, 2)
61 return 0
62}
63
64// Print `len` bytes from `p` as space-separated hex to fd. Replaces
65// non-printable bytes in a trailing ASCII column with `.`.
66func nx_diff_hex_window(fd: i64, p: *u8, len: i64) -> i64 {
67 var i: i64 = 0
68 while i < len {
69 nx_diff_hex_byte(fd, p[i])
70 sys_write(fd, " " as *u8, 1)
71 i = i + 1
72 }
73 sys_write(fd, " | " as *u8, 3)
74 var j: i64 = 0
75 while j < len {
76 let c: i64 = p[j]
77 let one: *u8 = sys_mmap(4)
78 if c >= 0x20 {
79 if c < 0x7F { one[0] = c } else { one[0] = 0x2E }
80 } else {
81 one[0] = 0x2E
82 }
83 sys_write(fd, one, 1)
84 j = j + 1
85 }
86 return 0
87}
88
89// Print decimal i64 to fd.
90func nx_diff_dec(fd: i64, v: i64) -> i64 {
91 if v == 0 { sys_write(fd, "0" as *u8, 1); return 0 }
92 let buf: *u8 = sys_mmap(32)
93 var n: i64 = v
94 var neg: i64 = 0
95 if n < 0 { neg = 1; n = 0 - n }
96 var k: i64 = 0
97 while n > 0 {
98 buf[k] = 0x30 + (n - (n / 10) * 10)
99 n = n / 10
100 k = k + 1
101 }
102 if neg == 1 { sys_write(fd, "-" as *u8, 1) }
103 var j: i64 = k - 1
104 while j >= 0 {
105 sys_write(fd, (((buf as i64) + j) as *u8), 1)
106 j = j - 1
107 }
108 return 0
109}
110
111// Pretty-print: header + first-diff offset + 16-byte windows of
112// both buffers around the diff. Returns 0 if equal, 1 if differing.
113// Returns -1 if `len_a != len_b` (header still printed).
114func nx_diff_report(fd: i64, a: *u8, b: *u8, len_a: i64, len_b: i64) -> i64 {
115 sys_write(fd, "diff: " as *u8, 6)
116 nx_diff_dec(fd, len_a)
117 sys_write(fd, " vs " as *u8, 4)
118 nx_diff_dec(fd, len_b)
119 sys_write(fd, " bytes\n" as *u8, 7)
120
121 if len_a != len_b {
122 sys_write(fd, " (length mismatch)\n" as *u8, 20)
123 return -1
124 }
125
126 let off: i64 = nx_diff_first(a, b, len_a)
127 if off < 0 {
128 sys_write(fd, " identical\n" as *u8, 12)
129 return 0
130 }
131
132 sys_write(fd, " first diff at offset " as *u8, 23)
133 nx_diff_dec(fd, off)
134 sys_write(fd, "\n" as *u8, 1)
135
136 // Print a 16-byte window starting `lo` <= off, clamped to
137 // [0, len_a). Rounds down to 16-byte boundary for readability.
138 let lo: i64 = (off / 16) * 16
139 var win: i64 = 16
140 if lo + win > len_a { win = len_a - lo }
141
142 sys_write(fd, " A: " as *u8, 5)
143 nx_diff_hex_window(fd, (((a as i64) + lo) as *u8), win)
144 sys_write(fd, "\n" as *u8, 1)
145
146 sys_write(fd, " B: " as *u8, 5)
147 nx_diff_hex_window(fd, (((b as i64) + lo) as *u8), win)
148 sys_write(fd, "\n" as *u8, 1)
149
150 let total: i64 = nx_diff_count(a, b, len_a)
151 sys_write(fd, " total differing bytes: " as *u8, 25)
152 nx_diff_dec(fd, total)
153 sys_write(fd, "\n" as *u8, 1)
154 return 1
155}
156
157// ---- self-test ---------------------------------------------------
158
159func main() -> i64 {
160 let a: *u8 = sys_mmap(64)
161 let b: *u8 = sys_mmap(64)
162 var i: i64 = 0
163 while i < 64 {
164 a[i] = i
165 b[i] = i
166 i = i + 1
167 }
168
169 if nx_diff_first(a, b, 64) != -1 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
170 if nx_diff_count(a, b, 64) != 0 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
171
172 b[20] = 0xFF
173 b[33] = 0xAA
174
175 if nx_diff_first(a, b, 64) != 20 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
176 if nx_diff_count(a, b, 64) != 2 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
177
178 // Length-mismatch path (only checks return code).
179 if nx_diff_report(2, a, b, 64, 32) != -1 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
180
181 return 0
182}