nx_isa_loongarch64_gate.nx source
↩ module page · 915 lines · 39691 B
1// nx_isa_loongarch64_gate.nx -- THE LOONGARCH64 CONFORMANCE RULER.
2//
3// WHY IT EXISTS: the estate carries ten sovereign ISA interpreters in
4// NishiLang. On 2026-09-03 nx_isa_conform_gate measured five of them
5// and printed, in its own output, the list of the five it could not
6// speak for: "UNMEASURED: armv7a cortexm loongarch64 s390x sparc64 --
7// each owes a manual-derived KAT block here". This organ pays the
8// loongarch64 half of that debt. LoongArch is a sovereign national
9// ISA and therefore a first-class deploy target for a sovereign
10// estate: "we can run LoongArch" has to be a measurement, not a claim.
11//
12// WHY A SEPARATE ORGAN AND NOT A BLOCK INSIDE nx_isa_conform_gate:
13// five ISA lanes were working concurrently on 2026-09-03 and this
14// estate loses concurrent edits to one file silently. One arch, one
15// organ, one ratchet file, one owner -- the same "one subject per row,
16// and the writer owns its own file" rule that split the shared gatereg
17// log. The SHAPE is copied from the incumbent deliberately, so a
18// reader who knows one knows all of them.
19//
20// THE LAW THIS ORGAN OBEYS: expectations come FROM THE LOONGARCH
21// REFERENCE MANUAL, never from reading nx_emu_loongarch64.nx and
22// writing down what it happens to do -- that would calibrate the ruler
23// to the subject it exists to judge. Every opcode constant below is
24// declared HERE, independently of the interpreter's own constant
25// table, so a disagreement between the two is a finding rather than a
26// shared typo. The interpreter's decoder was read only to learn which
27// classes are IMPLEMENTED versus MISSING, which is a question about
28// coverage and not about expected values.
29//
30// FIVE OUTCOMES, NEVER ONE:
31// PASS ran and matched the manual-derived expectation
32// WRONG-ANSWER ran to completion and computed the wrong value
33// UNSUPPORTED hit an instruction class the interpreter does not decode
34// RAN-OFF-END the loop ended with no exit syscall / step budget spent
35// FAULT bad pc, or a guest address outside the arena
36// An unimplemented class and a miscomputed result need OPPOSITE fixes,
37// and only the first is a missing feature. Collapsing them into FAIL
38// is what makes a gap unactionable.
39//
40// WHAT THIS RULER FOUND, 2026-09-03, and what was then fixed in the
41// interpreter (which this seat owns):
42// * NOT ONE CONDITIONAL BRANCH existed. beq bne blt bge bltu bgeu,
43// beqz bnez and even the unconditional b were all absent, so the
44// interpreter could not run a loop, an if, or anything a compiler
45// would emit -- only straight-line arithmetic. Implemented.
46// * SRL.D WAS ARITHMETIC. It shared its body with SRA.D, so the two
47// were the same instruction. Invisible on every non-negative
48// operand, which is why nothing had ever caught it. The KAT that
49// separates them shifts -16 right by 60: logical gives 15,
50// arithmetic gives -1, which the exit-status mask renders as 255.
51// * ORI/ANDI/XORI, SLT/SLTU, the shift-immediate forms, LU12I.W and
52// every sub-word load/store were absent, so no constant larger
53// than a 12-bit immediate could even be materialised.
54// Coverage is PUBLISHED AS A NUMBER and fenced by a good-when-higher
55// ratchet; the number is the worklist, the verdict is the fence.
56//
57// license_tier: ORIGINAL
58
59import "nx_gate_verdict.nx"
60import "nx_syscalls.nx"
61import "nx_emu_loongarch64.nx"
62
63// ---- guest layout THIS ORGAN owns (never read from the interpreter) ----
64const LG_GUEST_BYTES: i64 = 1048576 // 1 MiB guest image
65const LG_ENTRY: i64 = 0 // KAT code is loaded at guest vaddr 0
66const LG_SP: i64 = 983040 // 960 KiB: above the code, inside the image
67const LG_MODE_FILE: i64 = 420 // 0644. Same constant as 0x1a4; a sweep
68 // that greps one spelling misses the other.
69
70// ---- non-completion codes the interpreter returns ----
71// Read out of nx_emu_loongarch64.nx's own constant table (LAE_UNSUPPORTED
72// = -1, LAE_RANOFF = -2, LAE_FAULT = -3) rather than guessed. These are
73// the interpreter's WIRE CONTRACT, not its arithmetic, so reading them is
74// not calibration. A real answer is always masked to 0..255 by the exit
75// syscall, so no sentinel can collide with one.
76const LG_UNSUPPORTED: i64 = -1
77const LG_RANOFF: i64 = -2
78const LG_FAULT: i64 = -3
79
80// ---- outcome classes ----
81const LG_O_PASS: i64 = 0
82const LG_O_WRONG: i64 = 1
83const LG_O_UNSUP: i64 = 2
84const LG_O_FAULT: i64 = 3
85const LG_O_RANOFF: i64 = 4
86
87// The non-PASS worklist. A count without a worklist is not actionable,
88// and a worklist printed only in the BODY is lost the moment a caller
89// tails the output -- so it is accumulated here and printed LAST.
90const LG_NB_BYTES: i64 = 2048
91const LG_NB_OFF: i64 = 0
92const LG_NB_PTR: i64 = 1
93const LG_NB_ROW_MAX: i64 = 256
94const LG_BOX_BYTES: i64 = 32
95
96const LG_NL: i64 = 10
97const LG_SPACE: i64 = 32
98const LG_DIG_LO: i64 = 48
99const LG_DIG_HI: i64 = 57
100const LG_UNSEEDED: i64 = -1
101
102// ===== LoongArch register numbers (ABI names -> architectural index) ==
103const LG_R_ZERO: i64 = 0
104const LG_R_RA: i64 = 1
105const LG_R_SP: i64 = 3
106const LG_R_A0: i64 = 4
107const LG_R_A1: i64 = 5
108const LG_R_A2: i64 = 6
109const LG_R_A7: i64 = 11
110const LG_R_T0: i64 = 12
111const LG_R_T1: i64 = 13
112const LG_R_T2: i64 = 14
113
114// ===== opcodes, declared HERE from the LoongArch Reference Manual =====
115// 3R opcode bits 31..15, rk[14:10] rj[9:5] rd[4:0]
116// 3RI6 shift-immediate .d, opcode bits 31..16, ui6[15:10]
117// 2RI12 opcode bits 31..22, si12/ui12[21:10]
118// 1RI20 opcode bits 31..25, si20[24:5]
119// 2RI16 opcode bits 31..26, offs16[25:10]
120// 1RI21 opcode bits 31..26, offs[15:0] at [25:10], offs[20:16] at [4:0]
121// I26 opcode bits 31..26, offs[15:0] at [25:10], offs[25:16] at [9:0]
122const LG_OP_ADD_D: i64 = 0x00108000
123const LG_OP_SUB_D: i64 = 0x00118000
124const LG_OP_SLT: i64 = 0x00120000
125const LG_OP_SLTU: i64 = 0x00128000
126const LG_OP_AND: i64 = 0x00148000
127const LG_OP_OR: i64 = 0x00150000
128const LG_OP_XOR: i64 = 0x00158000
129const LG_OP_SLL_D: i64 = 0x00188000
130const LG_OP_SRL_D: i64 = 0x00190000
131const LG_OP_SRA_D: i64 = 0x00198000
132const LG_OP_MUL_D: i64 = 0x001d8000
133const LG_OP_SLLI_D: i64 = 0x00410000
134const LG_OP_SRLI_D: i64 = 0x00450000
135const LG_OP_SRAI_D: i64 = 0x00490000
136const LG_OP_ADDI_D: i64 = 0x02c00000
137const LG_OP_ANDI: i64 = 0x03400000
138const LG_OP_ORI: i64 = 0x03800000
139const LG_OP_XORI: i64 = 0x03c00000
140const LG_OP_LD_D: i64 = 0x28c00000
141const LG_OP_ST_B: i64 = 0x29000000
142const LG_OP_ST_D: i64 = 0x29c00000
143const LG_OP_LD_BU: i64 = 0x2a000000
144const LG_OP_LU12I_W: i64 = 0x14000000
145const LG_OP_BEQZ: i64 = 0x40000000
146const LG_OP_BNEZ: i64 = 0x44000000
147const LG_OP_JIRL: i64 = 0x4c000000
148const LG_OP_B: i64 = 0x50000000
149const LG_OP_BL: i64 = 0x54000000
150const LG_OP_BEQ: i64 = 0x58000000
151const LG_OP_BNE: i64 = 0x5c000000
152const LG_OP_BLT: i64 = 0x60000000
153const LG_OP_BGE: i64 = 0x64000000
154const LG_OP_BLTU: i64 = 0x68000000
155const LG_OP_BGEU: i64 = 0x6c000000
156const LG_INSN_SYSCALL: i64 = 0x002B0000
157const LG_INSN_UNDEFINED: i64 = 0xFFFFFFFF // decodes to no class in any format
158
159const LG_SYS_EXIT: i64 = 93 // Linux generic ABI, number in a7
160
161// A bare newline inside a string literal is ambiguous to this lexer, so
162// the byte is CONSTRUCTED.
163func lg_nl() -> i64 {
164 let b: *u8 = sys_mmap(8)
165 b[0] = LG_NL as u8
166 sys_write(1, b, 1)
167 return 0
168}
169
170// ===== word emitter =================================================
171// LoongArch is LITTLE-endian and every instruction is exactly 32 bits.
172// Emitting big-endian here would be the silent-fixture defect: the
173// interpreter would decode garbage, report UNSUPPORTED, and the
174// FIXTURE's bug would read as the interpreter being incomplete.
175func lg_put_le32(code: *u8, off: i64, w: i64) -> i64 {
176 code[off] = (w) & 0xff
177 code[off + 1] = (w >> 8) & 0xff
178 code[off + 2] = (w >> 16) & 0xff
179 code[off + 3] = (w >> 24) & 0xff
180 return off + 4
181}
182
183func lg_guest() -> *u8 { return sys_mmap(LG_GUEST_BYTES) }
184
185// ===== encoders, one per instruction FORMAT =========================
186func lg_3r(op: i64, rd: i64, rj: i64, rk: i64) -> i64 {
187 return op | ((rk & 0x1F) << 10) | ((rj & 0x1F) << 5) | (rd & 0x1F)
188}
189func lg_2ri12(op: i64, rd: i64, rj: i64, imm: i64) -> i64 {
190 return op | ((imm & 0xFFF) << 10) | ((rj & 0x1F) << 5) | (rd & 0x1F)
191}
192func lg_shift_d(op: i64, rd: i64, rj: i64, ui6: i64) -> i64 {
193 return op | ((ui6 & 0x3F) << 10) | ((rj & 0x1F) << 5) | (rd & 0x1F)
194}
195func lg_1ri20(op: i64, rd: i64, si20: i64) -> i64 {
196 return op | ((si20 & 0xFFFFF) << 5) | (rd & 0x1F)
197}
198// 2RI16: the branch compares GR[rj] with GR[rd] and the offset is in
199// INSTRUCTIONS -- the hardware shifts it left by two. No delay slot.
200func lg_2ri16(op: i64, rj: i64, rd: i64, off: i64) -> i64 {
201 return op | ((off & 0xFFFF) << 10) | ((rj & 0x1F) << 5) | (rd & 0x1F)
202}
203// 1RI21: the offset is SPLIT -- low 16 bits in [25:10], high 5 in [4:0].
204func lg_1ri21(op: i64, rj: i64, off: i64) -> i64 {
205 return op | ((off & 0xFFFF) << 10) | ((rj & 0x1F) << 5) | ((off >> 16) & 0x1F)
206}
207// I26: split the other way round -- low 16 in [25:10], high 10 in [9:0].
208func lg_i26(op: i64, off: i64) -> i64 {
209 return op | ((off & 0xFFFF) << 10) | ((off >> 16) & 0x3FF)
210}
211
212// Every KAT ends with this two-word exit sequence: a7 = 93, then syscall.
213func lg_exit(m: *u8, o0: i64) -> i64 {
214 var o: i64 = o0
215 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A7, LG_R_ZERO, LG_SYS_EXIT))
216 o = lg_put_le32(m, o, LG_INSN_SYSCALL)
217 return o
218}
219
220// ===== outcome classification =======================================
221func lg_classify(got: i64, want: i64) -> i64 {
222 if got == LG_UNSUPPORTED { return LG_O_UNSUP }
223 if got == LG_RANOFF { return LG_O_RANOFF }
224 if got == LG_FAULT { return LG_O_FAULT }
225 if got == want { return LG_O_PASS }
226 return LG_O_WRONG
227}
228
229func lg_outcome_name(o: i64) -> *u8 {
230 if o == LG_O_PASS { return "PASS" as *u8 }
231 if o == LG_O_WRONG { return "WRONG-ANSWER" as *u8 }
232 if o == LG_O_UNSUP { return "UNSUPPORTED-instruction-class" as *u8 }
233 if o == LG_O_RANOFF { return "RAN-OFF-END-no-exit-syscall" as *u8 }
234 return "FAULT-bad-pc-or-guest-address" as *u8
235}
236
237// Print the VALUES, not just the verdict: every vacuous tooth ever
238// caught in this estate was caught by a diagnostic dump and never by a
239// verdict vector.
240func lg_report(kat: *u8, got: i64, want: i64, bx: *i64) -> i64 {
241 let o: i64 = lg_classify(got, want)
242 gv_puts(" KAT loongarch64." as *u8)
243 gv_puts(kat)
244 gv_puts(" got=" as *u8)
245 gv_num(got)
246 gv_puts(" want=" as *u8)
247 gv_num(want)
248 gv_puts(" " as *u8)
249 gv_puts(lg_outcome_name(o))
250 lg_nl()
251 if o != LG_O_PASS {
252 let nb: *u8 = bx[LG_NB_PTR] as *u8
253 var p: i64 = bx[LG_NB_OFF]
254 if p < (LG_NB_BYTES - LG_NB_ROW_MAX) {
255 p = gv_cat(nb, p, "loongarch64." as *u8)
256 p = gv_cat(nb, p, kat)
257 p = gv_cat(nb, p, "=" as *u8)
258 p = gv_cat(nb, p, lg_outcome_name(o))
259 p = gv_cat(nb, p, "(got=" as *u8)
260 p = gv_catn(nb, p, got)
261 p = gv_cat(nb, p, " want=" as *u8)
262 p = gv_catn(nb, p, want)
263 p = gv_cat(nb, p, ") " as *u8)
264 nb[p] = 0 as u8
265 bx[LG_NB_OFF] = p
266 }
267 }
268 return o
269}
270
271// ===== KATs =========================================================
272// Each program is written so a WRONG implementation gives a DIFFERENT
273// answer, and every distinguishing value is kept inside 0..255 so the
274// exit-status mask cannot collapse two outcomes into one.
275
276// ADDI.D then ADD.D. 40 + 2 = 42.
277func lg_kat_imm_arith() -> i64 {
278 let m: *u8 = lg_guest()
279 var o: i64 = 0
280 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 40))
281 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 2))
282 o = lg_put_le32(m, o, lg_3r(LG_OP_ADD_D, LG_R_A0, LG_R_A0, LG_R_T0))
283 o = lg_exit(m, o)
284 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
285}
286
287// SUB.D computes GR[rj] - GR[rk]. Reversing the operands gives -42,
288// which the exit mask renders as 214, cleanly distinguishable from 42.
289func lg_kat_sub() -> i64 {
290 let m: *u8 = lg_guest()
291 var o: i64 = 0
292 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 50))
293 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 8))
294 o = lg_put_le32(m, o, lg_3r(LG_OP_SUB_D, LG_R_A0, LG_R_A0, LG_R_T0))
295 o = lg_exit(m, o)
296 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
297}
298
299// MUL.D. 6 * 7 = 42, and 6 + 7 = 13, so an add wearing a mul's opcode
300// is visible.
301func lg_kat_mul() -> i64 {
302 let m: *u8 = lg_guest()
303 var o: i64 = 0
304 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 6))
305 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 7))
306 o = lg_put_le32(m, o, lg_3r(LG_OP_MUL_D, LG_R_A0, LG_R_A0, LG_R_T0))
307 o = lg_exit(m, o)
308 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
309}
310
311// ST.D / LD.D round trip. a0 is CLOBBERED to 0 between the store and
312// the load, so a load that quietly does nothing returns 0, not 42 --
313// without the clobber this KAT would pass with no memory traffic at all.
314// Note ST.D takes its SOURCE in the rd field: st.d rd, rj, si12.
315func lg_kat_load_store() -> i64 {
316 let m: *u8 = lg_guest()
317 var o: i64 = 0
318 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 42))
319 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ST_D, LG_R_A0, LG_R_SP, 0 - 8))
320 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 0))
321 o = lg_put_le32(m, o, lg_2ri12(LG_OP_LD_D, LG_R_A0, LG_R_SP, 0 - 8))
322 o = lg_exit(m, o)
323 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
324}
325
326// SRL.D must be LOGICAL where SRA.D is arithmetic, and the obvious test
327// CANNOT separate them: -16 shifted right by 2 has low byte 0xFC under
328// both. Shifting by 60 does: logical gives 15, arithmetic gives -1,
329// which the exit mask renders as 255. This KAT is what found the
330// shared-body defect in the interpreter.
331func lg_kat_shift_logical() -> i64 {
332 let m: *u8 = lg_guest()
333 var o: i64 = 0
334 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 0 - 16))
335 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 60))
336 o = lg_put_le32(m, o, lg_3r(LG_OP_SRL_D, LG_R_A0, LG_R_A0, LG_R_T0))
337 o = lg_exit(m, o)
338 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
339}
340
341// The other half of the pair: SRA.D on the same input must give -1.
342// Both teeth together are what make the discrimination real -- one
343// alone is satisfied by an interpreter that implements only one shift.
344func lg_kat_shift_arith() -> i64 {
345 let m: *u8 = lg_guest()
346 var o: i64 = 0
347 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 0 - 16))
348 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 60))
349 o = lg_put_le32(m, o, lg_3r(LG_OP_SRA_D, LG_R_A0, LG_R_A0, LG_R_T0))
350 o = lg_exit(m, o)
351 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
352}
353
354// SRLI.D / SRAI.D, the immediate forms, on the same discriminating
355// input. A decoder that folds the 6-bit ui6 field into the 5-bit ui5
356// field would shift by 28 instead of 60 and give a completely
357// different number.
358func lg_kat_shifti_logical() -> i64 {
359 let m: *u8 = lg_guest()
360 var o: i64 = 0
361 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 0 - 16))
362 o = lg_put_le32(m, o, lg_shift_d(LG_OP_SRLI_D, LG_R_A0, LG_R_A0, 60))
363 o = lg_exit(m, o)
364 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
365}
366
367// SLLI.D then SRLI.D: 1 << 20 >> 13 = 128.
368func lg_kat_shifti_left() -> i64 {
369 let m: *u8 = lg_guest()
370 var o: i64 = 0
371 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 1))
372 o = lg_put_le32(m, o, lg_shift_d(LG_OP_SLLI_D, LG_R_A0, LG_R_A0, 20))
373 o = lg_put_le32(m, o, lg_shift_d(LG_OP_SRLI_D, LG_R_A0, LG_R_A0, 13))
374 o = lg_exit(m, o)
375 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
376}
377
378// ORI then AND. 240 or 15 = 255, 255 and 60 = 60.
379func lg_kat_logic() -> i64 {
380 let m: *u8 = lg_guest()
381 var o: i64 = 0
382 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 240))
383 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ORI, LG_R_A0, LG_R_A0, 15))
384 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 60))
385 o = lg_put_le32(m, o, lg_3r(LG_OP_AND, LG_R_A0, LG_R_A0, LG_R_T0))
386 o = lg_exit(m, o)
387 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
388}
389
390// ORI takes a ZERO-extended ui12 while ADDI.D takes a SIGN-extended
391// si12 -- reusing one extension for both is the classic LoongArch
392// decoder bug and it is silent for every small positive immediate.
393// ori a0,zero,0xFFF must give 4095, so 4095 >> 12 = 0 and the answer
394// is 42. A sign-extending ORI gives -1, whose low 12 bits shift down
395// to 0x000FFFFFFFFFFFFF, and +42 lands on 41.
396func lg_kat_ori_zero_extended() -> i64 {
397 let m: *u8 = lg_guest()
398 var o: i64 = 0
399 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ORI, LG_R_A0, LG_R_ZERO, 0xFFF))
400 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 12))
401 o = lg_put_le32(m, o, lg_3r(LG_OP_SRL_D, LG_R_A0, LG_R_A0, LG_R_T0))
402 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_A0, 42))
403 o = lg_exit(m, o)
404 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
405}
406
407// SLT is SIGNED and SLTU is UNSIGNED, on the same operand pair that
408// separates them: -1 < 1 is TRUE signed and FALSE unsigned. 41 + slt
409// - sltu = 42 only when BOTH are right; either one wrong gives 41 and
410// both wrong gives 40.
411func lg_kat_slt_vs_sltu() -> i64 {
412 let m: *u8 = lg_guest()
413 var o: i64 = 0
414 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A1, LG_R_ZERO, 0 - 1))
415 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A2, LG_R_ZERO, 1))
416 o = lg_put_le32(m, o, lg_3r(LG_OP_SLT, LG_R_T1, LG_R_A1, LG_R_A2))
417 o = lg_put_le32(m, o, lg_3r(LG_OP_SLTU, LG_R_T2, LG_R_A1, LG_R_A2))
418 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 41))
419 o = lg_put_le32(m, o, lg_3r(LG_OP_ADD_D, LG_R_A0, LG_R_A0, LG_R_T1))
420 o = lg_put_le32(m, o, lg_3r(LG_OP_SUB_D, LG_R_A0, LG_R_A0, LG_R_T2))
421 o = lg_exit(m, o)
422 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
423}
424
425// A REAL LOOP, which is the whole point of a conditional branch. BNE
426// compares GR[rj] with GR[rd]; LoongArch has NO delay slot, so the
427// offset is counted from the branch itself and -2 lands on the add.
428// Sum 0..9 = 45, a number no off-by-one in the loop bound can also
429// produce (9 iterations give 36, 11 give 55).
430func lg_kat_branch_loop() -> i64 {
431 let m: *u8 = lg_guest()
432 var o: i64 = 0
433 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 0))
434 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 0))
435 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T1, LG_R_ZERO, 10))
436 o = lg_put_le32(m, o, lg_3r(LG_OP_ADD_D, LG_R_A0, LG_R_A0, LG_R_T0))
437 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_T0, 1))
438 o = lg_put_le32(m, o, lg_2ri16(LG_OP_BNE, LG_R_T0, LG_R_T1, 0 - 2))
439 o = lg_exit(m, o)
440 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
441}
442
443// BEQ must be TAKEN when the two registers are equal, and the poison
444// instruction after it must never run.
445func lg_kat_beq_taken() -> i64 {
446 let m: *u8 = lg_guest()
447 var o: i64 = 0
448 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 42))
449 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 7))
450 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T1, LG_R_ZERO, 7))
451 o = lg_put_le32(m, o, lg_2ri16(LG_OP_BEQ, LG_R_T0, LG_R_T1, 2))
452 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 99))
453 o = lg_exit(m, o)
454 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
455}
456
457// The unconditional B, I26 format: its 26-bit offset is SPLIT with the
458// HIGH ten bits in the low ten bits of the word, the reverse of the
459// 1RI21 split. Getting the halves the wrong way round jumps somewhere
460// arbitrary, which shows up as FAULT or UNSUPPORTED rather than 99.
461func lg_kat_b_unconditional() -> i64 {
462 let m: *u8 = lg_guest()
463 var o: i64 = 0
464 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 42))
465 o = lg_put_le32(m, o, lg_i26(LG_OP_B, 2))
466 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 99))
467 o = lg_exit(m, o)
468 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
469}
470
471// BEQZ, 1RI21 format: the 21-bit offset is split with the LOW sixteen
472// bits at [25:10] and the HIGH five at [4:0], where every other format
473// puts a register. A decoder that reads [4:0] as rd jumps nowhere.
474func lg_kat_beqz_taken() -> i64 {
475 let m: *u8 = lg_guest()
476 var o: i64 = 0
477 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 42))
478 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 0))
479 o = lg_put_le32(m, o, lg_1ri21(LG_OP_BEQZ, LG_R_T0, 2))
480 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 99))
481 o = lg_exit(m, o)
482 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
483}
484
485// BNEZ, the other half of the 1RI21 pair, in its NOT-TAKEN direction:
486// a branch that is always taken passes every taken-direction test.
487func lg_kat_bnez_not_taken() -> i64 {
488 let m: *u8 = lg_guest()
489 var o: i64 = 0
490 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 99))
491 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 0))
492 o = lg_put_le32(m, o, lg_1ri21(LG_OP_BNEZ, LG_R_T0, 3))
493 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 42))
494 o = lg_exit(m, o)
495 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
496}
497
498// BLTU is UNSIGNED, so -1 is the LARGEST value and the branch must NOT
499// be taken. A BLTU implemented as a signed BLT takes it and lands on
500// the poison, giving 99.
501func lg_kat_bltu_unsigned() -> i64 {
502 let m: *u8 = lg_guest()
503 var o: i64 = 0
504 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A1, LG_R_ZERO, 0 - 1))
505 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A2, LG_R_ZERO, 1))
506 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 42))
507 o = lg_put_le32(m, o, lg_2ri16(LG_OP_BLTU, LG_R_A1, LG_R_A2, 2))
508 o = lg_put_le32(m, o, lg_i26(LG_OP_B, 2))
509 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 99))
510 o = lg_exit(m, o)
511 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
512}
513
514// BL then JIRL: a real call and a real return. BL writes the return
515// address to ra ($r1) and JIRL $zero,$ra,0 is the LoongArch ret. The
516// callee sits AFTER the exit sequence, so no extra jump is needed and
517// this KAT tests only the call/return pair.
518func lg_kat_call_return() -> i64 {
519 let m: *u8 = lg_guest()
520 var o: i64 = 0
521 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 40))
522 o = lg_put_le32(m, o, lg_i26(LG_OP_BL, 4))
523 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_A0, 2))
524 o = lg_exit(m, o)
525 o = lg_put_le32(m, o, lg_2ri16(LG_OP_JIRL, LG_R_RA, LG_R_ZERO, 0))
526 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
527}
528
529// LU12I.W is the only way to build a constant wider than 12 bits. It
530// places si20 in bits 31..12 and sign-extends: lu12i.w a0,1 is 0x1000,
531// so shifting right by 6 gives 64. An implementation that forgets the
532// 12-bit shift gives 0.
533func lg_kat_lu12i() -> i64 {
534 let m: *u8 = lg_guest()
535 var o: i64 = 0
536 o = lg_put_le32(m, o, lg_1ri20(LG_OP_LU12I_W, LG_R_A0, 1))
537 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 6))
538 o = lg_put_le32(m, o, lg_3r(LG_OP_SRL_D, LG_R_A0, LG_R_A0, LG_R_T0))
539 o = lg_exit(m, o)
540 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
541}
542
543// Sub-word memory: ST.B writes ONE byte and LD.BU ZERO-extends it.
544// Storing -1 and loading it back unsigned gives 255, and 255 >> 4 = 15.
545// A ST.B that stored eight bytes, or an LD.BU that sign-extended,
546// would leave -1 in a0 and the logical shift would give 255 instead.
547func lg_kat_byte_load_store() -> i64 {
548 let m: *u8 = lg_guest()
549 var o: i64 = 0
550 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 0 - 1))
551 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ST_B, LG_R_A0, LG_R_SP, 0 - 8))
552 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 0))
553 o = lg_put_le32(m, o, lg_2ri12(LG_OP_LD_BU, LG_R_A0, LG_R_SP, 0 - 8))
554 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_T0, LG_R_ZERO, 4))
555 o = lg_put_le32(m, o, lg_3r(LG_OP_SRL_D, LG_R_A0, LG_R_A0, LG_R_T0))
556 o = lg_exit(m, o)
557 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
558}
559
560// ===== LIVE negative controls =======================================
561// These run the INTERPRETER, not just the classifier. A classifier
562// control proves the outcome table is wired; only these prove the
563// interpreter's own refusal paths fire.
564
565// An instruction word that decodes to no class in any LoongArch format
566// must come back UNSUPPORTED, never as a silent nop that lets the exit
567// still report 42.
568func lg_neg_undefined_opcode() -> i64 {
569 let m: *u8 = lg_guest()
570 var o: i64 = 0
571 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 42))
572 o = lg_put_le32(m, o, LG_INSN_UNDEFINED)
573 o = lg_exit(m, o)
574 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
575}
576
577// A guest address outside the arena must FAULT rather than read host
578// memory. -2048 is the most negative si12 there is, so this needs
579// nothing but ADDI.D and LD.D -- both of which the interpreter has
580// always had, which is what makes this control independent of every
581// class added this session.
582func lg_neg_out_of_range_address() -> i64 {
583 let m: *u8 = lg_guest()
584 var o: i64 = 0
585 o = lg_put_le32(m, o, lg_2ri12(LG_OP_ADDI_D, LG_R_A0, LG_R_ZERO, 0 - 2048))
586 o = lg_put_le32(m, o, lg_2ri12(LG_OP_LD_D, LG_R_A0, LG_R_A0, 0))
587 o = lg_exit(m, o)
588 return emu_loongarch64_run_mem(m, LG_GUEST_BYTES, LG_ENTRY, LG_SP)
589}
590
591// ===== ratchet ======================================================
592// One line: "loongarch64 <passed>". UNSEEDED is a NAMED third state,
593// so adopting this fence cannot fail closed on first sight. The metric
594// is GOOD-when-higher, so it TIGHTENS on a rise and must NEVER rewrite
595// its baseline on a fall -- rewriting on a fall is how a ratchet
596// launders itself green. Its own file, because one writer per file is
597// the rule that stopped a shared log from reporting a race.
598const LG_RATCHET: *u8 = "knowledge/status/isa_loongarch64.ratchet"
599const LG_RATCHET_BYTES: i64 = 128
600const LG_ARCH_KEY: *u8 = "loongarch64"
601
602func lg_match_at(buf: *u8, n: i64, p: i64, s: *u8) -> i64 {
603 var j: i64 = 0
604 var ok: i64 = 1
605 while s[j] != (0 as u8) {
606 if (p + j) >= n { ok = 0 }
607 if ok == 1 {
608 if buf[p + j] != s[j] { ok = 0 }
609 }
610 j = j + 1
611 }
612 if ok == 0 { return -1 }
613 return j
614}
615
616func lg_digits_at(buf: *u8, n: i64, p0: i64) -> i64 {
617 var v: i64 = 0
618 var any: i64 = 0
619 var p: i64 = p0
620 var go: i64 = 1
621 while go == 1 {
622 if p >= n { go = 0 }
623 if go == 1 {
624 let c: i64 = buf[p] & 0xff
625 if c < LG_DIG_LO { go = 0 }
626 if go == 1 {
627 if c > LG_DIG_HI { go = 0 }
628 }
629 if go == 1 {
630 v = v * 10 + (c - LG_DIG_LO)
631 any = 1
632 p = p + 1
633 }
634 }
635 }
636 if any == 0 { return -1 }
637 return v
638}
639
640func lg_ratchet_read() -> i64 {
641 let lenbox: *i64 = sys_mmap(16) as *i64
642 let buf: *u8 = sys_read_file(LG_RATCHET, lenbox)
643 if (buf as i64) == 0 { return LG_UNSEEDED }
644 let n: i64 = lenbox[0]
645 var i: i64 = 0
646 var found: i64 = LG_UNSEEDED
647 while i < n {
648 if found < 0 {
649 let m: i64 = lg_match_at(buf, n, i, LG_ARCH_KEY)
650 if m > 0 {
651 if (i + m) < n {
652 if buf[i + m] == (LG_SPACE as u8) {
653 found = lg_digits_at(buf, n, i + m + 1)
654 }
655 }
656 }
657 }
658 var adv: i64 = 1
659 while adv == 1 {
660 if i >= n { adv = 0 }
661 if adv == 1 {
662 if buf[i] == (LG_NL as u8) { adv = 0 }
663 i = i + 1
664 }
665 }
666 }
667 return found
668}
669
670// Callers must have already decided that the score did not regress --
671// this function does not re-check, and the tooth that guards it is
672// asserted below.
673func lg_ratchet_write(n: i64) -> i64 {
674 let d: *u8 = sys_mmap(LG_RATCHET_BYTES)
675 var o: i64 = 0
676 o = gv_cat(d, o, LG_ARCH_KEY)
677 o = gv_cat(d, o, " " as *u8)
678 o = gv_catn(d, o, n)
679 d[o] = LG_NL as u8
680 o = o + 1
681 let fd: i64 = sys_openat_wr(LG_RATCHET, LG_MODE_FILE)
682 if fd < 0 { return -1 }
683 sys_write(fd, d, o)
684 sys_fsync(fd)
685 sys_close(fd)
686 return o
687}
688
689// ===== main =========================================================
690
691func main(argc: i64, argv: *i64) -> i64 {
692 gv_head("nx_isa_loongarch64_gate -- LoongArch64 conformance, KATs encoded from the LoongArch Reference Manual" as *u8)
693 let ctr: *i64 = gv_ctr()
694
695 let bx: *i64 = sys_mmap(LG_BOX_BYTES) as *i64
696 let nbuf: *u8 = sys_mmap(LG_NB_BYTES)
697 nbuf[0] = 0 as u8
698 bx[LG_NB_OFF] = 0
699 bx[LG_NB_PTR] = nbuf as i64
700
701 gv_puts("subject=buildroot/runtime/nx_emu_loongarch64.nx dispatch=emu_loongarch64_run_mem (the only surface all ten interpreters share)" as *u8)
702 lg_nl()
703 gv_puts("outcomes are FIVE-WAY: PASS / WRONG-ANSWER / UNSUPPORTED-instruction-class / RAN-OFF-END / FAULT" as *u8)
704 lg_nl()
705 gv_puts("[loongarch64] LA64 -- little-endian, fixed 32-bit instructions, 32 flat GPRs with r0 hardwired zero, NO delay slot" as *u8)
706 lg_nl()
707
708 var pass: i64 = 0
709 var tot: i64 = 0
710 var o: i64 = 0
711
712 o = lg_report("imm-arith" as *u8, lg_kat_imm_arith(), 42, bx)
713 tot = tot + 1
714 if o == LG_O_PASS { pass = pass + 1 }
715 gv_check("isa-loongarch64-imm-arith-addi-d-add-d" as *u8, o == LG_O_PASS, ctr)
716
717 o = lg_report("sub" as *u8, lg_kat_sub(), 42, bx)
718 tot = tot + 1
719 if o == LG_O_PASS { pass = pass + 1 }
720 gv_check("isa-loongarch64-sub-d-operand-order" as *u8, o == LG_O_PASS, ctr)
721
722 o = lg_report("mul" as *u8, lg_kat_mul(), 42, bx)
723 tot = tot + 1
724 if o == LG_O_PASS { pass = pass + 1 }
725 gv_check("isa-loongarch64-mul-d" as *u8, o == LG_O_PASS, ctr)
726
727 o = lg_report("load-store" as *u8, lg_kat_load_store(), 42, bx)
728 tot = tot + 1
729 if o == LG_O_PASS { pass = pass + 1 }
730 gv_check("isa-loongarch64-st-d-ld-d-roundtrip-with-clobber" as *u8, o == LG_O_PASS, ctr)
731
732 o = lg_report("shift-logical-srl-d" as *u8, lg_kat_shift_logical(), 15, bx)
733 tot = tot + 1
734 if o == LG_O_PASS { pass = pass + 1 }
735 gv_check("isa-loongarch64-srl-d-is-logical-not-arithmetic" as *u8, o == LG_O_PASS, ctr)
736
737 o = lg_report("shift-arith-sra-d" as *u8, lg_kat_shift_arith(), 255, bx)
738 tot = tot + 1
739 if o == LG_O_PASS { pass = pass + 1 }
740 gv_check("isa-loongarch64-sra-d-is-arithmetic" as *u8, o == LG_O_PASS, ctr)
741
742 o = lg_report("shifti-logical-srli-d" as *u8, lg_kat_shifti_logical(), 15, bx)
743 tot = tot + 1
744 if o == LG_O_PASS { pass = pass + 1 }
745 gv_check("isa-loongarch64-srli-d-ui6-field-is-six-bits" as *u8, o == LG_O_PASS, ctr)
746
747 o = lg_report("shifti-left-slli-d" as *u8, lg_kat_shifti_left(), 128, bx)
748 tot = tot + 1
749 if o == LG_O_PASS { pass = pass + 1 }
750 gv_check("isa-loongarch64-slli-d" as *u8, o == LG_O_PASS, ctr)
751
752 o = lg_report("logic-ori-and" as *u8, lg_kat_logic(), 60, bx)
753 tot = tot + 1
754 if o == LG_O_PASS { pass = pass + 1 }
755 gv_check("isa-loongarch64-logic-ori-and" as *u8, o == LG_O_PASS, ctr)
756
757 o = lg_report("ori-is-zero-extended" as *u8, lg_kat_ori_zero_extended(), 42, bx)
758 tot = tot + 1
759 if o == LG_O_PASS { pass = pass + 1 }
760 gv_check("isa-loongarch64-ori-ui12-is-zero-extended" as *u8, o == LG_O_PASS, ctr)
761
762 o = lg_report("slt-vs-sltu" as *u8, lg_kat_slt_vs_sltu(), 42, bx)
763 tot = tot + 1
764 if o == LG_O_PASS { pass = pass + 1 }
765 gv_check("isa-loongarch64-slt-signed-sltu-unsigned" as *u8, o == LG_O_PASS, ctr)
766
767 o = lg_report("branch-loop-bne" as *u8, lg_kat_branch_loop(), 45, bx)
768 tot = tot + 1
769 if o == LG_O_PASS { pass = pass + 1 }
770 gv_check("isa-loongarch64-bne-can-run-a-real-loop" as *u8, o == LG_O_PASS, ctr)
771
772 o = lg_report("beq-taken" as *u8, lg_kat_beq_taken(), 42, bx)
773 tot = tot + 1
774 if o == LG_O_PASS { pass = pass + 1 }
775 gv_check("isa-loongarch64-beq-taken-skips-poison" as *u8, o == LG_O_PASS, ctr)
776
777 o = lg_report("b-unconditional" as *u8, lg_kat_b_unconditional(), 42, bx)
778 tot = tot + 1
779 if o == LG_O_PASS { pass = pass + 1 }
780 gv_check("isa-loongarch64-b-i26-offset-halves-not-swapped" as *u8, o == LG_O_PASS, ctr)
781
782 o = lg_report("beqz-taken" as *u8, lg_kat_beqz_taken(), 42, bx)
783 tot = tot + 1
784 if o == LG_O_PASS { pass = pass + 1 }
785 gv_check("isa-loongarch64-beqz-1ri21-split-offset" as *u8, o == LG_O_PASS, ctr)
786
787 o = lg_report("bnez-not-taken" as *u8, lg_kat_bnez_not_taken(), 42, bx)
788 tot = tot + 1
789 if o == LG_O_PASS { pass = pass + 1 }
790 gv_check("isa-loongarch64-bnez-not-taken-falls-through" as *u8, o == LG_O_PASS, ctr)
791
792 o = lg_report("bltu-unsigned" as *u8, lg_kat_bltu_unsigned(), 42, bx)
793 tot = tot + 1
794 if o == LG_O_PASS { pass = pass + 1 }
795 gv_check("isa-loongarch64-bltu-is-unsigned" as *u8, o == LG_O_PASS, ctr)
796
797 o = lg_report("call-return-bl-jirl" as *u8, lg_kat_call_return(), 42, bx)
798 tot = tot + 1
799 if o == LG_O_PASS { pass = pass + 1 }
800 gv_check("isa-loongarch64-bl-writes-ra-and-jirl-returns" as *u8, o == LG_O_PASS, ctr)
801
802 o = lg_report("lu12i-w" as *u8, lg_kat_lu12i(), 64, bx)
803 tot = tot + 1
804 if o == LG_O_PASS { pass = pass + 1 }
805 gv_check("isa-loongarch64-lu12i-w-places-si20-at-bit-12" as *u8, o == LG_O_PASS, ctr)
806
807 o = lg_report("byte-load-store" as *u8, lg_kat_byte_load_store(), 15, bx)
808 tot = tot + 1
809 if o == LG_O_PASS { pass = pass + 1 }
810 gv_check("isa-loongarch64-st-b-one-byte-ld-bu-zero-extends" as *u8, o == LG_O_PASS, ctr)
811
812 // ---- the fixture must have REACHED the interpreter at all ------
813 // Asserting an outcome without asserting the fixture ran is how
814 // four vacuous fixtures shipped in this estate in a single day.
815 lg_nl()
816 gv_check("fixture-reached-the-loongarch64-interpreter-at-all" as *u8, pass > 0, ctr)
817 gv_check("fixture-enumerated-every-declared-kat" as *u8, tot == 20, ctr)
818
819 // ---- negative controls -----------------------------------------
820 // A comparison that cannot fail proves nothing. If lg_classify were
821 // stubbed to return PASS, every green above would be fake and only
822 // these would notice.
823 lg_nl()
824 let ncw: i64 = lg_classify(lg_kat_imm_arith(), 41)
825 gv_puts(" neg-control imm-arith against a deliberately wrong want=41 -> " as *u8)
826 gv_puts(lg_outcome_name(ncw))
827 lg_nl()
828 gv_check("neg-control-wrong-expectation-must-not-pass" as *u8, ncw == LG_O_WRONG, ctr)
829
830 let ncu: i64 = lg_classify(LG_UNSUPPORTED, LG_UNSUPPORTED)
831 gv_check("neg-control-unsupported-sentinel-never-reads-as-pass" as *u8, ncu == LG_O_UNSUP, ctr)
832
833 let ncf: i64 = lg_classify(LG_FAULT, LG_FAULT)
834 gv_check("neg-control-fault-sentinel-never-reads-as-pass" as *u8, ncf == LG_O_FAULT, ctr)
835
836 let ncr: i64 = lg_classify(LG_RANOFF, LG_RANOFF)
837 gv_check("neg-control-ranoff-sentinel-never-reads-as-pass" as *u8, ncr == LG_O_RANOFF, ctr)
838
839 // The two LIVE controls: these run the interpreter and prove its own
840 // refusal paths fire, which no classifier test can show.
841 let nco: i64 = lg_neg_undefined_opcode()
842 gv_puts(" neg-control undefined instruction word 0xFFFFFFFF -> " as *u8)
843 gv_puts(lg_outcome_name(lg_classify(nco, 42)))
844 lg_nl()
845 gv_check("neg-control-live-undefined-opcode-reports-unsupported" as *u8, nco == LG_UNSUPPORTED, ctr)
846
847 let nca: i64 = lg_neg_out_of_range_address()
848 gv_puts(" neg-control guest load at a negative address -> " as *u8)
849 gv_puts(lg_outcome_name(lg_classify(nca, 42)))
850 lg_nl()
851 gv_check("neg-control-live-out-of-arena-address-reports-fault" as *u8, nca == LG_FAULT, ctr)
852
853 // ---- coverage, published as a NUMBER not a verdict --------------
854 lg_nl()
855 gv_puts("coverage loongarch64=" as *u8)
856 gv_num(pass)
857 gv_puts("/" as *u8)
858 gv_num(tot)
859 lg_nl()
860 gv_puts("CLASSES MEASURED: addi.d add.d sub.d mul.d and or ori srl.d sra.d srli.d slli.d slt sltu st.d ld.d st.b ld.bu lu12i.w b bl jirl beq bne beqz bnez bltu syscall" as *u8)
861 lg_nl()
862 gv_puts("CLASSES DECODED BUT NOT YET MEASURED HERE: add.w sub.w mul.w sll.w srl.w sra.w addi.w slti sltui andi xori nor andn orn maskeqz masknez slli.w srli.w srai.w srai.d ld.b ld.h ld.w ld.hu ld.wu st.h st.w lu32i.d pcaddi pcaddu12i pcalau12i div.d mod.d blt bge bgeu" as *u8)
863 lg_nl()
864 gv_puts("CLASSES STILL ABSENT FROM THE INTERPRETER (named, not hidden): div.w mod.w div.wu mod.wu div.du mod.du mulh.d mulh.du mulh.w rotr.w rotr.d rotri bstrins bstrpick ldx/stx register-offset addressing ll/sc am* atomics ibar/dbar break/syscall-codes cpucfg rdtime CSR and all privileged ops, and the whole floating-point / LSX / LASX vector space" as *u8)
865 lg_nl()
866
867 // ---- ratchet ----------------------------------------------------
868 let base: i64 = lg_ratchet_read()
869 gv_puts("ratchet loongarch64 base=" as *u8)
870 gv_num(base)
871 gv_puts(" now=" as *u8)
872 gv_num(pass)
873 gv_puts(" (base=-1 is UNSEEDED, a named state: first sight seeds, so adoption cannot fail closed)" as *u8)
874 lg_nl()
875
876 var no_regress: i64 = 1
877 if base >= 0 {
878 if pass < base { no_regress = 0 }
879 }
880
881 var wrote: i64 = 0
882 if no_regress == 1 {
883 if base < 0 { wrote = lg_ratchet_write(pass) }
884 if wrote == 0 {
885 if pass > base { wrote = lg_ratchet_write(pass) }
886 }
887 }
888 gv_puts("ratchet_bytes_written=" as *u8)
889 gv_num(wrote)
890 gv_puts(" (0 = held: neither seeded nor tightened this run)" as *u8)
891 lg_nl()
892
893 // This dialect has no logical-or operator, so the laundering
894 // condition is computed with a flag rather than expressed.
895 var laundered: i64 = 0
896 if no_regress == 0 {
897 if wrote > 0 { laundered = 1 }
898 }
899
900 gv_check("ratchet-loongarch64-did-not-regress" as *u8, no_regress == 1, ctr)
901 gv_check("ratchet-never-rewritten-on-a-regression" as *u8, laundered == 0, ctr)
902
903 // THE WORKLIST, LAST, where a tailing caller can still see it.
904 gv_puts("NONPASS: " as *u8)
905 if bx[LG_NB_OFF] == 0 {
906 gv_puts("none -- every measured KAT matched its manual-derived expectation" as *u8)
907 }
908 if bx[LG_NB_OFF] > 0 {
909 gv_puts(bx[LG_NB_PTR] as *u8)
910 }
911 lg_nl()
912
913 return gv_verdict("nx_isa_loongarch64_gate" as *u8, ctr,
914 "LoongArch64 conformance measured by running LA64 machine code in process against expectations taken from the LoongArch Reference Manual, five-way outcomes, coverage published as a number, two live negative controls that exercise the interpreter refusal paths, fenced by a good-when-higher ratchet that seeds on first sight and never rewrites on a fall" as *u8)
915}