code wiki / (root) / nx_emu_s390x.nx

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}