code wiki / (root) / nx_isa_loongarch64_gate.nx

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}