nx_isa_s390x_gate.nx source
↩ module page · 945 lines · 38247 B
1// nx_isa_s390x_gate.nx -- THE s390x CONFORMANCE RULER.
2//
3// WHY IT EXISTS: nx_isa_conform_gate measures rv64, mips64, arm64, riscv32 and
4// ppc64le and names s390x in its own output as UNMEASURED. An emulator nobody
5// runs machine code through is a claim, not a capability. This organ decides
6// "we emulate IBM z/Architecture" by RUNNING s390x machine code, in process,
7// against values taken FROM THE z/ARCHITECTURE PRINCIPLES OF OPERATION -- never
8// from nx_emu_s390x.nx itself, which would calibrate the ruler to its subject.
9//
10// SEPARATE ORGAN, NOT A PATCH TO THE INCUMBENT: five other ISA lanes are
11// editing the estate at the same time, and concurrent edits to one file
12// silently clobber each other here. The SHAPE is copied from
13// nx_isa_conform_gate (word emitters, five-way classification, a value-printing
14// report, negative controls, a fixture-reached tooth, a good-when-higher
15// ratchet); nothing is imported from it, so neither can break the other.
16//
17// FIVE OUTCOMES, NEVER ONE:
18// PASS ran and matched the manual-derived expectation
19// WRONG-ANSWER ran to completion and computed the wrong value
20// UNSUPPORTED hit an instruction class the emulator does not implement
21// RAN-OFF-END the interpreter loop ended without an exit syscall
22// FAULT bad pc, out-of-range access, or the step budget
23// Collapsing these into FAIL is what makes a gap unactionable: an unimplemented
24// instruction class and a miscomputed result need OPPOSITE fixes.
25//
26// WHAT MAKES s390x DIFFERENT FROM EVERY OTHER LANE ON THIS BOARD: it is the
27// only VARIABLE-LENGTH ISA here. The instruction length comes from the top two
28// bits of byte 0 (00 -> 2 bytes, 01/10 -> 4, 11 -> 6), so the emitter cannot be
29// a single put_word helper and every branch displacement is a HALFWORD count,
30// not a byte count. A fixture that assumed a fixed width would mis-place every
31// label and the resulting garbage would read as the emulator being incomplete.
32// So there is an explicit tooth asserting the emitter really returns 2, 4 and 6.
33//
34// BIG-ENDIAN. Every field below is written most-significant byte first. Getting
35// this wrong is the silent-fixture defect: the decoder reads garbage, reports
36// UNSUPPORTED, and the FIXTURE's bug wears the emulator's name.
37//
38// THE TWO NEGATIVE CONTROLS THAT ARE NOT DECORATION: nx_emu_s390x used to set
39// its "handled" flag for a whole opcode family BEFORE looking at the extended
40// opcode, so an unimplemented member of that family executed as a silent
41// no-operation and the program ran on to a wrong answer. An unknown SVC number
42// did the same. Both are the UNSUPPORTED-read-as-something-else collapse, so
43// both are bitten here with real guest programs rather than with a synthetic
44// sentinel: a run containing B90F (LRVGR, deliberately not implemented) and a
45// run containing SVC 99 must each come back UNSUPPORTED. If a later generation
46// implements LRVGR, THIS CONTROL MUST BE RE-POINTED at another unimplemented
47// opcode -- a control whose subject has been implemented is a tooth that can
48// never fire again, and it would go on reading green forever.
49//
50// WHY A RATCHET AND NOT A BAR: the emulator is honestly incomplete (no packed
51// decimal, no vector facility, no access registers, no floating point). A gate
52// demanding completeness would be permanently RED and a permanently RED
53// detector is one everyone learns to ignore. The VERDICT is "s390x did not
54// regress against its banked score"; the absolute coverage is PUBLISHED AS A
55// NUMBER beside it. 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_s390x.nx"
62
63// ---- guest layout THIS ORGAN owns (never read from the emulator) ----
64const SX_GUEST_BYTES: i64 = 1048576 // 1 MiB guest image
65const SX_ENTRY: i64 = 0 // KAT code is loaded at guest vaddr 0
66const SX_SP: i64 = 983040 // 960 KiB: above the code, inside the image
67const SX_SCRATCH: i64 = 512 // load/store KATs use this guest address
68const SX_MODE_FILE: i64 = 420 // 0644. 420 and 0x1a4 are the SAME constant
69 // and a sweep that greps one misses the other.
70
71// ---- non-completion codes the interpreter returns -------------------
72// Read out of nx_emu_s390x.nx's own constant table, not guessed:
73// SZ_UNSUPPORTED = -1, SZ_RANOFF = -2, SZ_FAULT = -3. Every exit path masks
74// with 255, so a real answer is 0..255 and cannot collide with these.
75const SX_UNSUPPORTED: i64 = -1
76const SX_RANOFF: i64 = -2
77const SX_FAULT: i64 = -3
78
79// ---- outcome classes ----
80const SX_O_PASS: i64 = 0
81const SX_O_WRONG: i64 = 1
82const SX_O_UNSUP: i64 = 2
83const SX_O_FAULT: i64 = 3
84const SX_O_RANOFF: i64 = 4
85
86// ---- s390x opcode bytes, from the PoP opcode tables -----------------
87const SX_OP_BCR: i64 = 0x07
88const SX_OP_SVC: i64 = 0x0A
89const SX_OP_RI: i64 = 0xA7
90const SX_OP_RRE: i64 = 0xB9
91const SX_OP_LA: i64 = 0x41
92const SX_OP_RXY: i64 = 0xE3
93const SX_OP_RSY: i64 = 0xEB
94const SX_OP_RIL: i64 = 0xC0
95
96// RI extended opcodes (the low nibble of byte 1). THE ORDER OF THIS TABLE IS
97// THE WHOLE POINT: A7xA is AHI (32-bit) and A7xB is AGHI (64-bit), and the
98// previous emulator generation had AGHI at A7xA with no A7xB at all.
99const SX_RI_BRC: i64 = 0x4
100const SX_RI_BRAS: i64 = 0x5
101const SX_RI_BRCT: i64 = 0x6
102const SX_RI_BRCTG: i64 = 0x7
103const SX_RI_LHI: i64 = 0x8
104const SX_RI_LGHI: i64 = 0x9
105const SX_RI_AHI: i64 = 0xA
106const SX_RI_AGHI: i64 = 0xB
107const SX_RI_MGHI: i64 = 0xD
108const SX_RI_CHI: i64 = 0xE
109const SX_RI_CGHI: i64 = 0xF
110
111// RRE second opcode byte
112const SX_RRE_LGR: i64 = 0x04
113const SX_RRE_AGR: i64 = 0x08
114const SX_RRE_SGR: i64 = 0x09
115const SX_RRE_MSGR: i64 = 0x0C
116const SX_RRE_CGR: i64 = 0x20
117const SX_RRE_CLGR: i64 = 0x21
118const SX_RRE_NGR: i64 = 0x80
119const SX_RRE_OGR: i64 = 0x81
120const SX_RRE_XGR: i64 = 0x82
121// LRVGR. Present in the architecture, DELIBERATELY not implemented by
122// nx_emu_s390x, and used below as a live UNSUPPORTED control.
123const SX_RRE_LRVGR: i64 = 0x0F
124
125// RXY / RSY trailing opcode byte
126const SX_RXY_LG: i64 = 0x04
127const SX_RXY_STG: i64 = 0x24
128const SX_RSY_SRAG: i64 = 0x0A
129const SX_RSY_SRLG: i64 = 0x0C
130const SX_RSY_SLLG: i64 = 0x0D
131
132// RIL extended opcodes
133const SX_RIL_BRCL: i64 = 0x4
134const SX_RIL_BRASL: i64 = 0x5
135
136// Branch masks. Bit 8 selects CC0, 4 CC1, 2 CC2, 1 CC3.
137const SX_M_ALWAYS: i64 = 15
138const SX_M_EQ: i64 = 8
139const SX_M_NE: i64 = 7
140const SX_M_LOW: i64 = 4
141const SX_M_HIGH: i64 = 2
142
143// Linux s390x syscall numbers used by the fixtures.
144const SX_SVC_EXIT: i64 = 1
145const SX_SVC_UNKNOWN: i64 = 99 // deliberately not implemented
146
147// Register numbers the KATs use by name rather than by digit.
148const SX_R0: i64 = 0
149const SX_R2: i64 = 2 // the exit-status register on Linux s390x
150const SX_R3: i64 = 3
151const SX_R4: i64 = 4
152const SX_R5: i64 = 5
153const SX_R14: i64 = 14 // the architected return-address register
154
155// Instruction lengths, so the variable-length tooth compares against named
156// values rather than against three bare digits.
157const SX_LEN_RR: i64 = 2
158const SX_LEN_RI: i64 = 4
159const SX_LEN_RIL: i64 = 6
160
161// The non-PASS worklist buffer. A count without a worklist is not actionable,
162// and a worklist printed only in the BODY is lost the moment a caller tails the
163// output, so it is accumulated here and printed at the END.
164const SX_NB_BYTES: i64 = 2048
165const SX_NB_OFF: i64 = 0
166const SX_NB_PTR: i64 = 1
167const SX_NB_ROW_MAX: i64 = 256
168const SX_BOX_BYTES: i64 = 32
169
170const SX_NL: i64 = 10
171const SX_SPACE: i64 = 32
172const SX_DIG_LO: i64 = 48
173const SX_DIG_HI: i64 = 57
174const SX_UNSEEDED: i64 = -1
175
176const SX_RATCHET: *u8 = "knowledge/status/isa_s390x.ratchet"
177
178// A bare newline inside a string literal is ambiguous to this lexer, so the
179// byte is CONSTRUCTED.
180func sx_nl() -> i64 {
181 let b: *u8 = sys_mmap(8)
182 b[0] = SX_NL as u8
183 sys_write(1, b, 1)
184 return 0
185}
186
187func sx_guest() -> *u8 { return sys_mmap(SX_GUEST_BYTES) }
188
189// ===== the variable-length BIG-ENDIAN emitters ==================
190// Each returns the NEXT offset, so a KAT reads as a program listing and the
191// instruction widths are carried by the encoder instead of hand-counted at
192// every call site -- a hand-counted length beside an encoding is a second copy
193// of that encoding's shape and the two drift silently.
194
195// RR, 2 bytes: op(8) r1(4) r2(4)
196func sx_rr(c: *u8, off: i64, op: i64, r1: i64, r2: i64) -> i64 {
197 c[off] = op & 0xff
198 c[off + 1] = ((r1 << 4) | r2) & 0xff
199 return off + SX_LEN_RR
200}
201
202// SVC, 2 bytes: 0A ii (ii is an 8-bit syscall number, NOT two nibbles)
203func sx_svc(c: *u8, off: i64, imm: i64) -> i64 {
204 c[off] = SX_OP_SVC & 0xff
205 c[off + 1] = imm & 0xff
206 return off + SX_LEN_RR
207}
208
209// RI, 4 bytes: A7 r1(4) op2(4) i2(16 signed, big-endian)
210func sx_ri(c: *u8, off: i64, r1: i64, op2: i64, i2: i64) -> i64 {
211 c[off] = SX_OP_RI & 0xff
212 c[off + 1] = ((r1 << 4) | op2) & 0xff
213 c[off + 2] = (i2 >> 8) & 0xff
214 c[off + 3] = (i2) & 0xff
215 return off + SX_LEN_RI
216}
217
218// RRE, 4 bytes: B9 op2(8) 00 r1(4) r2(4)
219func sx_rre(c: *u8, off: i64, op2: i64, r1: i64, r2: i64) -> i64 {
220 c[off] = SX_OP_RRE & 0xff
221 c[off + 1] = op2 & 0xff
222 c[off + 2] = 0 as u8
223 c[off + 3] = ((r1 << 4) | r2) & 0xff
224 return off + SX_LEN_RI
225}
226
227// RX-a, 4 bytes: 41 r1(4) x2(4) b2(4) d2(12 unsigned)
228func sx_rx(c: *u8, off: i64, r1: i64, x2: i64, b2: i64, d2: i64) -> i64 {
229 c[off] = SX_OP_LA & 0xff
230 c[off + 1] = ((r1 << 4) | x2) & 0xff
231 c[off + 2] = ((b2 << 4) | ((d2 >> 8) & 0xF)) & 0xff
232 c[off + 3] = (d2) & 0xff
233 return off + SX_LEN_RI
234}
235
236// RXY-a, 6 bytes: E3 r1(4) x2(4) b2(4) DL(12) DH(8) op2(8)
237func sx_rxy(c: *u8, off: i64, r1: i64, x2: i64, b2: i64, d2: i64, op2: i64) -> i64 {
238 c[off] = SX_OP_RXY & 0xff
239 c[off + 1] = ((r1 << 4) | x2) & 0xff
240 c[off + 2] = ((b2 << 4) | ((d2 >> 8) & 0xF)) & 0xff
241 c[off + 3] = (d2) & 0xff
242 c[off + 4] = (d2 >> 12) & 0xff
243 c[off + 5] = op2 & 0xff
244 return off + SX_LEN_RIL
245}
246
247// RSY-a, 6 bytes: EB r1(4) r3(4) b2(4) DL(12) DH(8) op2(8)
248func sx_rsy(c: *u8, off: i64, r1: i64, r3: i64, b2: i64, d2: i64, op2: i64) -> i64 {
249 c[off] = SX_OP_RSY & 0xff
250 c[off + 1] = ((r1 << 4) | r3) & 0xff
251 c[off + 2] = ((b2 << 4) | ((d2 >> 8) & 0xF)) & 0xff
252 c[off + 3] = (d2) & 0xff
253 c[off + 4] = (d2 >> 12) & 0xff
254 c[off + 5] = op2 & 0xff
255 return off + SX_LEN_RIL
256}
257
258// RIL, 6 bytes: C0 r1(4) op2(4) i2(32 signed, big-endian).
259// For the branch forms i2 is a HALFWORD count: target = pc + 2 * i2.
260func sx_ril(c: *u8, off: i64, r1: i64, op2: i64, i2: i64) -> i64 {
261 c[off] = SX_OP_RIL & 0xff
262 c[off + 1] = ((r1 << 4) | op2) & 0xff
263 c[off + 2] = (i2 >> 24) & 0xff
264 c[off + 3] = (i2 >> 16) & 0xff
265 c[off + 4] = (i2 >> 8) & 0xff
266 c[off + 5] = (i2) & 0xff
267 return off + SX_LEN_RIL
268}
269
270func sx_run(m: *u8) -> i64 { return emu_s390x_run_mem(m, SX_GUEST_BYTES, SX_ENTRY, SX_SP) }
271
272// ===== outcome classification ===================================
273
274func sx_classify(got: i64, want: i64) -> i64 {
275 if got == SX_UNSUPPORTED { return SX_O_UNSUP }
276 if got == SX_RANOFF { return SX_O_RANOFF }
277 if got == SX_FAULT { return SX_O_FAULT }
278 if got == want { return SX_O_PASS }
279 return SX_O_WRONG
280}
281
282func sx_outcome_name(o: i64) -> *u8 {
283 if o == SX_O_PASS { return "PASS" as *u8 }
284 if o == SX_O_WRONG { return "WRONG-ANSWER" as *u8 }
285 if o == SX_O_UNSUP { return "UNSUPPORTED-instruction-class" as *u8 }
286 if o == SX_O_RANOFF { return "RAN-OFF-END-no-exit-syscall" as *u8 }
287 return "FAULT-bad-pc-or-step-budget" as *u8
288}
289
290// Print the VALUES, not just the verdict: every vacuous tooth ever caught in
291// this estate was caught by a diagnostic dump and never by a verdict vector.
292func sx_report(kat: *u8, got: i64, want: i64, bx: *i64) -> i64 {
293 let o: i64 = sx_classify(got, want)
294 gv_puts(" KAT s390x." as *u8)
295 gv_puts(kat)
296 gv_puts(" got=" as *u8)
297 gv_num(got)
298 gv_puts(" want=" as *u8)
299 gv_num(want)
300 gv_puts(" " as *u8)
301 gv_puts(sx_outcome_name(o))
302 sx_nl()
303 if o != SX_O_PASS {
304 let nb: *u8 = bx[SX_NB_PTR] as *u8
305 var p: i64 = bx[SX_NB_OFF]
306 if p < (SX_NB_BYTES - SX_NB_ROW_MAX) {
307 p = gv_cat(nb, p, "s390x." as *u8)
308 p = gv_cat(nb, p, kat)
309 p = gv_cat(nb, p, "=" as *u8)
310 p = gv_cat(nb, p, sx_outcome_name(o))
311 p = gv_cat(nb, p, "(got=" as *u8)
312 p = gv_catn(nb, p, got)
313 p = gv_cat(nb, p, " want=" as *u8)
314 p = gv_catn(nb, p, want)
315 p = gv_cat(nb, p, ") " as *u8)
316 nb[p] = 0 as u8
317 bx[SX_NB_OFF] = p
318 }
319 }
320 return o
321}
322
323// ===== the KATs =================================================
324// Every expectation below is derived from the z/Architecture Principles of
325// Operation instruction definitions, and every program ends in SVC 1 so it
326// returns a REAL exit status. The exit status is masked to 8 bits by the ABI,
327// so no two expectations in this file are congruent modulo 256.
328
329// LGHI r,imm + AGR: the two classes the previous generation already had.
330// A regression here indicts a change rather than a missing feature.
331func sx_k_imm_arith() -> i64 {
332 let m: *u8 = sx_guest()
333 var o: i64 = 0
334 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 40)
335 o = sx_ri(m, o, SX_R3, SX_RI_LGHI, 2)
336 o = sx_rre(m, o, SX_RRE_AGR, SX_R2, SX_R3) // 40 + 2
337 o = sx_svc(m, o, SX_SVC_EXIT)
338 return sx_run(m)
339}
340
341// AGHI at its ARCHITECTED extended opcode A7xB. The previous generation put a
342// 64-bit add at A7xA and had no A7xB case at all, so this exact program came
343// back 40: the instruction assembled correctly and executed as nothing.
344func sx_k_aghi_opcode() -> i64 {
345 let m: *u8 = sx_guest()
346 var o: i64 = 0
347 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 40)
348 o = sx_ri(m, o, SX_R2, SX_RI_AGHI, 2)
349 o = sx_svc(m, o, SX_SVC_EXIT)
350 return sx_run(m)
351}
352
353// SGR is R1 = R1 - R2. Operand order is the discrimination: an emulator that
354// computed R2 - R1 would return 214 here (8 - 50 masked), not 42.
355func sx_k_sub() -> i64 {
356 let m: *u8 = sx_guest()
357 var o: i64 = 0
358 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 50)
359 o = sx_ri(m, o, SX_R3, SX_RI_LGHI, 8)
360 o = sx_rre(m, o, SX_RRE_SGR, SX_R2, SX_R3)
361 o = sx_svc(m, o, SX_SVC_EXIT)
362 return sx_run(m)
363}
364
365func sx_k_mul() -> i64 {
366 let m: *u8 = sx_guest()
367 var o: i64 = 0
368 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 6)
369 o = sx_ri(m, o, SX_R3, SX_RI_LGHI, 7)
370 o = sx_rre(m, o, SX_RRE_MSGR, SX_R2, SX_R3) // 6 * 7
371 o = sx_svc(m, o, SX_SVC_EXIT)
372 return sx_run(m)
373}
374
375// AND then OR then XOR, chained so that swapping ANY TWO of the three gives a
376// different answer. 60 & 15 = 12; 12 | 48 = 60; 60 ^ 18 = 46.
377func sx_k_logic() -> i64 {
378 let m: *u8 = sx_guest()
379 var o: i64 = 0
380 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 60)
381 o = sx_ri(m, o, SX_R3, SX_RI_LGHI, 15)
382 o = sx_rre(m, o, SX_RRE_NGR, SX_R2, SX_R3)
383 o = sx_ri(m, o, SX_R4, SX_RI_LGHI, 48)
384 o = sx_rre(m, o, SX_RRE_OGR, SX_R2, SX_R4)
385 o = sx_ri(m, o, SX_R5, SX_RI_LGHI, 18)
386 o = sx_rre(m, o, SX_RRE_XGR, SX_R2, SX_R5)
387 o = sx_svc(m, o, SX_SVC_EXIT)
388 return sx_run(m)
389}
390
391// The class the previous generation could not run at all: a LOOP. CGR sets the
392// condition code, BRC with mask 7 (CC1|CC2|CC3) is the architected "branch if
393// not equal". The branch displacement is a signed HALFWORD count from the
394// branch instruction's own address: the loop head is 12 bytes back from the
395// branch at 24, so I2 = -6, not -12.
396func sx_k_branch_loop() -> i64 {
397 let m: *u8 = sx_guest()
398 var o: i64 = 0
399 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 0) // 0 acc = 0
400 o = sx_ri(m, o, SX_R3, SX_RI_LGHI, 0) // 4 i = 0
401 o = sx_ri(m, o, SX_R4, SX_RI_LGHI, 10) // 8 n = 10
402 let loop_at: i64 = o // 12
403 o = sx_rre(m, o, SX_RRE_AGR, SX_R2, SX_R3) // 12 acc = acc + i
404 o = sx_ri(m, o, SX_R3, SX_RI_AGHI, 1) // 16 i = i + 1
405 o = sx_rre(m, o, SX_RRE_CGR, SX_R3, SX_R4) // 20 compare i, n
406 let br_at: i64 = o // 24
407 o = sx_ri(m, o, SX_M_NE, SX_RI_BRC, (loop_at - br_at) / 2)
408 o = sx_svc(m, o, SX_SVC_EXIT) // 28 0+1+..+9 = 45
409 return sx_run(m)
410}
411
412// BRCTG decrements the full 64-bit register and branches while it is nonzero.
413// Five iterations of "add 3" is 15, which is not congruent to the loop count
414// or to the addend modulo 256, so an off-by-one in either direction shows.
415func sx_k_brctg_loop() -> i64 {
416 let m: *u8 = sx_guest()
417 var o: i64 = 0
418 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 0)
419 o = sx_ri(m, o, SX_R3, SX_RI_LGHI, 5)
420 let loop_at: i64 = o
421 o = sx_ri(m, o, SX_R2, SX_RI_AGHI, 3)
422 let br_at: i64 = o
423 o = sx_ri(m, o, SX_R3, SX_RI_BRCTG, (loop_at - br_at) / 2)
424 o = sx_svc(m, o, SX_SVC_EXIT)
425 return sx_run(m)
426}
427
428// STG then LG through a base register, with the register reloaded in between so
429// a store that never happened cannot be masked by the value still sitting in
430// r2. The 6-byte RXY form is the variable-length case.
431func sx_k_load_store() -> i64 {
432 let m: *u8 = sx_guest()
433 var o: i64 = 0
434 o = sx_ri(m, o, SX_R4, SX_RI_LGHI, SX_SCRATCH)
435 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 42)
436 o = sx_rxy(m, o, SX_R2, SX_R0, SX_R4, 0, SX_RXY_STG)
437 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 7)
438 o = sx_rxy(m, o, SX_R2, SX_R0, SX_R4, 0, SX_RXY_LG)
439 o = sx_svc(m, o, SX_SVC_EXIT)
440 return sx_run(m)
441}
442
443// PoP: a zero in the B or X field means THE ABSENCE OF A BASE, not the contents
444// of general register 0. r0 is loaded with 100 first, so an emulator that reads
445// r[0] as a base returns 142 and one that honours the architecture returns 42.
446func sx_k_base_zero() -> i64 {
447 let m: *u8 = sx_guest()
448 var o: i64 = 0
449 o = sx_ri(m, o, SX_R0, SX_RI_LGHI, 100)
450 o = sx_rx(m, o, SX_R4, SX_R0, SX_R0, 42) // la r4,42
451 o = sx_rre(m, o, SX_RRE_LGR, SX_R2, SX_R4)
452 o = sx_svc(m, o, SX_SVC_EXIT)
453 return sx_run(m)
454}
455
456// SRLG vs SRAG on a NEGATIVE value, and the obvious version of this test CANNOT
457// discriminate: -16 shifted right by 2 has low byte 0xF0 under both, so an
458// emulator implementing only one of them passes. A shift by 60 separates them:
459// logical -> 15, arithmetic -> -1, which the exit mask renders as 255.
460func sx_k_shift_logical() -> i64 {
461 let m: *u8 = sx_guest()
462 var o: i64 = 0
463 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 0 - 16)
464 o = sx_rsy(m, o, SX_R2, SX_R2, SX_R0, 60, SX_RSY_SRLG)
465 o = sx_svc(m, o, SX_SVC_EXIT)
466 return sx_run(m)
467}
468
469func sx_k_shift_arith() -> i64 {
470 let m: *u8 = sx_guest()
471 var o: i64 = 0
472 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 0 - 16)
473 o = sx_rsy(m, o, SX_R2, SX_R2, SX_R0, 60, SX_RSY_SRAG)
474 o = sx_svc(m, o, SX_SVC_EXIT)
475 return sx_run(m)
476}
477
478// AHI is the 32-bit form: it addresses bits 32-63 and leaves bits 0-31 alone.
479// The naive test cannot see the difference, because both a 32-bit and a 64-bit
480// AHI leave the same low byte, so this one makes the low half CARRY --
481// 0xFFFFFFFF + 1 is 0 in 32 bits and 0x100000000 in 64 -- and then projects the
482// result back down through a shift.
483//
484// THE POISON IN r3 IS LOAD-BEARING AND WAS ADDED AFTER A BITE RUN. The first
485// version of this KAT PASSED against the pre-fix emulator FOR THE WRONG REASON:
486// there the two shifts were silent no-operations, r3 defaulted to zero, and
487// zero plus 42 is the right answer. Loading 7 into r3 first means a projecting
488// shift that never ran shows up as 49, a 64-bit AHI as 43, and only the
489// architected behaviour as 42. A green that a broken subject can also produce
490// is not evidence, and only running the ruler against the known-bad found it.
491func sx_k_ahi_is_32bit() -> i64 {
492 let m: *u8 = sx_guest()
493 var o: i64 = 0
494 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 1)
495 o = sx_rsy(m, o, SX_R2, SX_R2, SX_R0, 32, SX_RSY_SLLG) // r2 = 2^32
496 o = sx_ri(m, o, SX_R2, SX_RI_AGHI, 0 - 1) // r2 = 0xFFFFFFFF
497 o = sx_ri(m, o, SX_R2, SX_RI_AHI, 1) // 32-bit: 0 ; 64-bit: 0x100000000
498 o = sx_ri(m, o, SX_R3, SX_RI_LGHI, 7) // poison the projection target
499 o = sx_rsy(m, o, SX_R3, SX_R2, SX_R0, 32, SX_RSY_SRLG) // 0 correct, 1 wrong, 7 if absent
500 o = sx_ri(m, o, SX_R4, SX_RI_LGHI, 42)
501 o = sx_rre(m, o, SX_RRE_AGR, SX_R3, SX_R4)
502 o = sx_rre(m, o, SX_RRE_LGR, SX_R2, SX_R3)
503 o = sx_svc(m, o, SX_SVC_EXIT)
504 return sx_run(m)
505}
506
507// CGHI sets the condition code from an immediate compare; BRCL is the 6-byte
508// relative-long conditional branch. The skipped instruction loads 99, so a
509// branch that silently does not happen returns 136 rather than 42.
510func sx_k_brcl_cond() -> i64 {
511 let m: *u8 = sx_guest()
512 var o: i64 = 0
513 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 5)
514 o = sx_ri(m, o, SX_R2, SX_RI_CGHI, 5)
515 let br_at: i64 = o
516 let skip_at: i64 = br_at + SX_LEN_RIL + SX_LEN_RI
517 o = sx_ril(m, o, SX_M_EQ, SX_RIL_BRCL, (skip_at - br_at) / 2)
518 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 99)
519 o = sx_ri(m, o, SX_R2, SX_RI_AGHI, 37)
520 o = sx_svc(m, o, SX_SVC_EXIT)
521 return sx_run(m)
522}
523
524// BRASL saves the address of the NEXT SEQUENTIAL instruction (pc + 6, because
525// BRASL is six bytes) and BCR 15,r14 returns through it. A return address off
526// by one instruction lands on the callee again or on the exit early, and both
527// give a different status.
528func sx_k_call_return() -> i64 {
529 let m: *u8 = sx_guest()
530 var o: i64 = 0
531 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 40)
532 let br_at: i64 = o
533 let callee_at: i64 = br_at + SX_LEN_RIL + SX_LEN_RR
534 o = sx_ril(m, o, SX_R14, SX_RIL_BRASL, (callee_at - br_at) / 2)
535 o = sx_svc(m, o, SX_SVC_EXIT)
536 o = sx_ri(m, o, SX_R2, SX_RI_AGHI, 2)
537 o = sx_rr(m, o, SX_OP_BCR, SX_M_ALWAYS, SX_R14)
538 return sx_run(m)
539}
540
541// PoP: BCR with R2 = 0 is a no-operation WHATEVER the mask -- that is how
542// "bcr 15,0" serialises without branching. The previous generation branched to
543// whatever r0 held, which for the common case of zero is an infinite loop back
544// to the entry point, so this program never terminated.
545func sx_k_bcr_zero_is_nop() -> i64 {
546 let m: *u8 = sx_guest()
547 var o: i64 = 0
548 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 42)
549 o = sx_rr(m, o, SX_OP_BCR, SX_M_ALWAYS, SX_R0)
550 o = sx_svc(m, o, SX_SVC_EXIT)
551 return sx_run(m)
552}
553
554// CLGR is COMPARE LOGICAL: the operands are unsigned. -1 as an unsigned 64-bit
555// value is the largest there is, so the "first operand high" mask MUST branch.
556//
557// THE POLARITY HERE IS LOAD-BEARING AND WAS INVERTED AFTER A BITE RUN. The
558// first version put 42 on the fall-through path, so the pre-fix emulator --
559// which decoded neither CLGR nor BRC and silently no-opped both -- walked
560// straight into the right answer and the KAT reported PASS on a subject that
561// implements neither instruction. Now the right answer is only reachable BY
562// TAKING THE BRANCH: a signed comparison gives CC1 and exits 7, and an emulator
563// with no conditional branch at all also exits 7.
564func sx_k_clgr_unsigned() -> i64 {
565 let m: *u8 = sx_guest()
566 var o: i64 = 0
567 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 7) // the wrong answer, pre-loaded
568 o = sx_ri(m, o, SX_R3, SX_RI_LGHI, 0 - 1)
569 o = sx_ri(m, o, SX_R4, SX_RI_LGHI, 1)
570 o = sx_rre(m, o, SX_RRE_CLGR, SX_R3, SX_R4) // unsigned -> CC2 (high)
571 let br_at: i64 = o
572 let hi_at: i64 = br_at + SX_LEN_RI + SX_LEN_RR
573 o = sx_ri(m, o, SX_M_HIGH, SX_RI_BRC, (hi_at - br_at) / 2)
574 o = sx_svc(m, o, SX_SVC_EXIT) // not taken: exit 7
575 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 42) // hi_at
576 o = sx_svc(m, o, SX_SVC_EXIT)
577 return sx_run(m)
578}
579
580// ===== LIVE negative controls ===================================
581// These two are not synthetic sentinels: they are real guest programs whose
582// only correct outcome is UNSUPPORTED. Both bite the same defect class from
583// opposite ends -- an unimplemented thing that RUNS ANYWAY.
584
585// B90F LRVGR is architecturally real and deliberately unimplemented. If a later
586// generation implements it, RE-POINT this control at another absent opcode.
587func sx_nc_unimplemented_subopcode() -> i64 {
588 let m: *u8 = sx_guest()
589 var o: i64 = 0
590 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 42)
591 o = sx_rre(m, o, SX_RRE_LRVGR, SX_R2, SX_R2)
592 o = sx_svc(m, o, SX_SVC_EXIT)
593 return sx_run(m)
594}
595
596// An SVC the emulator does not implement must stop and say so, not fall through
597// to the next instruction and exit with a plausible number.
598func sx_nc_unknown_svc() -> i64 {
599 let m: *u8 = sx_guest()
600 var o: i64 = 0
601 o = sx_ri(m, o, SX_R2, SX_RI_LGHI, 42)
602 o = sx_svc(m, o, SX_SVC_UNKNOWN)
603 o = sx_svc(m, o, SX_SVC_EXIT)
604 return sx_run(m)
605}
606
607// ===== ratchet ==================================================
608// One line: "s390x <passed>". UNSEEDED is a NAMED third state, so adopting this
609// fence cannot fail closed on first sight. The metric is GOOD-when-higher, so
610// it TIGHTENS on a rise and must NEVER rewrite its baseline on a fall --
611// rewriting on a fall is how a ratchet launders itself green. It writes its OWN
612// file and never touches isa_conform.ratchet, which another lane owns.
613
614func sx_match_at(buf: *u8, n: i64, p: i64, s: *u8) -> i64 {
615 var j: i64 = 0
616 var ok: i64 = 1
617 while s[j] != (0 as u8) {
618 if (p + j) >= n { ok = 0 }
619 if ok == 1 {
620 if buf[p + j] != s[j] { ok = 0 }
621 }
622 j = j + 1
623 }
624 if ok == 0 { return -1 }
625 return j
626}
627
628func sx_digits_at(buf: *u8, n: i64, p0: i64) -> i64 {
629 var v: i64 = 0
630 var any: i64 = 0
631 var p: i64 = p0
632 var go: i64 = 1
633 while go == 1 {
634 if p >= n { go = 0 }
635 if go == 1 {
636 let c: i64 = buf[p] & 0xff
637 if c < SX_DIG_LO { go = 0 }
638 if go == 1 {
639 if c > SX_DIG_HI { go = 0 }
640 }
641 if go == 1 {
642 v = v * 10 + (c - SX_DIG_LO)
643 any = 1
644 p = p + 1
645 }
646 }
647 }
648 if any == 0 { return -1 }
649 return v
650}
651
652func sx_ratchet_read() -> i64 {
653 let lenbox: *i64 = sys_mmap(16) as *i64
654 let buf: *u8 = sys_read_file(SX_RATCHET, lenbox)
655 if (buf as i64) == 0 { return SX_UNSEEDED }
656 let n: i64 = lenbox[0]
657 var i: i64 = 0
658 var found: i64 = SX_UNSEEDED
659 while i < n {
660 if found < 0 {
661 let m: i64 = sx_match_at(buf, n, i, "s390x" as *u8)
662 if m > 0 {
663 if (i + m) < n {
664 if buf[i + m] == (SX_SPACE as u8) {
665 found = sx_digits_at(buf, n, i + m + 1)
666 }
667 }
668 }
669 }
670 var adv: i64 = 1
671 while adv == 1 {
672 if i >= n { adv = 0 }
673 if adv == 1 {
674 if buf[i] == (SX_NL as u8) { adv = 0 }
675 i = i + 1
676 }
677 }
678 }
679 return found
680}
681
682// Callers must have already decided that s390x did not regress; this function
683// does not re-check, and the tooth that guards it is asserted in main.
684func sx_ratchet_write(passed: i64) -> i64 {
685 let d: *u8 = sys_mmap(256)
686 var o: i64 = 0
687 o = gv_cat(d, o, "s390x " as *u8)
688 o = gv_catn(d, o, passed)
689 d[o] = SX_NL as u8
690 o = o + 1
691 let fd: i64 = sys_openat_wr(SX_RATCHET, SX_MODE_FILE)
692 if fd < 0 { return -1 }
693 sys_write(fd, d, o)
694 sys_fsync(fd)
695 sys_close(fd)
696 return o
697}
698
699// ===== main =====================================================
700
701func main(argc: i64, argv: *i64) -> i64 {
702 gv_head("nx_isa_s390x_gate -- IBM z/Architecture conformance, KATs encoded from the Principles of Operation" as *u8)
703 let ctr: *i64 = gv_ctr()
704
705 let bx: *i64 = sys_mmap(SX_BOX_BYTES) as *i64
706 let nbuf: *u8 = sys_mmap(SX_NB_BYTES)
707 nbuf[0] = 0 as u8
708 bx[SX_NB_OFF] = 0
709 bx[SX_NB_PTR] = nbuf as i64
710
711 gv_puts("dispatch=emu_s390x_run_mem (the only surface all ten estate emulators share)" as *u8)
712 sx_nl()
713 gv_puts("outcomes are FIVE-WAY: PASS / WRONG-ANSWER / UNSUPPORTED-instruction-class / RAN-OFF-END / FAULT" as *u8)
714 sx_nl()
715 gv_puts("s390x is BIG-ENDIAN and VARIABLE-LENGTH (2/4/6 bytes); branch displacements are HALFWORD counts" as *u8)
716 sx_nl()
717 sx_nl()
718
719 // ---- the emitter is the fixture, so measure the emitter FIRST ----
720 // If the encoder is wrong, every UNSUPPORTED below is the fixture's bug
721 // wearing the emulator's name.
722 let ec: *u8 = sx_guest()
723 let l_rr: i64 = sx_svc(ec, 0, SX_SVC_EXIT)
724 let l_ri: i64 = sx_ri(ec, 0, SX_R2, SX_RI_AGHI, 2)
725 let l_ril: i64 = sx_ril(ec, 0, SX_R14, SX_RIL_BRASL, 4)
726 var len_ok: i64 = 0
727 if l_rr == SX_LEN_RR {
728 if l_ri == SX_LEN_RI {
729 if l_ril == SX_LEN_RIL { len_ok = 1 }
730 }
731 }
732 gv_puts(" emitter widths RR=" as *u8)
733 gv_num(l_rr)
734 gv_puts(" RI=" as *u8)
735 gv_num(l_ri)
736 gv_puts(" RIL=" as *u8)
737 gv_num(l_ril)
738 sx_nl()
739 gv_check("fixture-variable-length-emitter-returns-2-4-6" as *u8, len_ok == 1, ctr)
740
741 // The exact byte string the PoP gives for AGHI r2,2 is A7 2B 00 02. This is
742 // the one encoding the previous emulator generation could not execute, so
743 // asserting it byte for byte separates "the fixture emitted the wrong
744 // opcode" from "the emulator does not decode the right one".
745 var eo: i64 = sx_ri(ec, 0, SX_R2, SX_RI_AGHI, 2)
746 var enc_ok: i64 = 0
747 if (ec[0] & 0xff) == SX_OP_RI {
748 if (ec[1] & 0xff) == 0x2B {
749 if (ec[2] & 0xff) == 0 {
750 if (ec[3] & 0xff) == 2 { enc_ok = 1 }
751 }
752 }
753 }
754 gv_check("fixture-encodes-aghi-as-the-manual-byte-string-A7-2B-00-02" as *u8, enc_ok == 1, ctr)
755 sx_nl()
756
757 // ---- the KATs ------------------------------------------------
758 var sx_pass: i64 = 0
759 var sx_tot: i64 = 0
760 var o: i64 = 0
761
762 o = sx_report("imm-arith-lghi-agr" as *u8, sx_k_imm_arith(), 42, bx)
763 sx_tot = sx_tot + 1
764 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
765 gv_check("isa-s390x-imm-arith" as *u8, o == SX_O_PASS, ctr)
766
767 o = sx_report("aghi-at-extended-opcode-A7xB" as *u8, sx_k_aghi_opcode(), 42, bx)
768 sx_tot = sx_tot + 1
769 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
770 gv_check("isa-s390x-aghi-extended-opcode" as *u8, o == SX_O_PASS, ctr)
771
772 o = sx_report("sub-sgr-operand-order" as *u8, sx_k_sub(), 42, bx)
773 sx_tot = sx_tot + 1
774 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
775 gv_check("isa-s390x-sub" as *u8, o == SX_O_PASS, ctr)
776
777 o = sx_report("mul-msgr" as *u8, sx_k_mul(), 42, bx)
778 sx_tot = sx_tot + 1
779 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
780 gv_check("isa-s390x-mul" as *u8, o == SX_O_PASS, ctr)
781
782 o = sx_report("logic-ngr-ogr-xgr" as *u8, sx_k_logic(), 46, bx)
783 sx_tot = sx_tot + 1
784 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
785 gv_check("isa-s390x-logic" as *u8, o == SX_O_PASS, ctr)
786
787 o = sx_report("branch-loop-cgr-brc" as *u8, sx_k_branch_loop(), 45, bx)
788 sx_tot = sx_tot + 1
789 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
790 gv_check("isa-s390x-branch-loop" as *u8, o == SX_O_PASS, ctr)
791
792 o = sx_report("brctg-count-loop" as *u8, sx_k_brctg_loop(), 15, bx)
793 sx_tot = sx_tot + 1
794 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
795 gv_check("isa-s390x-brctg-loop" as *u8, o == SX_O_PASS, ctr)
796
797 o = sx_report("load-store-stg-lg" as *u8, sx_k_load_store(), 42, bx)
798 sx_tot = sx_tot + 1
799 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
800 gv_check("isa-s390x-load-store" as *u8, o == SX_O_PASS, ctr)
801
802 o = sx_report("base-register-zero-is-no-base" as *u8, sx_k_base_zero(), 42, bx)
803 sx_tot = sx_tot + 1
804 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
805 gv_check("isa-s390x-base-register-zero" as *u8, o == SX_O_PASS, ctr)
806
807 o = sx_report("shift-logical-srlg" as *u8, sx_k_shift_logical(), 15, bx)
808 sx_tot = sx_tot + 1
809 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
810 gv_check("isa-s390x-shift-logical" as *u8, o == SX_O_PASS, ctr)
811
812 o = sx_report("shift-arith-srag" as *u8, sx_k_shift_arith(), 255, bx)
813 sx_tot = sx_tot + 1
814 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
815 gv_check("isa-s390x-shift-arith" as *u8, o == SX_O_PASS, ctr)
816
817 o = sx_report("ahi-is-32-bit-high-half-preserved" as *u8, sx_k_ahi_is_32bit(), 42, bx)
818 sx_tot = sx_tot + 1
819 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
820 gv_check("isa-s390x-ahi-is-32-bit" as *u8, o == SX_O_PASS, ctr)
821
822 o = sx_report("brcl-conditional-long-cghi" as *u8, sx_k_brcl_cond(), 42, bx)
823 sx_tot = sx_tot + 1
824 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
825 gv_check("isa-s390x-brcl-conditional" as *u8, o == SX_O_PASS, ctr)
826
827 o = sx_report("brasl-call-and-bcr-return" as *u8, sx_k_call_return(), 42, bx)
828 sx_tot = sx_tot + 1
829 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
830 gv_check("isa-s390x-call-return" as *u8, o == SX_O_PASS, ctr)
831
832 o = sx_report("bcr-r2-zero-is-a-no-operation" as *u8, sx_k_bcr_zero_is_nop(), 42, bx)
833 sx_tot = sx_tot + 1
834 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
835 gv_check("isa-s390x-bcr-r2-zero-is-nop" as *u8, o == SX_O_PASS, ctr)
836
837 o = sx_report("clgr-is-unsigned" as *u8, sx_k_clgr_unsigned(), 42, bx)
838 sx_tot = sx_tot + 1
839 if o == SX_O_PASS { sx_pass = sx_pass + 1 }
840 gv_check("isa-s390x-clgr-is-unsigned" as *u8, o == SX_O_PASS, ctr)
841
842 // ---- the fixture must have REACHED the emulator ---------------
843 // Asserting an outcome without asserting the fixture ran is how four
844 // vacuous fixtures shipped in this estate in a single day.
845 sx_nl()
846 gv_check("fixture-reached-s390x-emulator-at-all" as *u8, sx_pass > 0, ctr)
847
848 // ---- negative controls ---------------------------------------
849 // If sx_classify were stubbed to return PASS, every green above would be
850 // fake and only these would notice.
851 let ncw: i64 = sx_classify(sx_k_imm_arith(), 41)
852 gv_puts(" neg-control s390x.imm-arith against a deliberately wrong want=41 -> " as *u8)
853 gv_puts(sx_outcome_name(ncw))
854 sx_nl()
855 gv_check("neg-control-wrong-expectation-must-not-pass" as *u8, ncw == SX_O_WRONG, ctr)
856
857 let ncu: i64 = sx_classify(SX_UNSUPPORTED, -1)
858 gv_check("neg-control-unsupported-sentinel-never-reads-as-pass" as *u8, ncu == SX_O_UNSUP, ctr)
859
860 let ncf: i64 = sx_classify(SX_FAULT, -3)
861 gv_check("neg-control-fault-sentinel-never-reads-as-pass" as *u8, ncf == SX_O_FAULT, ctr)
862
863 let ncr: i64 = sx_classify(SX_RANOFF, -2)
864 gv_check("neg-control-ranoff-sentinel-never-reads-as-pass" as *u8, ncr == SX_O_RANOFF, ctr)
865
866 // LIVE controls: real guest programs whose only correct outcome is
867 // UNSUPPORTED. These are the ones that catch a decoder which sets its
868 // handled flag before it has looked at the extended opcode.
869 let ncs: i64 = sx_nc_unimplemented_subopcode()
870 gv_puts(" neg-control guest program containing B90F LRVGR (unimplemented) returned " as *u8)
871 gv_num(ncs)
872 gv_puts(" -> " as *u8)
873 gv_puts(sx_outcome_name(sx_classify(ncs, 42)))
874 sx_nl()
875 gv_check("neg-control-unimplemented-subopcode-must-not-silently-no-op" as *u8,
876 sx_classify(ncs, 42) == SX_O_UNSUP, ctr)
877
878 let ncv: i64 = sx_nc_unknown_svc()
879 gv_puts(" neg-control guest program containing SVC 99 (unimplemented) returned " as *u8)
880 gv_num(ncv)
881 gv_puts(" -> " as *u8)
882 gv_puts(sx_outcome_name(sx_classify(ncv, 42)))
883 sx_nl()
884 gv_check("neg-control-unknown-svc-must-not-fall-through" as *u8,
885 sx_classify(ncv, 42) == SX_O_UNSUP, ctr)
886
887 // ---- coverage, published as a NUMBER not a verdict ------------
888 sx_nl()
889 gv_puts("coverage s390x=" as *u8)
890 gv_num(sx_pass)
891 gv_puts("/" as *u8)
892 gv_num(sx_tot)
893 gv_puts(" classes=immediate-arith,extended-opcode-placement,subtract,multiply,logic,compare-and-branch,count-branch,load-store,base-register-zero,shift-logical,shift-arithmetic,32-bit-width,relative-long-branch,call-return,architected-nop,unsigned-compare" as *u8)
894 sx_nl()
895 gv_puts("NOT MEASURED, and therefore NOT CLAIMED: packed decimal, the vector facility, floating point, access registers, PSW/interrupt state, storage keys, LMG/STMG multi-register forms" as *u8)
896 sx_nl()
897
898 // ---- ratchet -------------------------------------------------
899 let base: i64 = sx_ratchet_read()
900 gv_puts("ratchet s390x base=" as *u8)
901 gv_num(base)
902 gv_puts(" now=" as *u8)
903 gv_num(sx_pass)
904 gv_puts(" (base=-1 is UNSEEDED, a named state: first sight seeds, so adoption cannot fail closed)" as *u8)
905 sx_nl()
906
907 var no_regress: i64 = 1
908 if base >= 0 {
909 if sx_pass < base { no_regress = 0 }
910 }
911
912 var wrote: i64 = 0
913 if no_regress == 1 {
914 if base < 0 { wrote = sx_ratchet_write(sx_pass) }
915 if wrote == 0 {
916 if sx_pass > base { wrote = sx_ratchet_write(sx_pass) }
917 }
918 }
919 gv_puts("ratchet_bytes_written=" as *u8)
920 gv_num(wrote)
921 gv_puts(" (0 = held: neither seeded nor tightened this run)" as *u8)
922 sx_nl()
923
924 // This dialect has no || , so the laundering condition is computed.
925 var laundered: i64 = 0
926 if no_regress == 0 {
927 if wrote > 0 { laundered = 1 }
928 }
929
930 gv_check("ratchet-s390x-did-not-regress" as *u8, no_regress == 1, ctr)
931 gv_check("ratchet-never-rewritten-on-a-regression" as *u8, laundered == 0, ctr)
932
933 // THE WORKLIST, LAST, where a tailing caller can still see it.
934 gv_puts("NONPASS: " as *u8)
935 if bx[SX_NB_OFF] == 0 {
936 gv_puts("none -- every measured KAT matched its Principles-of-Operation expectation" as *u8)
937 }
938 if bx[SX_NB_OFF] > 0 {
939 gv_puts(bx[SX_NB_PTR] as *u8)
940 }
941 sx_nl()
942
943 return gv_verdict("nx_isa_s390x_gate" as *u8, ctr,
944 "IBM z/Architecture conformance measured by running big-endian variable-length machine code through emu_s390x_run_mem against Principles-of-Operation expectations, five-way outcomes, 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)
945}