nx_emu_s390x.nx source
↩ module page · 403 lines · 19527 B
1// nx_emu_s390x.nx -- sovereign IBM Z (s390x) interpreter (NX-EMU).
2// 64-bit big-endian, 16 GPRs, VARIABLE-LENGTH instructions (length from the top 2
3// bits of byte0: 00->2, 01/10->4, 11->6). z/Architecture is the only variable
4// length ISA on the estate board, so the fetch width is decoded, never assumed.
5// Linux s390x: svc <imm> = syscall number, args r2.., exit = 1 (code in r2).
6// NO qemu. license_tier: ORIGINAL
7//
8// DECODED TODAY, by family:
9// RR 07 BCR (all 16 masks, and R2=0 is the architected no-operation)
10// RR 0A SVC (1 = exit; every other number is an honest UNSUPPORTED)
11// RI A7 x4 BRC x5 BRAS x6 BRCT x7 BRCTG x8 LHI x9 LGHI
12// xA AHI xB AGHI xD MGHI xE CHI xF CGHI
13// RRE B9 04 LGR 08 AGR 09 SGR 0C MSGR 20 CGR 21 CLGR
14// 80 NGR 81 OGR 82 XGR
15// RX 41 LA
16// RXY E3 04 LG 08 AG 09 SG 24 STG
17// RSY EB 04 LMG 0A SRAG 0C SRLG 0D SLLG 24 STMG
18// RIL C0 x0 LARL x1 LGFI x4 BRCL x5 BRASL
19//
20// SEVEN DEFECTS THIS GENERATION FIXES, each found by reading the z/Architecture
21// Principles of Operation extended-opcode tables rather than the source:
22// 1. AGHI WAS DECODED AT THE WRONG EXTENDED OPCODE. The RI table is
23// A7x8 LHI, A7x9 LGHI, A7xA AHI, A7xB AGHI. The previous generation
24// implemented A7xA as a 64-bit add labelled aghi and had NO A7xB case, so
25// a real AGHI executed as a SILENT NO-OP and a real AHI executed with the
26// wrong width. Both are wrong-answer generators that still run to exit.
27// 2. AN UNKNOWN EXTENDED OPCODE SILENTLY DID NOTHING. handled was set for the
28// whole A7 / B9 / E3 / EB / C0 family before the sub-opcode was examined, so
29// every unimplemented member of those families no-opped and the program ran
30// on with wrong data. That converts UNSUPPORTED into WRONG-ANSWER, which is
31// the one collapse an ISA ruler exists to refuse: a missing instruction and
32// a miscomputed one need OPPOSITE fixes. Each family now carries a subok flag (one per family).
33// 3. THERE WAS NO CONDITION CODE AND NO CONDITIONAL BRANCH AT ALL, so no loop
34// and no if could run. BCR honoured only mask 15. Added: a cc register set
35// by the arithmetic, compare and logic classes, plus BRC, BRCL, BRCT, BRCTG
36// and the full 16-way BCR mask.
37// 4. BASE REGISTER 0 WAS TREATED AS A REAL REGISTER. PoP: a zero in the B or X
38// field means THE ABSENCE OF A BASE, not the contents of general register 0.
39// The old code honoured that for X and not for B, so any program that keeps
40// a live value in r0 addressed the wrong storage.
41// 5. NON-COMPLETION HAD NO SENTINELS. result started at 0 and the step budget
42// halted without setting anything, so a runaway program and a program that
43// legitimately exits 0 returned the SAME value. Now RANOFF/-2 is the initial
44// value, FAULT/-3 covers the budget and a bad pc, UNSUPPORTED/-1 an
45// unimplemented class -- the same three sentinels nx_emu_rv64.nx uses, so one
46// classifier reads both. A real exit status is masked to 0..255 and can
47// never collide with them.
48// 6. THERE WAS NO BOUNDS CHECK ON THE PC OR ON ANY LOAD/STORE, so a wild branch
49// or displacement read outside the guest mapping.
50// 7. AN UNKNOWN SVC NUMBER FELL THROUGH AND KEPT RUNNING instead of naming
51// itself as an unimplemented syscall.
52//
53// DECLARED IMPRECISION, so the next reader does not trust this as exact: the
54// condition code for ADD and SUBTRACT is set to 0/1/2 from the sign of the
55// result and NEVER to 3, because overflow is not modelled. A wrapped 64-bit
56// result therefore reports 1 or 2 where real hardware reports 3. No KAT in
57// nx_isa_s390x_gate depends on that case, and every KAT stays inside the 64-bit
58// range. 32-bit forms (AHI, CHI, BRCT) operate on bits 32-63 and leave bits
59// 0-31 unchanged, which IS modelled.
60import "nx_syscalls_x86_64.nx"
61
62const SZ_GUEST: i64 = 16777216
63const SZ_SYS_EXIT: i64 = 1
64
65// Non-completion sentinels. Mirrors nx_emu_rv64.nx (EMU_RV_UNSUPPORTED = -1,
66// EMU_RV_RANOFF = -2, EMU_RV_FAULT = -3) so a single classifier reads every
67// emulator on the board.
68const SZ_UNSUPPORTED: i64 = -1
69const SZ_RANOFF: i64 = -2
70const SZ_FAULT: i64 = -3
71
72const SZ_STEP_BUDGET: i64 = 5000000
73const SZ_EXIT_MASK: i64 = 255
74const SZ_NREG: i64 = 16
75const SZ_REGBYTES: i64 = 8
76const SZ_MAXILEN: i64 = 6
77const SZ_SHIFT_MASK: i64 = 63
78const SZ_WORD: i64 = 8
79
80// low 32 bits, the 32-bit sign bit, and 2^32 -- named because 4294967295 and
81// 0xffffffff are the SAME constant to a human and two different constants to
82// every scanner.
83const SZ_LO32: i64 = 4294967295
84const SZ_SIGN32: i64 = 2147483648
85const SZ_2P32: i64 = 4294967296
86
87// RXY/RSY carry a signed 20-bit displacement split DL(12) DH(8).
88const SZ_D20_SIGN: i64 = 524288
89const SZ_D20_MOD: i64 = 1048576
90
91// Condition codes and the branch mask. M1 bit 8 selects CC0, 4 CC1, 2 CC2,
92// 1 CC3, so a branch is taken iff (M1 & (8 >> cc)) is nonzero.
93const SZ_CC_EQ: i64 = 0
94const SZ_CC_LO: i64 = 1
95const SZ_CC_HI: i64 = 2
96const SZ_CC_MASK_TOP: i64 = 8
97
98func sz_ld(mem: *u8, va: i64, width: i64) -> i64 { var v: i64 = 0; var i: i64 = 0; while i < width { v = (v << 8) | (mem[va + i] & 0xff); i = i + 1 } return v } // big-endian
99func sz_st(mem: *u8, va: i64, width: i64, val: i64) -> i64 { var i: i64 = 0; while i < width { mem[va + (width - 1 - i)] = (val >> (i * 8)) & 0xff; i = i + 1 } return 0 }
100func sz_sx(v: i64, bits: i64) -> i64 { let m: i64 = 1 << (bits - 1); if (v & m) != 0 { return v - (1 << bits) } return v }
101
102// A 32-bit instruction addresses bits 32-63 and leaves bits 0-31 UNCHANGED.
103// old - (old & LO32) is the high half without needing a bitwise NOT.
104func sz_put32(old: i64, lo: i64) -> i64 { return (old - (old & SZ_LO32)) + (lo & SZ_LO32) }
105
106// interpret the low 32 bits as a signed 32-bit integer
107func sz_s32(v: i64) -> i64 {
108 let x: i64 = v & SZ_LO32
109 if (x & SZ_SIGN32) != 0 { return x - SZ_2P32 }
110 return x
111}
112
113// unsigned a < b (flip the sign bit, then signed compare) -- the idiom
114// nx_emu_rv64.nx already carries; composed rather than re-derived.
115func sz_ltu(a: i64, b: i64) -> i64 {
116 let m: i64 = 1 << 63
117 if (a ^ m) < (b ^ m) { return 1 }
118 return 0
119}
120
121// logical (zero-fill) right shift; NishiLang >> is arithmetic, so mask.
122func sz_srl(v: i64, n: i64) -> i64 {
123 if n == 0 { return v }
124 let mask: i64 = (1 << (64 - n)) - 1
125 return (v >> n) & mask
126}
127
128// CC for ADD/SUBTRACT/LOAD-and-test style results. See DECLARED IMPRECISION.
129func sz_cc_of(v: i64) -> i64 {
130 if v == 0 { return SZ_CC_EQ }
131 if v < 0 { return SZ_CC_LO }
132 return SZ_CC_HI
133}
134
135// CC for COMPARE: 0 equal, 1 first operand low, 2 first operand high.
136func sz_cc_cmp(a: i64, b: i64) -> i64 {
137 if a == b { return SZ_CC_EQ }
138 if a < b { return SZ_CC_LO }
139 return SZ_CC_HI
140}
141
142// CC for AND/OR/XOR: 0 result zero, 1 result not zero. Deliberately NOT
143// sz_cc_of -- the logic classes never set 2, and reusing the arithmetic setter
144// here would be a duplicate ruler that disagrees on every negative result.
145func sz_cc_logic(v: i64) -> i64 {
146 if v == 0 { return SZ_CC_EQ }
147 return SZ_CC_LO
148}
149
150// 1 when the access lies outside the guest mapping.
151func sz_bad(ea: i64, width: i64, mem_size: i64) -> i64 {
152 if ea < 0 { return 1 }
153 if (ea + width) > mem_size { return 1 }
154 return 0
155}
156
157func emu_s390x_run_mem(mem: *u8, mem_size: i64, entry: i64, sp0: i64) -> i64 {
158 let r: *i64 = sys_mmap(SZ_NREG * SZ_REGBYTES) as *i64
159 var i: i64 = 0
160 while i < SZ_NREG { r[i] = 0; i = i + 1 }
161 r[15] = sp0
162 var pc: i64 = entry
163 var cc: i64 = SZ_CC_EQ
164 var result: i64 = SZ_RANOFF
165 var halted: i64 = 0
166 var steps: i64 = 0
167 while halted == 0 {
168 if steps > SZ_STEP_BUDGET { result = SZ_FAULT; halted = 1 }
169 if pc < 0 { result = SZ_FAULT; halted = 1 }
170 if (pc + SZ_MAXILEN) > mem_size { result = SZ_FAULT; halted = 1 }
171 if halted == 0 {
172 let b0: i64 = mem[pc] & 0xff
173 let hi2: i64 = b0 >> 6
174 var len: i64 = 4
175 if hi2 == 0 { len = 2 }
176 if hi2 == 3 { len = 6 }
177 var next: i64 = pc + len
178 var handled: i64 = 0
179 if b0 == 0x07 { // RR: BCR
180 handled = 1
181 let b1: i64 = mem[pc+1] & 0xff
182 let mask: i64 = b1 >> 4
183 let r2: i64 = b1 & 0xF
184 // PoP: R2 = 0 is a no-operation WHATEVER the mask. That is how
185 // bcr 15,0 serialises without branching, and reading r[0] here
186 // sends the pc to whatever r0 happened to hold.
187 if r2 != 0 {
188 if (mask & (SZ_CC_MASK_TOP >> cc)) != 0 { next = r[r2] }
189 }
190 }
191 if b0 == 0x0A { // RR: SVC
192 handled = 1
193 let imm: i64 = mem[pc+1] & 0xff
194 halted = 1
195 result = SZ_UNSUPPORTED
196 if imm == SZ_SYS_EXIT { result = r[2] & SZ_EXIT_MASK }
197 }
198 if b0 == 0xA7 { // RI-a / RI-b / RI-c
199 handled = 1
200 let b1: i64 = mem[pc+1] & 0xff
201 let r1: i64 = b1 >> 4
202 let op2: i64 = b1 & 0xF
203 let imm: i64 = sz_sx(sz_ld(mem, pc+2, 2), 16)
204 var subok: i64 = 0
205 if op2 == 0x4 { // BRC (r1 field is M1)
206 subok = 1
207 if (r1 & (SZ_CC_MASK_TOP >> cc)) != 0 { next = pc + imm * 2 }
208 }
209 if op2 == 0x5 { // BRAS
210 subok = 1
211 r[r1] = pc + 4
212 next = pc + imm * 2
213 }
214 if op2 == 0x6 { // BRCT (32-bit count)
215 subok = 1
216 let n32: i64 = sz_s32(sz_s32(r[r1]) - 1)
217 r[r1] = sz_put32(r[r1], n32)
218 if n32 != 0 { next = pc + imm * 2 }
219 }
220 if op2 == 0x7 { // BRCTG (64-bit count)
221 subok = 1
222 r[r1] = r[r1] - 1
223 if r[r1] != 0 { next = pc + imm * 2 }
224 }
225 if op2 == 0x8 { subok = 1; r[r1] = sz_put32(r[r1], imm) } // LHI
226 if op2 == 0x9 { subok = 1; r[r1] = imm } // LGHI
227 if op2 == 0xA { // AHI (32-bit)
228 subok = 1
229 let s: i64 = sz_s32(sz_s32(r[r1]) + imm)
230 r[r1] = sz_put32(r[r1], s)
231 cc = sz_cc_of(s)
232 }
233 if op2 == 0xB { // AGHI (64-bit)
234 subok = 1
235 r[r1] = r[r1] + imm
236 cc = sz_cc_of(r[r1])
237 }
238 if op2 == 0xD { subok = 1; r[r1] = r[r1] * imm } // MGHI (cc unchanged)
239 if op2 == 0xE { subok = 1; cc = sz_cc_cmp(sz_s32(r[r1]), imm) } // CHI
240 if op2 == 0xF { subok = 1; cc = sz_cc_cmp(r[r1], imm) } // CGHI
241 if subok == 0 { result = SZ_UNSUPPORTED; halted = 1 }
242 }
243 if b0 == 0xB9 { // RRE
244 handled = 1
245 let op2: i64 = mem[pc+1] & 0xff
246 let b3: i64 = mem[pc+3] & 0xff
247 let r1: i64 = b3 >> 4
248 let r2: i64 = b3 & 0xF
249 var subok: i64 = 0
250 if op2 == 0x04 { subok = 1; r[r1] = r[r2] } // LGR
251 if op2 == 0x08 { subok = 1; r[r1] = r[r1] + r[r2]; cc = sz_cc_of(r[r1]) } // AGR
252 if op2 == 0x09 { subok = 1; r[r1] = r[r1] - r[r2]; cc = sz_cc_of(r[r1]) } // SGR
253 if op2 == 0x0C { subok = 1; r[r1] = r[r1] * r[r2] } // MSGR (cc unchanged)
254 if op2 == 0x20 { subok = 1; cc = sz_cc_cmp(r[r1], r[r2]) } // CGR
255 if op2 == 0x21 { // CLGR (UNSIGNED)
256 subok = 1
257 var lc: i64 = SZ_CC_HI
258 if r[r1] == r[r2] { lc = SZ_CC_EQ }
259 if r[r1] != r[r2] {
260 if sz_ltu(r[r1], r[r2]) == 1 { lc = SZ_CC_LO }
261 }
262 cc = lc
263 }
264 if op2 == 0x80 { subok = 1; r[r1] = r[r1] & r[r2]; cc = sz_cc_logic(r[r1]) } // NGR
265 if op2 == 0x81 { subok = 1; r[r1] = r[r1] | r[r2]; cc = sz_cc_logic(r[r1]) } // OGR
266 if op2 == 0x82 { subok = 1; r[r1] = r[r1] ^ r[r2]; cc = sz_cc_logic(r[r1]) } // XGR
267 if subok == 0 { result = SZ_UNSUPPORTED; halted = 1 }
268 }
269 if b0 == 0x41 { // RX-a: LA
270 handled = 1
271 let b1: i64 = mem[pc+1] & 0xff
272 let r1: i64 = b1 >> 4
273 let x2: i64 = b1 & 0xF
274 let bb: i64 = mem[pc+2] & 0xff
275 let b2: i64 = bb >> 4
276 let d: i64 = ((bb & 0xF) << 8) | (mem[pc+3] & 0xff)
277 var ea: i64 = d
278 if b2 != 0 { ea = ea + r[b2] }
279 if x2 != 0 { ea = ea + r[x2] }
280 r[r1] = ea
281 }
282 if b0 == 0xE3 { // RXY-a
283 handled = 1
284 let b1: i64 = mem[pc+1] & 0xff
285 let r1: i64 = b1 >> 4
286 let x2: i64 = b1 & 0xF
287 let bb: i64 = mem[pc+2] & 0xff
288 let b2: i64 = bb >> 4
289 let DL: i64 = ((bb & 0xF) << 8) | (mem[pc+3] & 0xff)
290 let DH: i64 = mem[pc+4] & 0xff
291 let op2: i64 = mem[pc+5] & 0xff
292 var disp: i64 = DL | (DH << 12)
293 if (disp & SZ_D20_SIGN) != 0 { disp = disp - SZ_D20_MOD }
294 var ea: i64 = disp
295 if b2 != 0 { ea = ea + r[b2] }
296 if x2 != 0 { ea = ea + r[x2] }
297 var subok: i64 = 0
298 var oob: i64 = 0
299 if op2 == 0x04 { subok = 1 }
300 if op2 == 0x08 { subok = 1 }
301 if op2 == 0x09 { subok = 1 }
302 if op2 == 0x24 { subok = 1 }
303 if subok == 1 {
304 if sz_bad(ea, SZ_WORD, mem_size) == 1 { oob = 1; result = SZ_FAULT; halted = 1 }
305 }
306 if oob == 0 {
307 if op2 == 0x04 { r[r1] = sz_ld(mem, ea, SZ_WORD) } // LG
308 if op2 == 0x08 { r[r1] = r[r1] + sz_ld(mem, ea, SZ_WORD); cc = sz_cc_of(r[r1]) } // AG
309 if op2 == 0x09 { r[r1] = r[r1] - sz_ld(mem, ea, SZ_WORD); cc = sz_cc_of(r[r1]) } // SG
310 if op2 == 0x24 { sz_st(mem, ea, SZ_WORD, r[r1]) } // STG
311 }
312 if subok == 0 { result = SZ_UNSUPPORTED; halted = 1 }
313 }
314 if b0 == 0xEB { // RSY-a
315 handled = 1
316 let b1: i64 = mem[pc+1] & 0xff
317 let r1: i64 = b1 >> 4
318 let r3: i64 = b1 & 0xF
319 let bb: i64 = mem[pc+2] & 0xff
320 let b2: i64 = bb >> 4
321 let DL: i64 = ((bb & 0xF) << 8) | (mem[pc+3] & 0xff)
322 let DH: i64 = mem[pc+4] & 0xff
323 let op2: i64 = mem[pc+5] & 0xff
324 var disp: i64 = DL | (DH << 12)
325 if (disp & SZ_D20_SIGN) != 0 { disp = disp - SZ_D20_MOD }
326 var ea: i64 = disp
327 if b2 != 0 { ea = ea + r[b2] }
328 var subok: i64 = 0
329 // shift group: the second operand is a VALUE, not storage, and
330 // only its low 6 bits are used.
331 if op2 == 0x0A { // SRAG (arithmetic)
332 subok = 1
333 r[r1] = r[r3] >> (ea & SZ_SHIFT_MASK)
334 cc = sz_cc_of(r[r1])
335 }
336 if op2 == 0x0C { subok = 1; r[r1] = sz_srl(r[r3], ea & SZ_SHIFT_MASK) } // SRLG (cc unchanged)
337 if op2 == 0x0D { subok = 1; r[r1] = r[r3] << (ea & SZ_SHIFT_MASK) } // SLLG (cc unchanged)
338 if op2 == 0x04 { subok = 1 } // LMG
339 if op2 == 0x24 { subok = 1 } // STMG
340 if op2 == 0x04 {
341 var reg: i64 = r1
342 var addr: i64 = ea
343 var go: i64 = 1
344 while go == 1 {
345 if sz_bad(addr, SZ_WORD, mem_size) == 1 { result = SZ_FAULT; halted = 1; go = 0 }
346 if go == 1 {
347 r[reg] = sz_ld(mem, addr, SZ_WORD)
348 addr = addr + SZ_WORD
349 if reg == r3 { go = 0 }
350 if go == 1 { reg = (reg + 1) & 0xF }
351 }
352 }
353 }
354 if op2 == 0x24 {
355 var reg2: i64 = r1
356 var addr2: i64 = ea
357 var go2: i64 = 1
358 while go2 == 1 {
359 if sz_bad(addr2, SZ_WORD, mem_size) == 1 { result = SZ_FAULT; halted = 1; go2 = 0 }
360 if go2 == 1 {
361 sz_st(mem, addr2, SZ_WORD, r[reg2])
362 addr2 = addr2 + SZ_WORD
363 if reg2 == r3 { go2 = 0 }
364 if go2 == 1 { reg2 = (reg2 + 1) & 0xF }
365 }
366 }
367 }
368 if subok == 0 { result = SZ_UNSUPPORTED; halted = 1 }
369 }
370 if b0 == 0xC0 { // RIL-a / RIL-b / RIL-c
371 handled = 1
372 let b1: i64 = mem[pc+1] & 0xff
373 let r1: i64 = b1 >> 4
374 let op2: i64 = b1 & 0xF
375 let imm: i64 = sz_sx(sz_ld(mem, pc+2, 4), 32)
376 var subok: i64 = 0
377 if op2 == 0x0 { subok = 1; r[r1] = pc + imm * 2 } // LARL
378 if op2 == 0x1 { subok = 1; r[r1] = imm } // LGFI
379 if op2 == 0x4 { // BRCL (r1 field is M1)
380 subok = 1
381 if (r1 & (SZ_CC_MASK_TOP >> cc)) != 0 { next = pc + imm * 2 }
382 }
383 if op2 == 0x5 { // BRASL
384 subok = 1
385 r[r1] = pc + 6
386 next = pc + imm * 2
387 }
388 if subok == 0 { result = SZ_UNSUPPORTED; halted = 1 }
389 }
390 if handled == 0 { result = SZ_UNSUPPORTED; halted = 1 }
391 pc = next
392 steps = steps + 1
393 }
394 }
395 return result
396}
397
398func emu_s390x_run(code: *u8, code_len: i64) -> i64 {
399 let mem: *u8 = sys_mmap(SZ_GUEST)
400 var i: i64 = 0
401 while i < code_len { mem[i] = code[i]; i = i + 1 }
402 return emu_s390x_run_mem(mem, SZ_GUEST, 0, 0x00800000)
403}