code wiki / (root) / nx_x86.nx

nx_x86.nx source

↩ module page · 405 lines · 15942 B

1// nx_x86.nx -- Implements a 16-bit real-mode x86 CPU skeleton for running COM-style programs and DOS interrupts. 2const O_MAGIC_65536: i64 = 65536 3const O_MAGIC_65535: i64 = 65535 4// nx_x86.nx -- R1.0a SOVEREIGN x86 16-bit real-mode CPU skeleton (the start of the DOS target). Same proven 5// substrate as nx_chip8: fixed-offset base-relative state (runs native + wasm), fetch-decode-execute. This 6// rung covers a REAL register/immediate/jump subset (enough for a .COM-style countdown/branch program); 7// ModR/M memory operands, segmentation (seg*16+off), and BIOS/DOS INTs are the next rungs. Proven vs a 8// DOSBox/qemu oracle later (3rd-party-proving for the standard ISA). license_tier: ORIGINAL 9// 10// genealogy_id: sovereign_emulator_x86 lineage_id: nx_x86_v1 (parents: nx_chip8 fetch-decode-execute idiom) 11// 12// 16-bit GP regs in x86 opcode order: 0=AX 1=CX 2=DX 3=BX 4=SP 5=BP 6=SI 7=DI. COM model: one 64 KiB segment, 13// program loads at IP=0x100. Flags tracked: ZF, CF, SF. 14const O_MEM: i64 = 0 // 65536 guest bytes (one real-mode segment) 15const O_REGS: i64 = 65536 // 8 GP regs (i64, hold 16-bit values) -> 65600 16const O_IP: i64 = 65600 17const O_ZF: i64 = 65608 18const O_CF: i64 = 65616 19const O_SF: i64 = 65624 20const O_MM: i64 = 65632 // 4-i64 ModR/M decode scratch (reg, is_reg, ea) -- avoids per-step alloc 21const O_CON: i64 = 65664 // 4096-byte console output buffer (DOS INT 21h writes here) 22const O_CON_LEN: i64 = 69760 // console length cursor 23const O_EXIT: i64 = 69768 // set by INT 21h AH=4Ch (program terminate) 24const O_FSNAME: i64 = 69776 // Nishi-DOS sovereign FS: one file's name (ASCIIZ) 25const O_FSLEN: i64 = 69792 // its byte length 26const O_FSDATA: i64 = 69800 // its bytes (up to 4096) 27const O_HOPEN: i64 = 73896 // 8 file-handle open flags 28const O_HPOS: i64 = 73960 // 8 file-handle byte positions 29 30func x86_reset(base: i64) -> i64 { 31 let mem: *u8 = (base + O_MEM) as *u8 32 var i: i64 = 0 33 while i < O_MAGIC_65536 { mem[i] = 0 as u8; i = i + 1 } 34 let regs: *i64 = (base + O_REGS) as *i64 35 i = 0; while i < 8 { regs[i] = 0; i = i + 1 } 36 let ipp: *i64 = (base + O_IP) as *i64 37 ipp[0] = 256 // 0x100 -- COM entry 38 let z: *i64 = (base + O_ZF) as *i64; z[0] = 0 39 let c: *i64 = (base + O_CF) as *i64; c[0] = 0 40 let s: *i64 = (base + O_SF) as *i64; s[0] = 0 41 let cl: *i64 = (base + O_CON_LEN) as *i64; cl[0] = 0 42 let ex: *i64 = (base + O_EXIT) as *i64; ex[0] = 0 43 let flz: *i64 = (base + O_FSLEN) as *i64; flz[0] = 0 44 let hoz: *i64 = (base + O_HOPEN) as *i64 45 var hz: i64 = 0 46 while hz < 8 { hoz[hz] = 0; hz = hz + 1 } 47 return 0 48} 49func x86_load(base: i64, addr: i64, byte: i64) -> i64 { 50 let mem: *u8 = (base + O_MEM) as *u8 51 mem[addr] = (byte & 0xff) as u8 52 return 0 53} 54 55// set ZF + SF from a 16-bit result (CF is set per-opcode by the caller). 56func x86_setzsf(base: i64, res: i64) -> i64 { 57 let z: *i64 = (base + O_ZF) as *i64 58 let s: *i64 = (base + O_SF) as *i64 59 if (res & 0xffff) == 0 { z[0] = 1 } else { z[0] = 0 } 60 s[0] = (res >> 15) & 1 61 return 0 62} 63 64// ---- ModR/M (16-bit) + memory operands ---- 65func x86_rd16(base: i64, ea: i64) -> i64 { 66 let mem: *u8 = (base + O_MEM) as *u8 67 return (mem[ea & 0xffff] as i64) | ((mem[(ea + 1) & 0xffff] as i64) << 8) 68} 69func x86_wr16(base: i64, ea: i64, v: i64) -> i64 { 70 let mem: *u8 = (base + O_MEM) as *u8 71 mem[ea & 0xffff] = (v & 0xff) as u8 72 mem[(ea + 1) & 0xffff] = ((v >> 8) & 0xff) as u8 73 return 0 74} 75func x86_peek16(base: i64, addr: i64) -> i64 { return x86_rd16(base, addr) } // gate/IO read 76 77// decode ModR/M (+disp) at ip: mm[0]=reg field, mm[1]=is_register(1/0), mm[2]=reg index OR effective address. 78// returns the new ip. The 8 16-bit addressing modes, incl the rm=6 disp16(mod0) vs [BP+disp](mod1,2) quirk. 79func x86_modrm(base: i64, ip: i64, mm: *i64) -> i64 { 80 let mem: *u8 = (base + O_MEM) as *u8 81 let regs: *i64 = (base + O_REGS) as *i64 82 let modrm: i64 = mem[ip] as i64 83 var p: i64 = ip + 1 84 let md: i64 = (modrm >> 6) & 3 85 mm[0] = (modrm >> 3) & 7 86 let rm: i64 = modrm & 7 87 if md == 3 { mm[1] = 1; mm[2] = rm; return p } 88 mm[1] = 0 89 var ea: i64 = 0 90 if rm == 0 { ea = regs[3] + regs[6] } // BX+SI 91 if rm == 1 { ea = regs[3] + regs[7] } // BX+DI 92 if rm == 2 { ea = regs[5] + regs[6] } // BP+SI 93 if rm == 3 { ea = regs[5] + regs[7] } // BP+DI 94 if rm == 4 { ea = regs[6] } // SI 95 if rm == 5 { ea = regs[7] } // DI 96 if rm == 6 { if md == 0 { ea = 0 } else { ea = regs[5] } } // disp16 (mod0) / BP (mod1,2) 97 if rm == 7 { ea = regs[3] } // BX 98 if md == 1 { 99 var d8: i64 = mem[p] as i64 100 if d8 >= 128 { d8 = d8 - 256 } 101 p = p + 1 102 ea = ea + d8 103 } 104 if md == 2 { 105 let d16: i64 = (mem[p] as i64) | ((mem[p + 1] as i64) << 8) 106 p = p + 2 107 ea = ea + d16 108 } 109 if md == 0 { if rm == 6 { 110 let d16b: i64 = (mem[p] as i64) | ((mem[p + 1] as i64) << 8) 111 p = p + 2 112 ea = ea + d16b 113 } } 114 mm[2] = ea & 0xffff 115 return p 116} 117func x86_rm_get(base: i64, mm: *i64) -> i64 { 118 if mm[1] == 1 { let regs: *i64 = (base + O_REGS) as *i64; return regs[mm[2]] } 119 return x86_rd16(base, mm[2]) 120} 121func x86_rm_set(base: i64, mm: *i64, v: i64) -> i64 { 122 if mm[1] == 1 { let regs: *i64 = (base + O_REGS) as *i64; regs[mm[2]] = v & 0xffff; return 0 } 123 x86_wr16(base, mm[2], v & 0xffff) 124 return 0 125} 126 127// ---- 8-bit registers (AL/CL/DL/BL = r<4 low byte ; AH/CH/DH/BH = r>=4 high byte of regs[r-4]) ---- 128func x86_set_r8(base: i64, r: i64, v: i64) -> i64 { 129 let regs: *i64 = (base + O_REGS) as *i64 130 if r < 4 { regs[r] = (regs[r] & 0xff00) | (v & 0xff) } 131 else { regs[r - 4] = (regs[r - 4] & 0xff) | ((v & 0xff) << 8) } 132 return 0 133} 134 135// ---- Nishi-DOS sovereign virtual FS (R-DOS1): one host-loaded file (name + bytes) ---- 136func x86_fs_put(base: i64, name: *u8, data: *u8, len: i64) -> i64 { 137 let fsname: *u8 = (base + O_FSNAME) as *u8 138 var i: i64 = 0 139 while name[i] != (0 as u8) { fsname[i] = name[i]; i = i + 1 } 140 fsname[i] = 0 as u8 141 let fsdata: *u8 = (base + O_FSDATA) as *u8 142 i = 0 143 while i < len { fsdata[i] = data[i]; i = i + 1 } 144 let fl: *i64 = (base + O_FSLEN) as *i64 145 fl[0] = len 146 return 0 147} 148// is the guest ASCIIZ filename at mem[off..] equal to the FS file's name? 149func x86_fs_nameeq(base: i64, off: i64) -> i64 { 150 let mem: *u8 = (base + O_MEM) as *u8 151 let fsname: *u8 = (base + O_FSNAME) as *u8 152 var i: i64 = 0 153 while 1 == 1 { 154 let a: i64 = mem[(off + i) & 0xffff] as i64 155 let b: i64 = fsname[i] as i64 156 if a != b { return 0 } 157 if a == 0 { return 1 } 158 i = i + 1 159 } 160 return 0 161} 162 163// ---- HLE Nishi-DOS INT 21h: print (09/02), terminate (4C), open/read/close (3D/3F/3E, R-DOS1) ---- 164func x86_dos_int21(base: i64) -> i64 { 165 let regs: *i64 = (base + O_REGS) as *i64 166 let mem: *u8 = (base + O_MEM) as *u8 167 let con: *u8 = (base + O_CON) as *u8 168 let clen: *i64 = (base + O_CON_LEN) as *i64 169 let ah: i64 = (regs[0] >> 8) & 0xff 170 if ah == 9 { // print $-terminated string at DS:DX (DS=0 in COM) 171 var off: i64 = regs[2] & 0xffff 172 var guard: i64 = 0 173 while guard < O_MAGIC_65536 { 174 let ch: i64 = mem[off] as i64 175 if ch == 36 { break } // '$' 176 con[clen[0]] = ch as u8 177 clen[0] = clen[0] + 1 178 off = (off + 1) & 0xffff 179 guard = guard + 1 180 } 181 } 182 if ah == 2 { // print char in DL 183 con[clen[0]] = (regs[2] & 0xff) as u8 184 clen[0] = clen[0] + 1 185 } 186 if ah == 61 { // 3Dh open existing file (DS:DX = ASCIIZ name) -> AX=handle 187 let dx: i64 = regs[2] & 0xffff 188 let cf: *i64 = (base + O_CF) as *i64 189 if x86_fs_nameeq(base, dx) == 1 { 190 let hopen: *i64 = (base + O_HOPEN) as *i64 191 let hpos: *i64 = (base + O_HPOS) as *i64 192 var h: i64 = 5 // 0-4 reserved (stdin/out/err/aux/prn) 193 while h < 8 { if hopen[h] == 0 { break } h = h + 1 } 194 hopen[h] = 1 195 hpos[h] = 0 196 regs[0] = h 197 cf[0] = 0 198 } else { 199 regs[0] = 2 // ENOENT 200 cf[0] = 1 201 } 202 } 203 if ah == 63 { // 3Fh read (BX=handle, CX=count, DS:DX=buf) -> AX=bytes read 204 let bx: i64 = regs[3] & 0xffff 205 let cx: i64 = regs[1] & 0xffff 206 let dx: i64 = regs[2] & 0xffff 207 let hpos: *i64 = (base + O_HPOS) as *i64 208 let fl: *i64 = (base + O_FSLEN) as *i64 209 let fsdata: *u8 = (base + O_FSDATA) as *u8 210 let gmem: *u8 = (base + O_MEM) as *u8 211 var n: i64 = cx 212 let avail: i64 = fl[0] - hpos[bx] 213 if n > avail { n = avail } 214 var i: i64 = 0 215 while i < n { gmem[(dx + i) & 0xffff] = fsdata[hpos[bx] + i]; i = i + 1 } 216 hpos[bx] = hpos[bx] + n 217 regs[0] = n 218 } 219 if ah == 62 { // 3Eh close (BX=handle) 220 let bx: i64 = regs[3] & 0xffff 221 let hopen: *i64 = (base + O_HOPEN) as *i64 222 hopen[bx] = 0 223 } 224 if ah == 76 { // 4Ch terminate 225 let ex: *i64 = (base + O_EXIT) as *i64 226 ex[0] = 1 227 } 228 return 0 229} 230func x86_con_len(base: i64) -> i64 { let c: *i64 = (base + O_CON_LEN) as *i64; return c[0] } 231func x86_con_byte(base: i64, i: i64) -> i64 { let con: *u8 = (base + O_CON) as *u8; return con[i] as i64 } 232 233// execute ONE instruction. returns 0 normally, 1 on HLT (IP left on the HLT byte). 234func x86_step(base: i64) -> i64 { 235 let mem: *u8 = (base + O_MEM) as *u8 236 let regs: *i64 = (base + O_REGS) as *i64 237 let ipp: *i64 = (base + O_IP) as *i64 238 let zf: *i64 = (base + O_ZF) as *i64 239 let cf: *i64 = (base + O_CF) as *i64 240 let mm: *i64 = (base + O_MM) as *i64 241 242 var ip: i64 = ipp[0] 243 let op: i64 = mem[ip] as i64 244 ip = ip + 1 245 246 var halt: i64 = 0 247 248 // MOV r16, imm16 (B8+r) 249 if op >= 184 { if op <= 191 { 250 let r: i64 = op - 184 251 let imm: i64 = (mem[ip] as i64) | ((mem[ip + 1] as i64) << 8) 252 ip = ip + 2 253 regs[r] = imm & 0xffff 254 } } 255 // INC r16 (40+r) -- affects ZF,SF not CF 256 if op >= 64 { if op <= 71 { 257 let r: i64 = op - 64 258 let res: i64 = (regs[r] + 1) & 0xffff 259 regs[r] = res 260 x86_setzsf(base, res) 261 } } 262 // DEC r16 (48+r) -- affects ZF,SF not CF 263 if op >= 72 { if op <= 79 { 264 let r: i64 = op - 72 265 let res: i64 = (regs[r] - 1) & 0xffff 266 regs[r] = res 267 x86_setzsf(base, res) 268 } } 269 // ADD AX, imm16 (05) 270 if op == 5 { 271 let imm: i64 = (mem[ip] as i64) | ((mem[ip + 1] as i64) << 8) 272 ip = ip + 2 273 let res: i64 = regs[0] + imm 274 if res > O_MAGIC_65535 { cf[0] = 1 } else { cf[0] = 0 } 275 regs[0] = res & 0xffff 276 x86_setzsf(base, regs[0]) 277 } 278 // SUB AX, imm16 (2D) 279 if op == 45 { 280 let imm: i64 = (mem[ip] as i64) | ((mem[ip + 1] as i64) << 8) 281 ip = ip + 2 282 let a: i64 = regs[0] 283 if a < imm { cf[0] = 1 } else { cf[0] = 0 } 284 regs[0] = (a - imm) & 0xffff 285 x86_setzsf(base, regs[0]) 286 } 287 // CMP AX, imm16 (3D) -- sub, set flags, discard result 288 if op == 61 { 289 let imm: i64 = (mem[ip] as i64) | ((mem[ip + 1] as i64) << 8) 290 ip = ip + 2 291 let a: i64 = regs[0] 292 if a < imm { cf[0] = 1 } else { cf[0] = 0 } 293 x86_setzsf(base, (a - imm) & 0xffff) 294 } 295 // JMP short rel8 (EB) 296 if op == 235 { 297 var rel: i64 = mem[ip] as i64 298 if rel >= 128 { rel = rel - 256 } 299 ip = ip + 1 300 ip = ip + rel 301 } 302 // JZ/JE rel8 (74) ; JNZ/JNE rel8 (75) 303 if op == 116 { 304 var rel: i64 = mem[ip] as i64 305 if rel >= 128 { rel = rel - 256 } 306 ip = ip + 1 307 if zf[0] == 1 { ip = ip + rel } 308 } 309 if op == 117 { 310 var rel: i64 = mem[ip] as i64 311 if rel >= 128 { rel = rel - 256 } 312 ip = ip + 1 313 if zf[0] == 0 { ip = ip + rel } 314 } 315 // NOP (90) 316 if op == 144 { } 317 // HLT (F4) -- leave IP on the HLT byte, signal halt 318 if op == 244 { ip = ip - 1; halt = 1 } 319 320 // ---- ModR/M memory-operand opcodes (16-bit) ---- 321 if op == 137 { // 89 /r MOV r/m16, r16 322 ip = x86_modrm(base, ip, mm) 323 x86_rm_set(base, mm, regs[mm[0]]) 324 } 325 if op == 139 { // 8B /r MOV r16, r/m16 326 ip = x86_modrm(base, ip, mm) 327 regs[mm[0]] = x86_rm_get(base, mm) & 0xffff 328 } 329 if op == 1 { // 01 /r ADD r/m16, r16 330 ip = x86_modrm(base, ip, mm) 331 let a1: i64 = x86_rm_get(base, mm) 332 let r1: i64 = a1 + regs[mm[0]] 333 if r1 > O_MAGIC_65535 { cf[0] = 1 } else { cf[0] = 0 } 334 x86_rm_set(base, mm, r1) 335 x86_setzsf(base, r1 & 0xffff) 336 } 337 if op == 3 { // 03 /r ADD r16, r/m16 338 ip = x86_modrm(base, ip, mm) 339 let r2: i64 = regs[mm[0]] + x86_rm_get(base, mm) 340 if r2 > O_MAGIC_65535 { cf[0] = 1 } else { cf[0] = 0 } 341 regs[mm[0]] = r2 & 0xffff 342 x86_setzsf(base, regs[mm[0]]) 343 } 344 if op == 41 { // 29 /r SUB r/m16, r16 345 ip = x86_modrm(base, ip, mm) 346 let a3: i64 = x86_rm_get(base, mm) 347 let b3: i64 = regs[mm[0]] 348 if a3 < b3 { cf[0] = 1 } else { cf[0] = 0 } 349 x86_rm_set(base, mm, (a3 - b3) & 0xffff) 350 x86_setzsf(base, (a3 - b3) & 0xffff) 351 } 352 if op == 57 { // 39 /r CMP r/m16, r16 353 ip = x86_modrm(base, ip, mm) 354 let a4: i64 = x86_rm_get(base, mm) 355 let b4: i64 = regs[mm[0]] 356 if a4 < b4 { cf[0] = 1 } else { cf[0] = 0 } 357 x86_setzsf(base, (a4 - b4) & 0xffff) 358 } 359 if op == 49 { // 31 /r XOR r/m16, r16 360 ip = x86_modrm(base, ip, mm) 361 let r5: i64 = (x86_rm_get(base, mm) ^ regs[mm[0]]) & 0xffff 362 cf[0] = 0 363 x86_rm_set(base, mm, r5) 364 x86_setzsf(base, r5) 365 } 366 if op == 199 { // C7 /0 MOV r/m16, imm16 367 ip = x86_modrm(base, ip, mm) 368 let imm7: i64 = (mem[ip] as i64) | ((mem[ip + 1] as i64) << 8) 369 ip = ip + 2 370 x86_rm_set(base, mm, imm7 & 0xffff) 371 } 372 // MOV r8, imm8 (B0+r) 373 if op >= 176 { if op <= 183 { 374 let imm8: i64 = mem[ip] as i64 375 ip = ip + 1 376 x86_set_r8(base, op - 176, imm8) 377 } } 378 // INT imm8 (CD) -- high-level: INT 21h -> the DOS handler; AH=4Ch sets the exit flag -> halt 379 if op == 205 { 380 let n: i64 = mem[ip] as i64 381 ip = ip + 1 382 if n == 33 { x86_dos_int21(base) } 383 let exitp: *i64 = (base + O_EXIT) as *i64 384 if exitp[0] == 1 { halt = 1 } 385 } 386 387 ipp[0] = ip & 0xffff 388 return halt 389} 390 391// run until HLT or a step cap (cap guards against runaway loops). returns steps executed. 392func x86_run(base: i64, cap: i64) -> i64 { 393 var c: i64 = 0 394 while c < cap { 395 if x86_step(base) == 1 { return c + 1 } 396 c = c + 1 397 } 398 return c 399} 400 401func x86_reg(base: i64, r: i64) -> i64 { let regs: *i64 = (base + O_REGS) as *i64; return regs[r & 7] } 402func x86_ip(base: i64) -> i64 { let ipp: *i64 = (base + O_IP) as *i64; return ipp[0] } 403func x86_zf(base: i64) -> i64 { let z: *i64 = (base + O_ZF) as *i64; return z[0] } 404func x86_cf(base: i64) -> i64 { let c: *i64 = (base + O_CF) as *i64; return c[0] } 405func x86_sf(base: i64) -> i64 { let s: *i64 = (base + O_SF) as *i64; return s[0] }