blockyard 0.0.9 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +359 -1
  2. package/README.md +48 -27
  3. package/SECURITY.md +2 -2
  4. package/bin/blockyard.js +2 -1
  5. package/docs/API.md +17 -15
  6. package/docs/ARCHITECTURE.md +128 -10
  7. package/docs/CONFIGURATION.md +39 -30
  8. package/docs/DEFECTS.md +4 -1
  9. package/docs/GETTING-STARTED.md +18 -8
  10. package/docs/INSTALL.md +97 -37
  11. package/docs/MEASUREMENTS.md +147 -0
  12. package/docs/PLAN-SCORCHED-YARD.md +456 -0
  13. package/docs/PLAN-SKIES.md +142 -0
  14. package/docs/SECURITY-AUDIT-2026-09-16.md +647 -0
  15. package/docs/SECURITY.md +58 -22
  16. package/docs/TROUBLESHOOTING.md +44 -5
  17. package/docs/USER-GUIDE.md +505 -35
  18. package/package.json +4 -2
  19. package/public/404.html +1 -1
  20. package/public/css/app.css +393 -82
  21. package/public/donate-qr.png +0 -0
  22. package/public/index.html +353 -109
  23. package/public/js/agents.js +228 -51
  24. package/public/js/app.js +131 -16
  25. package/public/js/blockanoid.js +15 -7
  26. package/public/js/blockout.js +15 -7
  27. package/public/js/blockscene3d.js +230 -38
  28. package/public/js/charts.js +21 -21
  29. package/public/js/depthchart.js +31 -27
  30. package/public/js/details3d.js +1481 -73
  31. package/public/js/doom.js +31 -0
  32. package/public/js/dosaudio.js +48 -0
  33. package/public/js/dosgame.js +389 -0
  34. package/public/js/dosio.js +186 -0
  35. package/public/js/dospc.js +1353 -0
  36. package/public/js/dosworker.js +196 -0
  37. package/public/js/explorer.js +7 -1
  38. package/public/js/livingsky.js +494 -0
  39. package/public/js/login.js +8 -2
  40. package/public/js/markets.js +46 -8
  41. package/public/js/mining.js +314 -36
  42. package/public/js/panels.js +41 -28
  43. package/public/js/pricechart.js +14 -13
  44. package/public/js/quake.js +20 -0
  45. package/public/js/safenext.js +14 -0
  46. package/public/js/scorched.js +1051 -0
  47. package/public/js/scorchedai.js +227 -0
  48. package/public/js/scorchedair.js +286 -0
  49. package/public/js/scorchedfx.js +376 -0
  50. package/public/js/scorchedshop.js +105 -0
  51. package/public/js/scorchedwind.js +69 -0
  52. package/public/js/scorchedyard.js +1338 -0
  53. package/public/js/settings.js +368 -100
  54. package/public/js/soundcard.js +459 -0
  55. package/public/js/tetrust.js +15 -6
  56. package/public/js/tetsound.js +35 -5
  57. package/public/js/theme.js +235 -0
  58. package/public/js/wolf3d.js +22 -0
  59. package/public/js/x86.js +1978 -0
  60. package/scripts/check.js +46 -0
  61. package/scripts/donate-qr.py +12 -9
  62. package/scripts/dos-bench.js +56 -0
  63. package/scripts/index-build.js +9 -2
  64. package/scripts/pool-map.js +152 -36
  65. package/scripts/setup.js +142 -22
  66. package/scripts/shots.mjs +27 -0
  67. package/scripts/smoke.sh +7 -6
  68. package/scripts/tls.js +31 -0
  69. package/scripts/ui.js +4 -2
  70. package/server/auth/sessions.js +33 -13
  71. package/server/chain/blockfile.js +64 -5
  72. package/server/chain/index/build.js +451 -58
  73. package/server/chain/index/heights.js +29 -3
  74. package/server/chain/index/live.js +13 -7
  75. package/server/chain/index/rows.js +6 -1
  76. package/server/chain/index/store.js +28 -5
  77. package/server/chain/index/worker.js +23 -11
  78. package/server/collect/logparse.js +65 -18
  79. package/server/collect/markets.js +76 -7
  80. package/server/collect/mining.js +32 -0
  81. package/server/collect/monitor.js +54 -12
  82. package/server/collect/network.js +305 -0
  83. package/server/config.js +53 -22
  84. package/server/http/api.js +119 -18
  85. package/server/http/games.js +77 -0
  86. package/server/http/server.js +30 -5
  87. package/server/http/sse.js +53 -7
  88. package/server/main.js +66 -11
  89. package/server/rpc/allowlist.js +26 -0
  90. package/server/rpc/client.js +30 -2
  91. package/server/store/audit.js +6 -1
  92. package/server/store/history.js +19 -3
  93. package/server/store/ledger.js +15 -4
  94. package/server/tls/selfsigned.js +160 -0
  95. package/systemd/blockyard.service +41 -8
  96. package/docs/PRIVATE-LEADERBOARD.md +0 -230
  97. package/docs/STATE-2026-09-09.md +0 -200
