nx_isa_sparc64_gate.nx source
↩ module page · 1007 lines · 42728 B
1// nx_isa_sparc64_gate.nx -- THE SPARC64 CONFORMANCE RULER.
2//
3// WHY IT EXISTS: nx_isa_conform_gate measures five of the estate's ten
4// sovereign ISA emulators and names the other five in one line --
5// "UNMEASURED: armv7a cortexm loongarch64 s390x sparc64 -- each owes a
6// manual-derived KAT block here". This organ pays that debt for
7// sparc64. It is a SEPARATE organ rather than an edit to the incumbent
8// because five lanes are writing five arch blocks at once and
9// concurrent edits to one file silently clobber each other in this
10// tree; the shape is copied deliberately so the two rulers agree.
11//
12// THE LAW IT OBEYS: a capability asserted and never measured is a
13// claim, not coverage. "We emulate sparc64" is decided by running
14// SPARC V9 machine code, in process, against values taken FROM THE
15// SPARC V9 MANUAL -- never from the emulator's own source, which would
16// calibrate the ruler to the subject it exists to judge. The decoder
17// was read first, but ONLY to learn which instruction classes are
18// implemented versus missing; every expected value below is derived
19// from the manual's encoding tables and its Bicc/BPcc condition table.
20//
21// WHAT THE READ FOUND, AND WHAT WAS BUILT BECAUSE OF IT. On
22// 2026-09-03 nx_emu_sparc64 decoded CALL, SETHI, ADD/SUB/AND/OR/XOR,
23// MULX, SDIVX/UDIVX, two shifts, SAVE/RESTORE, JMPL, Tcc and four
24// load/store forms -- and NOTHING ELSE in format 2. It carried no
25// condition-code state and no branch of any kind, so it could not run a
26// LOOP: the sparc64 claim covered straight-line code only. Four
27// further defects sat in the classes it did decode:
28// 1. op3 0x08 is LDSW in V9 and was decoded as an UNSIGNED load, so
29// the only 32-bit load available answered with the wrong sign
30// extension; the real LDUW (op3 0x00) was absent entirely, so a
31// correct program reported UNSUPPORTED.
32// 2. SRL/SRLX (op3 0x26) was written with this dialect's arithmetic
33// right shift and SRA/SRAX (op3 0x27) was absent -- so the logical
34// and the arithmetic right shift were ONE expression and one of
35// the two was necessarily wrong for every negative operand.
36// 3. The shift count came from the sign-extended 13-bit simm without
37// reading the X bit, so the 32-bit shift forms could not be told
38// apart from the 64-bit ones.
39// 4. No sub-word loads or stores at all (LDUB/LDSB/LDUH/LDSH/STB/STH).
40// All of it was implemented in nx_emu_sparc64 -- condition codes for
41// BOTH %icc and %xcc, the *cc arithmetic that writes them, Bicc and
42// BPcc with the annul rule, ANDN/ORN/XNOR, the corrected shifts and the
43// full sub-word load/store set -- and every one of those classes has a
44// KAT below that a WRONG implementation answers differently.
45//
46// FIVE OUTCOMES, NEVER ONE:
47// PASS ran and matched the manual-derived expectation
48// WRONG-ANSWER ran to completion and computed the wrong value
49// UNSUPPORTED hit an instruction class the emulator does not decode
50// RAN-OFF-END the interpreter loop ended without an exit syscall
51// FAULT bad PC, out-of-range access, or the step budget
52// Collapsing these into "FAIL" is what makes a gap unactionable: an
53// unimplemented class and a miscomputed result need OPPOSITE fixes.
54//
55// HONEST LIMIT OF THE FIVE-WAY SPLIT ON THIS ARCH, MEASURED IN THE
56// SOURCE AND NOT ASSUMED: nx_emu_sparc64 initialises result to
57// SPE_FAULT (-3) and has no -2 sentinel at all, so RAN-OFF-END and
58// step-budget exhaustion are INDISTINGUISHABLE from a bad PC here.
59// That is a real reporting gap and it is printed rather than hidden;
60// rv64 and mips64 do carry the third code. This organ deliberately
61// does NOT invent a fix by mapping FAULT onto RANOFF: that would be a
62// guess wearing a classification.
63//
64// DISPATCH: emu_sparc64_run_mem(mem, size, entry, sp0) is the surface
65// all ten emulators share. _load_elf exists on this one but on only
66// six of the ten, so building on it would silently test a subset.
67//
68// license_tier: ORIGINAL
69
70import "nx_gate_verdict.nx"
71import "nx_syscalls.nx"
72import "nx_emu_sparc64.nx"
73
74// ---- guest layout THIS ORGAN owns (never read from the emulator) ----
75const IS_GUEST_BYTES: i64 = 1048576 // 1 MiB guest image
76const IS_ENTRY: i64 = 0 // KAT code is loaded at guest vaddr 0
77const IS_SP: i64 = 983040 // 960 KiB: above the code, inside the image
78const IS_MODE_FILE: i64 = 420 // 0644; 420 and 0x1a4 are the SAME constant
79 // and a sweep that greps one misses the other
80
81// ---- non-completion codes, read out of nx_emu_sparc64's own table ----
82const IS_UNSUPPORTED: i64 = -1 // SPE_UNSUPPORTED
83const IS_RANOFF: i64 = -2 // this emulator never returns it (see header)
84const IS_FAULT: i64 = -3 // SPE_FAULT
85
86const IS_O_PASS: i64 = 0
87const IS_O_WRONG: i64 = 1
88const IS_O_UNSUP: i64 = 2
89const IS_O_FAULT: i64 = 3
90const IS_O_RANOFF: i64 = 4
91
92// The non-PASS worklist. A count without a worklist is not actionable,
93// and a worklist printed only in the BODY is lost the moment a caller
94// tails the output -- so it is accumulated here and printed LAST.
95const IS_NB_BYTES: i64 = 2048
96const IS_NB_OFF: i64 = 0
97const IS_NB_PTR: i64 = 1
98const IS_NB_ROW_MAX: i64 = 256
99const IS_BOX_BYTES: i64 = 32
100
101const IS_NL: i64 = 10
102const IS_SPACE: i64 = 32
103const IS_DIG_LO: i64 = 48
104const IS_DIG_HI: i64 = 57
105const IS_UNSEEDED: i64 = -1
106
107// ===== SPARC V9 encoders, from the manual format tables ============
108// Format 1 (op=1): CALL, disp30.
109// Format 2 (op=0): the rd FIELD carries a (bit 29) and cond (28..25);
110// op2 selects BPcc(1) Bicc(2) SETHI(4); then disp19 / disp22 / imm22.
111// Format 3 (op=2 arithmetic, op=3 memory): rd, op3, rs1, i, then either
112// simm13 (i=1) or rs2 (i=0).
113const IS_OP_F2: i64 = 0
114const IS_OP_CALL: i64 = 1
115const IS_OP_ALU: i64 = 2
116const IS_OP_MEM: i64 = 3
117
118const IS_M13: i64 = 8191 // 0x1FFF simm13
119const IS_M19: i64 = 524287 // 0x7FFFF disp19
120const IS_M22: i64 = 4194303 // 0x3FFFFF imm22 and disp22
121const IS_M30: i64 = 1073741823 // 0x3FFFFFFF disp30
122const IS_XBIT: i64 = 4096 // bit 12: selects the 64-bit shift form
123
124const IS_OP2_BPCC: i64 = 1
125const IS_OP2_BICC: i64 = 2
126const IS_OP2_SETHI: i64 = 4
127
128// Register-file numbering: 0..7 %g, 8..15 %o, 16..23 %l, 24..31 %i.
129// %o0 (8) is the register the exit trap reads for the status.
130const IS_G0: i64 = 0
131const IS_G1: i64 = 1
132const IS_O0: i64 = 8
133const IS_O1: i64 = 9
134const IS_O2: i64 = 10
135const IS_O3: i64 = 11
136const IS_O6: i64 = 14
137const IS_O7: i64 = 15
138const IS_L0: i64 = 16
139const IS_I0: i64 = 24
140
141const IS_A_ADD: i64 = 0x00
142const IS_A_AND: i64 = 0x01
143const IS_A_OR: i64 = 0x02
144const IS_A_XOR: i64 = 0x03
145const IS_A_SUB: i64 = 0x04
146const IS_A_ANDN: i64 = 0x05
147const IS_A_MULX: i64 = 0x09
148const IS_A_ADDCC: i64 = 0x10
149const IS_A_SUBCC: i64 = 0x14
150const IS_A_SLLX: i64 = 0x25
151const IS_A_SRLX: i64 = 0x26
152const IS_A_SRAX: i64 = 0x27
153const IS_A_JMPL: i64 = 0x38
154const IS_A_TCC: i64 = 0x3a
155const IS_A_SAVE: i64 = 0x3c
156const IS_A_REST: i64 = 0x3d
157const IS_A_FPOP1: i64 = 0x34 // deliberately NOT implemented
158
159const IS_M_LDUW: i64 = 0x00
160const IS_M_LDUB: i64 = 0x01
161const IS_M_STW: i64 = 0x04
162const IS_M_STB: i64 = 0x05
163const IS_M_LDSW: i64 = 0x08
164const IS_M_LDSB: i64 = 0x09
165const IS_M_LDX: i64 = 0x0b
166const IS_M_STX: i64 = 0x0e
167
168const IS_C_E: i64 = 1 // BE / BZ Z
169const IS_C_L: i64 = 3 // BL N xor V
170const IS_C_CS: i64 = 5 // BCS / BLU C
171const IS_C_A: i64 = 8 // BA always
172const IS_C_NE: i64 = 9 // BNE / BNZ not Z
173const IS_CCSEL_ICC: i64 = 0
174const IS_CCSEL_XCC: i64 = 2
175
176// Linux/SPARC: the syscall number is in %g1, arguments from %o0, and
177// the trap instruction is Tcc.
178const IS_SYS_EXIT: i64 = 1
179const IS_TRAP_NR: i64 = 16 // ta 0x10
180
181// A bare newline inside a string literal is ambiguous to this lexer, so
182// the byte is CONSTRUCTED.
183func is_nl() -> i64 {
184 let b: *u8 = sys_mmap(8)
185 b[0] = IS_NL as u8
186 sys_write(1, b, 1)
187 return 0
188}
189
190// SPARC is BIG-ENDIAN with fixed 32-bit instructions. Getting this
191// wrong is the silent-fixture defect: the decoder reads garbage,
192// reports UNSUPPORTED, and the FIXTURE bug reads as the emulator being
193// incomplete.
194func is_put_be32(code: *u8, off: i64, w: i64) -> i64 {
195 code[off] = (w >> 24) & 0xff
196 code[off + 1] = (w >> 16) & 0xff
197 code[off + 2] = (w >> 8) & 0xff
198 code[off + 3] = (w) & 0xff
199 return off + 4
200}
201
202func is_guest() -> *u8 { return sys_mmap(IS_GUEST_BYTES) }
203
204func is_f3i(op: i64, rd: i64, op3: i64, rs1: i64, simm: i64) -> i64 {
205 return (op << 30) | (rd << 25) | (op3 << 19) | (rs1 << 14) | (1 << 13) | (simm & IS_M13)
206}
207func is_f3r(op: i64, rd: i64, op3: i64, rs1: i64, rs2: i64) -> i64 {
208 return (op << 30) | (rd << 25) | (op3 << 19) | (rs1 << 14) | rs2
209}
210// The 64-bit shift forms set the X bit and carry shcnt64 in bits 5..0.
211func is_shx(rd: i64, op3: i64, rs1: i64, cnt: i64) -> i64 {
212 return (IS_OP_ALU << 30) | (rd << 25) | (op3 << 19) | (rs1 << 14) | (1 << 13) | IS_XBIT | (cnt & 63)
213}
214func is_sethi(rd: i64, imm22: i64) -> i64 {
215 return (IS_OP_F2 << 30) | (rd << 25) | (IS_OP2_SETHI << 22) | (imm22 & IS_M22)
216}
217func is_nop() -> i64 { return is_sethi(IS_G0, 0) }
218func is_bicc(a: i64, cond: i64, disp22: i64) -> i64 {
219 return (IS_OP_F2 << 30) | (a << 29) | (cond << 25) | (IS_OP2_BICC << 22) | (disp22 & IS_M22)
220}
221func is_bpcc(a: i64, cond: i64, ccsel: i64, p: i64, disp19: i64) -> i64 {
222 return (IS_OP_F2 << 30) | (a << 29) | (cond << 25) | (IS_OP2_BPCC << 22) | (ccsel << 20) | (p << 19) | (disp19 & IS_M19)
223}
224func is_call(disp30: i64) -> i64 { return (IS_OP_CALL << 30) | (disp30 & IS_M30) }
225
226// %g1 = 1 (SunOS/Linux sparc exit) then the trap. The interpreter masks
227// the status with 0xff, so every expectation in this organ is 0..255 by
228// construction and no expected value can collide with a sentinel.
229func is_exit(m: *u8, off: i64) -> i64 {
230 var p: i64 = off
231 p = is_put_be32(m, p, is_f3i(IS_OP_ALU, IS_G1, IS_A_OR, IS_G0, IS_SYS_EXIT))
232 p = is_put_be32(m, p, is_f3i(IS_OP_ALU, IS_C_A, IS_A_TCC, IS_G0, IS_TRAP_NR))
233 return p
234}
235func is_run(m: *u8) -> i64 { return emu_sparc64_run_mem(m, IS_GUEST_BYTES, IS_ENTRY, IS_SP) }
236
237// ===== outcome classification ======================================
238
239func is_classify(got: i64, want: i64) -> i64 {
240 if got == IS_UNSUPPORTED { return IS_O_UNSUP }
241 if got == IS_RANOFF { return IS_O_RANOFF }
242 if got == IS_FAULT { return IS_O_FAULT }
243 if got == want { return IS_O_PASS }
244 return IS_O_WRONG
245}
246
247func is_outcome_name(o: i64) -> *u8 {
248 if o == IS_O_PASS { return "PASS" as *u8 }
249 if o == IS_O_WRONG { return "WRONG-ANSWER" as *u8 }
250 if o == IS_O_UNSUP { return "UNSUPPORTED-instruction-class" as *u8 }
251 if o == IS_O_RANOFF { return "RAN-OFF-END-no-exit-syscall" as *u8 }
252 return "FAULT-bad-pc-or-step-budget" as *u8
253}
254
255// Print the VALUES, not just the verdict: every vacuous tooth ever
256// caught in this estate was caught by a diagnostic dump and never by a
257// verdict vector.
258func is_report(kat: *u8, got: i64, want: i64, bx: *i64) -> i64 {
259 let o: i64 = is_classify(got, want)
260 gv_puts(" KAT sparc64." as *u8)
261 gv_puts(kat)
262 gv_puts(" got=" as *u8)
263 gv_num(got)
264 gv_puts(" want=" as *u8)
265 gv_num(want)
266 gv_puts(" " as *u8)
267 gv_puts(is_outcome_name(o))
268 is_nl()
269 if o != IS_O_PASS {
270 let nb: *u8 = bx[IS_NB_PTR] as *u8
271 var p: i64 = bx[IS_NB_OFF]
272 if p < (IS_NB_BYTES - IS_NB_ROW_MAX) {
273 p = gv_cat(nb, p, "sparc64." as *u8)
274 p = gv_cat(nb, p, kat)
275 p = gv_cat(nb, p, "=" as *u8)
276 p = gv_cat(nb, p, is_outcome_name(o))
277 p = gv_cat(nb, p, "(got=" as *u8)
278 p = gv_catn(nb, p, got)
279 p = gv_cat(nb, p, " want=" as *u8)
280 p = gv_catn(nb, p, want)
281 p = gv_cat(nb, p, ") " as *u8)
282 nb[p] = 0 as u8
283 bx[IS_NB_OFF] = p
284 }
285 }
286 return o
287}
288
289// ===== KATs, every word encoded from the SPARC V9 manual ===========
290// Each program is chosen so a WRONG implementation lands on a DIFFERENT
291// value. The wrong answer is named in the comment beside each, because
292// a test whose right and wrong answers agree is worth nothing and the
293// only way to know which it is, is to have computed both.
294
295// --- arithmetic ---------------------------------------------------
296
297func is_k_imm_arith() -> i64 { // want 42
298 let m: *u8 = is_guest()
299 var o: i64 = 0
300 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_G0, 40)) // add %g0,40,%o0
301 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 2)) // add %o0,2,%o0
302 o = is_exit(m, o)
303 return is_run(m)
304}
305
306func is_k_sub_reg() -> i64 { // want 42; operand order reversed gives 214
307 let m: *u8 = is_guest()
308 var o: i64 = 0
309 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 50))
310 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 8))
311 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_O0, IS_A_SUB, IS_O0, IS_O1)) // sub %o0,%o1,%o0
312 o = is_exit(m, o)
313 return is_run(m)
314}
315
316// 108 xor 53 = 89. Every neighbouring logical op on the same pair is a
317// DIFFERENT number (and 36, or 125, andn 72), so this cannot pass by
318// landing in the wrong branch of the decoder.
319func is_k_xor() -> i64 { // want 89
320 let m: *u8 = is_guest()
321 var o: i64 = 0
322 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 108))
323 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 53))
324 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_O0, IS_A_XOR, IS_O0, IS_O1))
325 o = is_exit(m, o)
326 return is_run(m)
327}
328
329// ANDN was absent before 2026-09-03: 108 and not 53 = 72. Note this is
330// NOT the same as xor for this pair (89), which is exactly why the pair
331// was chosen -- with a = 255 the two collapse and the KAT would be
332// vacuous against a decoder that confused them.
333func is_k_andn() -> i64 { // want 72; plain and gives 36, xor 89
334 let m: *u8 = is_guest()
335 var o: i64 = 0
336 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 108))
337 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 53))
338 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_O0, IS_A_ANDN, IS_O0, IS_O1))
339 o = is_exit(m, o)
340 return is_run(m)
341}
342
343func is_k_mulx() -> i64 { // want 42
344 let m: *u8 = is_guest()
345 var o: i64 = 0
346 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 6))
347 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 7))
348 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_O0, IS_A_MULX, IS_O0, IS_O1))
349 o = is_exit(m, o)
350 return is_run(m)
351}
352
353// SETHI writes imm22 SHIFTED LEFT BY TEN, which is the whole content of
354// the instruction; shifting it back by ten must return the immediate.
355func is_k_sethi() -> i64 { // want 42; a missing shift gives 0
356 let m: *u8 = is_guest()
357 var o: i64 = 0
358 o = is_put_be32(m, o, is_sethi(IS_O0, 42))
359 o = is_put_be32(m, o, is_shx(IS_O0, IS_A_SRLX, IS_O0, 10))
360 o = is_exit(m, o)
361 return is_run(m)
362}
363
364// --- shifts: the discrimination that matters ----------------------
365// -16 shifted right by 60 is 15 logically and -1 arithmetically, and
366// the exit mask renders -1 as 255. The obvious version of this test
367// CANNOT discriminate: -16 >> 2 has low byte 0xFC under both.
368
369func is_k_srlx() -> i64 { // want 15; an arithmetic shift gives 255
370 let m: *u8 = is_guest()
371 var o: i64 = 0
372 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, -16))
373 o = is_put_be32(m, o, is_shx(IS_O0, IS_A_SRLX, IS_O0, 60))
374 o = is_exit(m, o)
375 return is_run(m)
376}
377
378func is_k_srax() -> i64 { // want 255; a logical shift gives 15
379 let m: *u8 = is_guest()
380 var o: i64 = 0
381 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, -16))
382 o = is_put_be32(m, o, is_shx(IS_O0, IS_A_SRAX, IS_O0, 60))
383 o = is_exit(m, o)
384 return is_run(m)
385}
386
387// --- control flow: the class that was entirely missing -------------
388
389// SUM 0..9 = 45 with SUBcc setting %icc and Bicc BNE reading it. This
390// is the KAT that could not exist before: with no cc state and no
391// branch, the emulator could not run a loop at all.
392// 0x00 or %g0,0,%o0 sum
393// 0x04 or %g0,0,%o1 i
394// 0x08 or %g0,10,%o2 n
395// 0x0c add %o0,%o1,%o0 LOOP
396// 0x10 add %o1,1,%o1
397// 0x14 subcc %o1,%o2,%g0 compare, result discarded, cc kept
398// 0x18 bne LOOP disp22 = (0x0c - 0x18)/4 = -3
399// 0x1c nop delay slot
400// 0x20 exit
401func is_k_branch_loop() -> i64 { // want 45
402 let m: *u8 = is_guest()
403 var o: i64 = 0
404 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 0))
405 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 0))
406 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O2, IS_A_OR, IS_G0, 10))
407 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, IS_O1))
408 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_ADD, IS_O1, 1))
409 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_G0, IS_A_SUBCC, IS_O1, IS_O2))
410 o = is_put_be32(m, o, is_bicc(0, IS_C_NE, 0 - 3))
411 o = is_put_be32(m, o, is_nop())
412 o = is_exit(m, o)
413 return is_run(m)
414}
415
416// THE DELAY SLOT. The instruction after a taken branch executes anyway;
417// an implementation that jumps straight to the target skips it.
418// 0x00 or %g0,0,%o0
419// 0x04 subcc %g0,%g0,%g0 Z=1
420// 0x08 be SKIP disp22 = (0x14 - 0x08)/4 = 3, TAKEN
421// 0x0c or %g0,7,%o0 DELAY SLOT -- must execute
422// 0x10 or %g0,99,%o0 must be skipped
423// 0x14 add %o0,35,%o0 SKIP: 7 + 35
424// 0x18 exit
425func is_k_delay_slot() -> i64 { // want 42; slot dropped 35; branch missed 134
426 let m: *u8 = is_guest()
427 var o: i64 = 0
428 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 0))
429 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_G0, IS_A_SUBCC, IS_G0, IS_G0))
430 o = is_put_be32(m, o, is_bicc(0, IS_C_E, 3))
431 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 7))
432 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 99))
433 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 35))
434 o = is_exit(m, o)
435 return is_run(m)
436}
437
438// THE ANNUL BIT, the half of the rule an implementation usually drops:
439// on a CONDITIONAL branch a=1 annuls the delay instruction only when the
440// branch is NOT taken.
441// 0x00 or %g0,5,%o0
442// 0x04 subcc %g0,%g0,%g0 Z=1, so BNE is UNTAKEN
443// 0x08 bne,a SKIP disp22 = (0x1c - 0x08)/4 = 5
444// 0x0c or %g0,0,%o0 ANNULLED -- must NOT execute
445// 0x10 add %o0,37,%o0 5 + 37
446// 0x14 exit
447// 0x1c or %g0,200,%o0 SKIP: poison, reached only if wrongly taken
448// 0x20 exit
449func is_k_annul() -> i64 { // want 42; slot not annulled 37; taken 200
450 let m: *u8 = is_guest()
451 var o: i64 = 0
452 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 5))
453 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_G0, IS_A_SUBCC, IS_G0, IS_G0))
454 o = is_put_be32(m, o, is_bicc(1, IS_C_NE, 5))
455 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 0))
456 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 37))
457 o = is_exit(m, o)
458 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 200))
459 o = is_exit(m, o)
460 return is_run(m)
461}
462
463// %icc VERSUS %xcc -- the V9 property no 32-bit SPARC has. 2^32 has low
464// 32 bits of ZERO and is nonzero in 64, so after SUBcc the two
465// condition-code fields DISAGREE about Z and only an emulator that keeps
466// both can answer this.
467// 0x00 or %g0,1,%o1
468// 0x04 sllx %o1,32,%o1 o1 = 2^32
469// 0x08 or %g0,0,%o0
470// 0x0c subcc %o1,%g0,%g0 icc.Z=1 xcc.Z=0
471// 0x10 be %xcc,L1 disp19 = 3, NOT taken
472// 0x14 nop
473// 0x18 add %o0,2,%o0 reached only because xcc.Z was 0
474// 0x1c be %icc,L2 disp19 = 3, TAKEN
475// 0x20 nop
476// 0x24 add %o0,100,%o0 skipped only because icc.Z was 1
477// 0x28 add %o0,40,%o0
478// 0x2c exit
479func is_k_icc_vs_xcc() -> i64 { // want 42; xcc for both 142; icc for both 40
480 let m: *u8 = is_guest()
481 var o: i64 = 0
482 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 1))
483 o = is_put_be32(m, o, is_shx(IS_O1, IS_A_SLLX, IS_O1, 32))
484 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 0))
485 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_G0, IS_A_SUBCC, IS_O1, IS_G0))
486 o = is_put_be32(m, o, is_bpcc(0, IS_C_E, IS_CCSEL_XCC, 1, 3))
487 o = is_put_be32(m, o, is_nop())
488 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 2))
489 o = is_put_be32(m, o, is_bpcc(0, IS_C_E, IS_CCSEL_ICC, 1, 3))
490 o = is_put_be32(m, o, is_nop())
491 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 100))
492 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 40))
493 o = is_exit(m, o)
494 return is_run(m)
495}
496
497// THE CARRY POLARITY. SPARC sets C on a SUBcc to the BORROW, so BLU
498// means unsigned-less-than -- the OPPOSITE of the ARM convention a
499// reader may be carrying. -1 is unsigned-GREATER than 1 and signed-LESS
500// than 1, so BCS must be untaken while BL is taken; an emulator that
501// inverted C takes the first branch and reports 32.
502// 0x00 or %g0,0,%o0
503// 0x04 or %g0,-1,%o1
504// 0x08 or %g0,1,%o2
505// 0x0c subcc %o1,%o2,%g0
506// 0x10 bcs L1 disp22 = 3, NOT taken (C=0)
507// 0x14 nop
508// 0x18 add %o0,10,%o0
509// 0x1c bl L2 disp22 = 3, TAKEN (N xor V = 1)
510// 0x20 nop
511// 0x24 add %o0,100,%o0 skipped
512// 0x28 add %o0,32,%o0
513// 0x2c exit
514func is_k_carry_polarity() -> i64 { // want 42; inverted carry 32; no signed test 142
515 let m: *u8 = is_guest()
516 var o: i64 = 0
517 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 0))
518 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 0 - 1))
519 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O2, IS_A_OR, IS_G0, 1))
520 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_G0, IS_A_SUBCC, IS_O1, IS_O2))
521 o = is_put_be32(m, o, is_bicc(0, IS_C_CS, 3))
522 o = is_put_be32(m, o, is_nop())
523 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 10))
524 o = is_put_be32(m, o, is_bicc(0, IS_C_L, 3))
525 o = is_put_be32(m, o, is_nop())
526 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 100))
527 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 32))
528 o = is_exit(m, o)
529 return is_run(m)
530}
531
532// CALL sets %o7 to the address OF THE CALL, so the return is %o7 + 8 --
533// past the call and past its delay slot. An off-by-one lands on the
534// delay slot or back on the caller and the answer changes.
535// 0x00 or %g0,0,%o0
536// 0x04 call SUB disp30 = 5 -> 0x04 + 20 = 0x18
537// 0x08 nop delay slot
538// 0x0c add %o0,2,%o0 40 + 2
539// 0x10 exit
540// 0x18 or %g0,40,%o0 SUB
541// 0x1c jmpl %o7+8,%g0
542// 0x20 nop delay slot
543func is_k_call_ret() -> i64 { // want 42
544 let m: *u8 = is_guest()
545 var o: i64 = 0
546 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 0))
547 o = is_put_be32(m, o, is_call(5))
548 o = is_put_be32(m, o, is_nop())
549 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O0, 2))
550 o = is_exit(m, o)
551 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 40))
552 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_G0, IS_A_JMPL, IS_O7, 8))
553 o = is_put_be32(m, o, is_nop())
554 return is_run(m)
555}
556
557// REGISTER WINDOWS, the feature that makes this architecture what it is.
558// SAVE moves to a new window in which the caller %o registers appear as
559// %i; RESTORE reads its SOURCES in the old window and writes its
560// DESTINATION in the new one. An emulator that shifts the window before
561// reading the sources returns something else entirely.
562// 0x00 or %g0,21,%o0
563// 0x04 save %g0,%g0,%g0 cwp+1; the caller %o0 is now %i0
564// 0x08 add %i0,%i0,%l0 21 + 21
565// 0x0c restore %l0,%g0,%o0 cwp-1; reads %l0 here, writes %o0 there
566// 0x10 exit
567func is_k_save_restore() -> i64 { // want 42
568 let m: *u8 = is_guest()
569 var o: i64 = 0
570 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 21))
571 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_G0, IS_A_SAVE, IS_G0, IS_G0))
572 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_L0, IS_A_ADD, IS_I0, IS_I0))
573 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_O0, IS_A_REST, IS_L0, IS_G0))
574 o = is_exit(m, o)
575 return is_run(m)
576}
577
578// --- memory -------------------------------------------------------
579
580func is_k_ldx_stx() -> i64 { // want 42
581 let m: *u8 = is_guest()
582 var o: i64 = 0
583 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 42))
584 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O0, IS_M_STX, IS_O6, 0 - 8))
585 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_OR, IS_G0, 0))
586 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O0, IS_M_LDX, IS_O6, 0 - 8))
587 o = is_exit(m, o)
588 return is_run(m)
589}
590
591// LDSW (op3 0x08) must SIGN-extend. The low byte of -1 and of
592// 0xFFFFFFFF is identical, so the exit mask hides the difference unless
593// the high half is brought down: an arithmetic shift by 32 leaves -1 for
594// the correct load and 0 for a zero-extending one.
595func is_k_ldsw_sign_extends() -> i64 { // want 42; zero-extending load 43
596 let m: *u8 = is_guest()
597 var o: i64 = 0
598 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 0 - 1))
599 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O1, IS_M_STW, IS_O6, 0 - 8))
600 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O2, IS_M_LDSW, IS_O6, 0 - 8))
601 o = is_put_be32(m, o, is_shx(IS_O2, IS_A_SRAX, IS_O2, 32))
602 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O2, 43))
603 o = is_exit(m, o)
604 return is_run(m)
605}
606
607// LDUW (op3 0x00) must ZERO-extend -- and before 2026-09-03 op3 0x00 was
608// not decoded at all in the memory format, so this KAT reported
609// UNSUPPORTED rather than a wrong number.
610func is_k_lduw_zero_extends() -> i64 { // want 42; sign-extending load 41
611 let m: *u8 = is_guest()
612 var o: i64 = 0
613 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 0 - 1))
614 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O1, IS_M_STW, IS_O6, 0 - 8))
615 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O2, IS_M_LDUW, IS_O6, 0 - 8))
616 o = is_put_be32(m, o, is_shx(IS_O2, IS_A_SRLX, IS_O2, 32))
617 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O2, 42))
618 o = is_exit(m, o)
619 return is_run(m)
620}
621
622func is_k_stb_ldub() -> i64 { // want 42
623 let m: *u8 = is_guest()
624 var o: i64 = 0
625 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 200))
626 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O1, IS_M_STB, IS_O6, 0 - 4))
627 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O2, IS_M_LDUB, IS_O6, 0 - 4))
628 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_SUB, IS_O2, 158))
629 o = is_exit(m, o)
630 return is_run(m)
631}
632
633// LDSB of the byte 200 is -56; shifting arithmetically by 8 leaves -1
634// for the signed load and 0 for an unsigned one. Without the shift the
635// exit mask makes the two indistinguishable -- which is how a sign
636// defect survives an obvious test.
637func is_k_ldsb_sign_extends() -> i64 { // want 42; unsigned load 43
638 let m: *u8 = is_guest()
639 var o: i64 = 0
640 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 200))
641 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O1, IS_M_STB, IS_O6, 0 - 4))
642 o = is_put_be32(m, o, is_f3i(IS_OP_MEM, IS_O3, IS_M_LDSB, IS_O6, 0 - 4))
643 o = is_put_be32(m, o, is_shx(IS_O3, IS_A_SRAX, IS_O3, 8))
644 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O0, IS_A_ADD, IS_O3, 43))
645 o = is_exit(m, o)
646 return is_run(m)
647}
648
649// --- REAL non-completion controls ---------------------------------
650// These two run the EMULATOR and read its own sentinel, rather than
651// handing a constant to the classifier and calling that a control. A
652// hand-fed sentinel proves the classifier maps -1 to UNSUPPORTED; only
653// these prove the EMULATOR still says so.
654
655// FPop1 (op=2, op3 0x34) is a class this interpreter does not decode and
656// is not on any roadmap here. It must come back UNSUPPORTED and must
657// NEVER be reported as a pass.
658func is_k_unsupported_probe() -> i64 {
659 let m: *u8 = is_guest()
660 var o: i64 = 0
661 o = is_put_be32(m, o, is_f3r(IS_OP_ALU, IS_O0, IS_A_FPOP1, IS_O0, IS_O1))
662 o = is_exit(m, o)
663 return is_run(m)
664}
665
666// A jump to 2^40, far outside a 1 MiB guest. The interpreter checks the
667// PC before fetching, so this must come back FAULT.
668func is_k_fault_probe() -> i64 {
669 let m: *u8 = is_guest()
670 var o: i64 = 0
671 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_O1, IS_A_OR, IS_G0, 1))
672 o = is_put_be32(m, o, is_shx(IS_O1, IS_A_SLLX, IS_O1, 40))
673 o = is_put_be32(m, o, is_f3i(IS_OP_ALU, IS_G0, IS_A_JMPL, IS_O1, 0))
674 o = is_put_be32(m, o, is_nop())
675 o = is_exit(m, o)
676 return is_run(m)
677}
678
679// ===== ratchet =====================================================
680// One line: "sparc64 <passed>". UNSEEDED is a NAMED third state so
681// adopting this fence cannot fail closed on first sight. Polarity is
682// GOOD-WHEN-HIGHER, the inverse of an offender-count ratchet: it
683// TIGHTENS on a rise and must NEVER rewrite its baseline on a fall,
684// because rewriting on a fall is how a ratchet launders itself green.
685// It is a SEPARATE file from isa_conform.ratchet on purpose -- that one
686// belongs to nx_isa_conform_gate and two writers on one baseline is the
687// duplicate-ruler defect.
688const IS_RATCHET: *u8 = "knowledge/status/isa_sparc64.ratchet"
689const IS_ARCH: *u8 = "sparc64"
690
691func is_match_at(buf: *u8, n: i64, p: i64, s: *u8) -> i64 {
692 var j: i64 = 0
693 var ok: i64 = 1
694 while s[j] != (0 as u8) {
695 if (p + j) >= n { ok = 0 }
696 if ok == 1 {
697 if buf[p + j] != s[j] { ok = 0 }
698 }
699 j = j + 1
700 }
701 if ok == 0 { return -1 }
702 return j
703}
704
705func is_digits_at(buf: *u8, n: i64, p0: i64) -> i64 {
706 var v: i64 = 0
707 var any: i64 = 0
708 var p: i64 = p0
709 var go: i64 = 1
710 while go == 1 {
711 if p >= n { go = 0 }
712 if go == 1 {
713 let c: i64 = buf[p] & 0xff
714 if c < IS_DIG_LO { go = 0 }
715 if go == 1 {
716 if c > IS_DIG_HI { go = 0 }
717 }
718 if go == 1 {
719 v = v * 10 + (c - IS_DIG_LO)
720 any = 1
721 p = p + 1
722 }
723 }
724 }
725 if any == 0 { return -1 }
726 return v
727}
728
729func is_ratchet_read() -> i64 {
730 let lenbox: *i64 = sys_mmap(16) as *i64
731 let buf: *u8 = sys_read_file(IS_RATCHET, lenbox)
732 if (buf as i64) == 0 { return IS_UNSEEDED }
733 let n: i64 = lenbox[0]
734 var i: i64 = 0
735 var found: i64 = IS_UNSEEDED
736 while i < n {
737 if found < 0 {
738 let mm: i64 = is_match_at(buf, n, i, IS_ARCH)
739 if mm > 0 {
740 if (i + mm) < n {
741 if buf[i + mm] == (IS_SPACE as u8) {
742 found = is_digits_at(buf, n, i + mm + 1)
743 }
744 }
745 }
746 }
747 var adv: i64 = 1
748 while adv == 1 {
749 if i >= n { adv = 0 }
750 if adv == 1 {
751 if buf[i] == (IS_NL as u8) { adv = 0 }
752 i = i + 1
753 }
754 }
755 }
756 return found
757}
758
759func is_ratchet_write(np: i64) -> i64 {
760 let d: *u8 = sys_mmap(64)
761 var o: i64 = 0
762 o = gv_cat(d, o, "sparc64 " as *u8)
763 o = gv_catn(d, o, np)
764 d[o] = IS_NL as u8
765 o = o + 1
766 let fd: i64 = sys_openat_wr(IS_RATCHET, IS_MODE_FILE)
767 if fd < 0 { return -1 }
768 sys_write(fd, d, o)
769 sys_fsync(fd)
770 sys_close(fd)
771 return o
772}
773
774// ===== main ========================================================
775
776func main(argc: i64, argv: *i64) -> i64 {
777 gv_head("nx_isa_sparc64_gate -- sovereign SPARC V9 emulator conformance, KATs encoded from the SPARC V9 manual" as *u8)
778 let ctr: *i64 = gv_ctr()
779
780 let bx: *i64 = sys_mmap(IS_BOX_BYTES) as *i64
781 let nbuf: *u8 = sys_mmap(IS_NB_BYTES)
782 nbuf[0] = 0 as u8
783 bx[IS_NB_OFF] = 0
784 bx[IS_NB_PTR] = nbuf as i64
785
786 gv_puts("dispatch=emu_sparc64_run_mem (the surface all 10 emulators share; _load_elf exists here but on only 6 of 10)" as *u8)
787 is_nl()
788 gv_puts("outcomes are FIVE-WAY: PASS / WRONG-ANSWER / UNSUPPORTED-instruction-class / RAN-OFF-END / FAULT" as *u8)
789 is_nl()
790 gv_puts("SCOPE NOTE, measured in nx_emu_sparc64 and not assumed: result is initialised to SPE_FAULT and there is NO -2 sentinel," as *u8)
791 is_nl()
792 gv_puts("so on THIS arch a run-off-the-end and a step-budget exhaustion are INDISTINGUISHABLE from a bad PC. rv64 and mips64 do carry the third code." as *u8)
793 is_nl()
794 is_nl()
795
796 gv_puts("[sparc64] SPARC V9 -- BIG-ENDIAN, fixed 32-bit words, DELAY SLOTS, REGISTER WINDOWS, two condition-code fields" as *u8)
797 is_nl()
798 var sp_pass: i64 = 0
799 var sp_tot: i64 = 0
800 var o: i64 = 0
801
802 o = is_report("imm-arith" as *u8, is_k_imm_arith(), 42, bx)
803 sp_tot = sp_tot + 1
804 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
805 gv_check("isa-sparc64-imm-arith" as *u8, o == IS_O_PASS, ctr)
806
807 o = is_report("sub-reg-operand-order" as *u8, is_k_sub_reg(), 42, bx)
808 sp_tot = sp_tot + 1
809 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
810 gv_check("isa-sparc64-sub-reg-operand-order" as *u8, o == IS_O_PASS, ctr)
811
812 o = is_report("xor" as *u8, is_k_xor(), 89, bx)
813 sp_tot = sp_tot + 1
814 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
815 gv_check("isa-sparc64-xor" as *u8, o == IS_O_PASS, ctr)
816
817 o = is_report("andn-distinct-from-and-and-xor" as *u8, is_k_andn(), 72, bx)
818 sp_tot = sp_tot + 1
819 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
820 gv_check("isa-sparc64-andn-distinct-from-and-and-xor" as *u8, o == IS_O_PASS, ctr)
821
822 o = is_report("mulx" as *u8, is_k_mulx(), 42, bx)
823 sp_tot = sp_tot + 1
824 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
825 gv_check("isa-sparc64-mulx" as *u8, o == IS_O_PASS, ctr)
826
827 o = is_report("sethi-shifts-imm22-left-by-ten" as *u8, is_k_sethi(), 42, bx)
828 sp_tot = sp_tot + 1
829 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
830 gv_check("isa-sparc64-sethi-shifts-imm22-left-by-ten" as *u8, o == IS_O_PASS, ctr)
831
832 o = is_report("srlx-is-logical" as *u8, is_k_srlx(), 15, bx)
833 sp_tot = sp_tot + 1
834 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
835 gv_check("isa-sparc64-srlx-is-logical" as *u8, o == IS_O_PASS, ctr)
836
837 o = is_report("srax-is-arithmetic" as *u8, is_k_srax(), 255, bx)
838 sp_tot = sp_tot + 1
839 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
840 gv_check("isa-sparc64-srax-is-arithmetic" as *u8, o == IS_O_PASS, ctr)
841
842 o = is_report("branch-loop-subcc-plus-bicc" as *u8, is_k_branch_loop(), 45, bx)
843 sp_tot = sp_tot + 1
844 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
845 gv_check("isa-sparc64-branch-loop-subcc-plus-bicc" as *u8, o == IS_O_PASS, ctr)
846
847 o = is_report("branch-delay-slot-executes" as *u8, is_k_delay_slot(), 42, bx)
848 sp_tot = sp_tot + 1
849 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
850 gv_check("isa-sparc64-branch-delay-slot-executes" as *u8, o == IS_O_PASS, ctr)
851
852 o = is_report("annul-bit-skips-slot-only-when-untaken" as *u8, is_k_annul(), 42, bx)
853 sp_tot = sp_tot + 1
854 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
855 gv_check("isa-sparc64-annul-bit-skips-slot-only-when-untaken" as *u8, o == IS_O_PASS, ctr)
856
857 o = is_report("bpcc-icc-and-xcc-are-separate-fields" as *u8, is_k_icc_vs_xcc(), 42, bx)
858 sp_tot = sp_tot + 1
859 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
860 gv_check("isa-sparc64-bpcc-icc-and-xcc-are-separate-fields" as *u8, o == IS_O_PASS, ctr)
861
862 o = is_report("subcc-carry-is-a-borrow" as *u8, is_k_carry_polarity(), 42, bx)
863 sp_tot = sp_tot + 1
864 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
865 gv_check("isa-sparc64-subcc-carry-is-a-borrow" as *u8, o == IS_O_PASS, ctr)
866
867 o = is_report("call-sets-o7-to-its-own-address" as *u8, is_k_call_ret(), 42, bx)
868 sp_tot = sp_tot + 1
869 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
870 gv_check("isa-sparc64-call-sets-o7-to-its-own-address" as *u8, o == IS_O_PASS, ctr)
871
872 o = is_report("save-restore-window-overlap" as *u8, is_k_save_restore(), 42, bx)
873 sp_tot = sp_tot + 1
874 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
875 gv_check("isa-sparc64-save-restore-window-overlap" as *u8, o == IS_O_PASS, ctr)
876
877 o = is_report("ldx-stx-roundtrip" as *u8, is_k_ldx_stx(), 42, bx)
878 sp_tot = sp_tot + 1
879 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
880 gv_check("isa-sparc64-ldx-stx-roundtrip" as *u8, o == IS_O_PASS, ctr)
881
882 o = is_report("ldsw-sign-extends" as *u8, is_k_ldsw_sign_extends(), 42, bx)
883 sp_tot = sp_tot + 1
884 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
885 gv_check("isa-sparc64-ldsw-sign-extends" as *u8, o == IS_O_PASS, ctr)
886
887 o = is_report("lduw-zero-extends" as *u8, is_k_lduw_zero_extends(), 42, bx)
888 sp_tot = sp_tot + 1
889 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
890 gv_check("isa-sparc64-lduw-zero-extends" as *u8, o == IS_O_PASS, ctr)
891
892 o = is_report("stb-ldub-roundtrip" as *u8, is_k_stb_ldub(), 42, bx)
893 sp_tot = sp_tot + 1
894 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
895 gv_check("isa-sparc64-stb-ldub-roundtrip" as *u8, o == IS_O_PASS, ctr)
896
897 o = is_report("ldsb-sign-extends" as *u8, is_k_ldsb_sign_extends(), 42, bx)
898 sp_tot = sp_tot + 1
899 if o == IS_O_PASS { sp_pass = sp_pass + 1 }
900 gv_check("isa-sparc64-ldsb-sign-extends" as *u8, o == IS_O_PASS, ctr)
901
902 // ---- the fixture must have REACHED the emulator ---------------
903 // Asserting an outcome without asserting the fixture ran is how four
904 // vacuous fixtures shipped in this estate in a single day.
905 is_nl()
906 gv_check("fixture-reached-sparc64-emulator-at-all" as *u8, sp_pass > 0, ctr)
907
908 // ---- negative controls ----------------------------------------
909 // The first three interrogate the CLASSIFIER: if is_classify were
910 // stubbed to return PASS, every green above would be fake and only
911 // these would notice. The last two interrogate the EMULATOR, by
912 // running real machine code whose only correct answers are a
913 // refusal and a fault -- a hand-fed sentinel cannot do that.
914 let ncw: i64 = is_classify(is_k_imm_arith(), 41)
915 gv_puts(" neg-control sparc64.imm-arith against a deliberately wrong want=41 -> " as *u8)
916 gv_puts(is_outcome_name(ncw))
917 is_nl()
918 gv_check("neg-control-wrong-expectation-must-not-pass" as *u8, ncw == IS_O_WRONG, ctr)
919
920 let ncu: i64 = is_classify(IS_UNSUPPORTED, 42)
921 gv_check("neg-control-unsupported-sentinel-never-reads-as-pass" as *u8, ncu == IS_O_UNSUP, ctr)
922
923 let ncf: i64 = is_classify(IS_FAULT, 42)
924 gv_check("neg-control-fault-sentinel-never-reads-as-pass" as *u8, ncf == IS_O_FAULT, ctr)
925
926 let rawu: i64 = is_k_unsupported_probe()
927 let ru: i64 = is_classify(rawu, 42)
928 gv_puts(" neg-control-live sparc64.fpop1 raw=" as *u8)
929 gv_num(rawu)
930 gv_puts(" -> " as *u8)
931 gv_puts(is_outcome_name(ru))
932 is_nl()
933 gv_check("neg-control-live-undecoded-class-reports-UNSUPPORTED-not-a-number" as *u8, ru == IS_O_UNSUP, ctr)
934
935 let rawf: i64 = is_k_fault_probe()
936 let rf: i64 = is_classify(rawf, 42)
937 gv_puts(" neg-control-live sparc64.jump-to-2^40 raw=" as *u8)
938 gv_num(rawf)
939 gv_puts(" -> " as *u8)
940 gv_puts(is_outcome_name(rf))
941 is_nl()
942 gv_check("neg-control-live-out-of-range-pc-reports-FAULT-not-a-number" as *u8, rf == IS_O_FAULT, ctr)
943
944 // ---- coverage, published as a NUMBER not a verdict -------------
945 is_nl()
946 gv_puts("coverage sparc64=" as *u8)
947 gv_num(sp_pass)
948 gv_puts("/" as *u8)
949 gv_num(sp_tot)
950 gv_puts(" arch=sparc64 endianness=big word=32 delay_slots=yes register_windows=yes cc_fields=icc+xcc" as *u8)
951 is_nl()
952 gv_puts("classes measured: imm-arith sub logic andn mulx sethi shift-logical shift-arith branch-loop delay-slot annul bpcc-icc-vs-xcc carry-borrow call-jmpl save-restore ldx-stx ldsw lduw stb-ldub ldsb" as *u8)
953 is_nl()
954 gv_puts("classes STILL UNDECODED by nx_emu_sparc64 and therefore NOT claimed: floating point, BPr branch-on-register, MOVcc, RETURN, LDSTUB/SWAP/CAS, alternate-space loads, Tcc other than an unconditional trap, and the window-overflow/underflow traps." as *u8)
955 is_nl()
956
957 // ---- ratchet ---------------------------------------------------
958 let base: i64 = is_ratchet_read()
959 gv_puts("ratchet sparc64 base=" as *u8)
960 gv_num(base)
961 gv_puts(" now=" as *u8)
962 gv_num(sp_pass)
963 gv_puts(" (base=-1 is UNSEEDED, a named state: first sight seeds, so adoption cannot fail closed)" as *u8)
964 is_nl()
965
966 var no_regress: i64 = 1
967 if base >= 0 {
968 if sp_pass < base { no_regress = 0 }
969 }
970
971 // Seed when unseeded; tighten only on a rise; NEVER rewrite on a
972 // fall, which is the direction that would launder a regression.
973 var wrote: i64 = 0
974 if no_regress == 1 {
975 if base < 0 { wrote = is_ratchet_write(sp_pass) }
976 if wrote == 0 {
977 if sp_pass > base { wrote = is_ratchet_write(sp_pass) }
978 }
979 }
980 gv_puts("ratchet_bytes_written=" as *u8)
981 gv_num(wrote)
982 gv_puts(" (0 = held: neither seeded nor tightened this run)" as *u8)
983 is_nl()
984
985 // This dialect has no or-operator, so the laundering condition is
986 // COMPUTED rather than expressed.
987 var laundered: i64 = 0
988 if no_regress == 0 {
989 if wrote > 0 { laundered = 1 }
990 }
991
992 gv_check("ratchet-sparc64-did-not-regress" as *u8, no_regress == 1, ctr)
993 gv_check("ratchet-never-rewritten-on-a-regression" as *u8, laundered == 0, ctr)
994
995 // THE WORKLIST, LAST, where a tailing caller can still see it.
996 gv_puts("NONPASS: " as *u8)
997 if bx[IS_NB_OFF] == 0 {
998 gv_puts("none -- every sparc64 KAT matched its manual-derived expectation" as *u8)
999 }
1000 if bx[IS_NB_OFF] > 0 {
1001 gv_puts(bx[IS_NB_PTR] as *u8)
1002 }
1003 is_nl()
1004
1005 return gv_verdict("nx_isa_sparc64_gate" as *u8, ctr,
1006 "sparc64 conformance measured by running SPARC V9 machine code against manual-derived expectations with five-way outcomes, live UNSUPPORTED and FAULT controls taken from the emulator itself, coverage published as a number, fenced by a good-when-higher ratchet that seeds on first sight and never rewrites on a fall" as *u8)
1007}