nx_isa_conform_gate.nx source
↩ module page · 1094 lines · 44918 B
1// nx_isa_conform_gate.nx -- THE ISA CONFORMANCE RULER.
2//
3// WHY IT EXISTS: the estate carries TEN sovereign ISA emulators in
4// NishiLang (rv64 arm64 armv7a cortexm loongarch64 mips64 ppc64le
5// riscv32 s390x sparc64). Measured 2026-09-03 by nx_catalog: all ten
6// are SOURCE-ONLY -- 0 built, 0 promoted, 0 registered, 0 invoked --
7// ONE of them had a KAT (rv64), two more had x86 tests, and the only
8// differential proof in the estate was bench/gate_nx_emu_diff.sh, a
9// SHELL script that needs WSL plus riscv64 binutils plus qemu in order
10// to judge anything. Eight emulators had never been measured at all.
11//
12// THE LAW THIS ORGAN OBEYS: a capability asserted and never measured is
13// a claim, not coverage. "We emulate MIPS64" is decided by running
14// MIPS64 machine code, in process, against values taken FROM THE ISA
15// MANUAL -- never from the emulator's own source, which would calibrate
16// the ruler to the subject it is meant to judge.
17//
18// FIVE OUTCOMES, NEVER ONE:
19// PASS ran and matched the manual-derived expectation
20// WRONG-ANSWER ran to completion and computed the wrong value
21// UNSUPPORTED hit an instruction class the emulator does not implement
22// RAN-OFF-END the interpreter loop ended without an exit syscall
23// FAULT bad PC, out-of-range access, or the step budget
24// Collapsing these into "FAIL" is what makes a gap unactionable: an
25// unimplemented instruction class and a miscomputed result need
26// OPPOSITE fixes, and only the first is a missing feature.
27//
28// CORRECTION, 2026-09-03: the first cut of this header claimed "neither
29// emulator can return -2, so the codes do not collide with a real
30// answer". THAT WAS FALSE and I wrote it without reading far enough:
31// nx_emu_rv64.nx line 50 defines EMU_RV_RANOFF = -2 and line 123 uses
32// it as the INITIAL value of result, so -2 is exactly what comes back
33// when the loop ends with no exit syscall. Treating it as WRONG-ANSWER
34// would have reported a structural non-completion as an arithmetic
35// disagreement -- the same two-conditions-under-one-negative-word
36// defect this organ was written to refuse, committed inside the organ
37// that refuses it. Both exit paths mask with & 0xff (rv64 line 302,
38// mips64 SYSCALL), so a real answer is always 0..255 and the three
39// negative sentinels cannot collide with one.
40//
41// WHY A RATCHET AND NOT A BAR: several emulators are genuinely
42// incomplete today. When this organ was written mips64 implemented NO
43// conditional branch at all and could not run a loop; the ruler named
44// that gap, it was fixed the same day, and mips64 now measures 7/7.
45// Eight of the ten emulators are still entirely unmeasured.
46// A gate demanding completeness would be permanently RED, and a
47// permanently RED detector is one everyone learns to ignore. So the
48// VERDICT is "no arch regressed against its banked score" and the
49// absolute coverage is PUBLISHED AS A NUMBER beside it. The number is
50// the worklist; the verdict is the fence.
51//
52// DISPATCH, MEASURED NOT ASSUMED: emu_<arch>_run_mem(mem, size, entry,
53// sp0) is the ONLY surface all ten emulators share --
54// run_mem 10 of 10
55// run 6 of 10 (rv64 arm64 s390x armv7a cortexm riscv32)
56// load_elf 6 of 10 (rv64 mips64 arm64 ppc64le loongarch64 sparc64)
57// The two sixes are DIFFERENT SETS, so anything built on _run or on
58// _load_elf silently tests a subset while looking general. This organ
59// uses _run_mem only.
60//
61// license_tier: ORIGINAL
62
63import "nx_gate_verdict.nx"
64import "nx_syscalls.nx"
65import "nx_emu_rv64.nx"
66import "nx_emu_mips64.nx"
67import "nx_emu_arm64.nx"
68import "nx_emu_riscv32.nx"
69import "nx_emu_ppc64le.nx"
70
71// ---- guest layout THIS ORGAN owns (never read from an emulator) ----
72const IC_GUEST_BYTES: i64 = 1048576 // 1 MiB guest image
73const IC_ENTRY: i64 = 0 // KAT code is loaded at guest vaddr 0
74const IC_SP: i64 = 983040 // 960 KiB: above the code, inside the image
75const IC_MODE_FILE: i64 = 420 // 0644. Mirrors the consolidated MODE_0644;
76 // 420 and 0x1a4 are the SAME constant and a
77 // sweep that greps one misses the other.
78
79// ---- non-completion codes the interpreters return ----
80// Read out of the emulators' own constant tables, not guessed:
81// EMU_RV_UNSUPPORTED/MIE_UNSUPPORTED = -1, EMU_RV_RANOFF = -2,
82// EMU_RV_FAULT/MIE_FAULT = -3.
83const IC_UNSUPPORTED: i64 = -1
84const IC_RANOFF: i64 = -2
85const IC_FAULT: i64 = -3
86
87// ---- outcome classes ----
88const IC_O_PASS: i64 = 0
89const IC_O_WRONG: i64 = 1
90const IC_O_UNSUP: i64 = 2
91const IC_O_FAULT: i64 = 3
92const IC_O_RANOFF: i64 = 4
93
94// The non-PASS worklist buffer. A count without a worklist is not
95// actionable, and a worklist printed only in the BODY is lost the
96// moment a caller tails the output -- which is exactly how the first
97// run of this gate told me "rv64=5/6" and refused to say which one.
98// So the list is accumulated here and printed at the END, where every
99// positional reader looks.
100const IC_NB_BYTES: i64 = 2048
101const IC_NB_OFF: i64 = 0
102const IC_NB_PTR: i64 = 1
103// The longest single worklist row: two names, the outcome word, two
104// numbers and the punctuation. The write guard is DERIVED from this and
105// the buffer size rather than being a hand-picked cutoff -- a
106// hand-counted length beside a literal is a second copy of that
107// literal's shape, and the two drift silently.
108const IC_NB_ROW_MAX: i64 = 256
109const IC_BOX_BYTES: i64 = 32
110
111const IC_NL: i64 = 10
112const IC_SPACE: i64 = 32
113const IC_DIG_LO: i64 = 48
114const IC_DIG_HI: i64 = 57
115const IC_UNSEEDED: i64 = -1
116
117// A bare newline inside a string literal is ambiguous to this lexer, so
118// the byte is CONSTRUCTED. Same reason the estate's gallery emitter had
119// 780 unseparated ids once.
120func ic_nl() -> i64 {
121 let b: *u8 = sys_mmap(8)
122 b[0] = IC_NL as u8
123 sys_write(1, b, 1)
124 return 0
125}
126
127// ===== word emitters ============================================
128// Endianness is a PROPERTY OF THE TARGET ISA, so it is encoded per
129// architecture. rv64 is little-endian; MIPS64 n64 here is big-endian.
130// Getting this wrong is the silent-fixture defect: the emulator decodes
131// garbage, reports UNSUPPORTED, and the FIXTURE's bug reads as the
132// emulator being incomplete.
133
134func ic_put_le32(code: *u8, off: i64, w: i64) -> i64 {
135 code[off] = (w) & 0xff
136 code[off + 1] = (w >> 8) & 0xff
137 code[off + 2] = (w >> 16) & 0xff
138 code[off + 3] = (w >> 24) & 0xff
139 return off + 4
140}
141
142func ic_put_be32(code: *u8, off: i64, w: i64) -> i64 {
143 code[off] = (w >> 24) & 0xff
144 code[off + 1] = (w >> 16) & 0xff
145 code[off + 2] = (w >> 8) & 0xff
146 code[off + 3] = (w) & 0xff
147 return off + 4
148}
149
150func ic_guest() -> *u8 { return sys_mmap(IC_GUEST_BYTES) }
151
152// ===== outcome classification ===================================
153
154func ic_classify(got: i64, want: i64) -> i64 {
155 if got == IC_UNSUPPORTED { return IC_O_UNSUP }
156 if got == IC_RANOFF { return IC_O_RANOFF }
157 if got == IC_FAULT { return IC_O_FAULT }
158 if got == want { return IC_O_PASS }
159 return IC_O_WRONG
160}
161
162func ic_outcome_name(o: i64) -> *u8 {
163 if o == IC_O_PASS { return "PASS" as *u8 }
164 if o == IC_O_WRONG { return "WRONG-ANSWER" as *u8 }
165 if o == IC_O_UNSUP { return "UNSUPPORTED-instruction-class" as *u8 }
166 if o == IC_O_RANOFF { return "RAN-OFF-END-no-exit-syscall" as *u8 }
167 return "FAULT-bad-pc-or-step-budget" as *u8
168}
169
170// Print the VALUES, not just the verdict: every vacuous tooth ever
171// caught in this estate was caught by a diagnostic dump and never by a
172// verdict vector.
173func ic_report(arch: *u8, kat: *u8, got: i64, want: i64, bx: *i64) -> i64 {
174 let o: i64 = ic_classify(got, want)
175 gv_puts(" KAT " as *u8)
176 gv_puts(arch)
177 gv_puts("." as *u8)
178 gv_puts(kat)
179 gv_puts(" got=" as *u8)
180 gv_num(got)
181 gv_puts(" want=" as *u8)
182 gv_num(want)
183 gv_puts(" " as *u8)
184 gv_puts(ic_outcome_name(o))
185 ic_nl()
186 if o != IC_O_PASS {
187 let nb: *u8 = bx[IC_NB_PTR] as *u8
188 var p: i64 = bx[IC_NB_OFF]
189 if p < (IC_NB_BYTES - IC_NB_ROW_MAX) {
190 p = gv_cat(nb, p, arch)
191 p = gv_cat(nb, p, "." as *u8)
192 p = gv_cat(nb, p, kat)
193 p = gv_cat(nb, p, "=" as *u8)
194 p = gv_cat(nb, p, ic_outcome_name(o))
195 p = gv_cat(nb, p, "(got=" as *u8)
196 p = gv_catn(nb, p, got)
197 p = gv_cat(nb, p, " want=" as *u8)
198 p = gv_catn(nb, p, want)
199 p = gv_cat(nb, p, ") " as *u8)
200 nb[p] = 0 as u8
201 bx[IC_NB_OFF] = p
202 }
203 }
204 return o
205}
206
207// ===== RV64 KATs ================================================
208// RV64I encodings per the RISC-V unprivileged ISA manual. The first
209// three programs reuse words already oracle-verified against
210// riscv64-as and qemu-riscv64 by the incumbent harness, so a
211// disagreement on THOSE indicts a regression rather than a fresh
212// encoding mistake of mine.
213
214func ic_rv64_imm_arith() -> i64 {
215 let m: *u8 = ic_guest()
216 var o: i64 = 0
217 o = ic_put_le32(m, o, 0x02800513) // addi a0,zero,40
218 o = ic_put_le32(m, o, 0x00200293) // addi t0,zero,2
219 o = ic_put_le32(m, o, 0x00550533) // add a0,a0,t0 -> 42
220 o = ic_put_le32(m, o, 0x05d00893) // addi a7,zero,93 (exit)
221 o = ic_put_le32(m, o, 0x00000073) // ecall
222 return emu_rv64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
223}
224
225func ic_rv64_sub() -> i64 {
226 let m: *u8 = ic_guest()
227 var o: i64 = 0
228 o = ic_put_le32(m, o, 0x03200513) // addi a0,zero,50
229 o = ic_put_le32(m, o, 0x00800293) // addi t0,zero,8
230 o = ic_put_le32(m, o, 0x40550533) // sub a0,a0,t0 -> 42
231 o = ic_put_le32(m, o, 0x05d00893)
232 o = ic_put_le32(m, o, 0x00000073)
233 return emu_rv64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
234}
235
236func ic_rv64_branch_loop() -> i64 {
237 let m: *u8 = ic_guest()
238 var o: i64 = 0
239 o = ic_put_le32(m, o, 0x00000513) // li a0,0
240 o = ic_put_le32(m, o, 0x00000293) // li t0,0
241 o = ic_put_le32(m, o, 0x00a00313) // li t1,10
242 o = ic_put_le32(m, o, 0x00550533) // loop: add a0,a0,t0
243 o = ic_put_le32(m, o, 0x00128293) // addi t0,t0,1
244 o = ic_put_le32(m, o, 0xfe629ce3) // bne t0,t1,loop -> sum 0..9 = 45
245 o = ic_put_le32(m, o, 0x05d00893)
246 o = ic_put_le32(m, o, 0x00000073)
247 return emu_rv64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
248}
249
250// SRLI vs SRAI on a NEGATIVE value is the discrimination that matters,
251// and the obvious version of this test CANNOT make it: -16 shifted
252// right by 2 has low byte 0xFC under BOTH a logical and an arithmetic
253// shift, so an emulator that implements only one of them passes. A
254// shift by 60 separates them: logical -> 15, arithmetic -> -1, which
255// the exit-status mask renders as 255.
256func ic_rv64_shift_logical() -> i64 {
257 let m: *u8 = ic_guest()
258 var o: i64 = 0
259 o = ic_put_le32(m, o, 0xff000513) // addi a0,zero,-16
260 o = ic_put_le32(m, o, 0x03c55513) // srli a0,a0,60 -> 15
261 o = ic_put_le32(m, o, 0x05d00893)
262 o = ic_put_le32(m, o, 0x00000073)
263 return emu_rv64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
264}
265
266func ic_rv64_shift_arith() -> i64 {
267 let m: *u8 = ic_guest()
268 var o: i64 = 0
269 o = ic_put_le32(m, o, 0xff000513) // addi a0,zero,-16
270 o = ic_put_le32(m, o, 0x43c55513) // srai a0,a0,60 -> -1 -> 255
271 o = ic_put_le32(m, o, 0x05d00893)
272 o = ic_put_le32(m, o, 0x00000073)
273 return emu_rv64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
274}
275
276func ic_rv64_load_store() -> i64 {
277 let m: *u8 = ic_guest()
278 var o: i64 = 0
279 o = ic_put_le32(m, o, 0x02a00513) // addi a0,zero,42
280 o = ic_put_le32(m, o, 0xfea13c23) // sd a0,-8(sp)
281 o = ic_put_le32(m, o, 0xff813503) // ld a0,-8(sp) -> 42
282 o = ic_put_le32(m, o, 0x05d00893)
283 o = ic_put_le32(m, o, 0x00000073)
284 return emu_rv64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
285}
286
287// ===== MIPS64 encoders (from the MIPS64 instruction manual) ======
288// R-type: opcode 0, funct in bits 5..0. I-type: opcode in bits 31..26.
289// MIPS64 n64 exit is v0(r2)=5058 with the status in a0(r4).
290
291func ic_mi_addiu(rt: i64, rs: i64, imm: i64) -> i64 {
292 return (0x09 << 26) | (rs << 21) | (rt << 16) | (imm & 0xffff)
293}
294
295func ic_mi_ori(rt: i64, rs: i64, imm: i64) -> i64 {
296 return (0x0d << 26) | (rs << 21) | (rt << 16) | (imm & 0xffff)
297}
298
299func ic_mi_rtype(rs: i64, rt: i64, rd: i64, sa: i64, fn: i64) -> i64 {
300 return (rs << 21) | (rt << 16) | (rd << 11) | (sa << 6) | fn
301}
302
303func ic_mi_beq(rs: i64, rt: i64, off: i64) -> i64 {
304 return (0x04 << 26) | (rs << 21) | (rt << 16) | (off & 0xffff)
305}
306
307// Every MIPS64 KAT ends with this two-word exit sequence.
308func ic_mi_exit(m: *u8, o0: i64) -> i64 {
309 var o: i64 = o0
310 o = ic_put_be32(m, o, ic_mi_addiu(2, 0, 5058)) // li v0,5058 (exit)
311 o = ic_put_be32(m, o, ic_mi_rtype(0, 0, 0, 0, 0x0c)) // syscall
312 return o
313}
314
315func ic_mips64_imm_arith() -> i64 {
316 let m: *u8 = ic_guest()
317 var o: i64 = 0
318 o = ic_put_be32(m, o, ic_mi_addiu(4, 0, 40)) // li a0,40
319 o = ic_put_be32(m, o, ic_mi_addiu(5, 0, 2)) // li a1,2
320 o = ic_put_be32(m, o, ic_mi_rtype(4, 5, 4, 0, 0x2d)) // daddu a0,a0,a1 -> 42
321 o = ic_mi_exit(m, o)
322 return emu_mips64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
323}
324
325func ic_mips64_sub() -> i64 {
326 let m: *u8 = ic_guest()
327 var o: i64 = 0
328 o = ic_put_be32(m, o, ic_mi_addiu(4, 0, 50))
329 o = ic_put_be32(m, o, ic_mi_addiu(5, 0, 8))
330 o = ic_put_be32(m, o, ic_mi_rtype(4, 5, 4, 0, 0x2f)) // dsubu a0,a0,a1 -> 42
331 o = ic_mi_exit(m, o)
332 return emu_mips64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
333}
334
335func ic_mips64_logic() -> i64 {
336 let m: *u8 = ic_guest()
337 var o: i64 = 0
338 o = ic_put_be32(m, o, ic_mi_addiu(4, 0, 240))
339 o = ic_put_be32(m, o, ic_mi_ori(4, 4, 15)) // 240 or 15 = 255
340 o = ic_put_be32(m, o, ic_mi_addiu(5, 0, 60))
341 o = ic_put_be32(m, o, ic_mi_rtype(4, 5, 4, 0, 0x24)) // and a0,a0,a1 -> 60
342 o = ic_mi_exit(m, o)
343 return emu_mips64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
344}
345
346func ic_mips64_shift() -> i64 {
347 let m: *u8 = ic_guest()
348 var o: i64 = 0
349 o = ic_put_be32(m, o, ic_mi_addiu(4, 0, 1))
350 o = ic_put_be32(m, o, ic_mi_rtype(0, 4, 4, 20, 0x38)) // dsll a0,a0,20
351 o = ic_put_be32(m, o, ic_mi_rtype(0, 4, 4, 13, 0x3a)) // dsrl a0,a0,13 -> 128
352 o = ic_mi_exit(m, o)
353 return emu_mips64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
354}
355
356// DSRL must be LOGICAL where DSRA is arithmetic. The obvious test cannot tell
357// them apart: MIPS encodes the shift amount in a FIVE-bit field, so the largest
358// single DSRL is 31, and -16 shifted right by 31 has low byte 0xFF under BOTH
359// shifts -- the same trap as the rv64 pair, one field-width narrower. Shifting
360// TWICE by 31 separates them: logical gives 0x1FFFFFFFF then 3, arithmetic
361// gives -1 then -1 (255 after the exit mask).
362func ic_mips64_shift_logical() -> i64 {
363 let m: *u8 = ic_guest()
364 var o: i64 = 0
365 o = ic_put_be32(m, o, ic_mi_addiu(4, 0, 0 - 16)) // li a0,-16
366 o = ic_put_be32(m, o, ic_mi_rtype(0, 4, 4, 31, 0x3a)) // dsrl a0,a0,31
367 o = ic_put_be32(m, o, ic_mi_rtype(0, 4, 4, 31, 0x3a)) // dsrl a0,a0,31 -> 3
368 o = ic_mi_exit(m, o)
369 return emu_mips64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
370}
371
372// ADDIU is a 32-BIT add whose result is sign-extended to 64 bits; DADDIU is the
373// 64-bit form. Truncating to 32 bits NEVER changes the low byte, so no direct
374// comparison of exit statuses can separate them -- the difference lives entirely
375// in bits 32..63 and has to be shifted down to be seen. Build 0x80000000 (bit 31
376// set, bits 32+ clear), pass it through ADDIU, then shift right twice by 31: a
377// correct sign-extending ADDIU yields 3, an ADDIU that is secretly DADDIU yields 0.
378func ic_mips64_addiu32() -> i64 {
379 let m: *u8 = ic_guest()
380 var o: i64 = 0
381 o = ic_put_be32(m, o, ic_mi_addiu(4, 0, 1)) // li a0,1
382 o = ic_put_be32(m, o, ic_mi_rtype(0, 4, 4, 31, 0x38)) // dsll a0,a0,31 -> 0x80000000
383 o = ic_put_be32(m, o, ic_mi_addiu(5, 4, 0)) // addiu a1,a0,0 -> sign-extends
384 o = ic_put_be32(m, o, ic_mi_rtype(0, 5, 5, 31, 0x3a)) // dsrl a1,a1,31
385 o = ic_put_be32(m, o, ic_mi_rtype(0, 5, 5, 31, 0x3a)) // dsrl a1,a1,31 -> 3
386 o = ic_put_be32(m, o, ic_mi_rtype(5, 0, 4, 0, 0x25)) // or a0,a1,zero
387 o = ic_mi_exit(m, o)
388 return emu_mips64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
389}
390
391// Conditional branch WITH its delay slot honoured: the MIPS target is
392// (pc + 4) + (offset << 2) and the delay-slot instruction runs
393// REGARDLESS, so offset=2 lands past both the delay slot and the poison
394// instruction. This KAT is encoded CORRECTLY on purpose even though it
395// is expected to report UNSUPPORTED today: a fixture written to match
396// the current emulator would have to be rewritten the day someone
397// implements BEQ, and would silently bless whatever that
398// implementation happened to do.
399func ic_mips64_branch() -> i64 {
400 let m: *u8 = ic_guest()
401 var o: i64 = 0
402 o = ic_put_be32(m, o, ic_mi_addiu(4, 0, 42)) // li a0,42
403 o = ic_put_be32(m, o, ic_mi_beq(0, 0, 2)) // beq zero,zero,+2
404 o = ic_put_be32(m, o, ic_mi_rtype(0, 0, 0, 0, 0x00)) // nop (delay slot, always runs)
405 o = ic_put_be32(m, o, ic_mi_addiu(4, 0, 99)) // li a0,99 (must be skipped)
406 o = ic_mi_exit(m, o)
407 return emu_mips64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
408}
409
410// ===== AArch64 KATs =============================================
411// A64 encodings per the Arm Architecture Reference Manual. Register
412// field 31 is XZR, and the Linux aarch64 exit syscall is 93 in x8 with
413// the status in x0 -- the same number as RISC-V, which is a coincidence
414// of the Linux generic ABI and not of the two ISAs.
415// MOVZ Xd,#imm16 0xD2800000 | hw<<21 | imm16<<5 | Rd
416// MOVK Xd,#imm16,LSL#h 0xF2800000 | hw<<21 | imm16<<5 | Rd (keeps other lanes)
417// ADD Xd,Xn,Xm 0x8B000000 | Rm<<16 | Rn<<5 | Rd
418// MADD Xd,Xn,Xm,Xa 0x9B000000 | Rm<<16 | Ra<<10 | Rn<<5 | Rd
419// CBZ Xt,+imm19 0xB4000000 | imm19<<5 | Rt (offset in INSTRUCTIONS)
420// SVC #0 0xD4000001
421
422func ic_a64_movz(rd: i64, imm: i64, hw: i64) -> i64 {
423 return 0xD2800000 | (hw << 21) | ((imm & 0xffff) << 5) | rd
424}
425func ic_a64_movk(rd: i64, imm: i64, hw: i64) -> i64 {
426 return 0xF2800000 | (hw << 21) | ((imm & 0xffff) << 5) | rd
427}
428func ic_a64_add(rd: i64, rn: i64, rm: i64) -> i64 {
429 return 0x8B000000 | (rm << 16) | (rn << 5) | rd
430}
431func ic_a64_madd(rd: i64, rn: i64, rm: i64, ra: i64) -> i64 {
432 return 0x9B000000 | (rm << 16) | (ra << 10) | (rn << 5) | rd
433}
434func ic_a64_cbz(rt: i64, off: i64) -> i64 {
435 return 0xB4000000 | ((off & 0x7ffff) << 5) | rt
436}
437
438// Every AArch64 KAT ends with this two-word exit sequence.
439func ic_a64_exit(m: *u8, o0: i64) -> i64 {
440 var o: i64 = o0
441 o = ic_put_le32(m, o, ic_a64_movz(8, 93, 0)) // mov x8,#93 (exit)
442 o = ic_put_le32(m, o, 0xD4000001) // svc #0
443 return o
444}
445
446func ic_arm64_imm_arith() -> i64 {
447 let m: *u8 = ic_guest()
448 var o: i64 = 0
449 o = ic_put_le32(m, o, ic_a64_movz(0, 40, 0)) // mov x0,#40
450 o = ic_put_le32(m, o, ic_a64_movz(1, 2, 0)) // mov x1,#2
451 o = ic_put_le32(m, o, ic_a64_add(0, 0, 1)) // add x0,x0,x1 -> 42
452 o = ic_a64_exit(m, o)
453 return emu_arm64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
454}
455
456// MOVK must KEEP the lanes it is not writing. A MOVK implemented as a
457// second MOVZ would wipe the low half and return 0 -- which the exit
458// mask would render as 0, cleanly distinguishable from 42.
459func ic_arm64_movk_keeps_lanes() -> i64 {
460 let m: *u8 = ic_guest()
461 var o: i64 = 0
462 o = ic_put_le32(m, o, ic_a64_movz(0, 42, 0)) // mov x0,#42
463 o = ic_put_le32(m, o, ic_a64_movk(0, 1, 1)) // movk x0,#1,lsl#16 -> 0x1002A
464 o = ic_a64_exit(m, o) // exit status masks to 42
465 return emu_arm64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
466}
467
468func ic_arm64_madd() -> i64 {
469 let m: *u8 = ic_guest()
470 var o: i64 = 0
471 o = ic_put_le32(m, o, ic_a64_movz(1, 6, 0)) // mov x1,#6
472 o = ic_put_le32(m, o, ic_a64_movz(2, 7, 0)) // mov x2,#7
473 o = ic_put_le32(m, o, ic_a64_movz(3, 0, 0)) // mov x3,#0
474 o = ic_put_le32(m, o, ic_a64_madd(0, 1, 2, 3)) // madd x0,x1,x2,x3 -> 42
475 o = ic_a64_exit(m, o)
476 return emu_arm64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
477}
478
479// CBZ with a TAKEN branch. AArch64 has no delay slot, so the offset is
480// measured in instructions from the branch itself: +2 skips exactly the
481// one poison instruction that follows.
482func ic_arm64_cbz_taken() -> i64 {
483 let m: *u8 = ic_guest()
484 var o: i64 = 0
485 o = ic_put_le32(m, o, ic_a64_movz(0, 42, 0)) // mov x0,#42
486 o = ic_put_le32(m, o, ic_a64_movz(1, 0, 0)) // mov x1,#0
487 o = ic_put_le32(m, o, ic_a64_cbz(1, 2)) // cbz x1,+2 (taken)
488 o = ic_put_le32(m, o, ic_a64_movz(0, 99, 0)) // mov x0,#99 (must be skipped)
489 o = ic_a64_exit(m, o)
490 return emu_arm64_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
491}
492
493
494// ===== RISC-V RV32 KATs =========================================
495// RV32I shares its base encoding with RV64I, so the arithmetic words here are
496// the SAME oracle-verified words the rv64 block uses -- that is the point of a
497// shared base ISA and it makes these the cheapest KATs on the board. What is
498// NOT shared is the register width: RV32 masks every result to 32 bits.
499// The branch loop exercises conditional branches that DID NOT EXIST in this
500// interpreter until 2026-09-03; before that RV32 could not run a loop at all.
501
502func ic_riscv32_imm_arith() -> i64 {
503 let m: *u8 = ic_guest()
504 var o: i64 = 0
505 o = ic_put_le32(m, o, 0x02800513) // addi a0,zero,40
506 o = ic_put_le32(m, o, 0x00200293) // addi t0,zero,2
507 o = ic_put_le32(m, o, 0x00550533) // add a0,a0,t0 -> 42
508 o = ic_put_le32(m, o, 0x05d00893) // addi a7,zero,93
509 o = ic_put_le32(m, o, 0x00000073) // ecall
510 return emu_riscv32_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
511}
512
513func ic_riscv32_sub() -> i64 {
514 let m: *u8 = ic_guest()
515 var o: i64 = 0
516 o = ic_put_le32(m, o, 0x03200513) // addi a0,zero,50
517 o = ic_put_le32(m, o, 0x00800293) // addi t0,zero,8
518 o = ic_put_le32(m, o, 0x40550533) // sub a0,a0,t0 -> 42
519 o = ic_put_le32(m, o, 0x05d00893)
520 o = ic_put_le32(m, o, 0x00000073)
521 return emu_riscv32_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
522}
523
524func ic_riscv32_logic() -> i64 {
525 let m: *u8 = ic_guest()
526 var o: i64 = 0
527 o = ic_put_le32(m, o, 0x0f000513) // addi a0,zero,240
528 o = ic_put_le32(m, o, 0x00f00293) // addi t0,zero,15
529 o = ic_put_le32(m, o, 0x00556533) // or a0,a0,t0 -> 255
530 o = ic_put_le32(m, o, 0x03c00313) // addi t1,zero,60
531 o = ic_put_le32(m, o, 0x00657533) // and a0,a0,t1 -> 60
532 o = ic_put_le32(m, o, 0x05d00893)
533 o = ic_put_le32(m, o, 0x00000073)
534 return emu_riscv32_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
535}
536
537func ic_riscv32_mul() -> i64 {
538 let m: *u8 = ic_guest()
539 var o: i64 = 0
540 o = ic_put_le32(m, o, 0x00600513) // addi a0,zero,6
541 o = ic_put_le32(m, o, 0x00700293) // addi t0,zero,7
542 o = ic_put_le32(m, o, 0x02550533) // mul a0,a0,t0 -> 42 (M extension, funct7=1)
543 o = ic_put_le32(m, o, 0x05d00893)
544 o = ic_put_le32(m, o, 0x00000073)
545 return emu_riscv32_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
546}
547
548func ic_riscv32_load_store() -> i64 {
549 let m: *u8 = ic_guest()
550 var o: i64 = 0
551 o = ic_put_le32(m, o, 0x02a00513) // addi a0,zero,42
552 o = ic_put_le32(m, o, 0xfea12c23) // sw a0,-8(sp) (32-bit store, funct3=2)
553 o = ic_put_le32(m, o, 0xff812503) // lw a0,-8(sp) -> 42
554 o = ic_put_le32(m, o, 0x05d00893)
555 o = ic_put_le32(m, o, 0x00000073)
556 return emu_riscv32_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
557}
558
559func ic_riscv32_branch_loop() -> i64 {
560 let m: *u8 = ic_guest()
561 var o: i64 = 0
562 o = ic_put_le32(m, o, 0x00000513) // li a0,0
563 o = ic_put_le32(m, o, 0x00000293) // li t0,0
564 o = ic_put_le32(m, o, 0x00a00313) // li t1,10
565 o = ic_put_le32(m, o, 0x00550533) // loop: add a0,a0,t0
566 o = ic_put_le32(m, o, 0x00128293) // addi t0,t0,1
567 o = ic_put_le32(m, o, 0xfe629ce3) // bne t0,t1,loop -> sum 0..9 = 45
568 o = ic_put_le32(m, o, 0x05d00893)
569 o = ic_put_le32(m, o, 0x00000073)
570 return emu_riscv32_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
571}
572
573
574// ===== PowerPC64 LE KATs ========================================
575// Power ISA encodings. PPC numbers bits MSB=0, so a field at PPC bits [a..b]
576// sits at standard bits [31-b .. 31-a]; the emulator's own header states the
577// derivation and this block follows it: primary opcode = (w >> 26) & 0x3F,
578// RT/RS = (w >> 21) & 0x1F, RA = (w >> 16) & 0x1F, RB = (w >> 11) & 0x1F,
579// XO = (w >> 1) & 0x3FF.
580// ppc64le stores INSTRUCTIONS little-endian, so these are emitted LE even
581// though Power is a big-endian architecture by heritage -- getting that
582// backwards is the silent-fixture defect, and it is why the emitter is chosen
583// per ISA rather than per family.
584// Linux ppc exit is sc with r0 = 1 and the status in r3.
585// li RT,v = addi RT,0,v opcode 14
586// add RT,RA,RB opcode 31 XO 266 subf RT,RA,RB opcode 31 XO 40 (RB - RA)
587// mulld opcode 31 XO 233 or RA,RS,RB opcode 31 XO 444 (dest RA)
588// and RA,RS,RB opcode 31 XO 28 sc opcode 17
589
590func ic_pp_exit(m: *u8, o0: i64) -> i64 {
591 var o: i64 = o0
592 o = ic_put_le32(m, o, 0x38000001) // li r0,1 (exit)
593 o = ic_put_le32(m, o, 0x44000002) // sc
594 return o
595}
596
597func ic_ppc64le_imm_arith() -> i64 {
598 let m: *u8 = ic_guest()
599 var o: i64 = 0
600 o = ic_put_le32(m, o, 0x38600028) // li r3,40
601 o = ic_put_le32(m, o, 0x38800002) // li r4,2
602 o = ic_put_le32(m, o, 0x7c632214) // add r3,r3,r4 -> 42
603 o = ic_pp_exit(m, o)
604 return emu_ppc64le_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
605}
606
607// subf computes RB - RA, NOT RA - RB. Encoding it the intuitive way round is
608// the classic Power mistake and would silently yield -42 here.
609func ic_ppc64le_sub() -> i64 {
610 let m: *u8 = ic_guest()
611 var o: i64 = 0
612 o = ic_put_le32(m, o, 0x38600032) // li r3,50
613 o = ic_put_le32(m, o, 0x38800008) // li r4,8
614 o = ic_put_le32(m, o, 0x7c641850) // subf r3,r4,r3 -> r3 - r4 = 42
615 o = ic_pp_exit(m, o)
616 return emu_ppc64le_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
617}
618
619func ic_ppc64le_mul() -> i64 {
620 let m: *u8 = ic_guest()
621 var o: i64 = 0
622 o = ic_put_le32(m, o, 0x38600006) // li r3,6
623 o = ic_put_le32(m, o, 0x38800007) // li r4,7
624 o = ic_put_le32(m, o, 0x7c6321d2) // mulld r3,r3,r4 -> 42
625 o = ic_pp_exit(m, o)
626 return emu_ppc64le_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
627}
628
629// or and and write to RA, with the SOURCE in the RT/RS field -- the operand
630// roles are the reverse of every other arch on this board.
631func ic_ppc64le_logic() -> i64 {
632 let m: *u8 = ic_guest()
633 var o: i64 = 0
634 o = ic_put_le32(m, o, 0x386000f0) // li r3,240
635 o = ic_put_le32(m, o, 0x3880000f) // li r4,15
636 o = ic_put_le32(m, o, 0x7c632378) // or r3,r3,r4 -> 255
637 o = ic_put_le32(m, o, 0x38a0003c) // li r5,60
638 o = ic_put_le32(m, o, 0x7c632838) // and r3,r3,r5 -> 60
639 o = ic_pp_exit(m, o)
640 return emu_ppc64le_run_mem(m, IC_GUEST_BYTES, IC_ENTRY, IC_SP)
641}
642
643// ===== ratchet ==================================================
644// One line per arch: "<arch> <passed>". UNSEEDED is a NAMED third
645// state, so adopting this fence cannot fail closed on first sight.
646// Polarity matters and inverts from an offender-count ratchet: this
647// metric is GOOD-when-higher, so it TIGHTENS on a rise and must NEVER
648// rewrite its baseline on a fall -- rewriting on a fall is how a
649// ratchet launders itself green.
650
651const IC_RATCHET: *u8 = "knowledge/status/isa_conform.ratchet"
652
653func ic_match_at(buf: *u8, n: i64, p: i64, s: *u8) -> i64 {
654 var j: i64 = 0
655 var ok: i64 = 1
656 while s[j] != (0 as u8) {
657 if (p + j) >= n { ok = 0 }
658 if ok == 1 {
659 if buf[p + j] != s[j] { ok = 0 }
660 }
661 j = j + 1
662 }
663 if ok == 0 { return -1 }
664 return j
665}
666
667func ic_digits_at(buf: *u8, n: i64, p0: i64) -> i64 {
668 var v: i64 = 0
669 var any: i64 = 0
670 var p: i64 = p0
671 var go: i64 = 1
672 while go == 1 {
673 if p >= n { go = 0 }
674 if go == 1 {
675 let c: i64 = buf[p] & 0xff
676 if c < IC_DIG_LO { go = 0 }
677 if go == 1 {
678 if c > IC_DIG_HI { go = 0 }
679 }
680 if go == 1 {
681 v = v * 10 + (c - IC_DIG_LO)
682 any = 1
683 p = p + 1
684 }
685 }
686 }
687 if any == 0 { return -1 }
688 return v
689}
690
691func ic_ratchet_read(arch: *u8) -> i64 {
692 let lenbox: *i64 = sys_mmap(16) as *i64
693 let buf: *u8 = sys_read_file(IC_RATCHET, lenbox)
694 if (buf as i64) == 0 { return IC_UNSEEDED }
695 let n: i64 = lenbox[0]
696 var i: i64 = 0
697 var found: i64 = IC_UNSEEDED
698 while i < n {
699 if found < 0 {
700 let m: i64 = ic_match_at(buf, n, i, arch)
701 if m > 0 {
702 if (i + m) < n {
703 if buf[i + m] == (IC_SPACE as u8) {
704 found = ic_digits_at(buf, n, i + m + 1)
705 }
706 }
707 }
708 }
709 var adv: i64 = 1
710 while adv == 1 {
711 if i >= n { adv = 0 }
712 if adv == 1 {
713 if buf[i] == (IC_NL as u8) { adv = 0 }
714 i = i + 1
715 }
716 }
717 }
718 return found
719}
720
721// Writes the whole two-arch baseline. Callers must have already decided
722// that NO arch regressed -- this function does not re-check, and the
723// tooth that guards it is bite-proven in nx_isa_conform_bite_gate.
724func ic_ratchet_write(rv: i64, mi: i64, a64: i64, r32: i64) -> i64 {
725 let d: *u8 = sys_mmap(256)
726 var o: i64 = 0
727 o = gv_cat(d, o, "rv64 " as *u8)
728 o = gv_catn(d, o, rv)
729 d[o] = IC_NL as u8
730 o = o + 1
731 o = gv_cat(d, o, "mips64 " as *u8)
732 o = gv_catn(d, o, mi)
733 d[o] = IC_NL as u8
734 o = o + 1
735 o = gv_cat(d, o, "arm64 " as *u8)
736 o = gv_catn(d, o, a64)
737 d[o] = IC_NL as u8
738 o = o + 1
739 o = gv_cat(d, o, "riscv32 " as *u8)
740 o = gv_catn(d, o, r32)
741 d[o] = IC_NL as u8
742 o = o + 1
743 let fd: i64 = sys_openat_wr(IC_RATCHET, IC_MODE_FILE)
744 if fd < 0 { return -1 }
745 sys_write(fd, d, o)
746 sys_fsync(fd)
747 sys_close(fd)
748 return o
749}
750
751// ===== main =====================================================
752
753func main(argc: i64, argv: *i64) -> i64 {
754 gv_head("nx_isa_conform_gate -- sovereign ISA emulator conformance, KATs encoded from the ISA manuals" as *u8)
755 let ctr: *i64 = gv_ctr()
756
757 // The non-PASS worklist: buffer address and write offset in one box.
758 let bx: *i64 = sys_mmap(IC_BOX_BYTES) as *i64
759 let nbuf: *u8 = sys_mmap(IC_NB_BYTES)
760 nbuf[0] = 0 as u8
761 bx[IC_NB_OFF] = 0
762 bx[IC_NB_PTR] = nbuf as i64
763
764 gv_puts("dispatch=emu_<arch>_run_mem (the only surface all 10 emulators share: run 6/10, load_elf a DIFFERENT 6/10)" as *u8)
765 ic_nl()
766 gv_puts("outcomes are FIVE-WAY: PASS / WRONG-ANSWER / UNSUPPORTED-instruction-class / RAN-OFF-END / FAULT" as *u8)
767 ic_nl()
768 ic_nl()
769
770 // ---- rv64 ----
771 gv_puts("[rv64] RISC-V RV64I -- little-endian, no delay slots" as *u8)
772 ic_nl()
773 var rv_pass: i64 = 0
774 var rv_tot: i64 = 0
775 var o: i64 = 0
776
777 o = ic_report("rv64" as *u8, "imm-arith" as *u8, ic_rv64_imm_arith(), 42, bx)
778 rv_tot = rv_tot + 1
779 if o == IC_O_PASS { rv_pass = rv_pass + 1 }
780 gv_check("isa-rv64-imm-arith" as *u8, o == IC_O_PASS, ctr)
781
782 o = ic_report("rv64" as *u8, "sub" as *u8, ic_rv64_sub(), 42, bx)
783 rv_tot = rv_tot + 1
784 if o == IC_O_PASS { rv_pass = rv_pass + 1 }
785 gv_check("isa-rv64-sub" as *u8, o == IC_O_PASS, ctr)
786
787 o = ic_report("rv64" as *u8, "branch-loop" as *u8, ic_rv64_branch_loop(), 45, bx)
788 rv_tot = rv_tot + 1
789 if o == IC_O_PASS { rv_pass = rv_pass + 1 }
790 gv_check("isa-rv64-branch-loop" as *u8, o == IC_O_PASS, ctr)
791
792 o = ic_report("rv64" as *u8, "shift-logical" as *u8, ic_rv64_shift_logical(), 15, bx)
793 rv_tot = rv_tot + 1
794 if o == IC_O_PASS { rv_pass = rv_pass + 1 }
795 gv_check("isa-rv64-shift-logical" as *u8, o == IC_O_PASS, ctr)
796
797 o = ic_report("rv64" as *u8, "shift-arith" as *u8, ic_rv64_shift_arith(), 255, bx)
798 rv_tot = rv_tot + 1
799 if o == IC_O_PASS { rv_pass = rv_pass + 1 }
800 gv_check("isa-rv64-shift-arith" as *u8, o == IC_O_PASS, ctr)
801
802 o = ic_report("rv64" as *u8, "load-store" as *u8, ic_rv64_load_store(), 42, bx)
803 rv_tot = rv_tot + 1
804 if o == IC_O_PASS { rv_pass = rv_pass + 1 }
805 gv_check("isa-rv64-load-store" as *u8, o == IC_O_PASS, ctr)
806
807 // ---- mips64 ----
808 ic_nl()
809 gv_puts("[mips64] MIPS64 n64 -- BIG-endian, PC/nPC delay-slot model" as *u8)
810 ic_nl()
811 var mi_pass: i64 = 0
812 var mi_tot: i64 = 0
813
814 o = ic_report("mips64" as *u8, "imm-arith" as *u8, ic_mips64_imm_arith(), 42, bx)
815 mi_tot = mi_tot + 1
816 if o == IC_O_PASS { mi_pass = mi_pass + 1 }
817 gv_check("isa-mips64-imm-arith" as *u8, o == IC_O_PASS, ctr)
818
819 o = ic_report("mips64" as *u8, "sub" as *u8, ic_mips64_sub(), 42, bx)
820 mi_tot = mi_tot + 1
821 if o == IC_O_PASS { mi_pass = mi_pass + 1 }
822 gv_check("isa-mips64-sub" as *u8, o == IC_O_PASS, ctr)
823
824 o = ic_report("mips64" as *u8, "logic" as *u8, ic_mips64_logic(), 60, bx)
825 mi_tot = mi_tot + 1
826 if o == IC_O_PASS { mi_pass = mi_pass + 1 }
827 gv_check("isa-mips64-logic" as *u8, o == IC_O_PASS, ctr)
828
829 o = ic_report("mips64" as *u8, "shift" as *u8, ic_mips64_shift(), 128, bx)
830 mi_tot = mi_tot + 1
831 if o == IC_O_PASS { mi_pass = mi_pass + 1 }
832 gv_check("isa-mips64-shift" as *u8, o == IC_O_PASS, ctr)
833
834 o = ic_report("mips64" as *u8, "shift-logical-vs-arith" as *u8, ic_mips64_shift_logical(), 3, bx)
835 mi_tot = mi_tot + 1
836 if o == IC_O_PASS { mi_pass = mi_pass + 1 }
837 gv_check("isa-mips64-shift-logical-vs-arith" as *u8, o == IC_O_PASS, ctr)
838
839 o = ic_report("mips64" as *u8, "addiu-is-32-bit" as *u8, ic_mips64_addiu32(), 3, bx)
840 mi_tot = mi_tot + 1
841 if o == IC_O_PASS { mi_pass = mi_pass + 1 }
842 gv_check("isa-mips64-addiu-is-32-bit" as *u8, o == IC_O_PASS, ctr)
843
844 // Reported and COUNTED. This was deliberately NOT a gv_check while the
845 // emulator had no conditional branch at all, because a tooth that cannot
846 // pass makes a detector permanently RED. The branches landed 2026-09-03,
847 // so it is a full tooth now -- which is exactly the sequence a ratchet is
848 // supposed to produce: name the gap, close the gap, then fence it.
849 let mb: i64 = ic_report("mips64" as *u8, "branch-delay-slot" as *u8, ic_mips64_branch(), 42, bx)
850 mi_tot = mi_tot + 1
851 if mb == IC_O_PASS { mi_pass = mi_pass + 1 }
852 gv_check("isa-mips64-branch-delay-slot" as *u8, mb == IC_O_PASS, ctr)
853
854
855 // ---- arm64 ----
856 ic_nl()
857 gv_puts("[arm64] Arm A64 -- little-endian, no delay slots, XZR is register 31" as *u8)
858 ic_nl()
859 var a64_pass: i64 = 0
860 var a64_tot: i64 = 0
861
862 o = ic_report("arm64" as *u8, "imm-arith" as *u8, ic_arm64_imm_arith(), 42, bx)
863 a64_tot = a64_tot + 1
864 if o == IC_O_PASS { a64_pass = a64_pass + 1 }
865 gv_check("isa-arm64-imm-arith" as *u8, o == IC_O_PASS, ctr)
866
867 o = ic_report("arm64" as *u8, "movk-keeps-lanes" as *u8, ic_arm64_movk_keeps_lanes(), 42, bx)
868 a64_tot = a64_tot + 1
869 if o == IC_O_PASS { a64_pass = a64_pass + 1 }
870 gv_check("isa-arm64-movk-keeps-lanes" as *u8, o == IC_O_PASS, ctr)
871
872 o = ic_report("arm64" as *u8, "madd" as *u8, ic_arm64_madd(), 42, bx)
873 a64_tot = a64_tot + 1
874 if o == IC_O_PASS { a64_pass = a64_pass + 1 }
875 gv_check("isa-arm64-madd" as *u8, o == IC_O_PASS, ctr)
876
877 o = ic_report("arm64" as *u8, "cbz-taken" as *u8, ic_arm64_cbz_taken(), 42, bx)
878 a64_tot = a64_tot + 1
879 if o == IC_O_PASS { a64_pass = a64_pass + 1 }
880 gv_check("isa-arm64-cbz-taken" as *u8, o == IC_O_PASS, ctr)
881
882
883 // ---- riscv32 ----
884 ic_nl()
885 gv_puts("[riscv32] RISC-V RV32IM -- little-endian, 32-bit XLEN, shares the RV64 base encoding" as *u8)
886 ic_nl()
887 var r32_pass: i64 = 0
888 var r32_tot: i64 = 0
889
890 o = ic_report("riscv32" as *u8, "imm-arith" as *u8, ic_riscv32_imm_arith(), 42, bx)
891 r32_tot = r32_tot + 1
892 if o == IC_O_PASS { r32_pass = r32_pass + 1 }
893 gv_check("isa-riscv32-imm-arith" as *u8, o == IC_O_PASS, ctr)
894
895 o = ic_report("riscv32" as *u8, "sub" as *u8, ic_riscv32_sub(), 42, bx)
896 r32_tot = r32_tot + 1
897 if o == IC_O_PASS { r32_pass = r32_pass + 1 }
898 gv_check("isa-riscv32-sub" as *u8, o == IC_O_PASS, ctr)
899
900 o = ic_report("riscv32" as *u8, "logic" as *u8, ic_riscv32_logic(), 60, bx)
901 r32_tot = r32_tot + 1
902 if o == IC_O_PASS { r32_pass = r32_pass + 1 }
903 gv_check("isa-riscv32-logic" as *u8, o == IC_O_PASS, ctr)
904
905 o = ic_report("riscv32" as *u8, "mul" as *u8, ic_riscv32_mul(), 42, bx)
906 r32_tot = r32_tot + 1
907 if o == IC_O_PASS { r32_pass = r32_pass + 1 }
908 gv_check("isa-riscv32-mul" as *u8, o == IC_O_PASS, ctr)
909
910 o = ic_report("riscv32" as *u8, "load-store" as *u8, ic_riscv32_load_store(), 42, bx)
911 r32_tot = r32_tot + 1
912 if o == IC_O_PASS { r32_pass = r32_pass + 1 }
913 gv_check("isa-riscv32-load-store" as *u8, o == IC_O_PASS, ctr)
914
915 o = ic_report("riscv32" as *u8, "branch-loop" as *u8, ic_riscv32_branch_loop(), 45, bx)
916 r32_tot = r32_tot + 1
917 if o == IC_O_PASS { r32_pass = r32_pass + 1 }
918 gv_check("isa-riscv32-branch-loop" as *u8, o == IC_O_PASS, ctr)
919
920
921 // ---- ppc64le ----
922 ic_nl()
923 gv_puts("[ppc64le] PowerPC64 little-endian -- Power ISA, MSB=0 bit numbering, sc syscall" as *u8)
924 ic_nl()
925 var pp_pass: i64 = 0
926 var pp_tot: i64 = 0
927
928 o = ic_report("ppc64le" as *u8, "imm-arith" as *u8, ic_ppc64le_imm_arith(), 42, bx)
929 pp_tot = pp_tot + 1
930 if o == IC_O_PASS { pp_pass = pp_pass + 1 }
931 gv_check("isa-ppc64le-imm-arith" as *u8, o == IC_O_PASS, ctr)
932
933 o = ic_report("ppc64le" as *u8, "subf-operand-order" as *u8, ic_ppc64le_sub(), 42, bx)
934 pp_tot = pp_tot + 1
935 if o == IC_O_PASS { pp_pass = pp_pass + 1 }
936 gv_check("isa-ppc64le-subf-operand-order" as *u8, o == IC_O_PASS, ctr)
937
938 o = ic_report("ppc64le" as *u8, "mulld" as *u8, ic_ppc64le_mul(), 42, bx)
939 pp_tot = pp_tot + 1
940 if o == IC_O_PASS { pp_pass = pp_pass + 1 }
941 gv_check("isa-ppc64le-mulld" as *u8, o == IC_O_PASS, ctr)
942
943 o = ic_report("ppc64le" as *u8, "logic-dest-is-RA" as *u8, ic_ppc64le_logic(), 60, bx)
944 pp_tot = pp_tot + 1
945 if o == IC_O_PASS { pp_pass = pp_pass + 1 }
946 gv_check("isa-ppc64le-logic-dest-is-RA" as *u8, o == IC_O_PASS, ctr)
947
948 // ---- the fixture must have REACHED the condition -------------
949 // Asserting an outcome without asserting the fixture ran is how
950 // four vacuous fixtures shipped in this estate in a single day.
951 ic_nl()
952 gv_check("fixture-reached-rv64-emulator-at-all" as *u8, rv_pass > 0, ctr)
953 gv_check("fixture-reached-mips64-emulator-at-all" as *u8, mi_pass > 0, ctr)
954 gv_check("fixture-reached-arm64-emulator-at-all" as *u8, a64_pass > 0, ctr)
955 gv_check("fixture-reached-riscv32-emulator-at-all" as *u8, r32_pass > 0, ctr)
956 gv_check("fixture-reached-ppc64le-emulator-at-all" as *u8, pp_pass > 0, ctr)
957
958 // ---- negative controls ---------------------------------------
959 // A comparison that cannot fail proves nothing. If ic_classify were
960 // stubbed to return PASS, every green above would be fake and only
961 // these two teeth would notice.
962 let ncw: i64 = ic_classify(ic_rv64_imm_arith(), 41)
963 gv_puts(" neg-control rv64.imm-arith against a deliberately wrong want=41 -> " as *u8)
964 gv_puts(ic_outcome_name(ncw))
965 ic_nl()
966 gv_check("neg-control-wrong-expectation-must-not-pass" as *u8, ncw == IC_O_WRONG, ctr)
967
968 let ncu: i64 = ic_classify(IC_UNSUPPORTED, -1)
969 gv_check("neg-control-unsupported-never-reads-as-pass" as *u8, ncu == IC_O_UNSUP, ctr)
970
971 let ncf: i64 = ic_classify(IC_FAULT, -3)
972 gv_check("neg-control-fault-never-reads-as-pass" as *u8, ncf == IC_O_FAULT, ctr)
973
974 // ---- coverage, published as a NUMBER not a verdict ------------
975 ic_nl()
976 gv_puts("coverage rv64=" as *u8)
977 gv_num(rv_pass)
978 gv_puts("/" as *u8)
979 gv_num(rv_tot)
980 gv_puts(" mips64=" as *u8)
981 gv_num(mi_pass)
982 gv_puts("/" as *u8)
983 gv_num(mi_tot)
984 gv_puts(" arm64=" as *u8)
985 gv_num(a64_pass)
986 gv_puts("/" as *u8)
987 gv_num(a64_tot)
988 gv_puts(" riscv32=" as *u8)
989 gv_num(r32_pass)
990 gv_puts("/" as *u8)
991 gv_num(r32_tot)
992 gv_puts(" ppc64le=" as *u8)
993 gv_num(pp_pass)
994 gv_puts("/" as *u8)
995 gv_num(pp_tot)
996 gv_puts(" archs_measured=5 archs_present=10 archs_unmeasured=5" as *u8)
997 ic_nl()
998 gv_puts("UNMEASURED: armv7a cortexm loongarch64 s390x sparc64 -- each owes a manual-derived KAT block here" as *u8)
999 ic_nl()
1000
1001 // ---- ratchet -------------------------------------------------
1002 let rv_base: i64 = ic_ratchet_read("rv64" as *u8)
1003 let mi_base: i64 = ic_ratchet_read("mips64" as *u8)
1004 let a64_base: i64 = ic_ratchet_read("arm64" as *u8)
1005 let r32_base: i64 = ic_ratchet_read("riscv32" as *u8)
1006 gv_puts("ratchet rv64 base=" as *u8)
1007 gv_num(rv_base)
1008 gv_puts(" now=" as *u8)
1009 gv_num(rv_pass)
1010 gv_puts(" | mips64 base=" as *u8)
1011 gv_num(mi_base)
1012 gv_puts(" now=" as *u8)
1013 gv_num(mi_pass)
1014 gv_puts(" | arm64 base=" as *u8)
1015 gv_num(a64_base)
1016 gv_puts(" now=" as *u8)
1017 gv_num(a64_pass)
1018 gv_puts(" | riscv32 base=" as *u8)
1019 gv_num(r32_base)
1020 gv_puts(" now=" as *u8)
1021 gv_num(r32_pass)
1022 gv_puts(" (base=-1 is UNSEEDED, a named state: first sight seeds, so adoption cannot fail closed)" as *u8)
1023 ic_nl()
1024
1025 var no_regress: i64 = 1
1026 if rv_base >= 0 {
1027 if rv_pass < rv_base { no_regress = 0 }
1028 }
1029 if mi_base >= 0 {
1030 if mi_pass < mi_base { no_regress = 0 }
1031 }
1032 if a64_base >= 0 {
1033 if a64_pass < a64_base { no_regress = 0 }
1034 }
1035 if r32_base >= 0 {
1036 if r32_pass < r32_base { no_regress = 0 }
1037 }
1038
1039 // Seed when unseeded; tighten only on a rise; NEVER rewrite on a
1040 // fall, which is the direction that would launder a regression.
1041 var wrote: i64 = 0
1042 if no_regress == 1 {
1043 if rv_base < 0 { wrote = ic_ratchet_write(rv_pass, mi_pass, a64_pass, r32_pass) }
1044 if wrote == 0 {
1045 if mi_base < 0 { wrote = ic_ratchet_write(rv_pass, mi_pass, a64_pass, r32_pass) }
1046 }
1047 if wrote == 0 {
1048 if rv_pass > rv_base { wrote = ic_ratchet_write(rv_pass, mi_pass, a64_pass, r32_pass) }
1049 }
1050 if wrote == 0 {
1051 if mi_pass > mi_base { wrote = ic_ratchet_write(rv_pass, mi_pass, a64_pass, r32_pass) }
1052 }
1053 if wrote == 0 {
1054 if a64_base < 0 { wrote = ic_ratchet_write(rv_pass, mi_pass, a64_pass, r32_pass) }
1055 }
1056 if wrote == 0 {
1057 if a64_pass > a64_base { wrote = ic_ratchet_write(rv_pass, mi_pass, a64_pass, r32_pass) }
1058 }
1059 if wrote == 0 {
1060 if r32_base < 0 { wrote = ic_ratchet_write(rv_pass, mi_pass, a64_pass, r32_pass) }
1061 }
1062 if wrote == 0 {
1063 if r32_pass > r32_base { wrote = ic_ratchet_write(rv_pass, mi_pass, a64_pass, r32_pass) }
1064 }
1065 }
1066 gv_puts("ratchet_bytes_written=" as *u8)
1067 gv_num(wrote)
1068 gv_puts(" (0 = held: neither seeded nor tightened this run)" as *u8)
1069 ic_nl()
1070
1071 // This dialect has no || (zero occurrences across nx_ir.nx,
1072 // nx_gate_verdict.nx and nx_emu_arm64.nx -- measured, not assumed),
1073 // so the laundering condition is computed rather than expressed.
1074 var laundered: i64 = 0
1075 if no_regress == 0 {
1076 if wrote > 0 { laundered = 1 }
1077 }
1078
1079 gv_check("ratchet-no-arch-regressed" as *u8, no_regress == 1, ctr)
1080 gv_check("ratchet-never-rewritten-on-a-regression" as *u8, laundered == 0, ctr)
1081
1082 // THE WORKLIST, LAST, where a tailing caller can still see it.
1083 gv_puts("NONPASS: " as *u8)
1084 if bx[IC_NB_OFF] == 0 {
1085 gv_puts("none -- every measured KAT matched its manual-derived expectation" as *u8)
1086 }
1087 if bx[IC_NB_OFF] > 0 {
1088 gv_puts(bx[IC_NB_PTR] as *u8)
1089 }
1090 ic_nl()
1091
1092 return gv_verdict("nx_isa_conform_gate" as *u8, ctr,
1093 "per-ISA conformance measured against manual-derived KATs with four-way outcomes, coverage published as a number, fenced by a good-when-higher per-arch ratchet that seeds on first sight and never rewrites on a fall" as *u8)
1094}