@@ -0,0 +1,1978 @@
1
+ // X86: an i386 in protected mode, flat, interpreted -- the CPU the DOOM Diversion runs on
2
+ // (operator, 2026-09-15: "Get DOOM working as a diversion inside blockyard with zero dependancies").
3
+ //
4
+ // Our own implementation, written for one job: running a 32-bit DOS-extended program (a Watcom
5
+ // build under DOS/4GW) whose world the machine in dospc.js fakes around it. So this is a user-mode
6
+ // 386 with a 487 beside it, and deliberately no more: no paging, no privilege rings, no task
7
+ // switches, no real mode. Segment registers hold a selector and the base the machine assigns it
8
+ // (every flat selector is base 0); an INT, a port and a hardware interrupt are the machine's
9
+ // business, handed over through `bus`.
10
+ //
11
+ // SPEED IS THE WHOLE PROBLEM. DOOM wants about a million instructions a frame, and the page forbids
12
+ // eval (CSP `script-src 'self'`), so there is no JIT to hide behind: this interpreter has to run at
13
+ // tens of millions of instructions a second on its own. What that cost:
14
+ // - EVERY VALUE IS AN INT32. A 32-bit register, operand or result is a signed int32 in JS (8- and
15
+ // 16-bit ones are masked non-negative), and unsigned comparisons flip the sign bit instead of
16
+ // using `>>> 0`. A `>>> 0` above 2^31 is a double, and a double in a closure variable is an
17
+ // allocation: the first cut spent its time in the garbage collector.
18
+ // - FLAGS ARE LAZY. Arithmetic records its operands and result in an Int32Array, and CF/ZF/SF/OF/
19
+ // PF/AF are worked out only when something reads them -- a Jcc, a PUSHF, an ADC. `cond()` reads
20
+ // a cmp's operands directly. Rotates, shifts and multiplies set CF and OF eagerly.
21
+ // - no try/catch around each instruction (it cost 14% on its own); one around the loop.
22
+ // - the instructions a Watcom build is made of (mov, add/sub/cmp with an imm8, jcc, inc/dec) have
23
+ // 32-bit paths that skip the size switch.
24
+ //
25
+ // Memory is one flat Uint8Array; every address is masked to it rather than bounds-checked, so a
26
+ // wild pointer reads garbage instead of throwing. Writes into 0xA0000-0xBFFFF go to the machine's
27
+ // VGA, which has planes a byte array does not.
28
+
29
+ const S8 = 0, S16 = 1, S32 = 2;
30
+ const MASK = [0xff, 0xffff, -1];
31
+ const SIGN = [0x80, 0x8000, -0x80000000];
32
+ const BITS = [8, 16, 32];
33
+ const FLIP = -0x80000000; // x ^ FLIP turns an unsigned comparison into a signed one
34
+
35
+ // lazy flag producers
36
+ const F_NONE = 0, F_ADD = 1, F_ADC = 2, F_SUB = 3, F_SBB = 4, F_LOGIC = 5, F_INC = 6, F_DEC = 7, F_EAGER = 8;
37
+
38
+ const PARITY = new Uint8Array(256);
39
+ for (let i = 0; i < 256; i++) { let b = i, p = 1; while (b) { p ^= b & 1; b >>= 1; } PARITY[i] = p; }
40
+
41
+ const PREFIX = new Uint8Array(256);
42
+ for (const p of [0x26, 0x2e, 0x36, 0x3e, 0x64, 0x65, 0x66, 0x67, 0xf0, 0xf2, 0xf3]) PREFIX[p] = 1;
43
+
44
+ export const EAX = 0, ECX = 1, EDX = 2, EBX = 3, ESP = 4, EBP = 5, ESI = 6, EDI = 7;
45
+ export const ES = 0, CS = 1, SS = 2, DS = 3, FS = 4, GS = 5;
46
+
47
+ export class CpuFault extends Error {
48
+ constructor(msg, eip) { super(`${msg} at ${(eip >>> 0).toString(16)}`); this.eip = eip; }
49
+ }
50
+ class DivFault extends Error {}
51
+
52
+ /**
53
+ * The CPU. `bus` supplies:
54
+ * mem: Uint8Array (a power-of-two length, at least 1 MB)
55
+ * vgaWrite(addr, byte), vgaRead(addr)
56
+ * portIn(port, size) -> value, portOut(port, size, value) size: 0 byte, 1 word, 2 dword
57
+ * softInt(cpu, n) -> true when the machine handled INT n itself (else its vector is taken)
58
+ * vector(n) -> { sel, off }: where INT n (or IRQ) goes when the machine does not handle it
59
+ * selectorBase(sel) -> the linear base of a selector loaded into a segment register
60
+ */
61
+ export function createCpu(bus) {
62
+ const M = bus.mem;
63
+ const M32 = new Int32Array(M.buffer, M.byteOffset, M.length >> 2);
64
+ const M16 = new Uint16Array(M.buffer, M.byteOffset, M.length >> 1);
65
+ const AMASK = M.length - 1;
66
+ const R = new Int32Array(8);
67
+ const seg = new Uint16Array(6);
68
+ const segBase = new Int32Array(6);
69
+ const LF = new Int32Array(3); // lazy flags: operand a, operand b, result
70
+
71
+ let eip = 0;
72
+ let flags = 0x202; // DF, IF, TF always; CF..OF too while fOp is F_NONE
73
+ let fOp = F_NONE, fSz = S32, fCF = 0, fOF = 0;
74
+ let halted = false;
75
+ let cycles = 0;
76
+ let opEip = 0, os = S32, as32 = true, segOv = -1, rep = 0;
77
+ let stop = false;
78
+ let defBases = false; // DS or SS has a base other than 0
79
+ // CODE AND STACK BASES. EIP is kept LINEAR (fetch never adds a base); what the program sees -- a
80
+ // return address pushed, a jump target loaded -- is an offset from CS's base, converted at the
81
+ // edges. ESP is an offset from SS's base. Both are 0 under DOS/4GW; a DJGPP program's segments
82
+ // start at its memory block.
83
+ let csb = 0, ssb = 0;
84
+ let splitBases = false; // DS and SS have different bases
85
+ // a 16-bit code segment (its descriptor's D bit clear) decodes with 16-bit operands and addresses
86
+ // unless a prefix says otherwise: DJGPP's start-up copies a small 16-bit helper into DOS memory
87
+ let cs16 = false;
88
+
89
+ // ------------------------------------------------------------------ memory
90
+ const rb = (a) => M[a & AMASK];
91
+ function rw(a) { a &= AMASK; return (a & 1) === 0 ? M16[a >> 1] : M[a] | (M[(a + 1) & AMASK] << 8); }
92
+ function rd(a) { a &= AMASK; return (a & 3) === 0 ? M32[a >> 2] : (M[a] | (M[(a + 1) & AMASK] << 8) | (M[(a + 2) & AMASK] << 16) | (M[(a + 3) & AMASK] << 24)); }
93
+ function rbx(a) { a &= AMASK; return (a - 0xa0000) >>> 0 < 0x20000 ? bus.vgaRead(a) : M[a]; }
94
+ // A WRITE TO CACHED CODE (see "the decoded-instruction cache"): the page's decoded instructions
95
+ // go, so a program that patches its own code -- Quake's span drawers do -- runs what it wrote
96
+ function wb(a, v) {
97
+ a &= AMASK;
98
+ if ((a - 0xa0000) >>> 0 < 0x20000) { bus.vgaWrite(a, v & 0xff); return; }
99
+ if (codePage[a >>> 12] !== 0) touchCode(a, 1);
100
+ M[a] = v;
101
+ }
102
+ function ww(a, v) {
103
+ a &= AMASK;
104
+ if ((a - 0x9ffff) >>> 0 < 0x20001) { wb(a, v); wb(a + 1, v >> 8); return; }
105
+ if ((codePage[a >>> 12] | codePage[((a + 1) & AMASK) >>> 12]) !== 0) touchCode(a, 2);
106
+ if ((a & 1) === 0) M16[a >> 1] = v; else { M[a] = v; M[(a + 1) & AMASK] = v >> 8; }
107
+ }
108
+ function wd(a, v) {
109
+ a &= AMASK;
110
+ if ((a - 0x9fffd) >>> 0 < 0x20003) { wb(a, v); wb(a + 1, v >> 8); wb(a + 2, v >> 16); wb(a + 3, v >> 24); return; }
111
+ if ((codePage[a >>> 12] | codePage[((a + 3) & AMASK) >>> 12]) !== 0) touchCode(a, 4);
112
+ if ((a & 3) === 0) M32[a >> 2] = v;
113
+ else { M[a] = v; M[(a + 1) & AMASK] = v >> 8; M[(a + 2) & AMASK] = v >> 16; M[(a + 3) & AMASK] = v >> 24; }
114
+ }
115
+ const isVga = (a) => ((a & AMASK) - 0xa0000) >>> 0 < 0x20000;
116
+
117
+ // ------------------------------------------------------------------ fetch
118
+ function f8s() { return (M[eip++] << 24) >> 24; }
119
+ function f16() { const v = M[eip] | (M[eip + 1] << 8); eip += 2; return v; }
120
+ function f32() { const v = M[eip] | (M[eip + 1] << 8) | (M[eip + 2] << 16) | (M[eip + 3] << 24); eip += 4; return v; }
121
+ function fImm(s) { return s === S32 ? f32() : s === S16 ? f16() : M[eip++]; }
122
+
123
+ // ------------------------------------------------------------------ registers
124
+ function get8(i) { return i < 4 ? R[i] & 0xff : (R[i - 4] >> 8) & 0xff; }
125
+ function set8(i, v) { if (i < 4) R[i] = (R[i] & ~0xff) | (v & 0xff); else R[i - 4] = (R[i - 4] & ~0xff00) | ((v & 0xff) << 8); }
126
+ function getR(s, i) { return s === S32 ? R[i] : s === S16 ? R[i] & 0xffff : get8(i); }
127
+ function setR(s, i, v) { if (s === S32) R[i] = v; else if (s === S16) R[i] = (R[i] & ~0xffff) | (v & 0xffff); else set8(i, v); }
128
+
129
+ // ------------------------------------------------------------------ flags
130
+ function getCF() {
131
+ switch (fOp) {
132
+ case F_NONE: return flags & 1;
133
+ case F_ADD: return (LF[2] ^ FLIP) < (LF[0] ^ FLIP) ? 1 : 0;
134
+ case F_ADC: return (LF[2] ^ FLIP) < (LF[0] ^ FLIP) || (fCF && LF[2] === LF[0]) ? 1 : 0;
135
+ case F_SUB: return (LF[0] ^ FLIP) < (LF[1] ^ FLIP) ? 1 : 0;
136
+ case F_SBB: return (LF[0] ^ FLIP) < (LF[1] ^ FLIP) || (fCF && LF[0] === LF[1]) ? 1 : 0;
137
+ case F_LOGIC: return 0;
138
+ default: return fCF;
139
+ }
140
+ }
141
+ function getOF() {
142
+ switch (fOp) {
143
+ case F_NONE: return (flags >> 11) & 1;
144
+ case F_ADD: case F_ADC: return ((LF[0] ^ LF[2]) & (LF[1] ^ LF[2]) & SIGN[fSz]) !== 0 ? 1 : 0;
145
+ case F_SUB: case F_SBB: return ((LF[0] ^ LF[1]) & (LF[0] ^ LF[2]) & SIGN[fSz]) !== 0 ? 1 : 0;
146
+ case F_LOGIC: return 0;
147
+ case F_INC: return LF[2] === SIGN[fSz] ? 1 : 0;
148
+ case F_DEC: return LF[2] === ((SIGN[fSz] - 1) | 0) ? 1 : 0;
149
+ default: return fOF;
150
+ }
151
+ }
152
+ function getZF() { return fOp === F_NONE ? (flags >> 6) & 1 : LF[2] === 0 ? 1 : 0; }
153
+ function getSF() { return fOp === F_NONE ? (flags >> 7) & 1 : (LF[2] & SIGN[fSz]) !== 0 ? 1 : 0; }
154
+ function getPF() { return fOp === F_NONE ? (flags >> 2) & 1 : PARITY[LF[2] & 0xff]; }
155
+ function getAF() {
156
+ switch (fOp) {
157
+ case F_NONE: return (flags >> 4) & 1;
158
+ case F_ADD: case F_ADC: case F_SUB: case F_SBB: case F_INC: case F_DEC: return (LF[0] ^ LF[1] ^ LF[2]) & 0x10 ? 1 : 0;
159
+ default: return 0;
160
+ }
161
+ }
162
+ function getFlags() {
163
+ if (fOp === F_NONE) return (flags & 0x3f7fd7) | 2;
164
+ return (flags & 0x3f772a) | 2 | getCF() | (getPF() << 2) | (getAF() << 4) | (getZF() << 6) | (getSF() << 7) | (getOF() << 11);
165
+ }
166
+ function setFlags(v) { flags = (v & 0x3f7fd7) | 2; fOp = F_NONE; }
167
+ function setCFbit(c) { flags = (getFlags() & ~1) | (c & 1); fOp = F_NONE; }
168
+ function setCFOF(c, o) { flags = (getFlags() & ~0x801) | (c & 1) | ((o & 1) << 11); fOp = F_NONE; }
169
+ function zfSet(z) { flags = (getFlags() & ~0x40) | ((z & 1) << 6); fOp = F_NONE; }
170
+
171
+ function cond(c) {
172
+ if (fOp === F_SUB) {
173
+ const a = LF[0], b = LF[1];
174
+ switch (c) {
175
+ case 2: return (a ^ FLIP) < (b ^ FLIP);
176
+ case 3: return (a ^ FLIP) >= (b ^ FLIP);
177
+ case 4: return a === b;
178
+ case 5: return a !== b;
179
+ case 6: return (a ^ FLIP) <= (b ^ FLIP);
180
+ case 7: return (a ^ FLIP) > (b ^ FLIP);
181
+ case 0xc: case 0xd: case 0xe: case 0xf: {
182
+ let x = a, y = b;
183
+ if (fSz !== S32) { const sh = fSz === S8 ? 24 : 16; x = (a << sh) >> sh; y = (b << sh) >> sh; }
184
+ return c === 0xc ? x < y : c === 0xd ? x >= y : c === 0xe ? x <= y : x > y;
185
+ }
186
+ }
187
+ } else if (fOp === F_LOGIC || fOp === F_INC || fOp === F_DEC) {
188
+ switch (c) {
189
+ case 4: return LF[2] === 0;
190
+ case 5: return LF[2] !== 0;
191
+ }
192
+ }
193
+ switch (c) {
194
+ case 0: return getOF() === 1;
195
+ case 1: return getOF() === 0;
196
+ case 2: return getCF() === 1;
197
+ case 3: return getCF() === 0;
198
+ case 4: return getZF() === 1;
199
+ case 5: return getZF() === 0;
200
+ case 6: return (getCF() | getZF()) === 1;
201
+ case 7: return (getCF() | getZF()) === 0;
202
+ case 8: return getSF() === 1;
203
+ case 9: return getSF() === 0;
204
+ case 0xa: return getPF() === 1;
205
+ case 0xb: return getPF() === 0;
206
+ case 0xc: return getSF() !== getOF();
207
+ case 0xd: return getSF() === getOF();
208
+ case 0xe: return getZF() === 1 || getSF() !== getOF();
209
+ default: return getZF() === 0 && getSF() === getOF();
210
+ }
211
+ }
212
+
213
+ // ------------------------------------------------------------------ arithmetic
214
+ // operands arrive as the size's convention (int32, or masked non-negative) and results leave so
215
+ function alu32(op, a, b) {
216
+ let r;
217
+ switch (op) {
218
+ case 0: r = (a + b) | 0; fOp = F_ADD; break;
219
+ case 1: r = a | b; fOp = F_LOGIC; break;
220
+ case 2: { const c = getCF(); r = (a + b + c) | 0; fCF = c; fOp = F_ADC; break; }
221
+ case 3: { const c = getCF(); r = (a - b - c) | 0; fCF = c; fOp = F_SBB; break; }
222
+ case 4: r = a & b; fOp = F_LOGIC; break;
223
+ case 5: case 7: r = (a - b) | 0; fOp = F_SUB; break;
224
+ default: r = a ^ b; fOp = F_LOGIC; break;
225
+ }
226
+ LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32;
227
+ return r;
228
+ }
229
+ function arith(op, a, b, s) {
230
+ if (s === S32) return alu32(op, a, b);
231
+ const m = MASK[s];
232
+ let r;
233
+ switch (op) {
234
+ case 0: r = (a + b) & m; fOp = F_ADD; break;
235
+ case 1: r = a | b; fOp = F_LOGIC; break;
236
+ case 2: { const c = getCF(); r = (a + b + c) & m; fCF = c; fOp = F_ADC; break; }
237
+ case 3: { const c = getCF(); r = (a - b - c) & m; fCF = c; fOp = F_SBB; break; }
238
+ case 4: r = a & b; fOp = F_LOGIC; break;
239
+ case 5: case 7: r = (a - b) & m; fOp = F_SUB; break;
240
+ default: r = a ^ b; fOp = F_LOGIC; break;
241
+ }
242
+ LF[0] = a; LF[1] = b; LF[2] = r; fSz = s;
243
+ return r;
244
+ }
245
+ function inc(v, s) { fCF = getCF(); const r = (v + 1) & MASK[s]; LF[0] = v; LF[1] = 1; LF[2] = r; fSz = s; fOp = F_INC; return r; }
246
+ function dec(v, s) { fCF = getCF(); const r = (v - 1) & MASK[s]; LF[0] = v; LF[1] = 1; LF[2] = r; fSz = s; fOp = F_DEC; return r; }
247
+ function logicFlags(r, s) { LF[2] = r; fSz = s; fOp = F_LOGIC; }
248
+ function eager(r, s, c, o) { LF[2] = r; fSz = s; fCF = c; fOF = o; fOp = F_EAGER; }
249
+
250
+ function shift(op, v, cnt, s) {
251
+ cnt &= 31;
252
+ if (cnt === 0) return v;
253
+ const bits = BITS[s], m = MASK[s], top = bits - 1;
254
+ let r;
255
+ switch (op) {
256
+ case 0: { // ROL
257
+ const c = cnt % bits;
258
+ r = c === 0 ? v : ((v << c) | (v >>> (bits - c))) & m;
259
+ setCFOF(r & 1, ((r >>> top) & 1) ^ (r & 1));
260
+ return r;
261
+ }
262
+ case 1: { // ROR
263
+ const c = cnt % bits;
264
+ r = c === 0 ? v : ((v >>> c) | (v << (bits - c))) & m;
265
+ setCFOF((r >>> top) & 1, ((r >>> top) ^ (r >>> (top - 1))) & 1);
266
+ return r;
267
+ }
268
+ case 2: { // RCL
269
+ let c = cnt % (bits + 1), cf = getCF();
270
+ r = v;
271
+ while (c-- > 0) { const n = (r >>> top) & 1; r = ((r << 1) | cf) & m; cf = n; }
272
+ setCFOF(cf, ((r >>> top) & 1) ^ cf);
273
+ return r;
274
+ }
275
+ case 3: { // RCR
276
+ let c = cnt % (bits + 1), cf = getCF();
277
+ r = v;
278
+ while (c-- > 0) { const n = r & 1; r = ((r >>> 1) | (cf << top)) & m; cf = n; }
279
+ setCFOF(cf, ((r >>> top) ^ (r >>> (top - 1))) & 1);
280
+ return r;
281
+ }
282
+ case 4: case 6: { // SHL
283
+ r = cnt >= bits ? 0 : (v << cnt) & m;
284
+ const cf = cnt > bits ? 0 : (v >>> (bits - cnt)) & 1;
285
+ eager(r, s, cf, ((r >>> top) & 1) ^ cf);
286
+ return r;
287
+ }
288
+ case 5: { // SHR
289
+ r = cnt >= bits ? 0 : (v >>> cnt) & m;
290
+ const cf = cnt > bits ? 0 : (v >>> (cnt - 1)) & 1;
291
+ eager(r, s, cf, (v >>> top) & 1);
292
+ return r;
293
+ }
294
+ default: { // SAR
295
+ const sv = s === S32 ? v : s === S16 ? (v << 16) >> 16 : (v << 24) >> 24;
296
+ r = (sv >> cnt) & m;
297
+ eager(r, s, (sv >> (Math.min(cnt, bits) - 1)) & 1, 0);
298
+ return r;
299
+ }
300
+ }
301
+ }
302
+
303
+ // 32x32 products without losing bits to doubles
304
+ let mulHi = 0;
305
+ function mulu32(a, b) {
306
+ const aL = a & 0xffff, aH = a >>> 16, bL = b & 0xffff, bH = b >>> 16;
307
+ const ll = aL * bL, lh = aL * bH, hl = aH * bL, hh = aH * bH;
308
+ const mid = (ll >>> 16) + (lh & 0xffff) + (hl & 0xffff);
309
+ mulHi = (hh + (lh >>> 16) + (hl >>> 16) + (mid >>> 16)) | 0;
310
+ return (mid << 16) | (ll & 0xffff);
311
+ }
312
+ function muls32(a, b) {
313
+ const p = a * b;
314
+ if (p < 9007199254740992 && p > -9007199254740992) {
315
+ mulHi = Math.floor(p / 4294967296) | 0;
316
+ return p | 0;
317
+ }
318
+ const lo = mulu32(a, b);
319
+ let hi = mulHi;
320
+ if (a < 0) hi = (hi - b) | 0;
321
+ if (b < 0) hi = (hi - a) | 0;
322
+ mulHi = hi;
323
+ return lo;
324
+ }
325
+
326
+ function grp3(sub, s) {
327
+ const v = loadE(s);
328
+ switch (sub) {
329
+ case 0: case 1: logicFlags(v & fImm(s), s); return;
330
+ case 2: storeE(s, ~v & MASK[s]); return;
331
+ case 3: storeE(s, arith(5, 0, v, s)); return;
332
+ case 4: { // MUL
333
+ if (s === S8) { const p = (R[EAX] & 0xff) * v; R[EAX] = (R[EAX] & ~0xffff) | p; const o = p > 0xff ? 1 : 0; eager(p & 0xff, s, o, o); }
334
+ else if (s === S16) { const p = (R[EAX] & 0xffff) * v; setR(S16, EAX, p); setR(S16, EDX, p >>> 16); const o = p > 0xffff ? 1 : 0; eager(p & 0xffff, s, o, o); }
335
+ else { const lo = mulu32(R[EAX], v); R[EAX] = lo; R[EDX] = mulHi; const o = mulHi !== 0 ? 1 : 0; eager(lo, s, o, o); }
336
+ return;
337
+ }
338
+ case 5: { // IMUL
339
+ if (s === S8) { const p = ((R[EAX] << 24) >> 24) * ((v << 24) >> 24); R[EAX] = (R[EAX] & ~0xffff) | (p & 0xffff); const o = p !== ((p << 24) >> 24) ? 1 : 0; eager(p & 0xff, s, o, o); }
340
+ else if (s === S16) { const p = ((R[EAX] << 16) >> 16) * ((v << 16) >> 16); setR(S16, EAX, p); setR(S16, EDX, p >> 16); const o = p !== ((p << 16) >> 16) ? 1 : 0; eager(p & 0xffff, s, o, o); }
341
+ else { const lo = muls32(R[EAX], v); R[EAX] = lo; R[EDX] = mulHi; const o = mulHi !== (lo >> 31) ? 1 : 0; eager(lo, s, o, o); }
342
+ return;
343
+ }
344
+ case 6: { // DIV
345
+ if (v === 0) throw new DivFault();
346
+ if (s === S8) { const n = R[EAX] & 0xffff, q = Math.floor(n / v); if (q > 0xff) throw new DivFault(); R[EAX] = (R[EAX] & ~0xffff) | ((n % v) << 8) | q; }
347
+ else if (s === S16) { const n = (R[EDX] & 0xffff) * 65536 + (R[EAX] & 0xffff), q = Math.floor(n / v); if (q > 0xffff) throw new DivFault(); setR(S16, EAX, q); setR(S16, EDX, n % v); }
348
+ else {
349
+ const hi = R[EDX] >>> 0, lo = R[EAX] >>> 0, d = v >>> 0;
350
+ if (hi >= d) throw new DivFault();
351
+ if (hi < 0x200000) { const n = hi * 4294967296 + lo, q = Math.floor(n / d); R[EAX] = q; R[EDX] = n - q * d; }
352
+ else { const n = (BigInt(hi) << 32n) | BigInt(lo), bd = BigInt(d); R[EAX] = Number(n / bd); R[EDX] = Number(n % bd); }
353
+ }
354
+ return;
355
+ }
356
+ default: { // IDIV
357
+ if (v === 0) throw new DivFault();
358
+ if (s === S8) {
359
+ const n = (R[EAX] << 16) >> 16, d = (v << 24) >> 24, q = Math.trunc(n / d);
360
+ if (q > 127 || q < -128) throw new DivFault();
361
+ R[EAX] = (R[EAX] & ~0xffff) | (((n - q * d) & 0xff) << 8) | (q & 0xff);
362
+ } else if (s === S16) {
363
+ const n = (R[EDX] << 16) | (R[EAX] & 0xffff), d = (v << 16) >> 16, q = Math.trunc(n / d);
364
+ if (q > 32767 || q < -32768) throw new DivFault();
365
+ setR(S16, EAX, q); setR(S16, EDX, n - q * d);
366
+ } else {
367
+ const hi = R[EDX], lo = R[EAX] >>> 0;
368
+ if (hi >= -0x200000 && hi < 0x200000) {
369
+ const n = hi * 4294967296 + lo, q = Math.trunc(n / v);
370
+ if (q > 2147483647 || q < -2147483648) throw new DivFault();
371
+ R[EAX] = q; R[EDX] = n - q * v;
372
+ } else {
373
+ const n = (BigInt(hi) << 32n) | BigInt(lo), bd = BigInt(v), q = n / bd;
374
+ if (q > 2147483647n || q < -2147483648n) throw new DivFault();
375
+ R[EAX] = Number(q); R[EDX] = Number(n % bd);
376
+ }
377
+ }
378
+ }
379
+ }
380
+ }
381
+
382
+ // ------------------------------------------------------------------ addressing
383
+ let mrm = 0;
384
+ let eaddr = 0;
385
+ function ea() {
386
+ const mod = mrm >> 6, rm = mrm & 7;
387
+ let a;
388
+ if (as32) {
389
+ let baseReg = -1;
390
+ if (rm === 4) {
391
+ const sib = M[eip++], base = sib & 7, idx = (sib >> 3) & 7;
392
+ a = idx === 4 ? 0 : R[idx] << (sib >> 6);
393
+ if (base === 5 && mod === 0) a = (a + f32()) | 0; else { a = (a + R[base]) | 0; baseReg = base; }
394
+ } else if (rm === 5 && mod === 0) a = f32();
395
+ else { a = R[rm]; baseReg = rm; }
396
+ if (mod === 1) a = (a + ((M[eip++] << 24) >> 24)) | 0; else if (mod === 2) a = (a + f32()) | 0;
397
+ if (segOv >= 0) return (a + segBase[segOv]) | 0;
398
+ // DS and SS with a base: one addition when they share it (DJGPP's always do), and the stack's
399
+ // for an [esp]/[ebp] base only when they differ (the C runtime's start-up, for a moment)
400
+ if (defBases) a = (a + (splitBases && (baseReg === ESP || baseReg === EBP) ? segBase[SS] : segBase[DS])) | 0;
401
+ return a;
402
+ }
403
+ switch (rm) {
404
+ case 0: a = R[EBX] + R[ESI]; break;
405
+ case 1: a = R[EBX] + R[EDI]; break;
406
+ case 2: a = R[EBP] + R[ESI]; break;
407
+ case 3: a = R[EBP] + R[EDI]; break;
408
+ case 4: a = R[ESI]; break;
409
+ case 5: a = R[EDI]; break;
410
+ case 6: a = mod === 0 ? f16() : R[EBP]; break;
411
+ default: a = R[EBX]; break;
412
+ }
413
+ if (mod === 1) a += f8s(); else if (mod === 2) a += f16();
414
+ a &= 0xffff;
415
+ if (segOv >= 0) return (a + segBase[segOv]) | 0;
416
+ if (defBases) a += (rm === 2 || rm === 3 || (rm === 6 && mod !== 0)) ? segBase[SS] : segBase[DS];
417
+ return a | 0;
418
+ }
419
+ function loadE(s) {
420
+ if (mrm >= 0xc0) return getR(s, mrm & 7);
421
+ eaddr = ea();
422
+ return s === S32 ? rd(eaddr) : s === S16 ? rw(eaddr) : rbx(eaddr);
423
+ }
424
+ // write back to the operand loadE just read (no second decode)
425
+ function storeE(s, v) {
426
+ if (mrm >= 0xc0) { setR(s, mrm & 7, v); return; }
427
+ if (s === S32) wd(eaddr, v); else if (s === S16) ww(eaddr, v); else wb(eaddr, v);
428
+ }
429
+ function writeE(s, v) {
430
+ if (mrm >= 0xc0) { setR(s, mrm & 7, v); return; }
431
+ eaddr = ea();
432
+ if (s === S32) wd(eaddr, v); else if (s === S16) ww(eaddr, v); else wb(eaddr, v);
433
+ }
434
+
435
+ // ------------------------------------------------------------------ stack
436
+ // the stack pointer moves by 2 or 4; in real mode it is SP, wrapping inside its 64 KB segment
437
+ function spMove(by) {
438
+ const sp = realMode ? (R[ESP] & ~0xffff) | ((R[ESP] + by) & 0xffff) : (R[ESP] + by) | 0;
439
+ R[ESP] = sp;
440
+ return realMode ? sp & 0xffff : sp;
441
+ }
442
+ function push(v) { if (realMode) { wd(spMove(-4) + ssb, v); return; } const sp = (R[ESP] - 4) | 0; R[ESP] = sp; wd(sp + ssb, v); }
443
+ function pop() { if (realMode) { const at = R[ESP] & 0xffff; spMove(4); return rd(at + ssb); } const sp = R[ESP]; R[ESP] = (sp + 4) | 0; return rd(sp + ssb); }
444
+ function pushS(s, v) { if (s === S16) { ww(spMove(-2) + ssb, v); } else push(v); }
445
+ function popS(s) { if (s === S16) { const at = realMode ? R[ESP] & 0xffff : R[ESP]; const v = rw(at + ssb); spMove(2); return v; } return pop(); }
446
+
447
+ // REAL MODE (operator, 2026-09-15: "I added wolf3d_dos - Add that one next"): Wolfenstein 3D is a
448
+ // 16-bit real-mode program, so a segment register holds a segment and its base is that times
449
+ // sixteen, code is 16-bit, the stack pointer is SP, and an interrupt goes through the vector table
450
+ // at 0:0 (the machine's `vector`) with a 16-bit frame.
451
+ let realMode = false;
452
+ function loadSeg(i, sel) {
453
+ seg[i] = sel & 0xffff;
454
+ segBase[i] = realMode ? (sel & 0xffff) << 4 : bus.selectorBase(sel & 0xffff);
455
+ defBases = segBase[DS] !== 0 || segBase[SS] !== 0;
456
+ csb = segBase[CS]; ssb = segBase[SS];
457
+ splitBases = segBase[DS] !== segBase[SS];
458
+ if (i === CS) cs16 = realMode || bus.selectorIs16?.(sel & 0xffff) === true;
459
+ }
460
+
461
+ // ------------------------------------------------------------------ interrupts
462
+ function interrupt(n, retEip) {
463
+ const v = bus.vector(n);
464
+ if (!v) throw new CpuFault(`no handler for INT ${n.toString(16)}`, opEip);
465
+ if (realMode) {
466
+ pushS(S16, getFlags() & 0xffff);
467
+ pushS(S16, seg[CS]);
468
+ pushS(S16, (retEip - csb) & 0xffff);
469
+ flags &= ~0x300;
470
+ loadSeg(CS, v.sel);
471
+ eip = (v.off + csb) >>> 0;
472
+ return;
473
+ }
474
+ push(getFlags());
475
+ push(seg[CS]);
476
+ push((retEip - csb) | 0);
477
+ flags &= ~0x300; // IF and TF off in the handler
478
+ loadSeg(CS, v.sel);
479
+ eip = (v.off + csb) >>> 0;
480
+ }
481
+ function softInt(n) {
482
+ if (bus.softInt(api, n)) return;
483
+ interrupt(n, eip);
484
+ }
485
+
486
+ // ------------------------------------------------------------------ string ops
487
+ function stringOp(op) {
488
+ if (!as32) { stringOp16(op); return; }
489
+ const s = op & 1 ? os : S8;
490
+ const n = s === S32 ? 4 : s === S16 ? 2 : 1;
491
+ const d = (flags & 0x400) ? -n : n;
492
+ const cntReg = as32 ? R[ECX] >>> 0 : R[ECX] & 0xffff;
493
+ const srcBase = segOv >= 0 ? segBase[segOv] : segBase[DS];
494
+ const esb = segBase[ES];
495
+ const setCount = (c) => { if (as32) R[ECX] = c; else R[ECX] = (R[ECX] & ~0xffff) | c; };
496
+ const readS = (a) => (s === S32 ? rd(a) : s === S16 ? rw(a) : rbx(a));
497
+ const writeS = (a, v) => { if (s === S32) wd(a, v); else if (s === S16) ww(a, v); else wb(a, v); };
498
+ // a block that touches neither the VGA window nor the end of memory can be moved in one call
499
+ const plain = (a, len) => a + len <= M.length && (a + len <= 0xa0000 || a >= 0xc0000);
500
+ switch (op) {
501
+ case 0xa4: case 0xa5: { // MOVS
502
+ if (rep) {
503
+ let c = cntReg, si = R[ESI], di = R[EDI];
504
+ const src = (si + srcBase) & AMASK, dst = (di + esb) & AMASK, len = c * n;
505
+ if (d > 0 && len > 0 && plain(dst, len) && src + len <= M.length && (dst <= src || dst >= src + len)) {
506
+ invalidate(dst, len);
507
+ M.copyWithin(dst, src, src + len);
508
+ R[ESI] = si + len; R[EDI] = di + len; setCount(0);
509
+ return;
510
+ }
511
+ while (c > 0) { writeS(di + esb, readS(si + srcBase)); si += d; di += d; c--; }
512
+ R[ESI] = si; R[EDI] = di; setCount(0);
513
+ } else {
514
+ writeS(R[EDI] + esb, readS(R[ESI] + srcBase)); R[ESI] += d; R[EDI] += d;
515
+ }
516
+ return;
517
+ }
518
+ case 0xaa: case 0xab: { // STOS
519
+ const v = getR(s, EAX);
520
+ if (rep) {
521
+ let c = cntReg, di = R[EDI];
522
+ const dst = (di + esb) & AMASK, len = c * n;
523
+ if (s === S8 && d > 0 && len > 0 && plain(dst, len)) {
524
+ invalidate(dst, len);
525
+ M.fill(v, dst, dst + len); R[EDI] = di + len; setCount(0); return;
526
+ }
527
+ while (c > 0) { writeS(di + esb, v); di += d; c--; }
528
+ R[EDI] = di; setCount(0);
529
+ } else { writeS(R[EDI] + esb, v); R[EDI] += d; }
530
+ return;
531
+ }
532
+ case 0xac: case 0xad: { // LODS
533
+ if (rep) { let c = cntReg; while (c > 0) { setR(s, EAX, readS(R[ESI] + srcBase)); R[ESI] += d; c--; } setCount(0); }
534
+ else { setR(s, EAX, readS(R[ESI] + srcBase)); R[ESI] += d; }
535
+ return;
536
+ }
537
+ case 0xa6: case 0xa7: { // CMPS
538
+ if (rep) {
539
+ let c = cntReg;
540
+ while (c > 0) {
541
+ arith(7, readS(R[ESI] + srcBase), readS(R[EDI] + esb), s);
542
+ R[ESI] += d; R[EDI] += d; c--;
543
+ if ((rep === 0xf3) !== (getZF() === 1)) break;
544
+ }
545
+ setCount(c);
546
+ } else { arith(7, readS(R[ESI] + srcBase), readS(R[EDI] + esb), s); R[ESI] += d; R[EDI] += d; }
547
+ return;
548
+ }
549
+ case 0xae: case 0xaf: { // SCAS
550
+ const v = getR(s, EAX);
551
+ if (rep) {
552
+ let c = cntReg;
553
+ while (c > 0) {
554
+ arith(7, v, readS(R[EDI] + esb), s);
555
+ R[EDI] += d; c--;
556
+ if ((rep === 0xf3) !== (getZF() === 1)) break;
557
+ }
558
+ setCount(c);
559
+ } else { arith(7, v, readS(R[EDI] + esb), s); R[EDI] += d; }
560
+ return;
561
+ }
562
+ case 0x6c: case 0x6d: { // INS
563
+ let c = rep ? cntReg : 1;
564
+ while (c > 0) { writeS(R[EDI] + esb, bus.portIn(R[EDX] & 0xffff, s)); R[EDI] += d; c--; }
565
+ if (rep) setCount(0);
566
+ return;
567
+ }
568
+ default: { // OUTS
569
+ let c = rep ? cntReg : 1;
570
+ while (c > 0) { bus.portOut(R[EDX] & 0xffff, s, readS(R[ESI] + srcBase)); R[ESI] += d; c--; }
571
+ if (rep) setCount(0);
572
+ }
573
+ }
574
+ }
575
+
576
+ // the string instructions with 16-bit addressing: SI, DI and CX are 16-bit and wrap, as a real-mode
577
+ // program's pointers do
578
+ function stringOp16(op) {
579
+ const s = op & 1 ? os : S8;
580
+ const n = s === S32 ? 4 : s === S16 ? 2 : 1;
581
+ const d = (flags & 0x400) ? -n : n;
582
+ const srcBase = segOv >= 0 ? segBase[segOv] : segBase[DS];
583
+ const esb = segBase[ES];
584
+ const readS = (a) => (s === S32 ? rd(a) : s === S16 ? rw(a) : rbx(a));
585
+ const writeS = (a, v) => { if (s === S32) wd(a, v); else if (s === S16) ww(a, v); else wb(a, v); };
586
+ let si = R[ESI] & 0xffff, di = R[EDI] & 0xffff, c = rep ? R[ECX] & 0xffff : 1;
587
+ const done = () => {
588
+ R[ESI] = (R[ESI] & ~0xffff) | (si & 0xffff); R[EDI] = (R[EDI] & ~0xffff) | (di & 0xffff);
589
+ if (rep) R[ECX] = (R[ECX] & ~0xffff) | (c & 0xffff);
590
+ };
591
+ switch (op) {
592
+ case 0xa4: case 0xa5: while (c > 0) { writeS(di + esb, readS(si + srcBase)); si = (si + d) & 0xffff; di = (di + d) & 0xffff; c--; } break;
593
+ case 0xaa: case 0xab: { const v = getR(s, EAX); while (c > 0) { writeS(di + esb, v); di = (di + d) & 0xffff; c--; } break; }
594
+ case 0xac: case 0xad: while (c > 0) { setR(s, EAX, readS(si + srcBase)); si = (si + d) & 0xffff; c--; } break;
595
+ case 0xa6: case 0xa7:
596
+ while (c > 0) {
597
+ arith(7, readS(si + srcBase), readS(di + esb), s);
598
+ si = (si + d) & 0xffff; di = (di + d) & 0xffff; c--;
599
+ if (rep && (rep === 0xf3) !== (getZF() === 1)) break;
600
+ }
601
+ break;
602
+ case 0xae: case 0xaf: {
603
+ const v = getR(s, EAX);
604
+ while (c > 0) {
605
+ arith(7, v, readS(di + esb), s);
606
+ di = (di + d) & 0xffff; c--;
607
+ if (rep && (rep === 0xf3) !== (getZF() === 1)) break;
608
+ }
609
+ break;
610
+ }
611
+ case 0x6c: case 0x6d: while (c > 0) { writeS(di + esb, bus.portIn(R[EDX] & 0xffff, s)); di = (di + d) & 0xffff; c--; } break;
612
+ default: while (c > 0) { bus.portOut(R[EDX] & 0xffff, s, readS(si + srcBase)); si = (si + d) & 0xffff; c--; } break;
613
+ }
614
+ if (!rep) c = 0;
615
+ done();
616
+ }
617
+
618
+ // ------------------------------------------------------------------ the FPU
619
+ // An x87, in doubles. DOOM's C runtime only probes for one; Quake does its geometry, its lighting
620
+ // and a divide every sixteen pixels of every span in floating point, so this sits on the hot path
621
+ // too: the stack is a Float64Array indexed from `ftop`, and memory operands are converted through
622
+ // typed-array views of one scratch buffer rather than a DataView. Precision control is not modelled
623
+ // (every result is a double); rounding control is, because fist/fistp depend on it.
624
+ const ST = new Float64Array(8);
625
+ let ftop = 0, fcw = 0x37f, fsw = 0, ftag = 0xffff;
626
+ const scratch = new ArrayBuffer(8);
627
+ const sI32 = new Int32Array(scratch), sF32 = new Float32Array(scratch, 0, 1), sF64 = new Float64Array(scratch);
628
+ const K_F32 = 0, K_F64 = 1, K_I16 = 2, K_I32 = 3, K_I64 = 4, K_F80 = 5;
629
+ function fpush(v) { ftop = (ftop - 1) & 7; ST[ftop] = v; ftag &= ~(3 << (ftop * 2)); }
630
+ function fpop() { const v = ST[ftop]; ftag |= 3 << (ftop * 2); ftop = (ftop + 1) & 7; return v; }
631
+ function fround(v) {
632
+ switch ((fcw >> 10) & 3) {
633
+ case 0: {
634
+ const r = Math.round(v);
635
+ return r - v === 0.5 && (r & 1) !== 0 ? r - 1 : r; // ties to even: Math.round sends .5 up
636
+ }
637
+ case 1: return Math.floor(v);
638
+ case 2: return Math.ceil(v);
639
+ default: return Math.trunc(v);
640
+ }
641
+ }
642
+ function fcompare(a, b) {
643
+ let c;
644
+ if (a > b) c = 0; else if (a < b) c = 0x100; else if (a === b) c = 0x4000; else c = 0x4500;
645
+ fsw = (fsw & ~0x4700) | c;
646
+ }
647
+ const fswWord = () => (fsw & ~0x3800) | (ftop << 11);
648
+ function readReal(a, kind) {
649
+ switch (kind) {
650
+ case K_F32: sI32[0] = rd(a); return sF32[0];
651
+ case K_F64: sI32[0] = rd(a); sI32[1] = rd(a + 4); return sF64[0];
652
+ case K_I16: return (rw(a) << 16) >> 16;
653
+ case K_I32: return rd(a);
654
+ case K_I64: return rd(a + 4) * 4294967296 + (rd(a) >>> 0);
655
+ default: {
656
+ const lo = rd(a) >>> 0, hi = rd(a + 4) >>> 0, se = rw(a + 8);
657
+ const e = se & 0x7fff, sgn = se & 0x8000 ? -1 : 1;
658
+ if (e === 0 && lo === 0 && hi === 0) return sgn * 0;
659
+ if (e === 0x7fff) return (hi & 0x7fffffff) || lo ? NaN : sgn * Infinity;
660
+ return sgn * (hi * 4294967296 + lo) * Math.pow(2, e - 16383 - 63);
661
+ }
662
+ }
663
+ }
664
+ function writeReal(a, kind, v) {
665
+ switch (kind) {
666
+ case K_F32: sF32[0] = v; wd(a, sI32[0]); return;
667
+ case K_F64: sF64[0] = v; wd(a, sI32[0]); wd(a + 4, sI32[1]); return;
668
+ case K_I16: { const r = fround(v); ww(a, r >= -32768 && r <= 32767 ? r : 0x8000); return; }
669
+ case K_I32: { const r = fround(v); wd(a, r >= -2147483648 && r <= 2147483647 ? r : -0x80000000); return; }
670
+ case K_I64: {
671
+ const r = fround(v);
672
+ const b = Number.isFinite(r) && Math.abs(r) < 9.2e18 ? BigInt.asUintN(64, BigInt(r)) : 1n << 63n;
673
+ wd(a, Number(b & 0xffffffffn)); wd(a + 4, Number(b >> 32n));
674
+ return;
675
+ }
676
+ default: {
677
+ if (v === 0 || !Number.isFinite(v)) {
678
+ wd(a, 0); wd(a + 4, Number.isNaN(v) ? 0xc0000000 : v === 0 ? 0 : 0x80000000);
679
+ ww(a + 8, (v < 0 || Object.is(v, -0) ? 0x8000 : 0) | (v === 0 ? 0 : 0x7fff));
680
+ return;
681
+ }
682
+ const sgn = v < 0 ? 0x8000 : 0, x = Math.abs(v);
683
+ let e = Math.floor(Math.log2(x));
684
+ if (Math.pow(2, e) > x) e--;
685
+ if (Math.pow(2, e + 1) <= x) e++;
686
+ const b = BigInt(Math.round(x / Math.pow(2, e - 63)));
687
+ wd(a, Number(b & 0xffffffffn)); wd(a + 4, Number((b >> 32n) & 0xffffffffn)); ww(a + 8, sgn | (e + 16383));
688
+ }
689
+ }
690
+ }
691
+ // the D8/DC/DA/DE row's operation on (a, b): add, mul, -, -, sub (a-b), subr (b-a), div, divr
692
+ function farith(sub, a, b) {
693
+ switch (sub) {
694
+ case 0: return a + b;
695
+ case 1: return a * b;
696
+ case 4: return a - b;
697
+ case 5: return b - a;
698
+ case 6: return a / b;
699
+ default: return b / a;
700
+ }
701
+ }
702
+ const ROW_KIND = { 0xd8: K_F32, 0xdc: K_F64, 0xda: K_I32, 0xde: K_I16 };
703
+ function fpuOp(op) {
704
+ mrm = M[eip++];
705
+ fpuCore(op, mrm, mrm < 0xc0 ? ea() : 0);
706
+ }
707
+ // the x87 instruction `op` (D8-DF) with ModRM `m`; `a` is its operand's address for a memory form
708
+ function fpuCore(op, m, a) {
709
+ mrm = m;
710
+ const reg = (mrm >> 3) & 7;
711
+ if (mrm < 0xc0) {
712
+ switch (op) {
713
+ case 0xd8: case 0xdc: case 0xda: case 0xde: {
714
+ const v = readReal(a, ROW_KIND[op]);
715
+ if (reg === 2 || reg === 3) { fcompare(ST[ftop], v); if (reg === 3) fpop(); } else ST[ftop] = farith(reg, ST[ftop], v);
716
+ return;
717
+ }
718
+ case 0xd9:
719
+ switch (reg) {
720
+ case 0: fpush(readReal(a, K_F32)); return;
721
+ case 2: writeReal(a, K_F32, ST[ftop]); return;
722
+ case 3: writeReal(a, K_F32, fpop()); return;
723
+ case 4: fcw = rw(a) | 0x40; fsw = rw(a + 4); ftop = (fsw >> 11) & 7; ftag = rw(a + 8); return; // FLDENV
724
+ case 5: fcw = rw(a); return; // FLDCW
725
+ case 6: for (let i = 0; i < 28; i++) wb(a + i, 0); ww(a, fcw); ww(a + 4, fswWord()); ww(a + 8, ftag); return;
726
+ case 7: ww(a, fcw); return; // FNSTCW
727
+ }
728
+ break;
729
+ case 0xdb:
730
+ switch (reg) {
731
+ case 0: fpush(readReal(a, K_I32)); return;
732
+ case 1: { const v = fpop(); wd(a, Number.isFinite(v) && Math.abs(v) < 2147483648 ? Math.trunc(v) : -0x80000000); return; }
733
+ case 2: writeReal(a, K_I32, ST[ftop]); return;
734
+ case 3: writeReal(a, K_I32, fpop()); return;
735
+ case 5: fpush(readReal(a, K_F80)); return;
736
+ case 7: writeReal(a, K_F80, fpop()); return;
737
+ }
738
+ break;
739
+ case 0xdd:
740
+ switch (reg) {
741
+ case 0: fpush(readReal(a, K_F64)); return;
742
+ case 2: writeReal(a, K_F64, ST[ftop]); return;
743
+ case 3: writeReal(a, K_F64, fpop()); return;
744
+ case 4: // FRSTOR
745
+ fcw = rw(a); fsw = rw(a + 4); ftop = (fsw >> 11) & 7; ftag = rw(a + 8);
746
+ for (let i = 0; i < 8; i++) ST[(ftop + i) & 7] = readReal(a + 28 + i * 10, K_F80);
747
+ return;
748
+ case 6: // FNSAVE
749
+ for (let i = 0; i < 28; i++) wb(a + i, 0);
750
+ ww(a, fcw); ww(a + 4, fswWord()); ww(a + 8, ftag);
751
+ for (let i = 0; i < 8; i++) writeReal(a + 28 + i * 10, K_F80, ST[(ftop + i) & 7]);
752
+ fcw = 0x37f; fsw = 0; ftop = 0; ftag = 0xffff;
753
+ return;
754
+ case 7: ww(a, fswWord()); return;
755
+ }
756
+ break;
757
+ case 0xdf:
758
+ switch (reg) {
759
+ case 0: fpush(readReal(a, K_I16)); return;
760
+ case 2: writeReal(a, K_I16, ST[ftop]); return;
761
+ case 3: writeReal(a, K_I16, fpop()); return;
762
+ case 5: fpush(readReal(a, K_I64)); return;
763
+ case 7: writeReal(a, K_I64, fpop()); return;
764
+ }
765
+ break;
766
+ }
767
+ throw new CpuFault(`FPU ${op.toString(16)} /${reg}`, opEip);
768
+ }
769
+ const i = (ftop + (mrm & 7)) & 7;
770
+ switch (op) {
771
+ case 0xd8:
772
+ if (reg === 2 || reg === 3) { fcompare(ST[ftop], ST[i]); if (reg === 3) fpop(); } else ST[ftop] = farith(reg, ST[ftop], ST[i]);
773
+ return;
774
+ case 0xdc:
775
+ if (reg === 2 || reg === 3) { fcompare(ST[ftop], ST[i]); if (reg === 3) fpop(); return; }
776
+ ST[i] = farith(reg === 4 ? 5 : reg === 5 ? 4 : reg === 6 ? 7 : reg === 7 ? 6 : reg, ST[i], ST[ftop]);
777
+ return;
778
+ case 0xde:
779
+ if (mrm === 0xd9) { fcompare(ST[ftop], ST[(ftop + 1) & 7]); fpop(); fpop(); return; } // FCOMPP
780
+ if (reg === 2 || reg === 3) { fcompare(ST[ftop], ST[i]); fpop(); return; }
781
+ ST[i] = farith(reg === 4 ? 5 : reg === 5 ? 4 : reg === 6 ? 7 : reg === 7 ? 6 : reg, ST[i], ST[ftop]);
782
+ fpop();
783
+ return;
784
+ case 0xd9:
785
+ if (reg === 0) { fpush(ST[i]); return; } // FLD ST(i)
786
+ if (reg === 1) { const t = ST[ftop]; ST[ftop] = ST[i]; ST[i] = t; return; } // FXCH
787
+ switch (mrm) {
788
+ case 0xd0: return;
789
+ case 0xe0: ST[ftop] = -ST[ftop]; return;
790
+ case 0xe1: ST[ftop] = Math.abs(ST[ftop]); return;
791
+ case 0xe4: fcompare(ST[ftop], 0); return;
792
+ case 0xe5: {
793
+ const empty = ((ftag >> (ftop * 2)) & 3) === 3, v = ST[ftop];
794
+ const sign = v < 0 || Object.is(v, -0) ? 0x200 : 0;
795
+ const cls = empty ? 0x4100 : Number.isNaN(v) ? 0x100 : !Number.isFinite(v) ? 0x500 : v === 0 ? 0x4000 : 0x400;
796
+ fsw = (fsw & ~0x4700) | cls | sign;
797
+ return;
798
+ }
799
+ case 0xe8: fpush(1); return;
800
+ case 0xe9: fpush(Math.log2(10)); return;
801
+ case 0xea: fpush(Math.log2(Math.E)); return;
802
+ case 0xeb: fpush(Math.PI); return;
803
+ case 0xec: fpush(Math.log10(2)); return;
804
+ case 0xed: fpush(Math.LN2); return;
805
+ case 0xee: fpush(0); return;
806
+ case 0xf0: ST[ftop] = Math.pow(2, ST[ftop]) - 1; return;
807
+ case 0xf1: { const x = fpop(); ST[ftop] = ST[ftop] * Math.log2(x); return; }
808
+ case 0xf2: ST[ftop] = Math.tan(ST[ftop]); fpush(1); return;
809
+ case 0xf3: { const x = fpop(); ST[ftop] = Math.atan2(ST[ftop], x); return; }
810
+ case 0xf4: { const x = ST[ftop]; const e = x === 0 ? 0 : Math.floor(Math.log2(Math.abs(x))); ST[ftop] = e; fpush(x / Math.pow(2, e)); return; }
811
+ case 0xf5: case 0xf8: {
812
+ const a = ST[ftop], b = ST[(ftop + 1) & 7];
813
+ const q = mrm === 0xf8 ? Math.trunc(a / b) : Math.round(a / b);
814
+ ST[ftop] = a - q * b;
815
+ fsw = (fsw & ~0x4700) | ((q & 1) ? 0x200 : 0) | ((q & 2) ? 0x4000 : 0) | ((q & 4) ? 0x100 : 0);
816
+ return;
817
+ }
818
+ case 0xf6: ftop = (ftop - 1) & 7; return;
819
+ case 0xf7: ftop = (ftop + 1) & 7; return;
820
+ case 0xfa: ST[ftop] = Math.sqrt(ST[ftop]); return;
821
+ case 0xfb: { const x = ST[ftop]; ST[ftop] = Math.sin(x); fpush(Math.cos(x)); return; }
822
+ case 0xfc: ST[ftop] = fround(ST[ftop]); return;
823
+ case 0xfd: ST[ftop] = ST[ftop] * Math.pow(2, Math.trunc(ST[(ftop + 1) & 7])); return;
824
+ case 0xfe: ST[ftop] = Math.sin(ST[ftop]); return;
825
+ case 0xff: ST[ftop] = Math.cos(ST[ftop]); return;
826
+ }
827
+ break;
828
+ case 0xdd:
829
+ if (reg === 0) { ftag |= 3 << (i * 2); return; } // FFREE
830
+ if (reg === 2) { ST[i] = ST[ftop]; return; } // FST ST(i)
831
+ if (reg === 3) { ST[i] = ST[ftop]; fpop(); return; } // FSTP ST(i)
832
+ if (reg === 4 || reg === 5) { fcompare(ST[ftop], ST[i]); if (reg === 5) fpop(); return; } // FUCOM(P)
833
+ break;
834
+ case 0xdb:
835
+ if (mrm === 0xe2) { fsw &= 0x7f00; return; }
836
+ if (mrm === 0xe3) { fcw = 0x37f; fsw = 0; ftop = 0; ftag = 0xffff; return; }
837
+ if (mrm === 0xe0 || mrm === 0xe1 || mrm === 0xe4) return;
838
+ break;
839
+ case 0xdf:
840
+ if (mrm === 0xe0) { setR(S16, EAX, fswWord()); return; }
841
+ if (reg === 0) { ftag |= 3 << (i * 2); fpop(); return; }
842
+ break;
843
+ case 0xda:
844
+ if (mrm === 0xe9) { fcompare(ST[ftop], ST[(ftop + 1) & 7]); fpop(); fpop(); return; }
845
+ break;
846
+ }
847
+ throw new CpuFault(`FPU ${op.toString(16)} ${mrm.toString(16)}`, opEip);
848
+ }
849
+
850
+ // ------------------------------------------------------------------ one instruction
851
+ function step() {
852
+ opEip = eip;
853
+ let op = M[eip++];
854
+ os = S32; as32 = true; segOv = -1; rep = 0;
855
+ if (cs16) { os = S16; as32 = false; }
856
+ if (PREFIX[op] === 1) {
857
+ for (;;) {
858
+ switch (op) {
859
+ case 0x66: os = cs16 ? S32 : S16; break;
860
+ case 0x67: as32 = cs16; break;
861
+ case 0x26: segOv = ES; break;
862
+ case 0x2e: segOv = CS; break;
863
+ case 0x36: segOv = SS; break;
864
+ case 0x3e: segOv = DS; break;
865
+ case 0x64: segOv = FS; break;
866
+ case 0x65: segOv = GS; break;
867
+ case 0xf2: case 0xf3: rep = op; break;
868
+ }
869
+ op = M[eip++];
870
+ if (PREFIX[op] === 0) break;
871
+ }
872
+ // an override naming a base-0 segment is no override: forget it so ea() skips the addition,
873
+ // unless DS or SS has a base, when the override is what stops the default from applying
874
+ if (segOv >= 0 && segBase[segOv] === 0 && !defBases) segOv = -1;
875
+ }
876
+
877
+ switch (op) {
878
+ // ---- the ALU rows: add, or, adc, sbb, and, sub, xor, cmp
879
+ case 0x00: case 0x08: case 0x10: case 0x18: case 0x20: case 0x28: case 0x30: case 0x38: {
880
+ mrm = M[eip++]; const sub = op >> 3;
881
+ const r = arith(sub, loadE(S8), get8((mrm >> 3) & 7), S8);
882
+ if (sub !== 7) storeE(S8, r);
883
+ return;
884
+ }
885
+ case 0x01: case 0x09: case 0x11: case 0x19: case 0x21: case 0x29: case 0x31: case 0x39: {
886
+ mrm = M[eip++]; const sub = op >> 3;
887
+ if (os === S32) {
888
+ if (mrm >= 0xc0) { const r = alu32(sub, R[mrm & 7], R[(mrm >> 3) & 7]); if (sub !== 7) R[mrm & 7] = r; return; }
889
+ eaddr = ea();
890
+ const r = alu32(sub, rd(eaddr), R[(mrm >> 3) & 7]);
891
+ if (sub !== 7) wd(eaddr, r);
892
+ return;
893
+ }
894
+ const r = arith(sub, loadE(os), getR(os, (mrm >> 3) & 7), os);
895
+ if (sub !== 7) storeE(os, r);
896
+ return;
897
+ }
898
+ case 0x02: case 0x0a: case 0x12: case 0x1a: case 0x22: case 0x2a: case 0x32: case 0x3a: {
899
+ mrm = M[eip++]; const sub = op >> 3;
900
+ const b = loadE(S8);
901
+ const r = arith(sub, get8((mrm >> 3) & 7), b, S8);
902
+ if (sub !== 7) set8((mrm >> 3) & 7, r);
903
+ return;
904
+ }
905
+ case 0x03: case 0x0b: case 0x13: case 0x1b: case 0x23: case 0x2b: case 0x33: case 0x3b: {
906
+ mrm = M[eip++]; const sub = op >> 3, g = (mrm >> 3) & 7;
907
+ if (os === S32) {
908
+ const b = mrm >= 0xc0 ? R[mrm & 7] : rd(ea());
909
+ const r = alu32(sub, R[g], b);
910
+ if (sub !== 7) R[g] = r;
911
+ return;
912
+ }
913
+ const b = loadE(os);
914
+ const r = arith(sub, getR(os, g), b, os);
915
+ if (sub !== 7) setR(os, g, r);
916
+ return;
917
+ }
918
+ case 0x04: case 0x0c: case 0x14: case 0x1c: case 0x24: case 0x2c: case 0x34: case 0x3c: {
919
+ const sub = op >> 3;
920
+ const r = arith(sub, R[EAX] & 0xff, M[eip++], S8);
921
+ if (sub !== 7) set8(0, r);
922
+ return;
923
+ }
924
+ case 0x05: case 0x0d: case 0x15: case 0x1d: case 0x25: case 0x2d: case 0x35: case 0x3d: {
925
+ const sub = op >> 3;
926
+ const b = fImm(os);
927
+ const r = arith(sub, getR(os, EAX), b, os);
928
+ if (sub !== 7) setR(os, EAX, r);
929
+ return;
930
+ }
931
+
932
+ case 0x06: pushS(os, seg[ES]); return;
933
+ case 0x07: loadSeg(ES, popS(os)); return;
934
+ case 0x0e: pushS(os, seg[CS]); return;
935
+ case 0x16: pushS(os, seg[SS]); return;
936
+ case 0x17: loadSeg(SS, popS(os)); return;
937
+ case 0x1e: pushS(os, seg[DS]); return;
938
+ case 0x1f: loadSeg(DS, popS(os)); return;
939
+ case 0x0f: twoByte(); return;
940
+ case 0x27: case 0x2f: { // DAA / DAS
941
+ const old = R[EAX] & 0xff, cf = getCF(), af = getAF();
942
+ let al = old, ncf = 0, naf = 0;
943
+ if ((al & 0xf) > 9 || af) { al = op === 0x27 ? al + 6 : al - 6; naf = 1; }
944
+ if (old > 0x99 || cf) { al = op === 0x27 ? al + 0x60 : al - 0x60; ncf = 1; }
945
+ set8(0, al);
946
+ const z = (al & 0xff) === 0 ? 1 : 0, sgn = (al & 0x80) ? 1 : 0;
947
+ flags = (getFlags() & ~0x8d5) | ncf | (PARITY[al & 0xff] << 2) | (naf << 4) | (z << 6) | (sgn << 7);
948
+ fOp = F_NONE;
949
+ return;
950
+ }
951
+ case 0x37: case 0x3f: { // AAA / AAS
952
+ if ((R[EAX] & 0xf) > 9 || getAF()) {
953
+ setR(S16, EAX, (op === 0x37 ? R[EAX] + 0x106 : R[EAX] - 0x106) & 0xff0f);
954
+ flags = getFlags() | 0x11; fOp = F_NONE;
955
+ } else { setR(S16, EAX, R[EAX] & 0xff0f); flags = getFlags() & ~0x11; fOp = F_NONE; }
956
+ return;
957
+ }
958
+ case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
959
+ if (os === S32) R[op & 7] = inc(R[op & 7], S32); else setR(os, op & 7, inc(R[op & 7] & 0xffff, S16));
960
+ return;
961
+ case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
962
+ if (os === S32) R[op & 7] = dec(R[op & 7], S32); else setR(os, op & 7, dec(R[op & 7] & 0xffff, S16));
963
+ return;
964
+ case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
965
+ if (os === S32) push(R[op & 7]); else pushS(os, R[op & 7] & 0xffff);
966
+ return;
967
+ case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f:
968
+ if (os === S32) R[op & 7] = pop(); else setR(S16, op & 7, popS(S16));
969
+ return;
970
+ case 0x60: { const sp = getR(os, ESP); for (let i = 0; i < 8; i++) pushS(os, i === ESP ? sp : getR(os, i)); return; }
971
+ case 0x61: for (let i = 7; i >= 0; i--) { const v = popS(os); if (i !== ESP) setR(os, i, v); } return;
972
+ case 0x62: mrm = M[eip++]; ea(); return; // BOUND: never out of bounds here
973
+ case 0x68: pushS(os, fImm(os)); return;
974
+ case 0x6a: pushS(os, f8s() & MASK[os]); return;
975
+ case 0x69: case 0x6b: {
976
+ mrm = M[eip++];
977
+ const v = loadE(os);
978
+ const imm = op === 0x6b ? f8s() : os === S32 ? f32() : (f16() << 16) >> 16;
979
+ imul2(os, (mrm >> 3) & 7, v, imm);
980
+ return;
981
+ }
982
+ case 0x6c: case 0x6d: case 0x6e: case 0x6f: stringOp(op); return;
983
+ case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
984
+ case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: {
985
+ const d = (M[eip++] << 24) >> 24;
986
+ if (cond(op & 15)) eip += d;
987
+ return;
988
+ }
989
+ case 0x80: case 0x82: {
990
+ mrm = M[eip++]; const sub = (mrm >> 3) & 7;
991
+ const a = loadE(S8);
992
+ const r = arith(sub, a, M[eip++], S8);
993
+ if (sub !== 7) storeE(S8, r);
994
+ return;
995
+ }
996
+ case 0x81: case 0x83: {
997
+ mrm = M[eip++]; const sub = (mrm >> 3) & 7;
998
+ if (os === S32) {
999
+ if (mrm >= 0xc0) {
1000
+ const b = op === 0x83 ? (M[eip++] << 24) >> 24 : f32();
1001
+ const r = alu32(sub, R[mrm & 7], b);
1002
+ if (sub !== 7) R[mrm & 7] = r;
1003
+ return;
1004
+ }
1005
+ eaddr = ea();
1006
+ const b = op === 0x83 ? (M[eip++] << 24) >> 24 : f32();
1007
+ const r = alu32(sub, rd(eaddr), b);
1008
+ if (sub !== 7) wd(eaddr, r);
1009
+ return;
1010
+ }
1011
+ const a = loadE(os);
1012
+ const b = op === 0x83 ? f8s() & MASK[os] : fImm(os);
1013
+ const r = arith(sub, a, b, os);
1014
+ if (sub !== 7) storeE(os, r);
1015
+ return;
1016
+ }
1017
+ case 0x84: { mrm = M[eip++]; const a = loadE(S8); logicFlags(a & get8((mrm >> 3) & 7), S8); return; }
1018
+ case 0x85: {
1019
+ mrm = M[eip++];
1020
+ if (os === S32) { const a = mrm >= 0xc0 ? R[mrm & 7] : rd(ea()); logicFlags(a & R[(mrm >> 3) & 7], S32); return; }
1021
+ const a = loadE(os); logicFlags(a & getR(os, (mrm >> 3) & 7), os);
1022
+ return;
1023
+ }
1024
+ case 0x86: case 0x87: {
1025
+ const s = op & 1 ? os : S8;
1026
+ mrm = M[eip++];
1027
+ const a = loadE(s), g = (mrm >> 3) & 7, b = getR(s, g);
1028
+ storeE(s, b); setR(s, g, a);
1029
+ return;
1030
+ }
1031
+ case 0x88: {
1032
+ mrm = M[eip++];
1033
+ const v = get8((mrm >> 3) & 7);
1034
+ if (mrm >= 0xc0) set8(mrm & 7, v); else wb(ea(), v);
1035
+ return;
1036
+ }
1037
+ case 0x89: {
1038
+ mrm = M[eip++];
1039
+ if (os === S32) { if (mrm >= 0xc0) R[mrm & 7] = R[(mrm >> 3) & 7]; else wd(ea(), R[(mrm >> 3) & 7]); return; }
1040
+ writeE(os, getR(os, (mrm >> 3) & 7));
1041
+ return;
1042
+ }
1043
+ case 0x8a: mrm = M[eip++]; set8((mrm >> 3) & 7, loadE(S8)); return;
1044
+ case 0x8b: {
1045
+ mrm = M[eip++];
1046
+ if (os === S32) { R[(mrm >> 3) & 7] = mrm >= 0xc0 ? R[mrm & 7] : rd(ea()); return; }
1047
+ setR(os, (mrm >> 3) & 7, loadE(os));
1048
+ return;
1049
+ }
1050
+ case 0x8c: mrm = M[eip++]; if (mrm >= 0xc0) setR(os, mrm & 7, seg[(mrm >> 3) & 7]); else writeE(S16, seg[(mrm >> 3) & 7]); return;
1051
+ case 0x8d: {
1052
+ mrm = M[eip++];
1053
+ const so = segOv, db = defBases;
1054
+ segOv = -1; defBases = false;
1055
+ const a = ea();
1056
+ segOv = so; defBases = db;
1057
+ setR(os, (mrm >> 3) & 7, a);
1058
+ return;
1059
+ }
1060
+ case 0x8e: mrm = M[eip++]; loadSeg((mrm >> 3) & 7, loadE(S16)); return;
1061
+ case 0x8f: { mrm = M[eip++]; const v = popS(os); writeE(os, v); return; }
1062
+ case 0x90: return;
1063
+ case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: {
1064
+ const a = getR(os, EAX), b = getR(os, op & 7); setR(os, EAX, b); setR(os, op & 7, a);
1065
+ return;
1066
+ }
1067
+ case 0x98: if (os === S32) R[EAX] = (R[EAX] << 16) >> 16; else setR(S16, EAX, (R[EAX] << 24) >> 24); return;
1068
+ case 0x99: if (os === S32) R[EDX] = R[EAX] >> 31; else setR(S16, EDX, (R[EAX] << 16) >> 31); return;
1069
+ case 0x9a: { const off = os === S32 ? f32() : f16(); const sel = f16(); pushS(os, seg[CS]); pushS(os, eip - csb); loadSeg(CS, sel); eip = (off + csb) >>> 0; return; }
1070
+ case 0x9b: return;
1071
+ case 0x9c: pushS(os, getFlags() & 0xfcffff); return;
1072
+ case 0x9d: { const v = popS(os); setFlags(os === S16 ? (getFlags() & ~0xffff) | v : v); return; }
1073
+ case 0x9e: setFlags((getFlags() & ~0xd5) | ((R[EAX] >> 8) & 0xd5)); return;
1074
+ case 0x9f: set8(4, getFlags() & 0xff); return;
1075
+ case 0xa0: case 0xa1: case 0xa2: case 0xa3: {
1076
+ const s = op & 1 ? os : S8;
1077
+ let a = as32 ? f32() : f16();
1078
+ a = (a + (segOv >= 0 ? segBase[segOv] : segBase[DS])) | 0;
1079
+ if (op < 0xa2) { if (s === S32) R[EAX] = rd(a); else setR(s, EAX, s === S16 ? rw(a) : rbx(a)); }
1080
+ else { const v = getR(s, EAX); if (s === S32) wd(a, v); else if (s === S16) ww(a, v); else wb(a, v); }
1081
+ return;
1082
+ }
1083
+ case 0xa4: case 0xa5: case 0xa6: case 0xa7: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf:
1084
+ stringOp(op); return;
1085
+ case 0xa8: logicFlags(R[EAX] & M[eip++] & 0xff, S8); return;
1086
+ case 0xa9: { const v = fImm(os); logicFlags(getR(os, EAX) & v, os); return; }
1087
+ case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7:
1088
+ set8(op & 7, M[eip++]); return;
1089
+ case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
1090
+ if (os === S32) R[op & 7] = f32(); else setR(S16, op & 7, f16());
1091
+ return;
1092
+ case 0xc0: case 0xc1: case 0xd0: case 0xd1: case 0xd2: case 0xd3: {
1093
+ const s = op & 1 ? os : S8;
1094
+ mrm = M[eip++];
1095
+ const v = loadE(s);
1096
+ const cnt = op <= 0xc1 ? M[eip++] : op <= 0xd1 ? 1 : R[ECX] & 0xff;
1097
+ if ((cnt & 31) === 0) return;
1098
+ storeE(s, shift((mrm >> 3) & 7, v, cnt, s));
1099
+ return;
1100
+ }
1101
+ case 0xc2: { const n = f16(); eip = (popS(os) + csb) >>> 0; R[ESP] += n; return; }
1102
+ case 0xc3: eip = ((os === S32 ? pop() : popS(S16)) + csb) >>> 0; return;
1103
+ case 0xc4: case 0xc5: {
1104
+ mrm = M[eip++];
1105
+ const a = ea();
1106
+ const off = os === S32 ? rd(a) : rw(a), sel = rw(a + (os === S32 ? 4 : 2));
1107
+ loadSeg(op === 0xc4 ? ES : DS, sel); setR(os, (mrm >> 3) & 7, off);
1108
+ return;
1109
+ }
1110
+ case 0xc6: mrm = M[eip++]; if (mrm >= 0xc0) set8(mrm & 7, M[eip++]); else { eaddr = ea(); wb(eaddr, M[eip++]); } return;
1111
+ case 0xc7: {
1112
+ mrm = M[eip++];
1113
+ if (mrm >= 0xc0) { setR(os, mrm & 7, fImm(os)); return; }
1114
+ eaddr = ea();
1115
+ if (os === S32) wd(eaddr, f32()); else ww(eaddr, f16());
1116
+ return;
1117
+ }
1118
+ case 0xc8: {
1119
+ const size = f16(), level = M[eip++] & 31;
1120
+ pushS(os, getR(os, EBP));
1121
+ const frame = R[ESP];
1122
+ for (let i = 1; i < level; i++) { R[EBP] -= os === S32 ? 4 : 2; pushS(os, os === S32 ? rd(R[EBP] + ssb) : rw(R[EBP] + ssb)); }
1123
+ if (level > 0) pushS(os, frame);
1124
+ setR(os, EBP, frame);
1125
+ R[ESP] -= size;
1126
+ return;
1127
+ }
1128
+ case 0xc9: R[ESP] = R[EBP]; setR(os, EBP, popS(os)); return;
1129
+ case 0xca: { const n = f16(); const off = popS(os); loadSeg(CS, popS(os)); eip = (off + csb) >>> 0; R[ESP] += n; return; }
1130
+ case 0xcb: { const off = popS(os); loadSeg(CS, popS(os)); eip = (off + csb) >>> 0; return; }
1131
+ case 0xcc: softInt(3); return;
1132
+ case 0xcd: softInt(M[eip++]); return;
1133
+ case 0xce: if (getOF()) softInt(4); return;
1134
+ case 0xcf: {
1135
+ const off = popS(os);
1136
+ loadSeg(CS, popS(os));
1137
+ eip = (off + csb) >>> 0;
1138
+ const f = popS(os);
1139
+ setFlags(os === S16 ? (getFlags() & ~0xffff) | f : f);
1140
+ return;
1141
+ }
1142
+ case 0xd4: { const b = M[eip++] || 10; const al = R[EAX] & 0xff; setR(S16, EAX, (Math.floor(al / b) << 8) | (al % b)); logicFlags(al % b, S8); return; }
1143
+ case 0xd5: { const b = M[eip++]; const al = ((R[EAX] >> 8) & 0xff) * b + (R[EAX] & 0xff); setR(S16, EAX, al & 0xff); logicFlags(al & 0xff, S8); return; }
1144
+ case 0xd6: set8(0, getCF() ? 0xff : 0); return;
1145
+ case 0xd7: { const a = (R[EBX] + (R[EAX] & 0xff) + (segOv >= 0 ? segBase[segOv] : segBase[DS])) | 0; set8(0, rbx(a)); return; }
1146
+ case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
1147
+ fpuOp(op); return;
1148
+ case 0xe0: case 0xe1: case 0xe2: {
1149
+ const d = f8s();
1150
+ let c;
1151
+ if (as32) { c = R[ECX] = (R[ECX] - 1) | 0; } else { c = (R[ECX] - 1) & 0xffff; setR(S16, ECX, c); }
1152
+ let take = c !== 0;
1153
+ if (op === 0xe0) take = take && getZF() === 0;
1154
+ else if (op === 0xe1) take = take && getZF() === 1;
1155
+ if (take) eip += d;
1156
+ return;
1157
+ }
1158
+ case 0xe3: { const d = f8s(); if ((as32 ? R[ECX] : R[ECX] & 0xffff) === 0) eip += d; return; }
1159
+ case 0xe4: set8(0, bus.portIn(M[eip++], S8)); return;
1160
+ case 0xe5: setR(os, EAX, bus.portIn(M[eip++], os)); return;
1161
+ case 0xe6: bus.portOut(M[eip++], S8, R[EAX] & 0xff); return;
1162
+ case 0xe7: bus.portOut(M[eip++], os, getR(os, EAX)); return;
1163
+ case 0xe8: {
1164
+ if (os === S32) { const d = f32(); push(eip - csb); eip = (eip + d) >>> 0; return; }
1165
+ const d = (f16() << 16) >> 16; pushS(S16, eip - csb); eip = (eip + d) >>> 0;
1166
+ return;
1167
+ }
1168
+ case 0xe9: { const d = os === S32 ? f32() : (f16() << 16) >> 16; eip = (eip + d) >>> 0; return; }
1169
+ case 0xea: { const off = os === S32 ? f32() : f16(); const sel = f16(); loadSeg(CS, sel); eip = (off + csb) >>> 0; return; }
1170
+ case 0xeb: { const d = (M[eip++] << 24) >> 24; eip += d; return; }
1171
+ case 0xec: set8(0, bus.portIn(R[EDX] & 0xffff, S8)); return;
1172
+ case 0xed: setR(os, EAX, bus.portIn(R[EDX] & 0xffff, os)); return;
1173
+ case 0xee: bus.portOut(R[EDX] & 0xffff, S8, R[EAX] & 0xff); return;
1174
+ case 0xef: bus.portOut(R[EDX] & 0xffff, os, getR(os, EAX)); return;
1175
+ case 0xf4: halted = true; stop = true; return;
1176
+ case 0xf5: setCFbit(getCF() ^ 1); return;
1177
+ case 0xf6: mrm = M[eip++]; grp3((mrm >> 3) & 7, S8); return;
1178
+ case 0xf7: mrm = M[eip++]; grp3((mrm >> 3) & 7, os); return;
1179
+ case 0xf8: setCFbit(0); return;
1180
+ case 0xf9: setCFbit(1); return;
1181
+ case 0xfa: flags &= ~0x200; return;
1182
+ case 0xfb: flags |= 0x200; return;
1183
+ case 0xfc: flags &= ~0x400; return;
1184
+ case 0xfd: flags |= 0x400; return;
1185
+ case 0xfe: {
1186
+ mrm = M[eip++];
1187
+ const sub = (mrm >> 3) & 7, v = loadE(S8);
1188
+ if (sub === 0) storeE(S8, inc(v, S8)); else if (sub === 1) storeE(S8, dec(v, S8));
1189
+ else throw new CpuFault(`FE /${sub}`, opEip);
1190
+ return;
1191
+ }
1192
+ case 0xff: {
1193
+ mrm = M[eip++];
1194
+ switch ((mrm >> 3) & 7) {
1195
+ case 0: storeE(os, inc(loadE(os), os)); return;
1196
+ case 1: storeE(os, dec(loadE(os), os)); return;
1197
+ case 2: { const t = loadE(os); pushS(os, eip - csb); eip = (t + csb) >>> 0; return; }
1198
+ case 3: { const a = ea(); const off = os === S32 ? rd(a) : rw(a); const sel = rw(a + (os === S32 ? 4 : 2)); pushS(os, seg[CS]); pushS(os, eip - csb); loadSeg(CS, sel); eip = (off + csb) >>> 0; return; }
1199
+ case 4: eip = (loadE(os) + csb) >>> 0; return;
1200
+ case 5: { const a = ea(); const off = os === S32 ? rd(a) : rw(a); const sel = rw(a + (os === S32 ? 4 : 2)); loadSeg(CS, sel); eip = (off + csb) >>> 0; return; }
1201
+ case 6: pushS(os, loadE(os)); return;
1202
+ }
1203
+ throw new CpuFault('FF /7', opEip);
1204
+ }
1205
+ }
1206
+ throw new CpuFault(`opcode ${op.toString(16)}`, opEip);
1207
+ }
1208
+
1209
+ function imul2(s, g, a, b) {
1210
+ if (s === S32) {
1211
+ const lo = Math.imul(a, b);
1212
+ const o = a * b !== lo ? 1 : 0;
1213
+ R[g] = lo; eager(lo, s, o, o);
1214
+ } else {
1215
+ const p = ((a << 16) >> 16) * ((b << 16) >> 16);
1216
+ setR(S16, g, p); const o = p !== ((p << 16) >> 16) ? 1 : 0; eager(p & 0xffff, s, o, o);
1217
+ }
1218
+ }
1219
+
1220
+ function bitOp(sub, s, bitFromReg, immBit) {
1221
+ // sub: 4 BT, 5 BTS, 6 BTR, 7 BTC
1222
+ const bits = BITS[s];
1223
+ let v, bit, addr = 0;
1224
+ const isReg = mrm >= 0xc0;
1225
+ if (isReg) { v = getR(s, mrm & 7); bit = (bitFromReg ? getR(s, (mrm >> 3) & 7) : immBit) & (bits - 1); }
1226
+ else {
1227
+ addr = ea();
1228
+ if (bitFromReg) {
1229
+ const off = s === S32 ? R[(mrm >> 3) & 7] : (R[(mrm >> 3) & 7] << 16) >> 16;
1230
+ addr = (addr + Math.floor(off / bits) * (bits >> 3)) | 0;
1231
+ bit = off & (bits - 1);
1232
+ } else bit = immBit & (bits - 1);
1233
+ v = s === S32 ? rd(addr) : rw(addr);
1234
+ }
1235
+ setCFbit((v >>> bit) & 1);
1236
+ if (sub === 4) return;
1237
+ const r = (sub === 5 ? v | (1 << bit) : sub === 6 ? v & ~(1 << bit) : v ^ (1 << bit)) & MASK[s];
1238
+ if (isReg) setR(s, mrm & 7, r); else if (s === S32) wd(addr, r); else ww(addr, r);
1239
+ }
1240
+
1241
+ function twoByte() {
1242
+ const op = M[eip++];
1243
+ if (op >= 0x80 && op <= 0x8f) {
1244
+ const d = os === S32 ? f32() : (f16() << 16) >> 16;
1245
+ if (cond(op & 15)) eip = (eip + d) >>> 0;
1246
+ return;
1247
+ }
1248
+ if (op >= 0x90 && op <= 0x9f) { mrm = M[eip++]; writeE(S8, cond(op & 15) ? 1 : 0); return; }
1249
+ if (op >= 0xc8 && op <= 0xcf) {
1250
+ const v = R[op & 7];
1251
+ R[op & 7] = ((v & 0xff) << 24) | (((v >> 8) & 0xff) << 16) | (((v >> 16) & 0xff) << 8) | ((v >>> 24) & 0xff);
1252
+ return;
1253
+ }
1254
+ switch (op) {
1255
+ case 0x00: case 0x01: {
1256
+ mrm = M[eip++];
1257
+ const sub = (mrm >> 3) & 7;
1258
+ if (op === 0x01 && sub === 4) { writeE(S16, 0x0011); return; } // SMSW: protected, FPU present
1259
+ if (mrm < 0xc0) { const a = ea(); if (op === 0x01 && (sub === 0 || sub === 1)) { ww(a, 0xff); wd(a + 2, 0); } }
1260
+ else if (op === 0x00 && (sub === 0 || sub === 1)) setR(os, mrm & 7, 0);
1261
+ return;
1262
+ }
1263
+ case 0x02: mrm = M[eip++]; loadE(S16); setR(os, (mrm >> 3) & 7, 0xcf9a00); zfSet(1); return; // LAR
1264
+ case 0x03: mrm = M[eip++]; loadE(S16); setR(os, (mrm >> 3) & 7, -1); zfSet(1); return; // LSL
1265
+ case 0x06: return;
1266
+ case 0x1f: mrm = M[eip++]; if (mrm < 0xc0) ea(); return;
1267
+ case 0x20: case 0x21: case 0x22: case 0x23:
1268
+ mrm = M[eip++];
1269
+ if (op === 0x20 || op === 0x21) R[mrm & 7] = op === 0x20 && ((mrm >> 3) & 7) === 0 ? 0x11 : 0;
1270
+ return;
1271
+ case 0x31: R[EAX] = cycles; R[EDX] = 0; return;
1272
+ case 0xa0: pushS(os, seg[FS]); return;
1273
+ case 0xa1: loadSeg(FS, popS(os)); return;
1274
+ case 0xa8: pushS(os, seg[GS]); return;
1275
+ case 0xa9: loadSeg(GS, popS(os)); return;
1276
+ case 0xa2: // CPUID: a 486DX
1277
+ if (R[EAX] === 0) { R[EAX] = 1; R[EBX] = 0x756e6547; R[EDX] = 0x49656e69; R[ECX] = 0x6c65746e; }
1278
+ else { R[EAX] = 0x0421; R[EBX] = 0; R[ECX] = 0; R[EDX] = 1; }
1279
+ return;
1280
+ case 0xa3: mrm = M[eip++]; bitOp(4, os, true, 0); return;
1281
+ case 0xab: mrm = M[eip++]; bitOp(5, os, true, 0); return;
1282
+ case 0xb3: mrm = M[eip++]; bitOp(6, os, true, 0); return;
1283
+ case 0xbb: mrm = M[eip++]; bitOp(7, os, true, 0); return;
1284
+ case 0xba: {
1285
+ mrm = M[eip++];
1286
+ const sub = (mrm >> 3) & 7;
1287
+ if (sub < 4) throw new CpuFault(`0f ba /${sub}`, opEip);
1288
+ if (mrm >= 0xc0) { bitOp(sub, os, false, M[eip++]); return; }
1289
+ // the immediate follows the displacement: decode once to find it, then again for real
1290
+ const save = eip; ea(); const imm = M[eip]; const after = eip + 1; eip = save;
1291
+ bitOp(sub, os, false, imm); eip = after;
1292
+ return;
1293
+ }
1294
+ case 0xa4: case 0xa5: case 0xac: case 0xad: {
1295
+ mrm = M[eip++];
1296
+ const s = os, bits = BITS[s], g = getR(s, (mrm >> 3) & 7);
1297
+ const v = loadE(s);
1298
+ const cnt = (op === 0xa4 || op === 0xac ? M[eip++] : R[ECX]) & 31;
1299
+ if (cnt === 0) return;
1300
+ let r, cf;
1301
+ if (s === S32) {
1302
+ if (op <= 0xa5) { r = (v << cnt) | (g >>> (32 - cnt)); cf = (v >>> (32 - cnt)) & 1; }
1303
+ else { r = (v >>> cnt) | (g << (32 - cnt)); cf = (v >>> (cnt - 1)) & 1; }
1304
+ } else if (op <= 0xa5) {
1305
+ r = ((((v << 16) | g) << cnt) >>> 16) & 0xffff; cf = (v >>> (16 - cnt)) & 1;
1306
+ } else {
1307
+ r = (((g << 16) | v) >>> cnt) & 0xffff; cf = (v >>> (cnt - 1)) & 1;
1308
+ }
1309
+ eager(r, s, cf, ((r ^ v) >>> (bits - 1)) & 1);
1310
+ storeE(s, r);
1311
+ return;
1312
+ }
1313
+ case 0xaf: {
1314
+ mrm = M[eip++];
1315
+ const v = loadE(os);
1316
+ imul2(os, (mrm >> 3) & 7, getR(os, (mrm >> 3) & 7), v);
1317
+ return;
1318
+ }
1319
+ case 0xb0: case 0xb1: {
1320
+ const s = op & 1 ? os : S8;
1321
+ mrm = M[eip++];
1322
+ const v = loadE(s), a = getR(s, EAX);
1323
+ arith(7, a, v, s);
1324
+ if (a === v) storeE(s, getR(s, (mrm >> 3) & 7)); else { setR(s, EAX, v); storeE(s, v); }
1325
+ return;
1326
+ }
1327
+ case 0xb2: case 0xb4: case 0xb5: {
1328
+ mrm = M[eip++];
1329
+ const a = ea();
1330
+ const off = os === S32 ? rd(a) : rw(a), sel = rw(a + (os === S32 ? 4 : 2));
1331
+ loadSeg(op === 0xb2 ? SS : op === 0xb4 ? FS : GS, sel); setR(os, (mrm >> 3) & 7, off);
1332
+ return;
1333
+ }
1334
+ case 0xb6: mrm = M[eip++]; setR(os, (mrm >> 3) & 7, mrm >= 0xc0 ? get8(mrm & 7) : rbx(ea())); return;
1335
+ case 0xb7: mrm = M[eip++]; setR(os, (mrm >> 3) & 7, mrm >= 0xc0 ? R[mrm & 7] & 0xffff : rw(ea())); return;
1336
+ case 0xbe: mrm = M[eip++]; setR(os, (mrm >> 3) & 7, ((mrm >= 0xc0 ? get8(mrm & 7) : rbx(ea())) << 24) >> 24); return;
1337
+ case 0xbf: mrm = M[eip++]; setR(os, (mrm >> 3) & 7, ((mrm >= 0xc0 ? R[mrm & 7] : rw(ea())) << 16) >> 16); return;
1338
+ case 0xbc: case 0xbd: {
1339
+ mrm = M[eip++];
1340
+ const v = loadE(os);
1341
+ if (v === 0) { zfSet(1); return; }
1342
+ setR(os, (mrm >> 3) & 7, op === 0xbc ? 31 - Math.clz32(v & -v) : 31 - Math.clz32(v));
1343
+ zfSet(0);
1344
+ return;
1345
+ }
1346
+ case 0xc0: case 0xc1: {
1347
+ const s = op & 1 ? os : S8;
1348
+ mrm = M[eip++];
1349
+ const v = loadE(s), g = (mrm >> 3) & 7, b = getR(s, g);
1350
+ const r = arith(0, v, b, s);
1351
+ setR(s, g, v); storeE(s, r);
1352
+ return;
1353
+ }
1354
+ }
1355
+ throw new CpuFault(`opcode 0f ${op.toString(16)}`, opEip);
1356
+ }
1357
+
1358
+ // ------------------------------------------------------------------ the decoded-instruction cache
1359
+ // (operator, 2026-09-15: "What do you recommend to improve the in-browser speed for Quake?" -- this,
1360
+ // and a smaller view). step() decodes an instruction from its bytes every time it runs, and in a
1361
+ // loop that is most of the work. Here each instruction is decoded ONCE into three int32s -- a
1362
+ // handler number with its registers packed beside it, a displacement, an immediate -- kept per 4 KB
1363
+ // page in an Int32Array, and the loop dispatches on the handler number with one switch.
1364
+ //
1365
+ // NOT CLOSURES. The first cut decoded each instruction into its own closure, and ran SLOWER than
1366
+ // the interpreter (Quake 77 -> 69 MIPS, DOOM 90 -> 68): a call site that meets a different closure
1367
+ // every instruction is megamorphic in V8, and paying that per instruction cost more than decoding.
1368
+ // A dense switch compiles to a jump table, and typed arrays allocate nothing.
1369
+ //
1370
+ // Only the forms compilers emit in bulk have a handler (mov, the ALU rows, lea, inc/dec, push/pop,
1371
+ // jumps, calls, shifts, test, movzx/movsx, imul, setcc). Anything else -- a prefix, a string
1372
+ // instruction, the FPU, an interrupt -- is marked to run through step(), so the cache is never less
1373
+ // correct than the interpreter under it.
1374
+ //
1375
+ // CACHED CODE IS INVALIDATED ON WRITE: a page holding decoded instructions is marked, and a write
1376
+ // into a marked page (wb/ww/wd, the string ops' block moves, the machine's own copies through
1377
+ // invalidate()) drops that page's decodings. An instruction that runs over a page's end is not
1378
+ // cached, so a page can always be dropped on its own. A 16-bit code segment bypasses the cache.
1379
+ //
1380
+ // word layout: bits 0-9 handler, 10-13 length, 14-16 g (the ModRM reg field, or an ALU/shift
1381
+ // sub-operation), 17-19 e (the ModRM rm register), 20-23 base register (8 = none), 24-27 index
1382
+ // register (8 = none), 28-29 scale, 30 the stack segment is the default (base ESP/EBP).
1383
+ const H_STEP = 1023;
1384
+ const MEM_OPERAND = 1 << 31; // the word has a ModRM memory operand: the loop forms its address
1385
+ const ALU_OP = ['ADD', 'OR', null, null, 'AND', 'SUB', 'XOR', 'CMP']; // ADC and SBB keep the shared routine
1386
+ let hid = 1;
1387
+ const H = {};
1388
+ for (const name of [
1389
+ 'ALU32_EG_R', 'ALU32_EG_M', 'ALU32_GE_R', 'ALU32_GE_M', 'ALU8_EG_R', 'ALU8_EG_M', 'ALU8_GE_R', 'ALU8_GE_M',
1390
+ 'ALU_AL', 'ALU_EAX', 'GRP32_R', 'GRP32_M', 'GRP8_R', 'GRP8_M',
1391
+ 'INC32', 'DEC32', 'PUSH32', 'POP32', 'PUSHI', 'JCC', 'JMP', 'CALL', 'RET', 'RETN', 'LEAVE',
1392
+ 'TEST32_R', 'TEST32_M', 'TEST8_R', 'TEST8_M',
1393
+ 'MOV8_EG_R', 'MOV8_EG_M', 'MOV32_EG_R', 'MOV32_EG_M', 'MOV8_GE_R', 'MOV8_GE_M', 'MOV32_GE_R', 'MOV32_GE_M',
1394
+ 'LEA', 'NOP', 'CWDE', 'CDQ', 'A0', 'A1', 'A2', 'A3', 'MOV8_IR', 'MOV32_IR', 'MOV8_IM', 'MOV32_IM', 'MOV32_IRM',
1395
+ 'SH32_R', 'SH32_M', 'SH8_R', 'SH8_M',
1396
+ 'TESTI8_R', 'TESTI8_M', 'TESTI32_R', 'TESTI32_M',
1397
+ 'INC32_M', 'DEC32_M', 'CALL_R', 'CALL_M', 'JMP_R', 'JMP_M', 'PUSH_M',
1398
+ 'SETCC_R', 'SETCC_M', 'IMUL_R', 'IMUL_M', 'IMULI_R', 'IMULI_M',
1399
+ 'MOVZX8_R', 'MOVZX8_M', 'MOVZX16_R', 'MOVZX16_M', 'MOVSX8_R', 'MOVSX8_M', 'MOVSX16_R', 'MOVSX16_M',
1400
+ 'FPU_R', 'FPU_M',
1401
+ // the ALU operations split by operation, and the x87 forms Quake runs most (see the switch)
1402
+ 'ADD_EG_R',
1403
+ 'ADD_EG_M',
1404
+ 'ADD_GE_R',
1405
+ 'ADD_GE_M',
1406
+ 'ADD_I_R',
1407
+ 'ADD_I_M',
1408
+ 'ADD_EAX',
1409
+ 'OR_EG_R',
1410
+ 'OR_EG_M',
1411
+ 'OR_GE_R',
1412
+ 'OR_GE_M',
1413
+ 'OR_I_R',
1414
+ 'OR_I_M',
1415
+ 'OR_EAX',
1416
+ 'AND_EG_R',
1417
+ 'AND_EG_M',
1418
+ 'AND_GE_R',
1419
+ 'AND_GE_M',
1420
+ 'AND_I_R',
1421
+ 'AND_I_M',
1422
+ 'AND_EAX',
1423
+ 'SUB_EG_R',
1424
+ 'SUB_EG_M',
1425
+ 'SUB_GE_R',
1426
+ 'SUB_GE_M',
1427
+ 'SUB_I_R',
1428
+ 'SUB_I_M',
1429
+ 'SUB_EAX',
1430
+ 'XOR_EG_R',
1431
+ 'XOR_EG_M',
1432
+ 'XOR_GE_R',
1433
+ 'XOR_GE_M',
1434
+ 'XOR_I_R',
1435
+ 'XOR_I_M',
1436
+ 'XOR_EAX',
1437
+ 'CMP_EG_R',
1438
+ 'CMP_EG_M',
1439
+ 'CMP_GE_R',
1440
+ 'CMP_GE_M',
1441
+ 'CMP_I_R',
1442
+ 'CMP_I_M',
1443
+ 'CMP_EAX',
1444
+ 'FLD_M32',
1445
+ 'FLD_M64',
1446
+ 'FST_M32',
1447
+ 'FSTP_M32',
1448
+ 'FST_M64',
1449
+ 'FSTP_M64',
1450
+ 'FILD_M32',
1451
+ 'FISTP_M32',
1452
+ 'FAR_M32_0',
1453
+ 'FAR_M32_1',
1454
+ 'FAR_M32_4',
1455
+ 'FAR_M32_5',
1456
+ 'FAR_M32_6',
1457
+ 'FAR_M32_7',
1458
+ 'FAR_M64_0',
1459
+ 'FAR_M64_1',
1460
+ 'FAR_M64_4',
1461
+ 'FAR_M64_5',
1462
+ 'FAR_M64_6',
1463
+ 'FAR_M64_7',
1464
+ 'FXCH_I',
1465
+ 'FLD_I',
1466
+ 'FST_I',
1467
+ 'FSTP_I',
1468
+ 'FAR_I_0',
1469
+ 'FAR_I_1',
1470
+ 'FAR_I_4',
1471
+ 'FAR_I_5',
1472
+ 'FAR_I_6',
1473
+ 'FAR_I_7',
1474
+ 'FARP_I_0',
1475
+ 'FARP_I_1',
1476
+ 'FARP_I_4',
1477
+ 'FARP_I_5',
1478
+ 'FARP_I_6',
1479
+ 'FARP_I_7',
1480
+ 'FNSTSW_AX',
1481
+ 'FCHS',
1482
+ 'FABS',
1483
+ 'FLD1',
1484
+ 'FLDZ',
1485
+ 'FCOMP_M32',
1486
+ 'FCOM_I',
1487
+ 'FCOMP_I',
1488
+ 'FCOMPP',
1489
+ ]) H[name] = hid++;
1490
+
1491
+ const PAGES = M.length >>> 12;
1492
+ const codePage = new Uint8Array(PAGES);
1493
+ const pageTab = new Array(PAGES).fill(null); // Int32Array(4096 * 3) per page, or null
1494
+ function dropPage(p) { pageTab[p] = null; codePage[p] = 0; }
1495
+ // A WRITE CLEARS ONLY WHAT IT HITS. DOOM's span drawer patches constants into its own instructions
1496
+ // on every call, and dropping the whole page for that re-decoded the page thousands of times a
1497
+ // second (DOOM ran slower with the cache than without it). A decoded instruction is at most 15
1498
+ // bytes and never runs off its page, so the ones a write can touch start within 14 bytes before
1499
+ // it on the same page.
1500
+ function touchCode(a, len) {
1501
+ const end = (a & AMASK) + len;
1502
+ for (let x = a & AMASK; x < end;) {
1503
+ const p = x >>> 12, pageEnd = Math.min(end, (p + 1) << 12);
1504
+ const tab = pageTab[p];
1505
+ if (tab !== null) {
1506
+ const lo = Math.max(p << 12, x - 14);
1507
+ for (let s = lo; s < pageEnd; s++) {
1508
+ const k = (s & 4095) * 3, w = tab[k];
1509
+ if (w !== 0 && (w & 1023) !== H_STEP && s + ((w >>> 10) & 15) > x) tab[k] = 0;
1510
+ }
1511
+ }
1512
+ x = pageEnd;
1513
+ }
1514
+ }
1515
+ function invalidate(a, len) {
1516
+ if (len > 256) {
1517
+ const first = (a & AMASK) >>> 12, last = ((a + len - 1) & AMASK) >>> 12;
1518
+ for (let p = first; p <= last; p++) if (codePage[p] !== 0) dropPage(p);
1519
+ return;
1520
+ }
1521
+ const first = (a & AMASK) >>> 12, last = ((a + Math.max(1, len) - 1) & AMASK) >>> 12;
1522
+ for (let p = first; p <= last; p++) if (codePage[p] !== 0) { touchCode(a, len); return; }
1523
+ }
1524
+
1525
+ // ModRM operand at `at` (the byte after ModRM): the packed base/index/scale/stack bits, the
1526
+ // displacement, and the bytes used
1527
+ let dDisp = 0, dUsed = 0;
1528
+ function decodeEA(m, at) {
1529
+ const mod = m >> 6, rm = m & 7;
1530
+ let used = 0, base = 8, idx = 8, scale = 0, disp = 0;
1531
+ if (rm === 4) {
1532
+ const sib = M[at]; used = 1;
1533
+ const b = sib & 7, x = (sib >> 3) & 7;
1534
+ scale = sib >> 6;
1535
+ if (x !== 4) idx = x;
1536
+ if (b === 5 && mod === 0) { disp = M[at + 1] | (M[at + 2] << 8) | (M[at + 3] << 16) | (M[at + 4] << 24); used += 4; }
1537
+ else base = b;
1538
+ } else if (rm === 5 && mod === 0) { disp = M[at] | (M[at + 1] << 8) | (M[at + 2] << 16) | (M[at + 3] << 24); used = 4; }
1539
+ else base = rm;
1540
+ if (mod === 1) { disp = (M[at + used] << 24) >> 24; used += 1; }
1541
+ else if (mod === 2) { disp = M[at + used] | (M[at + used + 1] << 8) | (M[at + used + 2] << 16) | (M[at + used + 3] << 24); used += 4; }
1542
+ dDisp = disp; dUsed = used;
1543
+ return (base << 20) | (idx << 24) | (scale << 28) | (base === ESP || base === EBP ? 1 << 30 : 0);
1544
+ }
1545
+ function eaOf(w, disp) {
1546
+ let x = disp;
1547
+ const b = (w >>> 20) & 15;
1548
+ if (b !== 8) x += R[b];
1549
+ const i = (w >>> 24) & 15;
1550
+ if (i !== 8) x += R[i] << ((w >>> 28) & 3);
1551
+ return (x + segBase[(w & (1 << 30)) !== 0 ? SS : DS]) | 0;
1552
+ }
1553
+
1554
+ // decode the instruction at linear `a` into tab[k..k+2]; returns the word (H_STEP for "use step()")
1555
+ function decodeAt(tab, k, a) {
1556
+ const op = M[a];
1557
+ const i32at = (x) => M[x] | (M[x + 1] << 8) | (M[x + 2] << 16) | (M[x + 3] << 24);
1558
+ let h = 0, len = 0, g = 0, e = 0, ea = 0, disp = 0, imm = 0;
1559
+ const modrm = (at) => {
1560
+ const m = M[at];
1561
+ g = (m >> 3) & 7; e = m & 7;
1562
+ if (m >= 0xc0) { dUsed = 0; return true; }
1563
+ ea = decodeEA(m, at + 1) | MEM_OPERAND; disp = dDisp;
1564
+ return false;
1565
+ };
1566
+ if (op < 0x40 && (op & 7) < 6) {
1567
+ const sub = op >> 3, form = op & 7;
1568
+ if (form === 4) { h = H.ALU_AL; g = sub; imm = M[a + 1]; len = 2; }
1569
+ else if (form === 5) { h = ALU_OP[sub] ? H[`${ALU_OP[sub]}_EAX`] : H.ALU_EAX; g = sub; imm = i32at(a + 1); len = 5; }
1570
+ else {
1571
+ const reg = modrm(a + 1);
1572
+ imm = sub; len = 2 + dUsed;
1573
+ h = [[H.ALU8_EG_M, H.ALU8_EG_R], [H.ALU32_EG_M, H.ALU32_EG_R], [H.ALU8_GE_M, H.ALU8_GE_R], [H.ALU32_GE_M, H.ALU32_GE_R]][form][reg ? 1 : 0];
1574
+ if ((form === 1 || form === 3) && ALU_OP[sub]) h = H[`${ALU_OP[sub]}_${form === 1 ? 'EG' : 'GE'}_${reg ? 'R' : 'M'}`];
1575
+ }
1576
+ } else {
1577
+ switch (op) {
1578
+ case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: h = H.INC32; e = op & 7; len = 1; break;
1579
+ case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f: h = H.DEC32; e = op & 7; len = 1; break;
1580
+ case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: h = H.PUSH32; e = op & 7; len = 1; break;
1581
+ case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: h = H.POP32; e = op & 7; len = 1; break;
1582
+ case 0x68: h = H.PUSHI; imm = i32at(a + 1); len = 5; break;
1583
+ case 0x6a: h = H.PUSHI; imm = (M[a + 1] << 24) >> 24; len = 2; break;
1584
+ case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
1585
+ case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
1586
+ h = H.JCC; disp = op & 15; imm = (a + 2 + ((M[a + 1] << 24) >> 24)) | 0; len = 2; break;
1587
+ case 0x80: case 0x81: case 0x83: {
1588
+ const reg = modrm(a + 1);
1589
+ const ia = a + 2 + dUsed;
1590
+ if (op === 0x80) { h = reg ? H.GRP8_R : H.GRP8_M; imm = M[ia]; len = 3 + dUsed; }
1591
+ else if (op === 0x83) { h = reg ? H.GRP32_R : H.GRP32_M; imm = (M[ia] << 24) >> 24; len = 3 + dUsed; }
1592
+ else { h = reg ? H.GRP32_R : H.GRP32_M; imm = i32at(ia); len = 6 + dUsed; }
1593
+ if (op !== 0x80 && ALU_OP[g]) h = H[`${ALU_OP[g]}_I_${reg ? 'R' : 'M'}`];
1594
+ break;
1595
+ }
1596
+ case 0x84: case 0x85: { const reg = modrm(a + 1); h = op === 0x85 ? (reg ? H.TEST32_R : H.TEST32_M) : (reg ? H.TEST8_R : H.TEST8_M); len = 2 + dUsed; break; }
1597
+ case 0x88: case 0x89: case 0x8a: case 0x8b: {
1598
+ const reg = modrm(a + 1);
1599
+ h = [[H.MOV8_EG_M, H.MOV8_EG_R], [H.MOV32_EG_M, H.MOV32_EG_R], [H.MOV8_GE_M, H.MOV8_GE_R], [H.MOV32_GE_M, H.MOV32_GE_R]][op - 0x88][reg ? 1 : 0];
1600
+ len = 2 + dUsed;
1601
+ break;
1602
+ }
1603
+ case 0x8d: if (modrm(a + 1)) return H_STEP; h = H.LEA; len = 2 + dUsed; break;
1604
+ case 0x90: h = H.NOP; len = 1; break;
1605
+ case 0x98: h = H.CWDE; len = 1; break;
1606
+ case 0x99: h = H.CDQ; len = 1; break;
1607
+ case 0xa0: case 0xa1: case 0xa2: case 0xa3: h = H.A0 + (op - 0xa0); imm = i32at(a + 1); len = 5; break;
1608
+ case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: h = H.MOV8_IR; e = op & 7; imm = M[a + 1]; len = 2; break;
1609
+ case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: h = H.MOV32_IR; e = op & 7; imm = i32at(a + 1); len = 5; break;
1610
+ case 0xc0: case 0xc1: case 0xd0: case 0xd1: case 0xd2: case 0xd3: {
1611
+ const reg = modrm(a + 1);
1612
+ if (g === 6) return H_STEP;
1613
+ const s32 = (op & 1) === 1;
1614
+ h = s32 ? (reg ? H.SH32_R : H.SH32_M) : (reg ? H.SH8_R : H.SH8_M);
1615
+ if (op <= 0xc1) { imm = M[a + 2 + dUsed]; len = 3 + dUsed; }
1616
+ else { imm = op <= 0xd1 ? 1 : -1; len = 2 + dUsed; } // -1: the count is CL
1617
+ break;
1618
+ }
1619
+ case 0xc2: h = H.RETN; imm = M[a + 1] | (M[a + 2] << 8); len = 3; break;
1620
+ case 0xc3: h = H.RET; len = 1; break;
1621
+ case 0xc6: case 0xc7: {
1622
+ const reg = modrm(a + 1);
1623
+ if (g !== 0) return H_STEP;
1624
+ const ia = a + 2 + dUsed;
1625
+ if (op === 0xc6) { h = reg ? H.MOV8_IR : H.MOV8_IM; imm = M[ia]; len = 3 + dUsed; }
1626
+ else { h = reg ? H.MOV32_IRM : H.MOV32_IM; imm = i32at(ia); len = 6 + dUsed; }
1627
+ break;
1628
+ }
1629
+ case 0xc9: h = H.LEAVE; len = 1; break;
1630
+ case 0xe8: h = H.CALL; imm = (a + 5 + i32at(a + 1)) | 0; len = 5; break;
1631
+ case 0xe9: h = H.JMP; imm = (a + 5 + i32at(a + 1)) | 0; len = 5; break;
1632
+ case 0xeb: h = H.JMP; imm = (a + 2 + ((M[a + 1] << 24) >> 24)) | 0; len = 2; break;
1633
+ case 0xf6: case 0xf7: {
1634
+ const reg = modrm(a + 1);
1635
+ if (g !== 0) return H_STEP; // TEST only; MUL/DIV and friends use step()
1636
+ const ia = a + 2 + dUsed;
1637
+ if (op === 0xf6) { h = reg ? H.TESTI8_R : H.TESTI8_M; imm = M[ia]; len = 3 + dUsed; }
1638
+ else { h = reg ? H.TESTI32_R : H.TESTI32_M; imm = i32at(ia); len = 6 + dUsed; }
1639
+ break;
1640
+ }
1641
+ case 0xff: {
1642
+ const reg = modrm(a + 1);
1643
+ len = 2 + dUsed;
1644
+ switch (g) {
1645
+ case 0: h = reg ? H.INC32 : H.INC32_M; break;
1646
+ case 1: h = reg ? H.DEC32 : H.DEC32_M; break;
1647
+ case 2: h = reg ? H.CALL_R : H.CALL_M; break;
1648
+ case 4: h = reg ? H.JMP_R : H.JMP_M; break;
1649
+ case 6: h = reg ? H.PUSH32 : H.PUSH_M; break;
1650
+ default: return H_STEP;
1651
+ }
1652
+ break;
1653
+ }
1654
+ case 0x69: case 0x6b: {
1655
+ const reg = modrm(a + 1);
1656
+ const ia = a + 2 + dUsed;
1657
+ h = reg ? H.IMULI_R : H.IMULI_M;
1658
+ if (op === 0x6b) { imm = (M[ia] << 24) >> 24; len = 3 + dUsed; } else { imm = i32at(ia); len = 6 + dUsed; }
1659
+ break;
1660
+ }
1661
+ case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf: {
1662
+ // the FPU: decoded once, executed by the same x87 core step() uses
1663
+ const reg = modrm(a + 1);
1664
+ h = reg ? H.FPU_R : H.FPU_M; imm = op | (M[a + 1] << 8); len = 2 + dUsed;
1665
+ const m = M[a + 1];
1666
+ if (!reg) {
1667
+ if (op === 0xd9 && g === 0) h = H.FLD_M32;
1668
+ else if (op === 0xd9 && g === 2) h = H.FST_M32;
1669
+ else if (op === 0xd9 && g === 3) h = H.FSTP_M32;
1670
+ else if (op === 0xdd && g === 0) h = H.FLD_M64;
1671
+ else if (op === 0xdd && g === 2) h = H.FST_M64;
1672
+ else if (op === 0xdd && g === 3) h = H.FSTP_M64;
1673
+ else if (op === 0xdb && g === 0) h = H.FILD_M32;
1674
+ else if (op === 0xdb && g === 3) h = H.FISTP_M32;
1675
+ else if (op === 0xd8 && g === 3) h = H.FCOMP_M32;
1676
+ else if ((op === 0xd8 || op === 0xdc) && g !== 2 && g !== 3) h = H[`FAR_M${op === 0xd8 ? 32 : 64}_${g}`];
1677
+ } else {
1678
+ if (op === 0xd9 && g === 1) h = H.FXCH_I;
1679
+ else if (op === 0xd9 && g === 0) h = H.FLD_I;
1680
+ else if (op === 0xdd && g === 2) h = H.FST_I;
1681
+ else if (op === 0xdd && g === 3) h = H.FSTP_I;
1682
+ else if (op === 0xd8 && g === 2) h = H.FCOM_I;
1683
+ else if (op === 0xd8 && g === 3) h = H.FCOMP_I;
1684
+ else if (op === 0xd8) h = H[`FAR_I_${g}`];
1685
+ else if (op === 0xde && m === 0xd9) h = H.FCOMPP;
1686
+ else if (op === 0xde && g !== 2 && g !== 3) h = H[`FARP_I_${g}`];
1687
+ else if (op === 0xdf && m === 0xe0) h = H.FNSTSW_AX;
1688
+ else if (op === 0xd9 && m === 0xe0) h = H.FCHS;
1689
+ else if (op === 0xd9 && m === 0xe1) h = H.FABS;
1690
+ else if (op === 0xd9 && m === 0xe8) h = H.FLD1;
1691
+ else if (op === 0xd9 && m === 0xee) h = H.FLDZ;
1692
+ }
1693
+ break;
1694
+ }
1695
+ case 0x0f: {
1696
+ const op2 = M[a + 1];
1697
+ if (op2 >= 0x80 && op2 <= 0x8f) { h = H.JCC; disp = op2 & 15; imm = (a + 6 + i32at(a + 2)) | 0; len = 6; break; }
1698
+ if (op2 >= 0x90 && op2 <= 0x9f) { const reg = modrm(a + 2); h = reg ? H.SETCC_R : H.SETCC_M; imm = op2 & 15; len = 3 + dUsed; break; }
1699
+ switch (op2) {
1700
+ case 0xaf: { const reg = modrm(a + 2); h = reg ? H.IMUL_R : H.IMUL_M; len = 3 + dUsed; break; }
1701
+ case 0xb6: { const reg = modrm(a + 2); h = reg ? H.MOVZX8_R : H.MOVZX8_M; len = 3 + dUsed; break; }
1702
+ case 0xb7: { const reg = modrm(a + 2); h = reg ? H.MOVZX16_R : H.MOVZX16_M; len = 3 + dUsed; break; }
1703
+ case 0xbe: { const reg = modrm(a + 2); h = reg ? H.MOVSX8_R : H.MOVSX8_M; len = 3 + dUsed; break; }
1704
+ case 0xbf: { const reg = modrm(a + 2); h = reg ? H.MOVSX16_R : H.MOVSX16_M; len = 3 + dUsed; break; }
1705
+ default: return H_STEP;
1706
+ }
1707
+ break;
1708
+ }
1709
+ default: return H_STEP;
1710
+ }
1711
+ }
1712
+ if (h === 0 || (a & 4095) + len > 4096) return H_STEP;
1713
+ const w = h | (len << 10) | (g << 14) | (e << 17) | ea;
1714
+ tab[k + 1] = disp; tab[k + 2] = imm;
1715
+ return w;
1716
+ }
1717
+
1718
+ // ------------------------------------------------------------------ the loop
1719
+ /** Run up to `n` instructions (fewer if one halts). Returns the number executed. */
1720
+ function run(n) {
1721
+ stop = false;
1722
+ let i = 0;
1723
+ while (i < n) {
1724
+ try {
1725
+ // the page last dispatched from, kept across instructions: most run on from the one before.
1726
+ // A cached handler never halts, never changes CS and never drops a page table (its writes go
1727
+ // through wb/ww/wd, which only zero entries; whole tables are dropped by block copies and the
1728
+ // machine, which run inside step()), so those checks are made only after step() ran.
1729
+ let curP = -1, tab = null;
1730
+ if (cs16) { i++; opEip = eip; step(); if (stop) n = i; continue; }
1731
+ while (i < n) {
1732
+ i++;
1733
+ const a = eip;
1734
+ const p = a >>> 12;
1735
+ if (p !== curP) {
1736
+ tab = pageTab[p];
1737
+ if (tab === null) { tab = pageTab[p] = new Int32Array(4096 * 3); codePage[p] = 1; }
1738
+ curP = p;
1739
+ }
1740
+ const k = (a & 4095) * 3;
1741
+ let w = tab[k];
1742
+ if (w === 0) { w = decodeAt(tab, k, a); tab[k] = w; }
1743
+ const hnum = w & 1023;
1744
+ if (hnum === H_STEP) {
1745
+ opEip = a;
1746
+ step();
1747
+ if (stop) { n = i; break; }
1748
+ if (cs16) break;
1749
+ curP = -1; // step() may have loaded a page table afresh (a far jump, a block copy)
1750
+ continue;
1751
+ }
1752
+ eip = a + ((w >>> 10) & 15);
1753
+ const g = (w >>> 14) & 7, e = (w >>> 17) & 7;
1754
+ // THE ADDRESS, formed here once and inline rather than in each handler through a call: the
1755
+ // switch is too big for V8 to inline a helper into every case, and the calls were 5%
1756
+ let ad = 0;
1757
+ if (w < 0) {
1758
+ ad = tab[k + 1];
1759
+ const b = (w >>> 20) & 15;
1760
+ if (b !== 8) ad += R[b];
1761
+ const x = (w >>> 24) & 15;
1762
+ if (x !== 8) ad += R[x] << ((w >>> 28) & 3);
1763
+ ad = (ad + segBase[(w & (1 << 30)) !== 0 ? SS : DS]) | 0;
1764
+ }
1765
+ switch (hnum) {
1766
+ case 1: { const r = alu32(tab[k + 2], R[e], R[g]); if (tab[k + 2] !== 7) R[e] = r; break; } // ALU32_EG_R
1767
+ case 2: { const sub = tab[k + 2]; const r = alu32(sub, rd(ad), R[g]); if (sub !== 7) wd(ad, r); break; }
1768
+ case 3: { const r = alu32(tab[k + 2], R[g], R[e]); if (tab[k + 2] !== 7) R[g] = r; break; }
1769
+ case 4: { const sub = tab[k + 2]; const r = alu32(sub, R[g], rd(ad)); if (sub !== 7) R[g] = r; break; }
1770
+ case 5: { const sub = tab[k + 2]; const r = arith(sub, get8(e), get8(g), S8); if (sub !== 7) set8(e, r); break; }
1771
+ case 6: { const sub = tab[k + 2]; const r = arith(sub, rbx(ad), get8(g), S8); if (sub !== 7) wb(ad, r); break; }
1772
+ case 7: { const sub = tab[k + 2]; const r = arith(sub, get8(g), get8(e), S8); if (sub !== 7) set8(g, r); break; }
1773
+ case 8: { const sub = tab[k + 2]; const r = arith(sub, get8(g), rbx(ad), S8); if (sub !== 7) set8(g, r); break; }
1774
+ case 9: { const r = arith(g, R[EAX] & 0xff, tab[k + 2], S8); if (g !== 7) set8(0, r); break; } // ALU_AL
1775
+ case 10: { const r = alu32(g, R[EAX], tab[k + 2]); if (g !== 7) R[EAX] = r; break; } // ALU_EAX
1776
+ case 11: { const r = alu32(g, R[e], tab[k + 2]); if (g !== 7) R[e] = r; break; } // GRP32_R
1777
+ case 12: { const r = alu32(g, rd(ad), tab[k + 2]); if (g !== 7) wd(ad, r); break; }
1778
+ case 13: { const r = arith(g, get8(e), tab[k + 2], S8); if (g !== 7) set8(e, r); break; } // GRP8_R
1779
+ case 14: { const r = arith(g, rbx(ad), tab[k + 2], S8); if (g !== 7) wb(ad, r); break; }
1780
+ case 15: R[e] = inc(R[e], S32); break; // INC32
1781
+ case 16: R[e] = dec(R[e], S32); break;
1782
+ case 17: push(R[e]); break; // PUSH32
1783
+ case 18: R[e] = pop(); break;
1784
+ case 19: push(tab[k + 2]); break; // PUSHI
1785
+ case 20: if (cond(tab[k + 1])) eip = tab[k + 2] >>> 0; break; // JCC
1786
+ case 21: eip = tab[k + 2] >>> 0; break; // JMP
1787
+ case 22: push((eip - csb) | 0); eip = tab[k + 2] >>> 0; break; // CALL
1788
+ case 23: eip = (pop() + csb) >>> 0; break; // RET
1789
+ case 24: eip = (pop() + csb) >>> 0; R[ESP] += tab[k + 2]; break; // RETN
1790
+ case 25: R[ESP] = R[EBP]; R[EBP] = pop(); break; // LEAVE
1791
+ case 26: logicFlags(R[e] & R[g], S32); break; // TEST32_R
1792
+ case 27: logicFlags(rd(ad) & R[g], S32); break;
1793
+ case 28: logicFlags(get8(e) & get8(g), S8); break;
1794
+ case 29: logicFlags(rbx(ad) & get8(g), S8); break;
1795
+ case 30: set8(e, get8(g)); break; // MOV8_EG_R
1796
+ case 31: wb(ad, get8(g)); break;
1797
+ case 32: R[e] = R[g]; break; // MOV32_EG_R
1798
+ case 33: { // MOV32_EG_M
1799
+ // the common case inline: an aligned write to plain memory, not video, not cached code
1800
+ const x = ad & AMASK;
1801
+ if ((x & 3) === 0 && (x - 0x9fffd) >>> 0 >= 0x20003 && (codePage[x >>> 12] | codePage[(x + 3) >>> 12]) === 0) M32[x >> 2] = R[g];
1802
+ else wd(ad, R[g]);
1803
+ break;
1804
+ }
1805
+ case 34: set8(g, get8(e)); break; // MOV8_GE_R
1806
+ case 35: { const x = ad & AMASK; set8(g, (x - 0xa0000) >>> 0 < 0x20000 ? bus.vgaRead(x) : M[x]); break; } // MOV8_GE_M
1807
+ case 36: R[g] = R[e]; break; // MOV32_GE_R
1808
+ case 37: { const x = ad & AMASK; R[g] = (x & 3) === 0 ? M32[x >> 2] : rd(ad); break; } // MOV32_GE_M
1809
+ case 38: R[g] = (ad - segBase[(w & (1 << 30)) !== 0 ? SS : DS]) | 0; break; // LEA
1810
+ case 39: break; // NOP
1811
+ case 40: R[EAX] = (R[EAX] << 16) >> 16; break; // CWDE
1812
+ case 41: R[EDX] = R[EAX] >> 31; break; // CDQ
1813
+ case 42: set8(0, rbx((tab[k + 2] + segBase[DS]) | 0)); break; // A0
1814
+ case 43: R[EAX] = rd((tab[k + 2] + segBase[DS]) | 0); break; // A1
1815
+ case 44: wb((tab[k + 2] + segBase[DS]) | 0, R[EAX] & 0xff); break; // A2
1816
+ case 45: wd((tab[k + 2] + segBase[DS]) | 0, R[EAX]); break; // A3
1817
+ case 46: set8(e, tab[k + 2]); break; // MOV8_IR
1818
+ case 47: R[e] = tab[k + 2]; break; // MOV32_IR
1819
+ case 48: wb(ad, tab[k + 2]); break; // MOV8_IM
1820
+ case 49: wd(ad, tab[k + 2]); break; // MOV32_IM
1821
+ case 50: R[e] = tab[k + 2]; break; // MOV32_IRM
1822
+ case 51: { const c = tab[k + 2] < 0 ? R[ECX] & 0xff : tab[k + 2]; if ((c & 31) !== 0) R[e] = shift(g, R[e], c, S32); break; }
1823
+ case 52: { const c = tab[k + 2] < 0 ? R[ECX] & 0xff : tab[k + 2]; if ((c & 31) !== 0) wd(ad, shift(g, rd(ad), c, S32)); break; }
1824
+ case 53: { const c = tab[k + 2] < 0 ? R[ECX] & 0xff : tab[k + 2]; if ((c & 31) !== 0) set8(e, shift(g, get8(e), c, S8)); break; }
1825
+ case 54: { const c = tab[k + 2] < 0 ? R[ECX] & 0xff : tab[k + 2]; if ((c & 31) !== 0) wb(ad, shift(g, rbx(ad), c, S8)); break; }
1826
+ case 55: logicFlags(get8(e) & tab[k + 2], S8); break; // TESTI8_R
1827
+ case 56: logicFlags(rbx(ad) & tab[k + 2], S8); break;
1828
+ case 57: logicFlags(R[e] & tab[k + 2], S32); break;
1829
+ case 58: logicFlags(rd(ad) & tab[k + 2], S32); break;
1830
+ case 59: { wd(ad, inc(rd(ad), S32)); break; } // INC32_M
1831
+ case 60: { wd(ad, dec(rd(ad), S32)); break; }
1832
+ case 61: { const t = R[e]; push((eip - csb) | 0); eip = (t + csb) >>> 0; break; } // CALL_R
1833
+ case 62: { const t = rd(ad); push((eip - csb) | 0); eip = (t + csb) >>> 0; break; }
1834
+ case 63: eip = (R[e] + csb) >>> 0; break; // JMP_R
1835
+ case 64: eip = (rd(ad) + csb) >>> 0; break;
1836
+ case 65: push(rd(ad)); break; // PUSH_M
1837
+ case 66: set8(e, cond(tab[k + 2]) ? 1 : 0); break; // SETCC_R
1838
+ case 67: wb(ad, cond(tab[k + 2]) ? 1 : 0); break;
1839
+ case 68: imul2(S32, g, R[g], R[e]); break; // IMUL_R
1840
+ case 69: imul2(S32, g, R[g], rd(ad)); break;
1841
+ case 70: imul2(S32, g, R[e], tab[k + 2]); break; // IMULI_R
1842
+ case 71: imul2(S32, g, rd(ad), tab[k + 2]); break;
1843
+ case 72: R[g] = get8(e); break; // MOVZX8_R
1844
+ case 73: R[g] = rbx(ad); break;
1845
+ case 74: R[g] = R[e] & 0xffff; break; // MOVZX16_R
1846
+ case 75: R[g] = rw(ad); break;
1847
+ case 76: R[g] = (get8(e) << 24) >> 24; break; // MOVSX8_R
1848
+ case 77: R[g] = (rbx(ad) << 24) >> 24; break;
1849
+ case 78: R[g] = (R[e] << 16) >> 16; break; // MOVSX16_R
1850
+ case 79: R[g] = (rw(ad) << 16) >> 16; break;
1851
+ case 80: { opEip = a; const x = tab[k + 2]; fpuCore(x & 255, x >>> 8, 0); break; } // FPU_R
1852
+ case 81: { opEip = a; const x = tab[k + 2]; fpuCore(x & 255, x >>> 8, ad); break; } // FPU_M
1853
+ case 82: { const a = R[e], b = R[g]; const r = ((a) + (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_ADD; R[e] = r; break; }
1854
+ case 83: { const a = rd(ad), b = R[g]; const r = ((a) + (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_ADD; wd(ad, r); break; }
1855
+ case 84: { const a = R[g], b = R[e]; const r = ((a) + (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_ADD; R[g] = r; break; }
1856
+ case 85: { const a = R[g], b = rd(ad); const r = ((a) + (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_ADD; R[g] = r; break; }
1857
+ case 86: { const a = R[e], b = tab[k + 2]; const r = ((a) + (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_ADD; R[e] = r; break; }
1858
+ case 87: { const a = rd(ad), b = tab[k + 2]; const r = ((a) + (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_ADD; wd(ad, r); break; }
1859
+ case 88: { const a = R[EAX], b = tab[k + 2]; const r = ((a) + (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_ADD; R[EAX] = r; break; }
1860
+ case 89: { const a = R[e], b = R[g]; const r = (a) | (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[e] = r; break; }
1861
+ case 90: { const a = rd(ad), b = R[g]; const r = (a) | (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; wd(ad, r); break; }
1862
+ case 91: { const a = R[g], b = R[e]; const r = (a) | (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[g] = r; break; }
1863
+ case 92: { const a = R[g], b = rd(ad); const r = (a) | (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[g] = r; break; }
1864
+ case 93: { const a = R[e], b = tab[k + 2]; const r = (a) | (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[e] = r; break; }
1865
+ case 94: { const a = rd(ad), b = tab[k + 2]; const r = (a) | (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; wd(ad, r); break; }
1866
+ case 95: { const a = R[EAX], b = tab[k + 2]; const r = (a) | (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[EAX] = r; break; }
1867
+ case 96: { const a = R[e], b = R[g]; const r = (a) & (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[e] = r; break; }
1868
+ case 97: { const a = rd(ad), b = R[g]; const r = (a) & (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; wd(ad, r); break; }
1869
+ case 98: { const a = R[g], b = R[e]; const r = (a) & (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[g] = r; break; }
1870
+ case 99: { const a = R[g], b = rd(ad); const r = (a) & (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[g] = r; break; }
1871
+ case 100: { const a = R[e], b = tab[k + 2]; const r = (a) & (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[e] = r; break; }
1872
+ case 101: { const a = rd(ad), b = tab[k + 2]; const r = (a) & (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; wd(ad, r); break; }
1873
+ case 102: { const a = R[EAX], b = tab[k + 2]; const r = (a) & (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[EAX] = r; break; }
1874
+ case 103: { const a = R[e], b = R[g]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; R[e] = r; break; }
1875
+ case 104: { const a = rd(ad), b = R[g]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; wd(ad, r); break; }
1876
+ case 105: { const a = R[g], b = R[e]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; R[g] = r; break; }
1877
+ case 106: { const a = R[g], b = rd(ad); const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; R[g] = r; break; }
1878
+ case 107: { const a = R[e], b = tab[k + 2]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; R[e] = r; break; }
1879
+ case 108: { const a = rd(ad), b = tab[k + 2]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; wd(ad, r); break; }
1880
+ case 109: { const a = R[EAX], b = tab[k + 2]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; R[EAX] = r; break; }
1881
+ case 110: { const a = R[e], b = R[g]; const r = (a) ^ (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[e] = r; break; }
1882
+ case 111: { const a = rd(ad), b = R[g]; const r = (a) ^ (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; wd(ad, r); break; }
1883
+ case 112: { const a = R[g], b = R[e]; const r = (a) ^ (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[g] = r; break; }
1884
+ case 113: { const a = R[g], b = rd(ad); const r = (a) ^ (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[g] = r; break; }
1885
+ case 114: { const a = R[e], b = tab[k + 2]; const r = (a) ^ (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[e] = r; break; }
1886
+ case 115: { const a = rd(ad), b = tab[k + 2]; const r = (a) ^ (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; wd(ad, r); break; }
1887
+ case 116: { const a = R[EAX], b = tab[k + 2]; const r = (a) ^ (b); LF[2] = r; fSz = S32; fOp = F_LOGIC; R[EAX] = r; break; }
1888
+ case 117: { const a = R[e], b = R[g]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; break; }
1889
+ case 118: { const a = rd(ad), b = R[g]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; break; }
1890
+ case 119: { const a = R[g], b = R[e]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; break; }
1891
+ case 120: { const a = R[g], b = rd(ad); const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; break; }
1892
+ case 121: { const a = R[e], b = tab[k + 2]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; break; }
1893
+ case 122: { const a = rd(ad), b = tab[k + 2]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; break; }
1894
+ case 123: { const a = R[EAX], b = tab[k + 2]; const r = ((a) - (b)) | 0; LF[0] = a; LF[1] = b; LF[2] = r; fSz = S32; fOp = F_SUB; break; }
1895
+ case 124: { sI32[0] = rd(ad); fpush(sF32[0]); break; }
1896
+ case 125: { sI32[0] = rd(ad); sI32[1] = rd(ad + 4); fpush(sF64[0]); break; }
1897
+ case 126: { sF32[0] = ST[ftop]; wd(ad, sI32[0]); break; }
1898
+ case 127: { sF32[0] = fpop(); wd(ad, sI32[0]); break; }
1899
+ case 128: { sF64[0] = ST[ftop]; wd(ad, sI32[0]); wd(ad + 4, sI32[1]); break; }
1900
+ case 129: { sF64[0] = fpop(); wd(ad, sI32[0]); wd(ad + 4, sI32[1]); break; }
1901
+ case 130: fpush(rd(ad)); break;
1902
+ case 131: { writeReal(ad, K_I32, fpop()); break; }
1903
+ case 132: { sI32[0] = rd(ad); const v = sF32[0], x = ST[ftop]; ST[ftop] = x + v; break; }
1904
+ case 138: { sI32[0] = rd(ad); sI32[1] = rd(ad + 4); const v = sF64[0], x = ST[ftop]; ST[ftop] = x + v; break; }
1905
+ case 148: { const v = ST[(ftop + e) & 7], x = ST[ftop]; ST[ftop] = x + v; break; }
1906
+ case 154: { const j = (ftop + e) & 7, y = ST[j], x = ST[ftop]; ST[j] = y + x; fpop(); break; }
1907
+ case 133: { sI32[0] = rd(ad); const v = sF32[0], x = ST[ftop]; ST[ftop] = x * v; break; }
1908
+ case 139: { sI32[0] = rd(ad); sI32[1] = rd(ad + 4); const v = sF64[0], x = ST[ftop]; ST[ftop] = x * v; break; }
1909
+ case 149: { const v = ST[(ftop + e) & 7], x = ST[ftop]; ST[ftop] = x * v; break; }
1910
+ case 155: { const j = (ftop + e) & 7, y = ST[j], x = ST[ftop]; ST[j] = y * x; fpop(); break; }
1911
+ case 134: { sI32[0] = rd(ad); const v = sF32[0], x = ST[ftop]; ST[ftop] = x - v; break; }
1912
+ case 140: { sI32[0] = rd(ad); sI32[1] = rd(ad + 4); const v = sF64[0], x = ST[ftop]; ST[ftop] = x - v; break; }
1913
+ case 150: { const v = ST[(ftop + e) & 7], x = ST[ftop]; ST[ftop] = x - v; break; }
1914
+ case 156: { const j = (ftop + e) & 7, y = ST[j], x = ST[ftop]; ST[j] = x - y; fpop(); break; }
1915
+ case 135: { sI32[0] = rd(ad); const v = sF32[0], x = ST[ftop]; ST[ftop] = v - x; break; }
1916
+ case 141: { sI32[0] = rd(ad); sI32[1] = rd(ad + 4); const v = sF64[0], x = ST[ftop]; ST[ftop] = v - x; break; }
1917
+ case 151: { const v = ST[(ftop + e) & 7], x = ST[ftop]; ST[ftop] = v - x; break; }
1918
+ case 157: { const j = (ftop + e) & 7, y = ST[j], x = ST[ftop]; ST[j] = y - x; fpop(); break; }
1919
+ case 136: { sI32[0] = rd(ad); const v = sF32[0], x = ST[ftop]; ST[ftop] = x / v; break; }
1920
+ case 142: { sI32[0] = rd(ad); sI32[1] = rd(ad + 4); const v = sF64[0], x = ST[ftop]; ST[ftop] = x / v; break; }
1921
+ case 152: { const v = ST[(ftop + e) & 7], x = ST[ftop]; ST[ftop] = x / v; break; }
1922
+ case 158: { const j = (ftop + e) & 7, y = ST[j], x = ST[ftop]; ST[j] = x / y; fpop(); break; }
1923
+ case 137: { sI32[0] = rd(ad); const v = sF32[0], x = ST[ftop]; ST[ftop] = v / x; break; }
1924
+ case 143: { sI32[0] = rd(ad); sI32[1] = rd(ad + 4); const v = sF64[0], x = ST[ftop]; ST[ftop] = v / x; break; }
1925
+ case 153: { const v = ST[(ftop + e) & 7], x = ST[ftop]; ST[ftop] = v / x; break; }
1926
+ case 159: { const j = (ftop + e) & 7, y = ST[j], x = ST[ftop]; ST[j] = y / x; fpop(); break; }
1927
+ case 144: { const j = (ftop + e) & 7, t = ST[ftop]; ST[ftop] = ST[j]; ST[j] = t; break; }
1928
+ case 145: fpush(ST[(ftop + e) & 7]); break;
1929
+ case 146: ST[(ftop + e) & 7] = ST[ftop]; break;
1930
+ case 147: ST[(ftop + e) & 7] = ST[ftop]; fpop(); break;
1931
+ case 160: R[EAX] = (R[EAX] & ~0xffff) | fswWord(); break;
1932
+ case 161: ST[ftop] = -ST[ftop]; break;
1933
+ case 162: ST[ftop] = Math.abs(ST[ftop]); break;
1934
+ case 163: fpush(1); break;
1935
+ case 164: fpush(0); break;
1936
+ case 165: { sI32[0] = rd(ad); fcompare(ST[ftop], sF32[0]); fpop(); break; }
1937
+ case 166: fcompare(ST[ftop], ST[(ftop + e) & 7]); break;
1938
+ case 167: fcompare(ST[ftop], ST[(ftop + e) & 7]); fpop(); break;
1939
+ case 168: fcompare(ST[ftop], ST[(ftop + 1) & 7]); fpop(); fpop(); break;
1940
+ default: opEip = a; throw new CpuFault(`cache handler ${hnum}`, a);
1941
+ }
1942
+ }
1943
+ } catch (e) {
1944
+ if (e instanceof DivFault) { eip = opEip; interrupt(0, opEip); continue; }
1945
+ eip = opEip;
1946
+ cycles += i - 1;
1947
+ throw e;
1948
+ }
1949
+ }
1950
+ cycles += i;
1951
+ return i;
1952
+ }
1953
+
1954
+ /** One instruction through the interpreter alone (tests, and the lock-step against the cache). */
1955
+ function stepUncached() { opEip = eip; step(); cycles++; }
1956
+
1957
+ const api = {
1958
+ R, seg, segBase, M,
1959
+ get eip() { return eip; }, set eip(v) { eip = v >>> 0; },
1960
+ get opEip() { return opEip; },
1961
+ get halted() { return halted; }, set halted(v) { halted = v; },
1962
+ get cycles() { return cycles; }, set cycles(v) { cycles = v; },
1963
+ get flags() { return getFlags(); }, set flags(v) { setFlags(v); },
1964
+ get IF() { return (flags & 0x200) !== 0; },
1965
+ get realMode() { return realMode; },
1966
+ set realMode(v) { realMode = !!v; for (let i = 0; i < 6; i++) loadSeg(i, seg[i]); },
1967
+ setCF(c) { setCFbit(c ? 1 : 0); },
1968
+ setZF(z) { zfSet(z ? 1 : 0); },
1969
+ loadSeg, push, pop, rb, rw, rd, wb, ww, wd,
1970
+ interrupt(n) { halted = false; interrupt(n, eip); },
1971
+ stop() { stop = true; },
1972
+ run, step: stepUncached,
1973
+ /** The machine wrote memory behind the CPU's back: drop any decoded code in that range. */
1974
+ invalidate,
1975
+ get fpu() { return { st: Array.from({ length: 8 }, (_, k) => ST[(ftop + k) & 7]), top: ftop, cw: fcw, sw: fswWord() }; },
1976
+ };
1977
+ return api;
1978
+ }