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] }