sandboxedjs 0.1.8 → 0.1.10

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.
package/dist/index.cjs CHANGED
@@ -13328,11 +13328,11 @@ sys ${fmt2(Math.floor(ms * 0.2))}
13328
13328
  if (fn) return await this.callFunction(name, fn, args, assignments, io);
13329
13329
  const builtin = getBuiltin(name);
13330
13330
  if (builtin) {
13331
- const restore = await this.applyTemporaryAssignments(assignments, isSpecialBuiltin(name));
13331
+ const restore2 = await this.applyTemporaryAssignments(assignments, isSpecialBuiltin(name));
13332
13332
  try {
13333
13333
  return await builtin({ shell: this, argv: words, io });
13334
13334
  } finally {
13335
- restore();
13335
+ restore2();
13336
13336
  }
13337
13337
  }
13338
13338
  return await this.runExternal(words, assignments, io);
@@ -13345,7 +13345,7 @@ sys ${fmt2(Math.floor(ms * 0.2))}
13345
13345
  }
13346
13346
  const savedPositional = this.positional;
13347
13347
  const savedName = this.scriptName;
13348
- const restore = await this.applyTemporaryAssignments(assignments, true);
13348
+ const restore2 = await this.applyTemporaryAssignments(assignments, true);
13349
13349
  this.positional = args;
13350
13350
  this.vars.pushScope();
13351
13351
  this.functionDepth++;
@@ -13360,7 +13360,7 @@ sys ${fmt2(Math.floor(ms * 0.2))}
13360
13360
  this.vars.popScope();
13361
13361
  this.positional = savedPositional;
13362
13362
  this.scriptName = savedName;
13363
- restore();
13363
+ restore2();
13364
13364
  }
13365
13365
  }
13366
13366
  async runExternal(words, assignments, io) {
@@ -17074,7 +17074,7 @@ var AwkParser = class {
17074
17074
  /** `print a, b` — a comma list where `>` means redirection, not comparison. */
17075
17075
  parseExprList(noGt) {
17076
17076
  if (this.isOp("(")) {
17077
- const save = this.i;
17077
+ const save2 = this.i;
17078
17078
  this.i++;
17079
17079
  const items = [];
17080
17080
  try {
@@ -17086,7 +17086,7 @@ var AwkParser = class {
17086
17086
  }
17087
17087
  } catch {
17088
17088
  }
17089
- this.i = save;
17089
+ this.i = save2;
17090
17090
  }
17091
17091
  const out = [this.parseTernary(noGt)];
17092
17092
  while (this.eatOp(",")) {
@@ -21565,6 +21565,208 @@ var helpCmd = defineCommand({
21565
21565
  });
21566
21566
  var commands10 = [man, whatis, apropos, less, helpCmd];
21567
21567
 
21568
+ // src/runtime/child-process-shim.ts
21569
+ var CHILD_PROCESS_SHIM = `
21570
+ (function () {
21571
+ try {
21572
+ var cp;
21573
+ try { cp = require('child_process'); } catch (e) { return; }
21574
+ if (!cp) return;
21575
+ // The module is a proxy that rejects plain assignment, so the "already
21576
+ // patched" flag cannot live on it.
21577
+ if (globalThis.__sbxChildProcessPatched) return;
21578
+ globalThis.__sbxChildProcessPatched = true;
21579
+
21580
+ var fs = require('fs');
21581
+ var path = require('path');
21582
+
21583
+ function head(file) {
21584
+ try {
21585
+ var fd = fs.openSync(file, 'r');
21586
+ var buf = Buffer.alloc(64);
21587
+ var read = fs.readSync(fd, buf, 0, 64, 0);
21588
+ fs.closeSync(fd);
21589
+ return buf.subarray(0, read);
21590
+ } catch (e) { return null; }
21591
+ }
21592
+
21593
+ // Kept in step with src/runtime/native-format.ts and src/runtime/wasm.ts.
21594
+ var MACHINES = { 0x03: 'x86', 0x28: 'ARM', 0x3e: 'x86-64', 0xb7: 'AArch64', 0xf3: 'RISC-V' };
21595
+ function classify(bytes) {
21596
+ if (!bytes || bytes.length < 4) return null;
21597
+ if (bytes[0] === 0x00 && bytes[1] === 0x61 && bytes[2] === 0x73 && bytes[3] === 0x6d) {
21598
+ return 'WebAssembly module';
21599
+ }
21600
+ if (bytes[0] === 0x7f && bytes[1] === 0x45 && bytes[2] === 0x4c && bytes[3] === 0x46) {
21601
+ var machine = bytes.length >= 20 ? bytes.readUInt16LE(18) : 0;
21602
+ return 'ELF ' + (bytes[4] === 2 ? '64-bit' : '32-bit') + ' executable, ' +
21603
+ (MACHINES[machine] || ('machine 0x' + machine.toString(16)));
21604
+ }
21605
+ var magic = bytes.readUInt32BE(0);
21606
+ if (magic === 0xfeedface || magic === 0xfeedfacf || magic === 0xcefaedfe ||
21607
+ magic === 0xcffaedfe || magic === 0xcafebabe) return 'Mach-O executable (macOS)';
21608
+ if (bytes[0] === 0x4d && bytes[1] === 0x5a) return 'PE executable (Windows)';
21609
+ return null;
21610
+ }
21611
+
21612
+ function locate(command) {
21613
+ if (typeof command !== 'string' || command === '') return null;
21614
+ if (command.indexOf('/') >= 0) {
21615
+ try { return fs.existsSync(command) ? command : null; } catch (e) { return null; }
21616
+ }
21617
+ var dirs = String(process.env.PATH || '').split(':');
21618
+ for (var i = 0; i < dirs.length; i++) {
21619
+ if (!dirs[i]) continue;
21620
+ var candidate = path.join(dirs[i], command);
21621
+ try { if (fs.existsSync(candidate)) return candidate; } catch (e) {}
21622
+ }
21623
+ return null;
21624
+ }
21625
+
21626
+ /** null when the command looks runnable; otherwise why it is not. */
21627
+ function diagnose(command) {
21628
+ var found = locate(command);
21629
+ if (found === null) {
21630
+ return {
21631
+ code: 'ENOENT',
21632
+ lines: [
21633
+ command + ': not found.',
21634
+ 'Nothing at that path, and nothing by that name on $PATH. A package whose',
21635
+ 'install step downloads a prebuilt binary will land here, because the',
21636
+ 'download either failed or produced a file this container cannot use.',
21637
+ ],
21638
+ };
21639
+ }
21640
+ var kind = classify(head(found));
21641
+ if (kind === null || kind === 'WebAssembly module') return null;
21642
+ if (kind.indexOf('ELF') === 0) {
21643
+ // The container does run these \u2014 on the emulated CPU, dispatched by the
21644
+ // kernel. What cannot reach that is child_process from inside a Node
21645
+ // script, which is served by the pod's own shell.
21646
+ return {
21647
+ code: 'ENOEXEC',
21648
+ lines: [
21649
+ found + ': ' + kind + '.',
21650
+ 'The container can run this from the shell, on its emulated x86-64 CPU, but',
21651
+ 'child_process inside a Node script cannot reach it. Run it from the shell.',
21652
+ ],
21653
+ };
21654
+ }
21655
+ return {
21656
+ code: 'ENOEXEC',
21657
+ lines: [
21658
+ found + ': ' + kind + '.',
21659
+ 'This container runs x86-64 Linux binaries, JavaScript and WebAssembly. It',
21660
+ 'does not emulate other operating systems, so this file cannot start.',
21661
+ ],
21662
+ };
21663
+ }
21664
+
21665
+ function report(problem) {
21666
+ try {
21667
+ for (var i = 0; i < problem.lines.length; i++) {
21668
+ process.stderr.write(problem.lines[i] + '\\n');
21669
+ }
21670
+ } catch (e) {}
21671
+ }
21672
+
21673
+ /** A plain, writable stand-in for the module, with the wrappers applied. */
21674
+ var replacement = null;
21675
+ function wrapModule(module) {
21676
+ if (replacement) return replacement;
21677
+ var out = {};
21678
+ try {
21679
+ for (var key in module) {
21680
+ try { out[key] = module[key]; } catch (e) {}
21681
+ }
21682
+ } catch (e) {}
21683
+ // Reading through the proxy by name, in case enumeration reported nothing.
21684
+ ['spawn', 'spawnSync', 'exec', 'execSync', 'execFile', 'execFileSync', 'fork', 'ChildProcess']
21685
+ .forEach(function (name) {
21686
+ try { if (out[name] === undefined) out[name] = module[name]; } catch (e) {}
21687
+ });
21688
+
21689
+ var spawn = out.spawn;
21690
+ if (typeof spawn === 'function') {
21691
+ out.spawn = function (command) {
21692
+ var problem = diagnose(command);
21693
+ if (problem) report(problem);
21694
+ var child = spawn.apply(module, arguments);
21695
+ if (problem && child && typeof child.emit === 'function') {
21696
+ // Real Node reports a failed exec through 'error', not 'exit'.
21697
+ // Scripts that listen get their handler; scripts that do not get a
21698
+ // crash, which is the point \u2014 an unnoticed failure is worse.
21699
+ var error = new Error('spawn ' + command + ' ' + problem.code);
21700
+ error.code = problem.code;
21701
+ error.syscall = 'spawn ' + command;
21702
+ error.path = command;
21703
+ process.nextTick(function () {
21704
+ try { child.emit('error', error); } catch (e) {}
21705
+ });
21706
+ }
21707
+ return child;
21708
+ };
21709
+ }
21710
+
21711
+ ['spawnSync', 'execFile', 'execFileSync', 'exec', 'execSync'].forEach(function (name) {
21712
+ var original = out[name];
21713
+ if (typeof original !== 'function') return;
21714
+ out[name] = function (command) {
21715
+ // exec/execSync take a whole command line; the program is its first word.
21716
+ var program = (name === 'exec' || name === 'execSync')
21717
+ ? String(command || '').trim().split(/\\s+/)[0]
21718
+ : command;
21719
+ var problem = diagnose(program);
21720
+ if (problem) report(problem);
21721
+ return original.apply(module, arguments);
21722
+ };
21723
+ });
21724
+
21725
+ replacement = out;
21726
+ return out;
21727
+ }
21728
+
21729
+ function isChildProcess(id) {
21730
+ return id === 'child_process' || id === 'node:child_process';
21731
+ }
21732
+
21733
+ // Preferred: patch the loader itself, so a spawn inside a dependency is
21734
+ // covered too. Not always reachable, hence the fallback below.
21735
+ try {
21736
+ var moduleModule = require('module');
21737
+ var Mod = moduleModule && (moduleModule.Module || moduleModule.default);
21738
+ if (Mod && Mod.prototype && typeof Mod.prototype.require === 'function') {
21739
+ var protoRequire = Mod.prototype.require;
21740
+ Mod.prototype.require = function (id) {
21741
+ var loaded = protoRequire.apply(this, arguments);
21742
+ return isChildProcess(id) ? wrapModule(loaded) : loaded;
21743
+ };
21744
+ }
21745
+ } catch (e) { /* fall through to the local require */ }
21746
+
21747
+ // Always: this script's own require, which is where launcher scripts call in.
21748
+ try {
21749
+ var realRequire = require;
21750
+ require = function (id) {
21751
+ var loaded = realRequire.apply(this, arguments);
21752
+ return isChildProcess(id) ? wrapModule(loaded) : loaded;
21753
+ };
21754
+ // require.main === module, and require.resolve, are load-bearing for many
21755
+ // entry points; carry every property across.
21756
+ for (var prop in realRequire) {
21757
+ try { require[prop] = realRequire[prop]; } catch (e) {}
21758
+ }
21759
+ ['main', 'resolve', 'cache', 'extensions'].forEach(function (prop) {
21760
+ try { if (require[prop] === undefined) require[prop] = realRequire[prop]; } catch (e) {}
21761
+ });
21762
+ } catch (e) { /* nothing else to try */ }
21763
+ } catch (e) {
21764
+ /* The pod may expose child_process as a sealed proxy. Diagnosis is a
21765
+ * courtesy; never let it stop the script it was meant to help. */
21766
+ }
21767
+ })();
21768
+ `;
21769
+
21568
21770
  // src/runtime/node.ts
21569
21771
  init_path();
21570
21772
  var NODE_VERSION = "v22.12.0";
@@ -21600,7 +21802,7 @@ function buildPrelude(scriptPath, argv, stdinPath) {
21600
21802
  const argvLiteral = JSON.stringify(["/usr/bin/node", scriptPath, ...argv]);
21601
21803
  const pathLiteral = JSON.stringify(scriptPath);
21602
21804
  const stdinLiteral = JSON.stringify(stdinPath);
21603
- return `${BUFFER_COMPAT_SHIM}
21805
+ return `${BUFFER_COMPAT_SHIM}${CHILD_PROCESS_SHIM}
21604
21806
  process.argv = ${argvLiteral};
21605
21807
  try {
21606
21808
  __filename = ${pathLiteral};
@@ -21823,7 +22025,7 @@ __sbxStdin.isTTY = false;
21823
22025
  __sbxStdin.setRawMode = function () { return this; };
21824
22026
  try { Object.defineProperty(process, 'stdin', { value: __sbxStdin, configurable: true, writable: true }); } catch (e) { process.stdin = __sbxStdin; }
21825
22027
  ` : "";
21826
- return `${BUFFER_COMPAT_SHIM}process.argv = ${JSON.stringify(argv)};
22028
+ return `${BUFFER_COMPAT_SHIM}${CHILD_PROCESS_SHIM}process.argv = ${JSON.stringify(argv)};
21827
22029
  ${prelude}${source}
21828
22030
  `;
21829
22031
  }
@@ -22796,14 +22998,14 @@ var WasiPreview1 = class {
22796
22998
  this.randomFill = opts.randomFill ?? ((buffer) => {
22797
22999
  for (let i = 0; i < buffer.length; i++) buffer[i] = Math.floor(Math.random() * 256);
22798
23000
  });
22799
- const blank = { pos: 0, data: new Uint8Array(0), dirty: false, fdflags: 0, rights: RIGHTS_ALL };
22800
- this.fds.set(0, { kind: "stdin", path: "/dev/stdin", ...blank, data: opts.stdin ?? new Uint8Array(0) });
22801
- this.fds.set(1, { kind: "stdout", path: "/dev/stdout", ...blank });
22802
- this.fds.set(2, { kind: "stderr", path: "/dev/stderr", ...blank });
23001
+ const blank2 = { pos: 0, data: new Uint8Array(0), dirty: false, fdflags: 0, rights: RIGHTS_ALL };
23002
+ this.fds.set(0, { kind: "stdin", path: "/dev/stdin", ...blank2, data: opts.stdin ?? new Uint8Array(0) });
23003
+ this.fds.set(1, { kind: "stdout", path: "/dev/stdout", ...blank2 });
23004
+ this.fds.set(2, { kind: "stderr", path: "/dev/stderr", ...blank2 });
22803
23005
  const preopens = opts.preopens ?? { "/": "/", ".": opts.cwd };
22804
23006
  for (const [name, path] of Object.entries(preopens)) {
22805
23007
  const fd = this.nextFd++;
22806
- this.fds.set(fd, { kind: "dir", path, ...blank, preopen: name });
23008
+ this.fds.set(fd, { kind: "dir", path, ...blank2, preopen: name });
22807
23009
  }
22808
23010
  }
22809
23011
  /** Give the host access to the instantiated module's memory. */
@@ -23462,6 +23664,9 @@ async function compile(ctx, path) {
23462
23664
  compiled2.set(path, { mtimeMs: st.mtimeMs, size: st.size, module });
23463
23665
  return module;
23464
23666
  }
23667
+ function clearWasmCache() {
23668
+ compiled2.clear();
23669
+ }
23465
23670
  async function runWasi(ctx, path, argv0) {
23466
23671
  let module;
23467
23672
  try {
@@ -23633,50 +23838,13 @@ function wasmCommands() {
23633
23838
 
23634
23839
  // src/runtime/native-format.ts
23635
23840
  init_path();
23636
- var ELF_MACHINES = {
23637
- 2: "SPARC",
23638
- 3: "x86",
23639
- 8: "MIPS",
23640
- 20: "PowerPC",
23641
- 21: "PowerPC 64-bit",
23642
- 22: "S/390",
23643
- 40: "ARM",
23644
- 42: "SuperH",
23645
- 50: "IA-64",
23646
- 62: "x86-64",
23647
- 183: "AArch64",
23648
- 243: "RISC-V"
23649
- };
23650
- var ELF_TYPES = {
23651
- 1: "relocatable",
23652
- 2: "executable",
23653
- 3: "shared object",
23654
- 4: "core dump"
23655
- };
23656
- function describeElf(head2) {
23657
- const bits = head2[4] === 2 ? "64-bit" : "32-bit";
23658
- const endian = head2[5] === 2 ? "MSB" : "LSB";
23659
- if (head2.length < 20) return `ELF ${bits} ${endian} object`;
23660
- const view = new DataView(head2.buffer, head2.byteOffset, head2.byteLength);
23661
- const little = head2[5] !== 2;
23662
- const type = ELF_TYPES[view.getUint16(16, little)] ?? "object";
23663
- const machine = view.getUint16(18, little);
23664
- return `ELF ${bits} ${endian} ${type}, ${ELF_MACHINES[machine] ?? `machine 0x${machine.toString(16)}`}`;
23665
- }
23666
23841
  var SUGGEST_WASM = [
23667
- "Nothing in this container can execute machine code \u2014 there is no CPU here to",
23668
- "run it on, only JavaScript and WebAssembly.",
23842
+ "This container runs x86-64 Linux binaries on an emulated CPU, plus JavaScript",
23843
+ "and WebAssembly natively. It does not emulate other operating systems.",
23669
23844
  "",
23670
- "If the tool has a WebAssembly build, it will run here as-is: drop the .wasm",
23671
- "file in /usr/local/bin, chmod +x it, and call it by name."
23845
+ "A Linux build of the same tool will run here. So will a WebAssembly build:",
23846
+ "drop the .wasm file in /usr/local/bin, chmod +x it, and call it by name."
23672
23847
  ];
23673
- var elfFormat = {
23674
- name: "elf",
23675
- priority: 20,
23676
- matches: (head2) => head2.length >= 4 && head2[0] === 127 && head2[1] === 69 && head2[2] === 76 && head2[3] === 70,
23677
- describe: describeElf,
23678
- explain: (head2, path) => [`${basename(path)}: ${describeElf(head2)}.`, ...SUGGEST_WASM]
23679
- };
23680
23848
  var machoFormat = {
23681
23849
  name: "mach-o",
23682
23850
  priority: 20,
@@ -23697,146 +23865,3149 @@ var peFormat = {
23697
23865
  explain: (_head, path) => [`${basename(path)}: PE executable (Windows native binary).`, ...SUGGEST_WASM]
23698
23866
  };
23699
23867
  function nativeFormats() {
23700
- return [elfFormat, machoFormat, peFormat];
23868
+ return [machoFormat, peFormat];
23701
23869
  }
23702
- var DECLARES_EXPORTS = /(?:^|[\s;{)])(?:var|let|const|function|class)\s+exports\b/;
23703
- var NAME = "exports";
23704
- function renameShadowedExports(source) {
23705
- if (!DECLARES_EXPORTS.test(source)) return null;
23706
- let ast;
23707
- try {
23708
- ast = acorn.parse(source, {
23709
- ecmaVersion: "latest",
23710
- sourceType: "module",
23711
- allowAwaitOutsideFunction: true,
23712
- allowHashBang: true
23713
- });
23714
- } catch {
23715
- return null;
23870
+
23871
+ // src/runtime/x86/cpu.ts
23872
+ var RAX = 0;
23873
+ var RCX = 1;
23874
+ var RDX = 2;
23875
+ var RBX = 3;
23876
+ var RSP = 4;
23877
+ var RBP = 5;
23878
+ var RSI = 6;
23879
+ var RDI = 7;
23880
+ var R8 = 8;
23881
+ var R9 = 9;
23882
+ var R10 = 10;
23883
+ var R11 = 11;
23884
+ var MASK64 = (1n << 64n) - 1n;
23885
+ function maskFor(size) {
23886
+ return (1n << BigInt(size * 8)) - 1n;
23887
+ }
23888
+ function signed(value, size) {
23889
+ const bits = BigInt(size * 8);
23890
+ const sign = 1n << bits - 1n;
23891
+ return value & sign ? value - (1n << bits) : value;
23892
+ }
23893
+ var ProcessExit = class extends Error {
23894
+ constructor(code) {
23895
+ super(`exit ${code}`);
23896
+ this.code = code;
23897
+ this.name = "ProcessExit";
23716
23898
  }
23717
- const body = ast.body;
23718
- const isModule = body.some(
23719
- (node2) => node2.type.startsWith("Import") || node2.type.startsWith("Export")
23720
- );
23721
- if (!isModule) return null;
23722
- const programScope = { bindsExports: hoistedNames(body).has(NAME), parent: null };
23723
- if (!programScope.bindsExports) return null;
23724
- const targets = [];
23725
- let bail = false;
23726
- visit(ast, programScope);
23727
- if (bail || targets.length === 0) return null;
23728
- const replacement = freshName(source);
23729
- let out = source;
23730
- for (const target of [...targets].sort((a, b) => b.start - a.start)) {
23731
- const text = target.shorthand ? `${NAME}: ${replacement}` : replacement;
23732
- out = out.slice(0, target.start) + text + out.slice(target.end);
23899
+ code;
23900
+ };
23901
+ var UnsupportedInstruction = class extends Error {
23902
+ constructor(opcode, rip) {
23903
+ super(`unsupported instruction ${opcode} at 0x${rip.toString(16)}`);
23904
+ this.opcode = opcode;
23905
+ this.rip = rip;
23906
+ this.name = "UnsupportedInstruction";
23907
+ }
23908
+ opcode;
23909
+ rip;
23910
+ };
23911
+ var Cpu = class _Cpu {
23912
+ memory;
23913
+ regs = new BigUint64Array(16);
23914
+ rip = 0;
23915
+ // Flags, held individually rather than packed: every arithmetic instruction
23916
+ // touches several, and packing would mean masking on each one.
23917
+ cf = false;
23918
+ zf = false;
23919
+ sf = false;
23920
+ of = false;
23921
+ pf = false;
23922
+ af = false;
23923
+ df = false;
23924
+ /** `%fs` base, which is where thread-local storage lives on x86-64 Linux. */
23925
+ fsBase = 0;
23926
+ gsBase = 0;
23927
+ /**
23928
+ * The sixteen 128-bit vector registers, split into halves.
23929
+ *
23930
+ * Not optional extras: a compiler zeroes memory with `xorps`/`movups` and
23931
+ * finds a NUL byte with `pcmpeqb`/`pmovmskb`, so ordinary code reaches for
23932
+ * these within the first hundred instructions. Only the moves, the bitwise
23933
+ * operations and the byte compares are implemented — the arithmetic that
23934
+ * makes SSE interesting for numerics is not, and raises the usual error.
23935
+ */
23936
+ xmmLo = new BigUint64Array(16);
23937
+ xmmHi = new BigUint64Array(16);
23938
+ instructions = 0;
23939
+ limit;
23940
+ onSyscall;
23941
+ /* Decoder state for the instruction being executed. */
23942
+ rex = 0;
23943
+ hasRex = false;
23944
+ opSize = 4;
23945
+ addrSize = 8;
23946
+ rep = 0;
23947
+ // 0xf3 or 0xf2 when present
23948
+ opcodeStart = 0;
23949
+ constructor(opts) {
23950
+ this.memory = opts.memory;
23951
+ this.onSyscall = opts.onSyscall;
23952
+ this.limit = opts.instructionLimit ?? 2e9;
23733
23953
  }
23734
- return out;
23735
- function visit(node2, scope) {
23736
- if (bail) return;
23737
- let childScope = scope;
23738
- let skip = NOTHING;
23739
- switch (node2.type) {
23740
- case "FunctionDeclaration":
23741
- case "FunctionExpression":
23742
- case "ArrowFunctionExpression": {
23743
- const names = /* @__PURE__ */ new Set();
23744
- for (const param of node2.params ?? []) collectPattern(param, names);
23745
- if (isExports(node2.id) && node2.type === "FunctionExpression") names.add(NAME);
23746
- const fnBody = node2.body;
23747
- if (fnBody?.type === "BlockStatement") {
23748
- for (const name of hoistedNames(fnBody.body)) names.add(name);
23749
- }
23750
- childScope = { bindsExports: names.has(NAME), parent: scope };
23751
- break;
23752
- }
23753
- case "CatchClause": {
23754
- const names = /* @__PURE__ */ new Set();
23755
- if (node2.param) collectPattern(node2.param, names);
23756
- childScope = { bindsExports: names.has(NAME), parent: scope };
23757
- break;
23954
+ get(index) {
23955
+ return this.regs[index];
23956
+ }
23957
+ set(index, value) {
23958
+ this.regs[index] = value & MASK64;
23959
+ }
23960
+ /* ── instruction stream ─────────────────────────────────────────────────── */
23961
+ fetch8() {
23962
+ return this.memory.read8(this.rip++);
23963
+ }
23964
+ fetch(size) {
23965
+ const value = this.memory.read(this.rip, size);
23966
+ this.rip += size;
23967
+ return value;
23968
+ }
23969
+ fetchSigned(size) {
23970
+ return signed(this.fetch(size), size);
23971
+ }
23972
+ /* ── register file ──────────────────────────────────────────────────────── */
23973
+ /**
23974
+ * Read a register at a given width.
23975
+ *
23976
+ * The 8-bit encodings are the awkward part: with no REX prefix, indices 4–7
23977
+ * name AH/CH/DH/BH — the *high* byte of the first four registers — while any
23978
+ * REX prefix at all reassigns them to SPL/BPL/SIL/DIL.
23979
+ */
23980
+ readReg(index, size) {
23981
+ if (size === 1 && !this.hasRex && index >= 4 && index < 8) {
23982
+ return this.regs[index - 4] >> 8n & 0xffn;
23983
+ }
23984
+ return this.regs[index] & maskFor(size);
23985
+ }
23986
+ writeReg(index, size, value) {
23987
+ if (size === 1 && !this.hasRex && index >= 4 && index < 8) {
23988
+ const target = index - 4;
23989
+ this.regs[target] = this.regs[target] & ~0xff00n | (value & 0xffn) << 8n;
23990
+ return;
23991
+ }
23992
+ if (size === 8) {
23993
+ this.regs[index] = value & MASK64;
23994
+ } else if (size === 4) {
23995
+ this.regs[index] = value & 0xffffffffn;
23996
+ } else {
23997
+ const mask = maskFor(size);
23998
+ this.regs[index] = this.regs[index] & ~mask | value & mask;
23999
+ }
24000
+ }
24001
+ /* ── ModRM ──────────────────────────────────────────────────────────────── */
24002
+ modrm = {
24003
+ mod: 0,
24004
+ reg: 0,
24005
+ rm: 0,
24006
+ address: 0,
24007
+ isReg: false
24008
+ };
24009
+ /** Decode a ModRM byte, resolving the effective address when there is one. */
24010
+ decodeModrm() {
24011
+ const byte = this.fetch8();
24012
+ const mod = byte >> 6;
24013
+ const reg = byte >> 3 & 7 | ((this.rex & 4) !== 0 ? 8 : 0);
24014
+ let rm2 = byte & 7 | ((this.rex & 1) !== 0 ? 8 : 0);
24015
+ const segment = this.takeSegment();
24016
+ if (mod === 3) {
24017
+ this.modrm = { mod, reg, rm: rm2, address: 0, isReg: true };
24018
+ return;
24019
+ }
24020
+ let address = segment;
24021
+ const rmLow = byte & 7;
24022
+ if (rmLow === 4) {
24023
+ const sib = this.fetch8();
24024
+ const scale = 1 << (sib >> 6);
24025
+ const indexReg = sib >> 3 & 7 | ((this.rex & 2) !== 0 ? 8 : 0);
24026
+ const baseReg = sib & 7 | ((this.rex & 1) !== 0 ? 8 : 0);
24027
+ if (indexReg !== 4) address += Number(BigInt.asIntN(64, this.regs[indexReg])) * scale;
24028
+ if ((sib & 7) === 5 && mod === 0) {
24029
+ address += Number(this.fetchSigned(4));
24030
+ } else {
24031
+ address += Number(BigInt.asIntN(64, this.regs[baseReg]));
23758
24032
  }
23759
- case "ClassDeclaration":
23760
- case "ClassExpression":
23761
- if (isExports(node2.id) && node2.type === "ClassExpression") {
23762
- childScope = { bindsExports: true, parent: scope };
23763
- }
23764
- break;
23765
- case "BlockStatement":
23766
- case "StaticBlock":
23767
- if (node2 !== ast.body) {
23768
- childScope = {
23769
- bindsExports: blockNames(node2.body).has(NAME),
23770
- parent: scope
23771
- };
23772
- }
23773
- break;
23774
- case "ForStatement":
23775
- case "ForInStatement":
23776
- case "ForOfStatement": {
23777
- const head2 = node2.init ?? node2.left;
23778
- if (head2?.type === "VariableDeclaration" && head2.kind !== "var") {
23779
- const names = /* @__PURE__ */ new Set();
23780
- for (const declarator of head2.declarations) {
23781
- collectPattern(declarator.id, names);
24033
+ rm2 = baseReg;
24034
+ } else if (rmLow === 5 && mod === 0) {
24035
+ const displacement = Number(this.fetchSigned(4));
24036
+ this.modrm = { mod, reg, rm: -1, address: displacement + segment, isReg: false };
24037
+ this.ripRelative = true;
24038
+ return;
24039
+ } else {
24040
+ address += Number(BigInt.asIntN(64, this.regs[rm2]));
24041
+ }
24042
+ if (mod === 1) address += Number(this.fetchSigned(1));
24043
+ else if (mod === 2) address += Number(this.fetchSigned(4));
24044
+ this.modrm = { mod, reg, rm: rm2, address, isReg: false };
24045
+ }
24046
+ ripRelative = false;
24047
+ /**
24048
+ * Finish a RIP-relative address once the whole instruction has been decoded.
24049
+ * Called by the operand accessors, which run after any immediate is read.
24050
+ */
24051
+ effectiveAddress() {
24052
+ if (this.ripRelative) {
24053
+ this.ripRelative = false;
24054
+ this.modrm.address += this.rip;
24055
+ }
24056
+ return this.modrm.address;
24057
+ }
24058
+ readRm(size) {
24059
+ if (this.modrm.isReg) return this.readReg(this.modrm.rm, size);
24060
+ return this.memory.read(this.effectiveAddress(), size);
24061
+ }
24062
+ writeRm(size, value) {
24063
+ if (this.modrm.isReg) this.writeReg(this.modrm.rm, size, value);
24064
+ else this.memory.write(this.effectiveAddress(), size, value & maskFor(size));
24065
+ }
24066
+ /* ── flags ──────────────────────────────────────────────────────────────── */
24067
+ setLogicFlags(result, size) {
24068
+ const masked = result & maskFor(size);
24069
+ this.zf = masked === 0n;
24070
+ this.sf = signed(masked, size) < 0n;
24071
+ this.cf = false;
24072
+ this.of = false;
24073
+ this.pf = parity(masked);
24074
+ }
24075
+ setAddFlags(a, b, result, size) {
24076
+ const mask = maskFor(size);
24077
+ const masked = result & mask;
24078
+ this.zf = masked === 0n;
24079
+ this.sf = signed(masked, size) < 0n;
24080
+ this.cf = result > mask;
24081
+ const sa = signed(a & mask, size) < 0n;
24082
+ const sb = signed(b & mask, size) < 0n;
24083
+ const sr = signed(masked, size) < 0n;
24084
+ this.of = sa === sb && sr !== sa;
24085
+ this.af = ((a ^ b ^ masked) & 0x10n) !== 0n;
24086
+ this.pf = parity(masked);
24087
+ }
24088
+ setSubFlags(a, b, result, size) {
24089
+ const mask = maskFor(size);
24090
+ const masked = result & mask;
24091
+ this.zf = masked === 0n;
24092
+ this.sf = signed(masked, size) < 0n;
24093
+ this.cf = (a & mask) < (b & mask);
24094
+ const sa = signed(a & mask, size) < 0n;
24095
+ const sb = signed(b & mask, size) < 0n;
24096
+ const sr = signed(masked, size) < 0n;
24097
+ this.of = sa !== sb && sr !== sa;
24098
+ this.af = ((a ^ b ^ masked) & 0x10n) !== 0n;
24099
+ this.pf = parity(masked);
24100
+ }
24101
+ /** Evaluate one of the sixteen condition codes shared by Jcc/SETcc/CMOVcc. */
24102
+ condition(code) {
24103
+ switch (code) {
24104
+ case 0:
24105
+ return this.of;
24106
+ case 1:
24107
+ return !this.of;
24108
+ case 2:
24109
+ return this.cf;
24110
+ case 3:
24111
+ return !this.cf;
24112
+ case 4:
24113
+ return this.zf;
24114
+ case 5:
24115
+ return !this.zf;
24116
+ case 6:
24117
+ return this.cf || this.zf;
24118
+ case 7:
24119
+ return !this.cf && !this.zf;
24120
+ case 8:
24121
+ return this.sf;
24122
+ case 9:
24123
+ return !this.sf;
24124
+ case 10:
24125
+ return this.pf;
24126
+ case 11:
24127
+ return !this.pf;
24128
+ case 12:
24129
+ return this.sf !== this.of;
24130
+ case 13:
24131
+ return this.sf === this.of;
24132
+ case 14:
24133
+ return this.zf || this.sf !== this.of;
24134
+ default:
24135
+ return !this.zf && this.sf === this.of;
24136
+ }
24137
+ }
24138
+ /* ── stack ──────────────────────────────────────────────────────────────── */
24139
+ push(value) {
24140
+ const rsp = Number(this.regs[RSP]) - 8;
24141
+ this.regs[RSP] = BigInt(rsp);
24142
+ this.memory.write(rsp, 8, value & MASK64);
24143
+ }
24144
+ pop() {
24145
+ const rsp = Number(this.regs[RSP]);
24146
+ const value = this.memory.read(rsp, 8);
24147
+ this.regs[RSP] = BigInt(rsp + 8);
24148
+ return value;
24149
+ }
24150
+ /* ── main loop ──────────────────────────────────────────────────────────── */
24151
+ /**
24152
+ * Run until the guest exits.
24153
+ *
24154
+ * Yields to the event loop periodically: the interpreter is synchronous, and
24155
+ * without this a long-running guest would block the host completely.
24156
+ */
24157
+ async run() {
24158
+ for (; ; ) {
24159
+ try {
24160
+ for (let slice = 0; slice < 2e5; slice++) {
24161
+ const pending = this.step();
24162
+ if (pending !== null) await pending;
24163
+ if (++this.instructions > this.limit) {
24164
+ throw new Error(`instruction limit reached (${this.limit})`);
23782
24165
  }
23783
- childScope = { bindsExports: names.has(NAME), parent: scope };
23784
24166
  }
23785
- break;
24167
+ } catch (e) {
24168
+ if (e instanceof ProcessExit) return e.code;
24169
+ throw e;
23786
24170
  }
23787
- /* `export { exports }` would have to become `exports as exports`, and
23788
- * `import { exports as x }` names someone else's binding. Neither is
23789
- * worth handling; refuse rather than rewrite them wrongly. */
23790
- case "ExportSpecifier":
23791
- case "ImportSpecifier":
23792
- if (isExports(node2.local) || isExports(node2.exported) || isExports(node2.imported)) {
23793
- bail = true;
23794
- }
23795
- return;
23796
- case "Identifier":
23797
- if (node2.name === NAME && resolvesToProgram(scope)) {
23798
- targets.push({ start: node2.start, end: node2.end, shorthand: false });
23799
- }
23800
- return;
23801
- // Property positions are names, not references to the binding.
23802
- case "MemberExpression":
23803
- case "MethodDefinition":
23804
- case "PropertyDefinition":
23805
- skip = node2.computed ? NOTHING : PROPERTY;
23806
- break;
23807
- case "Property":
23808
- if (node2.computed) break;
23809
- if (node2.shorthand) {
23810
- const value = node2.value;
23811
- if (isExports(value) && resolvesToProgram(scope)) {
23812
- targets.push({ start: value.start, end: value.end, shorthand: true });
23813
- }
23814
- return;
23815
- }
23816
- skip = PROPERTY;
23817
- break;
23818
- case "LabeledStatement":
23819
- case "BreakStatement":
23820
- case "ContinueStatement":
23821
- skip = LABEL;
23822
- break;
24171
+ await new Promise((resolve2) => setImmediate(resolve2));
23823
24172
  }
23824
- for (const [key, value] of Object.entries(node2)) {
23825
- if (key === "type" || key === "start" || key === "end" || skip.includes(key)) continue;
23826
- if (Array.isArray(value)) {
23827
- for (const item of value) if (isNode2(item)) visit(item, childScope);
23828
- } else if (isNode2(value)) {
23829
- visit(value, childScope);
24173
+ }
24174
+ /**
24175
+ * Execute one instruction. Returns a promise only when the instruction was a
24176
+ * syscall that needs to await, so the common path allocates nothing.
24177
+ */
24178
+ step() {
24179
+ this.opcodeStart = this.rip;
24180
+ this.rex = 0;
24181
+ this.hasRex = false;
24182
+ this.opSize = 4;
24183
+ this.addrSize = 8;
24184
+ this.rep = 0;
24185
+ this.ripRelative = false;
24186
+ for (; ; ) {
24187
+ const byte = this.memory.read8(this.rip);
24188
+ if (byte === 102) {
24189
+ this.opSize = 2;
24190
+ this.rip++;
24191
+ } else if (byte === 103) {
24192
+ this.addrSize = 4;
24193
+ this.rip++;
24194
+ } else if (byte === 242 || byte === 243) {
24195
+ this.rep = byte;
24196
+ this.rip++;
24197
+ } else if (byte === 46 || byte === 54 || byte === 62 || byte === 38) {
24198
+ this.rip++;
24199
+ } else if (byte === 100) {
24200
+ this.rip++;
24201
+ this.segmentBase = this.fsBase;
24202
+ } else if (byte === 101) {
24203
+ this.rip++;
24204
+ this.segmentBase = this.gsBase;
24205
+ } else if (byte === 240) {
24206
+ this.rip++;
24207
+ } else if (byte >= 64 && byte <= 79) {
24208
+ this.rex = byte & 15;
24209
+ this.hasRex = true;
24210
+ if (byte & 8) this.opSize = 8;
24211
+ this.rip++;
24212
+ } else {
24213
+ break;
23830
24214
  }
23831
24215
  }
24216
+ const opcode = this.fetch8();
24217
+ return this.execute(opcode);
23832
24218
  }
23833
- }
23834
- function resolvesToProgram(scope) {
23835
- for (let current = scope; current; current = current.parent) {
23836
- if (current.bindsExports) return current.parent === null;
24219
+ /** Set by an `fs:`/`gs:` prefix, consumed by the next memory operand. */
24220
+ segmentBase = 0;
24221
+ takeSegment() {
24222
+ const base = this.segmentBase;
24223
+ this.segmentBase = 0;
24224
+ return base;
23837
24225
  }
23838
- return false;
23839
- }
24226
+ execute(opcode) {
24227
+ const size = this.opSize;
24228
+ const arith = ARITH_BY_OPCODE[opcode & 248];
24229
+ if (arith !== void 0 && (opcode & 7) <= 5) {
24230
+ const form = opcode & 7;
24231
+ if (form === 0 || form === 1) {
24232
+ const width = form === 0 ? 1 : size;
24233
+ this.decodeModrm();
24234
+ const b = this.readReg(this.modrm.reg, width);
24235
+ const a = this.readRm(width);
24236
+ const result = this.applyArith(arith, a, b, width);
24237
+ if (arith !== "cmp") this.writeRm(width, result);
24238
+ } else if (form === 2 || form === 3) {
24239
+ const width = form === 2 ? 1 : size;
24240
+ this.decodeModrm();
24241
+ const a = this.readReg(this.modrm.reg, width);
24242
+ const b = this.readRm(width);
24243
+ const result = this.applyArith(arith, a, b, width);
24244
+ if (arith !== "cmp") this.writeReg(this.modrm.reg, width, result);
24245
+ } else {
24246
+ const width = form === 4 ? 1 : size;
24247
+ const immSize = width === 8 ? 4 : width;
24248
+ const b = signed(this.fetch(immSize), immSize) & maskFor(width);
24249
+ const a = this.readReg(RAX, width);
24250
+ const result = this.applyArith(arith, a, b, width);
24251
+ if (arith !== "cmp") this.writeReg(RAX, width, result);
24252
+ }
24253
+ return null;
24254
+ }
24255
+ switch (opcode) {
24256
+ case 15:
24257
+ return this.executeTwoByte(this.fetch8());
24258
+ /* ── stack ────────────────────────────────────────────────────────── */
24259
+ case 80:
24260
+ case 81:
24261
+ case 82:
24262
+ case 83:
24263
+ case 84:
24264
+ case 85:
24265
+ case 86:
24266
+ case 87:
24267
+ this.push(this.regs[opcode - 80 | ((this.rex & 1) !== 0 ? 8 : 0)]);
24268
+ return null;
24269
+ case 88:
24270
+ case 89:
24271
+ case 90:
24272
+ case 91:
24273
+ case 92:
24274
+ case 93:
24275
+ case 94:
24276
+ case 95:
24277
+ this.regs[opcode - 88 | ((this.rex & 1) !== 0 ? 8 : 0)] = this.pop();
24278
+ return null;
24279
+ case 104:
24280
+ this.push(signed(this.fetch(4), 4) & MASK64);
24281
+ return null;
24282
+ case 106:
24283
+ this.push(signed(this.fetch(1), 1) & MASK64);
24284
+ return null;
24285
+ /* ── movsxd ───────────────────────────────────────────────────────── */
24286
+ case 99: {
24287
+ this.decodeModrm();
24288
+ const value = signed(this.readRm(4), 4);
24289
+ this.writeReg(this.modrm.reg, size, value & maskFor(size));
24290
+ return null;
24291
+ }
24292
+ /* ── imul with immediate ──────────────────────────────────────────── */
24293
+ case 105:
24294
+ case 107: {
24295
+ this.decodeModrm();
24296
+ const a = signed(this.readRm(size), size);
24297
+ const immSize = opcode === 107 ? 1 : size === 8 ? 4 : size;
24298
+ const b = signed(this.fetch(immSize), immSize);
24299
+ const full = a * b;
24300
+ const truncated = full & maskFor(size);
24301
+ this.writeReg(this.modrm.reg, size, truncated);
24302
+ this.of = this.cf = signed(truncated, size) !== full;
24303
+ return null;
24304
+ }
24305
+ /* ── conditional jumps ────────────────────────────────────────────── */
24306
+ case 112:
24307
+ case 113:
24308
+ case 114:
24309
+ case 115:
24310
+ case 116:
24311
+ case 117:
24312
+ case 118:
24313
+ case 119:
24314
+ case 120:
24315
+ case 121:
24316
+ case 122:
24317
+ case 123:
24318
+ case 124:
24319
+ case 125:
24320
+ case 126:
24321
+ case 127: {
24322
+ const offset = Number(this.fetchSigned(1));
24323
+ if (this.condition(opcode & 15)) this.rip += offset;
24324
+ return null;
24325
+ }
24326
+ /* ── group 1: arithmetic with immediate ───────────────────────────── */
24327
+ case 128:
24328
+ case 129:
24329
+ case 131: {
24330
+ const width = opcode === 128 ? 1 : size;
24331
+ this.decodeModrm();
24332
+ const immSize = opcode === 129 ? width === 8 ? 4 : width : 1;
24333
+ this.rip;
24334
+ const peek = this.peekImmediate(immSize);
24335
+ const b = signed(peek, immSize) & maskFor(width);
24336
+ const a = this.readRm(width);
24337
+ const op = ARITH_BY_INDEX[this.modrm.reg & 7];
24338
+ const result = this.applyArith(op, a, b, width);
24339
+ if (op !== "cmp") this.writeRm(width, result);
24340
+ return null;
24341
+ }
24342
+ /* ── test / xchg / mov ────────────────────────────────────────────── */
24343
+ case 132:
24344
+ case 133: {
24345
+ const width = opcode === 132 ? 1 : size;
24346
+ this.decodeModrm();
24347
+ const a = this.readRm(width);
24348
+ const b = this.readReg(this.modrm.reg, width);
24349
+ this.setLogicFlags(a & b, width);
24350
+ return null;
24351
+ }
24352
+ case 134:
24353
+ case 135: {
24354
+ const width = opcode === 134 ? 1 : size;
24355
+ this.decodeModrm();
24356
+ const a = this.readRm(width);
24357
+ const b = this.readReg(this.modrm.reg, width);
24358
+ this.writeRm(width, b);
24359
+ this.writeReg(this.modrm.reg, width, a);
24360
+ return null;
24361
+ }
24362
+ case 136:
24363
+ case 137: {
24364
+ const width = opcode === 136 ? 1 : size;
24365
+ this.decodeModrm();
24366
+ this.writeRm(width, this.readReg(this.modrm.reg, width));
24367
+ return null;
24368
+ }
24369
+ case 138:
24370
+ case 139: {
24371
+ const width = opcode === 138 ? 1 : size;
24372
+ this.decodeModrm();
24373
+ this.writeReg(this.modrm.reg, width, this.readRm(width));
24374
+ return null;
24375
+ }
24376
+ case 141: {
24377
+ this.decodeModrm();
24378
+ this.writeReg(this.modrm.reg, size, BigInt(this.effectiveAddress()) & maskFor(size));
24379
+ return null;
24380
+ }
24381
+ case 143:
24382
+ this.decodeModrm();
24383
+ this.writeRm(8, this.pop());
24384
+ return null;
24385
+ case 144:
24386
+ return null;
24387
+ // NOP (XCHG rax, rax)
24388
+ case 152:
24389
+ this.writeReg(RAX, size, signed(this.readReg(RAX, size / 2), size / 2) & maskFor(size));
24390
+ return null;
24391
+ case 153: {
24392
+ const value = signed(this.readReg(RAX, size), size);
24393
+ this.writeReg(RDX, size, value < 0n ? maskFor(size) : 0n);
24394
+ return null;
24395
+ }
24396
+ /* ── string operations ────────────────────────────────────────────── */
24397
+ case 164:
24398
+ case 165:
24399
+ return this.stringOp("movs", opcode === 164 ? 1 : size);
24400
+ case 166:
24401
+ case 167:
24402
+ return this.stringOp("cmps", opcode === 166 ? 1 : size);
24403
+ case 170:
24404
+ case 171:
24405
+ return this.stringOp("stos", opcode === 170 ? 1 : size);
24406
+ case 172:
24407
+ case 173:
24408
+ return this.stringOp("lods", opcode === 172 ? 1 : size);
24409
+ case 174:
24410
+ case 175:
24411
+ return this.stringOp("scas", opcode === 174 ? 1 : size);
24412
+ case 168:
24413
+ case 169: {
24414
+ const width = opcode === 168 ? 1 : size;
24415
+ const immSize = width === 8 ? 4 : width;
24416
+ const b = signed(this.fetch(immSize), immSize) & maskFor(width);
24417
+ this.setLogicFlags(this.readReg(RAX, width) & b, width);
24418
+ return null;
24419
+ }
24420
+ /* ── mov immediate ────────────────────────────────────────────────── */
24421
+ case 176:
24422
+ case 177:
24423
+ case 178:
24424
+ case 179:
24425
+ case 180:
24426
+ case 181:
24427
+ case 182:
24428
+ case 183:
24429
+ this.writeReg(opcode - 176 | ((this.rex & 1) !== 0 ? 8 : 0), 1, this.fetch(1));
24430
+ return null;
24431
+ case 184:
24432
+ case 185:
24433
+ case 186:
24434
+ case 187:
24435
+ case 188:
24436
+ case 189:
24437
+ case 190:
24438
+ case 191: {
24439
+ const index = opcode - 184 | ((this.rex & 1) !== 0 ? 8 : 0);
24440
+ this.writeReg(index, size, this.fetch(size === 8 ? 8 : size));
24441
+ return null;
24442
+ }
24443
+ /* ── shifts ───────────────────────────────────────────────────────── */
24444
+ case 192:
24445
+ case 193: {
24446
+ const width = opcode === 192 ? 1 : size;
24447
+ this.decodeModrm();
24448
+ const count = Number(this.peekImmediate(1)) & (width === 8 ? 63 : 31);
24449
+ this.shift(this.modrm.reg & 7, width, count);
24450
+ return null;
24451
+ }
24452
+ case 208:
24453
+ case 209: {
24454
+ const width = opcode === 208 ? 1 : size;
24455
+ this.decodeModrm();
24456
+ this.shift(this.modrm.reg & 7, width, 1);
24457
+ return null;
24458
+ }
24459
+ case 210:
24460
+ case 211: {
24461
+ const width = opcode === 210 ? 1 : size;
24462
+ this.decodeModrm();
24463
+ this.shift(this.modrm.reg & 7, width, Number(this.readReg(RCX, 1)) & (width === 8 ? 63 : 31));
24464
+ return null;
24465
+ }
24466
+ /* ── control flow ─────────────────────────────────────────────────── */
24467
+ case 194: {
24468
+ const extra = Number(this.fetch(2));
24469
+ this.rip = Number(this.pop());
24470
+ this.regs[RSP] = this.regs[RSP] + BigInt(extra) & MASK64;
24471
+ return null;
24472
+ }
24473
+ case 195:
24474
+ this.rip = Number(this.pop());
24475
+ return null;
24476
+ case 198:
24477
+ case 199: {
24478
+ const width = opcode === 198 ? 1 : size;
24479
+ this.decodeModrm();
24480
+ const immSize = width === 8 ? 4 : width;
24481
+ const value = signed(this.peekImmediate(immSize), immSize) & maskFor(width);
24482
+ this.writeRm(width, value);
24483
+ return null;
24484
+ }
24485
+ case 201: {
24486
+ this.regs[RSP] = this.regs[RBP];
24487
+ this.regs[RBP] = this.pop();
24488
+ return null;
24489
+ }
24490
+ case 204:
24491
+ throw new UnsupportedInstruction("int3", this.opcodeStart);
24492
+ case 232: {
24493
+ const offset = Number(this.fetchSigned(4));
24494
+ this.push(BigInt(this.rip));
24495
+ this.rip += offset;
24496
+ return null;
24497
+ }
24498
+ // The displacement must be fetched into a local first: `rip += fetch()`
24499
+ // reads rip before the fetch advances it, landing short by the width of
24500
+ // the immediate.
24501
+ case 233: {
24502
+ const offset = Number(this.fetchSigned(4));
24503
+ this.rip += offset;
24504
+ return null;
24505
+ }
24506
+ case 235: {
24507
+ const offset = Number(this.fetchSigned(1));
24508
+ this.rip += offset;
24509
+ return null;
24510
+ }
24511
+ case 224:
24512
+ case 225:
24513
+ case 226: {
24514
+ const offset = Number(this.fetchSigned(1));
24515
+ const next = this.regs[RCX] - 1n & MASK64;
24516
+ this.regs[RCX] = next;
24517
+ const taken = next !== 0n && (opcode === 226 || (opcode === 225 ? this.zf : !this.zf));
24518
+ if (taken) this.rip += offset;
24519
+ return null;
24520
+ }
24521
+ case 227: {
24522
+ const offset = Number(this.fetchSigned(1));
24523
+ if (this.regs[RCX] === 0n) this.rip += offset;
24524
+ return null;
24525
+ }
24526
+ /* ── group 3 ──────────────────────────────────────────────────────── */
24527
+ case 246:
24528
+ case 247: {
24529
+ const width = opcode === 246 ? 1 : size;
24530
+ this.decodeModrm();
24531
+ return this.group3(width);
24532
+ }
24533
+ case 248:
24534
+ this.cf = false;
24535
+ return null;
24536
+ case 249:
24537
+ this.cf = true;
24538
+ return null;
24539
+ case 252:
24540
+ this.df = false;
24541
+ return null;
24542
+ case 253:
24543
+ this.df = true;
24544
+ return null;
24545
+ /* ── group 4 and 5 ────────────────────────────────────────────────── */
24546
+ case 254:
24547
+ case 255: {
24548
+ const width = opcode === 254 ? 1 : size;
24549
+ this.decodeModrm();
24550
+ const op = this.modrm.reg & 7;
24551
+ if (op === 0 || op === 1) {
24552
+ const carry = this.cf;
24553
+ const a = this.readRm(width);
24554
+ const result = op === 0 ? a + 1n : a - 1n;
24555
+ if (op === 0) this.setAddFlags(a, 1n, result, width);
24556
+ else this.setSubFlags(a, 1n, result, width);
24557
+ this.cf = carry;
24558
+ this.writeRm(width, result);
24559
+ return null;
24560
+ }
24561
+ if (op === 2) {
24562
+ const target = Number(this.readRm(8));
24563
+ this.push(BigInt(this.rip));
24564
+ this.rip = target;
24565
+ return null;
24566
+ }
24567
+ if (op === 4) {
24568
+ this.rip = Number(this.readRm(8));
24569
+ return null;
24570
+ }
24571
+ if (op === 6) {
24572
+ this.push(this.readRm(8));
24573
+ return null;
24574
+ }
24575
+ throw new UnsupportedInstruction(`ff /${op}`, this.opcodeStart);
24576
+ }
24577
+ default:
24578
+ throw new UnsupportedInstruction(`0x${opcode.toString(16)}`, this.opcodeStart);
24579
+ }
24580
+ }
24581
+ /**
24582
+ * Read an immediate that follows the ModRM byte without disturbing the
24583
+ * pending RIP-relative displacement.
24584
+ *
24585
+ * x86 puts the immediate after the address bytes but resolves RIP-relative
24586
+ * addresses against the end of the *whole* instruction, so the immediate has
24587
+ * to be consumed first and the address computed afterwards.
24588
+ */
24589
+ peekImmediate(size) {
24590
+ const value = this.memory.read(this.rip, size);
24591
+ this.rip += size;
24592
+ return value;
24593
+ }
24594
+ applyArith(op, a, b, size) {
24595
+ const mask = maskFor(size);
24596
+ switch (op) {
24597
+ case "add": {
24598
+ const result = (a & mask) + (b & mask);
24599
+ this.setAddFlags(a, b, result, size);
24600
+ return result & mask;
24601
+ }
24602
+ case "adc": {
24603
+ const carry = this.cf ? 1n : 0n;
24604
+ const result = (a & mask) + (b & mask) + carry;
24605
+ this.setAddFlags(a, b + carry, result, size);
24606
+ return result & mask;
24607
+ }
24608
+ case "sub":
24609
+ case "cmp": {
24610
+ const result = (a & mask) - (b & mask);
24611
+ this.setSubFlags(a, b, result, size);
24612
+ return result & mask;
24613
+ }
24614
+ case "sbb": {
24615
+ const borrow = this.cf ? 1n : 0n;
24616
+ const result = (a & mask) - (b & mask) - borrow;
24617
+ this.setSubFlags(a, b + borrow, result, size);
24618
+ return result & mask;
24619
+ }
24620
+ case "and": {
24621
+ const result = a & b & mask;
24622
+ this.setLogicFlags(result, size);
24623
+ return result;
24624
+ }
24625
+ case "or": {
24626
+ const result = (a | b) & mask;
24627
+ this.setLogicFlags(result, size);
24628
+ return result;
24629
+ }
24630
+ case "xor": {
24631
+ const result = (a ^ b) & mask;
24632
+ this.setLogicFlags(result, size);
24633
+ return result;
24634
+ }
24635
+ }
24636
+ }
24637
+ shift(op, size, count) {
24638
+ if (count === 0) {
24639
+ this.readRm(size);
24640
+ return;
24641
+ }
24642
+ const mask = maskFor(size);
24643
+ const bits = BigInt(size * 8);
24644
+ const value = this.readRm(size) & mask;
24645
+ const n = BigInt(count);
24646
+ let result;
24647
+ switch (op) {
24648
+ case 0:
24649
+ result = (value << n % bits | value >> bits - n % bits) & mask;
24650
+ this.cf = (result & 1n) !== 0n;
24651
+ break;
24652
+ case 1:
24653
+ result = (value >> n % bits | value << bits - n % bits) & mask;
24654
+ this.cf = result >> bits - 1n !== 0n;
24655
+ break;
24656
+ case 2:
24657
+ // RCL
24658
+ case 3: {
24659
+ const total = bits + 1n;
24660
+ const n2 = BigInt(count) % total;
24661
+ if (n2 === 0n) {
24662
+ this.writeRm(size, value);
24663
+ return;
24664
+ }
24665
+ const wide = value | (this.cf ? 1n << bits : 0n);
24666
+ const wideMask = (1n << total) - 1n;
24667
+ const rotated = op === 2 ? (wide << n2 | wide >> total - n2) & wideMask : (wide >> n2 | wide << total - n2) & wideMask;
24668
+ result = rotated & mask;
24669
+ this.cf = (rotated >> bits & 1n) !== 0n;
24670
+ this.of = (result >> bits - 1n & 1n) !== (this.cf ? 1n : 0n);
24671
+ this.writeRm(size, result);
24672
+ return;
24673
+ }
24674
+ case 4:
24675
+ // SHL
24676
+ case 6:
24677
+ result = value << n & mask;
24678
+ this.cf = n <= bits && (value >> bits - n & 1n) !== 0n;
24679
+ this.setShiftFlags(result, size);
24680
+ this.of = (result >> bits - 1n & 1n) !== (this.cf ? 1n : 0n);
24681
+ this.writeRm(size, result);
24682
+ return;
24683
+ case 5:
24684
+ result = value >> n;
24685
+ this.cf = (value >> n - 1n & 1n) !== 0n;
24686
+ this.setShiftFlags(result, size);
24687
+ this.of = (value >> bits - 1n & 1n) !== 0n;
24688
+ this.writeRm(size, result);
24689
+ return;
24690
+ case 7: {
24691
+ const s = signed(value, size);
24692
+ result = s >> n & mask;
24693
+ this.cf = (s >> n - 1n & 1n) !== 0n;
24694
+ this.setShiftFlags(result, size);
24695
+ this.of = false;
24696
+ this.writeRm(size, result);
24697
+ return;
24698
+ }
24699
+ default:
24700
+ throw new UnsupportedInstruction(`shift /${op}`, this.opcodeStart);
24701
+ }
24702
+ this.writeRm(size, result);
24703
+ }
24704
+ setShiftFlags(result, size) {
24705
+ const masked = result & maskFor(size);
24706
+ this.zf = masked === 0n;
24707
+ this.sf = signed(masked, size) < 0n;
24708
+ this.pf = parity(masked);
24709
+ }
24710
+ group3(size) {
24711
+ const op = this.modrm.reg & 7;
24712
+ const mask = maskFor(size);
24713
+ switch (op) {
24714
+ case 0:
24715
+ case 1: {
24716
+ const immSize = size === 8 ? 4 : size;
24717
+ const b = signed(this.peekImmediate(immSize), immSize) & mask;
24718
+ this.setLogicFlags(this.readRm(size) & b, size);
24719
+ return null;
24720
+ }
24721
+ case 2: {
24722
+ this.writeRm(size, ~this.readRm(size) & mask);
24723
+ return null;
24724
+ }
24725
+ case 3: {
24726
+ const a = this.readRm(size);
24727
+ const result = 0n - a & mask;
24728
+ this.setSubFlags(0n, a, 0n - a, size);
24729
+ this.cf = (a & mask) !== 0n;
24730
+ this.writeRm(size, result);
24731
+ return null;
24732
+ }
24733
+ case 4: {
24734
+ const a = this.readReg(RAX, size);
24735
+ const b = this.readRm(size);
24736
+ const full = a * b;
24737
+ this.writeReg(RAX, size, full & mask);
24738
+ if (size === 1) this.writeReg(RAX, 2, full & 0xffffn);
24739
+ else this.writeReg(RDX, size, full >> BigInt(size * 8) & mask);
24740
+ this.cf = this.of = full >> BigInt(size * 8) !== 0n;
24741
+ return null;
24742
+ }
24743
+ case 5: {
24744
+ const a = signed(this.readReg(RAX, size), size);
24745
+ const b = signed(this.readRm(size), size);
24746
+ const full = a * b;
24747
+ this.writeReg(RAX, size, full & mask);
24748
+ if (size === 1) this.writeReg(RAX, 2, full & 0xffffn);
24749
+ else this.writeReg(RDX, size, full >> BigInt(size * 8) & mask);
24750
+ this.cf = this.of = signed(full & mask, size) !== full;
24751
+ return null;
24752
+ }
24753
+ case 6: {
24754
+ const divisor = this.readRm(size);
24755
+ if (divisor === 0n) throw new UnsupportedInstruction("divide by zero", this.opcodeStart);
24756
+ const high = size === 1 ? 0n : this.readReg(RDX, size);
24757
+ const low = this.readReg(RAX, size);
24758
+ const dividend = size === 1 ? this.readReg(RAX, 2) : high << BigInt(size * 8) | low;
24759
+ this.writeReg(RAX, size, dividend / divisor & mask);
24760
+ if (size === 1) this.writeReg(RAX, 1, dividend / divisor & 0xffn);
24761
+ else this.writeReg(RDX, size, dividend % divisor & mask);
24762
+ return null;
24763
+ }
24764
+ case 7: {
24765
+ const divisor = signed(this.readRm(size), size);
24766
+ if (divisor === 0n) throw new UnsupportedInstruction("divide by zero", this.opcodeStart);
24767
+ const high = size === 1 ? 0n : this.readReg(RDX, size);
24768
+ const low = this.readReg(RAX, size);
24769
+ const raw = size === 1 ? this.readReg(RAX, 2) : high << BigInt(size * 8) | low;
24770
+ const dividend = signed(raw, size === 1 ? 2 : size * 2);
24771
+ this.writeReg(RAX, size, dividend / divisor & mask);
24772
+ if (size !== 1) this.writeReg(RDX, size, dividend % divisor & mask);
24773
+ return null;
24774
+ }
24775
+ default:
24776
+ throw new UnsupportedInstruction(`f7 /${op}`, this.opcodeStart);
24777
+ }
24778
+ }
24779
+ /**
24780
+ * MOVS/STOS/LODS/SCAS/CMPS, with the REP prefixes.
24781
+ *
24782
+ * These are how a compiler open-codes `memcpy`, `memset` and `strlen`, so a
24783
+ * program that never calls libc still leans on them heavily.
24784
+ */
24785
+ stringOp(kind, size) {
24786
+ const step = BigInt(this.df ? -size : size);
24787
+ const repeat = this.rep !== 0;
24788
+ const whileEqual = this.rep === 243;
24789
+ for (; ; ) {
24790
+ if (repeat && this.regs[RCX] === 0n) break;
24791
+ const source = Number(this.regs[RSI]);
24792
+ const dest = Number(this.regs[RDI]);
24793
+ switch (kind) {
24794
+ case "movs":
24795
+ this.memory.write(dest, size, this.memory.read(source, size));
24796
+ this.regs[RSI] = this.regs[RSI] + step & MASK64;
24797
+ this.regs[RDI] = this.regs[RDI] + step & MASK64;
24798
+ break;
24799
+ case "stos":
24800
+ this.memory.write(dest, size, this.readReg(RAX, size));
24801
+ this.regs[RDI] = this.regs[RDI] + step & MASK64;
24802
+ break;
24803
+ case "lods":
24804
+ this.writeReg(RAX, size, this.memory.read(source, size));
24805
+ this.regs[RSI] = this.regs[RSI] + step & MASK64;
24806
+ break;
24807
+ case "scas": {
24808
+ const a = this.readReg(RAX, size);
24809
+ const b = this.memory.read(dest, size);
24810
+ this.setSubFlags(a, b, a - b, size);
24811
+ this.regs[RDI] = this.regs[RDI] + step & MASK64;
24812
+ break;
24813
+ }
24814
+ case "cmps": {
24815
+ const a = this.memory.read(source, size);
24816
+ const b = this.memory.read(dest, size);
24817
+ this.setSubFlags(a, b, a - b, size);
24818
+ this.regs[RSI] = this.regs[RSI] + step & MASK64;
24819
+ this.regs[RDI] = this.regs[RDI] + step & MASK64;
24820
+ break;
24821
+ }
24822
+ }
24823
+ if (!repeat) break;
24824
+ this.regs[RCX] = this.regs[RCX] - 1n & MASK64;
24825
+ if ((kind === "scas" || kind === "cmps") && this.zf !== whileEqual) break;
24826
+ }
24827
+ return null;
24828
+ }
24829
+ executeTwoByte(opcode) {
24830
+ const size = this.opSize;
24831
+ switch (opcode) {
24832
+ case 5: {
24833
+ const result = this.onSyscall(this);
24834
+ this.regs[R11] = 0x202n;
24835
+ return result instanceof Promise ? result : null;
24836
+ }
24837
+ case 11:
24838
+ throw new UnsupportedInstruction("ud2", this.opcodeStart);
24839
+ case 30: {
24840
+ this.fetch8();
24841
+ return null;
24842
+ }
24843
+ case 31:
24844
+ this.decodeModrm();
24845
+ this.effectiveAddress();
24846
+ return null;
24847
+ // multi-byte NOP
24848
+ case 13:
24849
+ case 24:
24850
+ case 25:
24851
+ case 26:
24852
+ case 27:
24853
+ case 28:
24854
+ case 29:
24855
+ this.decodeModrm();
24856
+ this.effectiveAddress();
24857
+ return null;
24858
+ // prefetch / hint NOPs
24859
+ case 49: {
24860
+ const ticks = BigInt(this.instructions) * 3n;
24861
+ this.writeReg(RAX, 4, ticks & 0xffffffffn);
24862
+ this.writeReg(RDX, 4, ticks >> 32n & 0xffffffffn);
24863
+ return null;
24864
+ }
24865
+ case 162:
24866
+ this.cpuid();
24867
+ return null;
24868
+ /* ── cmovcc ───────────────────────────────────────────────────────── */
24869
+ case 64:
24870
+ case 65:
24871
+ case 66:
24872
+ case 67:
24873
+ case 68:
24874
+ case 69:
24875
+ case 70:
24876
+ case 71:
24877
+ case 72:
24878
+ case 73:
24879
+ case 74:
24880
+ case 75:
24881
+ case 76:
24882
+ case 77:
24883
+ case 78:
24884
+ case 79: {
24885
+ this.decodeModrm();
24886
+ const value = this.readRm(size);
24887
+ if (this.condition(opcode & 15)) this.writeReg(this.modrm.reg, size, value);
24888
+ else if (size === 4) {
24889
+ this.writeReg(this.modrm.reg, 4, this.readReg(this.modrm.reg, 4));
24890
+ }
24891
+ return null;
24892
+ }
24893
+ /* ── jcc rel32 ────────────────────────────────────────────────────── */
24894
+ case 128:
24895
+ case 129:
24896
+ case 130:
24897
+ case 131:
24898
+ case 132:
24899
+ case 133:
24900
+ case 134:
24901
+ case 135:
24902
+ case 136:
24903
+ case 137:
24904
+ case 138:
24905
+ case 139:
24906
+ case 140:
24907
+ case 141:
24908
+ case 142:
24909
+ case 143: {
24910
+ const offset = Number(this.fetchSigned(4));
24911
+ if (this.condition(opcode & 15)) this.rip += offset;
24912
+ return null;
24913
+ }
24914
+ /* ── setcc ────────────────────────────────────────────────────────── */
24915
+ case 144:
24916
+ case 145:
24917
+ case 146:
24918
+ case 147:
24919
+ case 148:
24920
+ case 149:
24921
+ case 150:
24922
+ case 151:
24923
+ case 152:
24924
+ case 153:
24925
+ case 154:
24926
+ case 155:
24927
+ case 156:
24928
+ case 157:
24929
+ case 158:
24930
+ case 159:
24931
+ this.decodeModrm();
24932
+ this.writeRm(1, this.condition(opcode & 15) ? 1n : 0n);
24933
+ return null;
24934
+ /* ── bit test ─────────────────────────────────────────────────────── */
24935
+ case 163:
24936
+ case 171:
24937
+ case 179:
24938
+ case 187: {
24939
+ this.decodeModrm();
24940
+ const bits = BigInt(size * 8);
24941
+ const offset = signed(this.readReg(this.modrm.reg, size), size);
24942
+ const value = this.readRm(size);
24943
+ const bit = (offset % bits + bits) % bits;
24944
+ this.cf = (value >> bit & 1n) !== 0n;
24945
+ if (opcode === 171) this.writeRm(size, value | 1n << bit);
24946
+ else if (opcode === 179) this.writeRm(size, value & ~(1n << bit));
24947
+ else if (opcode === 187) this.writeRm(size, value ^ 1n << bit);
24948
+ return null;
24949
+ }
24950
+ case 186: {
24951
+ this.decodeModrm();
24952
+ const op = this.modrm.reg & 7;
24953
+ const bits = BigInt(size * 8);
24954
+ const bit = BigInt(Number(this.peekImmediate(1))) % bits;
24955
+ const value = this.readRm(size);
24956
+ this.cf = (value >> bit & 1n) !== 0n;
24957
+ if (op === 5) this.writeRm(size, value | 1n << bit);
24958
+ else if (op === 6) this.writeRm(size, value & ~(1n << bit));
24959
+ else if (op === 7) this.writeRm(size, value ^ 1n << bit);
24960
+ return null;
24961
+ }
24962
+ case 175: {
24963
+ this.decodeModrm();
24964
+ const a = signed(this.readReg(this.modrm.reg, size), size);
24965
+ const b = signed(this.readRm(size), size);
24966
+ const full = a * b;
24967
+ const truncated = full & maskFor(size);
24968
+ this.writeReg(this.modrm.reg, size, truncated);
24969
+ this.of = this.cf = signed(truncated, size) !== full;
24970
+ return null;
24971
+ }
24972
+ /* ── zero and sign extension ──────────────────────────────────────── */
24973
+ case 182:
24974
+ case 183: {
24975
+ const from = opcode === 182 ? 1 : 2;
24976
+ this.decodeModrm();
24977
+ this.writeReg(this.modrm.reg, size, this.readRm(from));
24978
+ return null;
24979
+ }
24980
+ case 190:
24981
+ case 191: {
24982
+ const from = opcode === 190 ? 1 : 2;
24983
+ this.decodeModrm();
24984
+ this.writeReg(this.modrm.reg, size, signed(this.readRm(from), from) & maskFor(size));
24985
+ return null;
24986
+ }
24987
+ /* ── bit scan ─────────────────────────────────────────────────────── */
24988
+ case 188:
24989
+ case 189: {
24990
+ this.decodeModrm();
24991
+ const value = this.readRm(size);
24992
+ if (value === 0n) {
24993
+ this.zf = true;
24994
+ return null;
24995
+ }
24996
+ this.zf = false;
24997
+ let index = 0n;
24998
+ if (opcode === 188) {
24999
+ while ((value >> index & 1n) === 0n) index++;
25000
+ } else {
25001
+ index = BigInt(value.toString(2).length - 1);
25002
+ }
25003
+ this.writeReg(this.modrm.reg, size, index);
25004
+ return null;
25005
+ }
25006
+ case 176:
25007
+ case 177: {
25008
+ const width = opcode === 176 ? 1 : size;
25009
+ this.decodeModrm();
25010
+ const destination = this.readRm(width);
25011
+ const accumulator = this.readReg(RAX, width);
25012
+ this.setSubFlags(accumulator, destination, accumulator - destination, width);
25013
+ if (accumulator === destination) {
25014
+ this.writeRm(width, this.readReg(this.modrm.reg, width));
25015
+ } else {
25016
+ this.writeReg(RAX, width, destination);
25017
+ }
25018
+ return null;
25019
+ }
25020
+ case 192:
25021
+ case 193: {
25022
+ const width = opcode === 192 ? 1 : size;
25023
+ this.decodeModrm();
25024
+ const a = this.readRm(width);
25025
+ const b = this.readReg(this.modrm.reg, width);
25026
+ const sum = a + b;
25027
+ this.setAddFlags(a, b, sum, width);
25028
+ this.writeReg(this.modrm.reg, width, a);
25029
+ this.writeRm(width, sum & maskFor(width));
25030
+ return null;
25031
+ }
25032
+ case 200:
25033
+ case 201:
25034
+ case 202:
25035
+ case 203:
25036
+ case 204:
25037
+ case 205:
25038
+ case 206:
25039
+ case 207: {
25040
+ const index = opcode - 200 | ((this.rex & 1) !== 0 ? 8 : 0);
25041
+ const value = this.readReg(index, size);
25042
+ let swapped = 0n;
25043
+ for (let i = 0; i < size; i++) {
25044
+ swapped = swapped << 8n | value >> BigInt(i * 8) & 0xffn;
25045
+ }
25046
+ this.writeReg(index, size, swapped);
25047
+ return null;
25048
+ }
25049
+ case 174:
25050
+ this.decodeModrm();
25051
+ if (this.modrm.isReg) return null;
25052
+ this.effectiveAddress();
25053
+ return null;
25054
+ default:
25055
+ return this.executeSse(opcode);
25056
+ }
25057
+ }
25058
+ /* ── SSE ────────────────────────────────────────────────────────────────── */
25059
+ readXmmRm() {
25060
+ if (this.modrm.isReg) return [this.xmmLo[this.modrm.rm], this.xmmHi[this.modrm.rm]];
25061
+ const address = this.effectiveAddress();
25062
+ return [this.memory.read(address, 8), this.memory.read(address + 8, 8)];
25063
+ }
25064
+ writeXmmRm(lo, hi) {
25065
+ if (this.modrm.isReg) {
25066
+ this.xmmLo[this.modrm.rm] = lo;
25067
+ this.xmmHi[this.modrm.rm] = hi;
25068
+ return;
25069
+ }
25070
+ const address = this.effectiveAddress();
25071
+ this.memory.write(address, 8, lo);
25072
+ this.memory.write(address + 8, 8, hi);
25073
+ }
25074
+ /** Byte `index` of a 128-bit value held as two halves. */
25075
+ static byteOf(lo, hi, index) {
25076
+ const half = index < 8 ? lo : hi;
25077
+ return Number(half >> BigInt(index % 8 * 8) & 0xffn);
25078
+ }
25079
+ static fromBytes(bytes) {
25080
+ let lo = 0n;
25081
+ let hi = 0n;
25082
+ for (let i = 7; i >= 0; i--) lo = lo << 8n | BigInt(bytes[i] & 255);
25083
+ for (let i = 15; i >= 8; i--) hi = hi << 8n | BigInt(bytes[i] & 255);
25084
+ return [lo, hi];
25085
+ }
25086
+ /** Shift each element of a vector register: /2 logical right, /4 arithmetic right, /6 left. */
25087
+ packedShift(target, element, op, count) {
25088
+ const bytes = bytesOf(this.xmmLo[target], this.xmmHi[target]);
25089
+ const out = new Array(16).fill(0);
25090
+ BigInt(element * 8);
25091
+ const n = BigInt(count);
25092
+ for (let at = 0; at < 16; at += element) {
25093
+ const value = elementAt(bytes, at, element);
25094
+ let result;
25095
+ if (count >= element * 8) {
25096
+ result = op === 4 && signed(value, element) < 0n ? maskFor(element) : 0n;
25097
+ } else if (op === 2) {
25098
+ result = value >> n;
25099
+ } else if (op === 4) {
25100
+ result = signed(value, element) >> n;
25101
+ } else if (op === 6) {
25102
+ result = value << n;
25103
+ } else {
25104
+ throw new UnsupportedInstruction(`packed shift /${op}`, this.opcodeStart);
25105
+ }
25106
+ putElement(out, at, element, result);
25107
+ }
25108
+ const [lo, hi] = _Cpu.fromBytes(out);
25109
+ this.xmmLo[target] = lo;
25110
+ this.xmmHi[target] = hi;
25111
+ }
25112
+ executeSse(opcode) {
25113
+ const packed = this.opSize === 2;
25114
+ const scalarSingle = this.rep === 243;
25115
+ const scalarDouble = this.rep === 242;
25116
+ switch (opcode) {
25117
+ /* ── moves ────────────────────────────────────────────────────────── */
25118
+ case 16: {
25119
+ this.decodeModrm();
25120
+ const reg = this.modrm.reg;
25121
+ if (scalarSingle || scalarDouble) {
25122
+ const [lo2, hi2] = this.readXmmRm();
25123
+ const width = scalarSingle ? 0xffffffffn : 0xffffffffffffffffn;
25124
+ if (this.modrm.isReg) {
25125
+ this.xmmLo[reg] = this.xmmLo[reg] & ~width | lo2 & width;
25126
+ } else {
25127
+ this.xmmLo[reg] = lo2 & width;
25128
+ this.xmmHi[reg] = 0n;
25129
+ }
25130
+ return null;
25131
+ }
25132
+ const [lo, hi] = this.readXmmRm();
25133
+ this.xmmLo[reg] = lo;
25134
+ this.xmmHi[reg] = hi;
25135
+ return null;
25136
+ }
25137
+ case 17: {
25138
+ this.decodeModrm();
25139
+ const reg = this.modrm.reg;
25140
+ if (scalarSingle || scalarDouble) {
25141
+ const width = scalarSingle ? 0xffffffffn : 0xffffffffffffffffn;
25142
+ if (this.modrm.isReg) {
25143
+ this.xmmLo[this.modrm.rm] = this.xmmLo[this.modrm.rm] & ~width | this.xmmLo[reg] & width;
25144
+ } else {
25145
+ this.memory.write(this.effectiveAddress(), scalarSingle ? 4 : 8, this.xmmLo[reg] & width);
25146
+ }
25147
+ return null;
25148
+ }
25149
+ this.writeXmmRm(this.xmmLo[reg], this.xmmHi[reg]);
25150
+ return null;
25151
+ }
25152
+ case 40: {
25153
+ this.decodeModrm();
25154
+ const [lo, hi] = this.readXmmRm();
25155
+ this.xmmLo[this.modrm.reg] = lo;
25156
+ this.xmmHi[this.modrm.reg] = hi;
25157
+ return null;
25158
+ }
25159
+ case 41:
25160
+ this.decodeModrm();
25161
+ this.writeXmmRm(this.xmmLo[this.modrm.reg], this.xmmHi[this.modrm.reg]);
25162
+ return null;
25163
+ case 111: {
25164
+ this.decodeModrm();
25165
+ const [lo, hi] = this.readXmmRm();
25166
+ this.xmmLo[this.modrm.reg] = lo;
25167
+ this.xmmHi[this.modrm.reg] = hi;
25168
+ return null;
25169
+ }
25170
+ case 127:
25171
+ this.decodeModrm();
25172
+ this.writeXmmRm(this.xmmLo[this.modrm.reg], this.xmmHi[this.modrm.reg]);
25173
+ return null;
25174
+ case 18:
25175
+ case 22: {
25176
+ this.decodeModrm();
25177
+ const [lo] = this.readXmmRm();
25178
+ if (opcode === 18) this.xmmLo[this.modrm.reg] = lo;
25179
+ else this.xmmHi[this.modrm.reg] = lo;
25180
+ return null;
25181
+ }
25182
+ case 19:
25183
+ case 23: {
25184
+ this.decodeModrm();
25185
+ const value = opcode === 19 ? this.xmmLo[this.modrm.reg] : this.xmmHi[this.modrm.reg];
25186
+ this.memory.write(this.effectiveAddress(), 8, value);
25187
+ return null;
25188
+ }
25189
+ case 110: {
25190
+ this.decodeModrm();
25191
+ const width = this.opSize === 8 ? 8 : 4;
25192
+ const value = this.modrm.isReg ? this.readReg(this.modrm.rm, width) : this.memory.read(this.effectiveAddress(), width);
25193
+ this.xmmLo[this.modrm.reg] = value;
25194
+ this.xmmHi[this.modrm.reg] = 0n;
25195
+ return null;
25196
+ }
25197
+ case 126: {
25198
+ this.decodeModrm();
25199
+ if (scalarSingle) {
25200
+ const [lo] = this.readXmmRm();
25201
+ this.xmmLo[this.modrm.reg] = lo;
25202
+ this.xmmHi[this.modrm.reg] = 0n;
25203
+ return null;
25204
+ }
25205
+ const width = this.opSize === 8 ? 8 : 4;
25206
+ const value = this.xmmLo[this.modrm.reg] & maskFor(width);
25207
+ if (this.modrm.isReg) this.writeReg(this.modrm.rm, width, value);
25208
+ else this.memory.write(this.effectiveAddress(), width, value);
25209
+ return null;
25210
+ }
25211
+ case 214:
25212
+ this.decodeModrm();
25213
+ if (this.modrm.isReg) {
25214
+ this.xmmLo[this.modrm.rm] = this.xmmLo[this.modrm.reg];
25215
+ this.xmmHi[this.modrm.rm] = 0n;
25216
+ } else {
25217
+ this.memory.write(this.effectiveAddress(), 8, this.xmmLo[this.modrm.reg]);
25218
+ }
25219
+ return null;
25220
+ /* ── bitwise ──────────────────────────────────────────────────────── */
25221
+ case 87:
25222
+ // xorps / xorpd
25223
+ case 239: {
25224
+ this.decodeModrm();
25225
+ const [lo, hi] = this.readXmmRm();
25226
+ this.xmmLo[this.modrm.reg] ^= lo;
25227
+ this.xmmHi[this.modrm.reg] ^= hi;
25228
+ return null;
25229
+ }
25230
+ case 84:
25231
+ // andps / andpd
25232
+ case 219: {
25233
+ this.decodeModrm();
25234
+ const [lo, hi] = this.readXmmRm();
25235
+ this.xmmLo[this.modrm.reg] &= lo;
25236
+ this.xmmHi[this.modrm.reg] &= hi;
25237
+ return null;
25238
+ }
25239
+ case 86:
25240
+ // orps / orpd
25241
+ case 235: {
25242
+ this.decodeModrm();
25243
+ const [lo, hi] = this.readXmmRm();
25244
+ this.xmmLo[this.modrm.reg] |= lo;
25245
+ this.xmmHi[this.modrm.reg] |= hi;
25246
+ return null;
25247
+ }
25248
+ case 85:
25249
+ // andnps
25250
+ case 223: {
25251
+ this.decodeModrm();
25252
+ const [lo, hi] = this.readXmmRm();
25253
+ const reg = this.modrm.reg;
25254
+ this.xmmLo[reg] = ~this.xmmLo[reg] & lo;
25255
+ this.xmmHi[reg] = ~this.xmmHi[reg] & hi;
25256
+ return null;
25257
+ }
25258
+ /* ── byte compare and mask: how strlen and memchr are written ─────── */
25259
+ case 116: {
25260
+ this.decodeModrm();
25261
+ const [lo, hi] = this.readXmmRm();
25262
+ const reg = this.modrm.reg;
25263
+ const out = [];
25264
+ for (let i = 0; i < 16; i++) {
25265
+ const a = _Cpu.byteOf(this.xmmLo[reg], this.xmmHi[reg], i);
25266
+ const b = _Cpu.byteOf(lo, hi, i);
25267
+ out.push(a === b ? 255 : 0);
25268
+ }
25269
+ const [resultLo, resultHi] = _Cpu.fromBytes(out);
25270
+ this.xmmLo[reg] = resultLo;
25271
+ this.xmmHi[reg] = resultHi;
25272
+ return null;
25273
+ }
25274
+ case 215: {
25275
+ this.decodeModrm();
25276
+ const [lo, hi] = this.modrm.isReg ? [this.xmmLo[this.modrm.rm], this.xmmHi[this.modrm.rm]] : this.readXmmRm();
25277
+ let mask = 0n;
25278
+ for (let i = 0; i < 16; i++) {
25279
+ if ((_Cpu.byteOf(lo, hi, i) & 128) !== 0) mask |= 1n << BigInt(i);
25280
+ }
25281
+ this.writeReg(this.modrm.reg, 4, mask);
25282
+ return null;
25283
+ }
25284
+ case 113:
25285
+ case 114:
25286
+ case 115: {
25287
+ this.decodeModrm();
25288
+ const op = this.modrm.reg & 7;
25289
+ const count = Number(this.peekImmediate(1));
25290
+ const target = this.modrm.rm;
25291
+ const element = opcode === 113 ? 2 : opcode === 114 ? 4 : 8;
25292
+ if (opcode === 115 && (op === 3 || op === 7)) {
25293
+ const bytes = bytesOf(this.xmmLo[target], this.xmmHi[target]);
25294
+ const clamped = Math.min(count, 16);
25295
+ const shifted = op === 3 ? [...bytes.slice(clamped), ...new Array(clamped).fill(0)] : [...new Array(clamped).fill(0), ...bytes.slice(0, 16 - clamped)];
25296
+ const [lo, hi] = _Cpu.fromBytes(shifted.slice(0, 16));
25297
+ this.xmmLo[target] = lo;
25298
+ this.xmmHi[target] = hi;
25299
+ return null;
25300
+ }
25301
+ this.packedShift(target, element, op, count);
25302
+ return null;
25303
+ }
25304
+ case 209:
25305
+ case 210:
25306
+ case 211:
25307
+ // psrlw / psrld / psrlq, count in xmm
25308
+ case 225:
25309
+ case 226:
25310
+ // psraw / psrad
25311
+ case 241:
25312
+ case 242:
25313
+ case 243: {
25314
+ this.decodeModrm();
25315
+ const [countLo] = this.readXmmRm();
25316
+ const count = countLo > 255n ? 255 : Number(countLo);
25317
+ const element = opcode === 209 || opcode === 225 || opcode === 241 ? 2 : opcode === 210 || opcode === 226 || opcode === 242 ? 4 : 8;
25318
+ const op = opcode >= 241 ? 6 : opcode >= 225 ? 4 : 2;
25319
+ this.packedShift(this.modrm.reg, element, op, count);
25320
+ return null;
25321
+ }
25322
+ /* ── packed integer ───────────────────────────────────────────────── */
25323
+ case 96:
25324
+ case 97:
25325
+ case 98:
25326
+ case 108:
25327
+ // punpckl bw / wd / dq / qdq
25328
+ case 104:
25329
+ case 105:
25330
+ case 106:
25331
+ case 109: {
25332
+ this.decodeModrm();
25333
+ const [lo, hi] = this.readXmmRm();
25334
+ const reg = this.modrm.reg;
25335
+ const destination = bytesOf(this.xmmLo[reg], this.xmmHi[reg]);
25336
+ const source = bytesOf(lo, hi);
25337
+ const element = opcode === 96 || opcode === 104 ? 1 : opcode === 97 || opcode === 105 ? 2 : opcode === 98 || opcode === 106 ? 4 : 8;
25338
+ const high = opcode >= 104 && opcode <= 109;
25339
+ const offset = high ? 8 : 0;
25340
+ const out = [];
25341
+ for (let i = 0; out.length < 16; i++) {
25342
+ for (let b = 0; b < element; b++) out.push(destination[offset + i * element + b]);
25343
+ for (let b = 0; b < element; b++) out.push(source[offset + i * element + b]);
25344
+ }
25345
+ const [resultLo, resultHi] = _Cpu.fromBytes(out.slice(0, 16));
25346
+ this.xmmLo[reg] = resultLo;
25347
+ this.xmmHi[reg] = resultHi;
25348
+ return null;
25349
+ }
25350
+ case 112: {
25351
+ this.decodeModrm();
25352
+ const [lo, hi] = this.readXmmRm();
25353
+ const order = Number(this.peekImmediate(1));
25354
+ const reg = this.modrm.reg;
25355
+ const source = bytesOf(lo, hi);
25356
+ const out = new Array(16).fill(0);
25357
+ if (packed) {
25358
+ for (let lane = 0; lane < 4; lane++) {
25359
+ const from = order >> lane * 2 & 3;
25360
+ for (let b = 0; b < 4; b++) out[lane * 4 + b] = source[from * 4 + b];
25361
+ }
25362
+ } else if (scalarSingle || scalarDouble) {
25363
+ const half = scalarSingle ? 8 : 0;
25364
+ for (let i = 0; i < 16; i++) out[i] = source[i];
25365
+ for (let lane = 0; lane < 4; lane++) {
25366
+ const from = order >> lane * 2 & 3;
25367
+ for (let b = 0; b < 2; b++) out[half + lane * 2 + b] = source[half + from * 2 + b];
25368
+ }
25369
+ } else {
25370
+ throw new UnsupportedInstruction("0f 70 (mmx pshufw)", this.opcodeStart);
25371
+ }
25372
+ const [resultLo, resultHi] = _Cpu.fromBytes(out);
25373
+ this.xmmLo[reg] = resultLo;
25374
+ this.xmmHi[reg] = resultHi;
25375
+ return null;
25376
+ }
25377
+ case 252:
25378
+ case 253:
25379
+ case 254:
25380
+ case 212:
25381
+ // paddb / paddw / paddd / paddq
25382
+ case 248:
25383
+ case 249:
25384
+ case 250:
25385
+ case 251:
25386
+ // psubb / psubw / psubd / psubq
25387
+ case 100:
25388
+ case 101:
25389
+ case 102:
25390
+ // pcmpgtb / w / d
25391
+ case 117:
25392
+ case 118:
25393
+ // pcmpeqw / pcmpeqd
25394
+ case 218:
25395
+ case 222: {
25396
+ this.decodeModrm();
25397
+ const [lo, hi] = this.readXmmRm();
25398
+ const reg = this.modrm.reg;
25399
+ const a = bytesOf(this.xmmLo[reg], this.xmmHi[reg]);
25400
+ const b = bytesOf(lo, hi);
25401
+ const element = opcode === 252 || opcode === 248 || opcode === 100 || opcode === 218 || opcode === 222 ? 1 : opcode === 253 || opcode === 249 || opcode === 101 || opcode === 117 ? 2 : opcode === 254 || opcode === 250 || opcode === 102 || opcode === 118 ? 4 : 8;
25402
+ const out = new Array(16).fill(0);
25403
+ for (let at = 0; at < 16; at += element) {
25404
+ const x = elementAt(a, at, element);
25405
+ const y = elementAt(b, at, element);
25406
+ let value;
25407
+ switch (opcode) {
25408
+ case 252:
25409
+ case 253:
25410
+ case 254:
25411
+ case 212:
25412
+ value = x + y;
25413
+ break;
25414
+ case 248:
25415
+ case 249:
25416
+ case 250:
25417
+ case 251:
25418
+ value = x - y;
25419
+ break;
25420
+ case 100:
25421
+ case 101:
25422
+ case 102:
25423
+ value = signed(x, element) > signed(y, element) ? maskFor(element) : 0n;
25424
+ break;
25425
+ case 117:
25426
+ case 118:
25427
+ value = x === y ? maskFor(element) : 0n;
25428
+ break;
25429
+ case 218:
25430
+ value = x < y ? x : y;
25431
+ break;
25432
+ default:
25433
+ value = x > y ? x : y;
25434
+ }
25435
+ putElement(out, at, element, value);
25436
+ }
25437
+ const [resultLo, resultHi] = _Cpu.fromBytes(out);
25438
+ this.xmmLo[reg] = resultLo;
25439
+ this.xmmHi[reg] = resultHi;
25440
+ return null;
25441
+ }
25442
+ /* ── floating point ───────────────────────────────────────────────── */
25443
+ case 46:
25444
+ // ucomiss / ucomisd
25445
+ case 47: {
25446
+ this.decodeModrm();
25447
+ const [lo] = this.readXmmRm();
25448
+ const a = packed ? f64(this.xmmLo[this.modrm.reg]) : f32(this.xmmLo[this.modrm.reg]);
25449
+ const b = packed ? f64(lo) : f32(lo);
25450
+ if (Number.isNaN(a) || Number.isNaN(b)) {
25451
+ this.zf = this.pf = this.cf = true;
25452
+ } else {
25453
+ this.pf = false;
25454
+ this.zf = a === b;
25455
+ this.cf = a < b;
25456
+ }
25457
+ this.of = this.sf = this.af = false;
25458
+ return null;
25459
+ }
25460
+ case 42: {
25461
+ this.decodeModrm();
25462
+ const width = this.opSize === 8 ? 8 : 4;
25463
+ const source = this.modrm.isReg ? this.readReg(this.modrm.rm, width) : this.memory.read(this.effectiveAddress(), width);
25464
+ const value = Number(signed(source, width));
25465
+ this.xmmLo[this.modrm.reg] = scalarDouble ? bitsF64(value) : bitsF32(value);
25466
+ return null;
25467
+ }
25468
+ case 44:
25469
+ // cvttss2si / cvttsd2si — truncating
25470
+ case 45: {
25471
+ this.decodeModrm();
25472
+ const [lo] = this.readXmmRm();
25473
+ const value = scalarDouble ? f64(lo) : f32(lo);
25474
+ const converted = opcode === 44 ? Math.trunc(value) : Math.round(value);
25475
+ const width = this.opSize === 8 ? 8 : 4;
25476
+ this.writeReg(this.modrm.reg, width, BigInt(Number.isFinite(converted) ? converted : 0) & maskFor(width));
25477
+ return null;
25478
+ }
25479
+ case 90: {
25480
+ this.decodeModrm();
25481
+ const [lo] = this.readXmmRm();
25482
+ this.xmmLo[this.modrm.reg] = scalarDouble ? bitsF32(f64(lo)) : bitsF64(f32(lo));
25483
+ return null;
25484
+ }
25485
+ case 81:
25486
+ // sqrt
25487
+ case 88:
25488
+ // add
25489
+ case 89:
25490
+ // mul
25491
+ case 92:
25492
+ // sub
25493
+ case 94:
25494
+ // div
25495
+ case 93:
25496
+ // min
25497
+ case 95: {
25498
+ this.decodeModrm();
25499
+ const [lo, hi] = this.readXmmRm();
25500
+ const reg = this.modrm.reg;
25501
+ const apply = (x, y) => {
25502
+ switch (opcode) {
25503
+ case 81:
25504
+ return Math.sqrt(y);
25505
+ case 88:
25506
+ return x + y;
25507
+ case 89:
25508
+ return x * y;
25509
+ case 92:
25510
+ return x - y;
25511
+ case 94:
25512
+ return x / y;
25513
+ case 93:
25514
+ return Math.min(x, y);
25515
+ default:
25516
+ return Math.max(x, y);
25517
+ }
25518
+ };
25519
+ if (scalarDouble) {
25520
+ this.xmmLo[reg] = bitsF64(apply(f64(this.xmmLo[reg]), f64(lo)));
25521
+ } else if (scalarSingle) {
25522
+ this.xmmLo[reg] = this.xmmLo[reg] & ~0xffffffffn | bitsF32(apply(f32(this.xmmLo[reg]), f32(lo)));
25523
+ } else if (packed) {
25524
+ this.xmmLo[reg] = bitsF64(apply(f64(this.xmmLo[reg]), f64(lo)));
25525
+ this.xmmHi[reg] = bitsF64(apply(f64(this.xmmHi[reg]), f64(hi)));
25526
+ } else {
25527
+ const lanes = (a, b) => {
25528
+ const low = bitsF32(apply(f32(a), f32(b)));
25529
+ const high = bitsF32(apply(f32(a >> 32n), f32(b >> 32n)));
25530
+ return low | high << 32n;
25531
+ };
25532
+ this.xmmLo[reg] = lanes(this.xmmLo[reg], lo);
25533
+ this.xmmHi[reg] = lanes(this.xmmHi[reg], hi);
25534
+ }
25535
+ return null;
25536
+ }
25537
+ case 174:
25538
+ throw new UnsupportedInstruction("0f ae (fxsave family)", this.opcodeStart);
25539
+ default:
25540
+ throw new UnsupportedInstruction(
25541
+ `0f 0x${opcode.toString(16)}${packed ? " (66)" : scalarSingle ? " (f3)" : scalarDouble ? " (f2)" : ""}`,
25542
+ this.opcodeStart
25543
+ );
25544
+ }
25545
+ }
25546
+ /**
25547
+ * CPUID, reporting a deliberately plain processor.
25548
+ *
25549
+ * Advertising SSE4.2 or AVX would be a trap: glibc's IFUNC resolvers select
25550
+ * string routines by feature bit, and would immediately pick vector code
25551
+ * this interpreter does not implement. Claiming the x86-64 baseline steers
25552
+ * every such resolver to the generic path.
25553
+ */
25554
+ cpuid() {
25555
+ const leaf = Number(this.readReg(RAX, 4));
25556
+ let eax = 0n, ebx = 0n, ecx = 0n, edx = 0n;
25557
+ if (leaf === 0) {
25558
+ eax = 1n;
25559
+ ebx = 0x756e6547n;
25560
+ edx = 0x49656e69n;
25561
+ ecx = 0x6c65746en;
25562
+ } else if (leaf === 1) {
25563
+ eax = 0x00000f00n;
25564
+ ebx = 0n;
25565
+ ecx = 0x00000001n;
25566
+ edx = 1n << 0n | 1n << 4n | 1n << 15n | 1n << 23n | 1n << 24n | 1n << 25n | 1n << 26n;
25567
+ }
25568
+ this.writeReg(RAX, 4, eax);
25569
+ this.writeReg(RBX, 4, ebx);
25570
+ this.writeReg(RCX, 4, ecx);
25571
+ this.writeReg(RDX, 4, edx);
25572
+ }
25573
+ /** A snapshot for diagnostics when something goes wrong. */
25574
+ describe() {
25575
+ const names = [
25576
+ "rax",
25577
+ "rcx",
25578
+ "rdx",
25579
+ "rbx",
25580
+ "rsp",
25581
+ "rbp",
25582
+ "rsi",
25583
+ "rdi",
25584
+ "r8",
25585
+ "r9",
25586
+ "r10",
25587
+ "r11",
25588
+ "r12",
25589
+ "r13",
25590
+ "r14",
25591
+ "r15"
25592
+ ];
25593
+ const lines = [`rip 0x${this.rip.toString(16)} instructions ${this.instructions}`];
25594
+ for (let i = 0; i < 16; i += 4) {
25595
+ lines.push(
25596
+ names.slice(i, i + 4).map((name, j) => `${name.padStart(3)} 0x${this.regs[i + j].toString(16).padStart(16, "0")}`).join(" ")
25597
+ );
25598
+ }
25599
+ return lines.join("\n");
25600
+ }
25601
+ };
25602
+ var ARITH_BY_INDEX = ["add", "or", "adc", "sbb", "and", "sub", "xor", "cmp"];
25603
+ var ARITH_BY_OPCODE = {
25604
+ 0: "add",
25605
+ 8: "or",
25606
+ 16: "adc",
25607
+ 24: "sbb",
25608
+ 32: "and",
25609
+ 40: "sub",
25610
+ 48: "xor",
25611
+ 56: "cmp"
25612
+ };
25613
+ function bytesOf(lo, hi) {
25614
+ const out = new Array(16);
25615
+ for (let i = 0; i < 8; i++) out[i] = Number(lo >> BigInt(i * 8) & 0xffn);
25616
+ for (let i = 0; i < 8; i++) out[8 + i] = Number(hi >> BigInt(i * 8) & 0xffn);
25617
+ return out;
25618
+ }
25619
+ function elementAt(bytes, at, size) {
25620
+ let value = 0n;
25621
+ for (let i = size - 1; i >= 0; i--) value = value << 8n | BigInt(bytes[at + i]);
25622
+ return value;
25623
+ }
25624
+ function putElement(bytes, at, size, value) {
25625
+ let remaining = value & maskFor(size);
25626
+ for (let i = 0; i < size; i++) {
25627
+ bytes[at + i] = Number(remaining & 0xffn);
25628
+ remaining >>= 8n;
25629
+ }
25630
+ }
25631
+ var scratch = new DataView(new ArrayBuffer(8));
25632
+ function f64(bits) {
25633
+ scratch.setBigUint64(0, bits & MASK64);
25634
+ return scratch.getFloat64(0);
25635
+ }
25636
+ function bitsF64(value) {
25637
+ scratch.setFloat64(0, value);
25638
+ return scratch.getBigUint64(0);
25639
+ }
25640
+ function f32(bits) {
25641
+ scratch.setUint32(0, Number(bits & 0xffffffffn));
25642
+ return scratch.getFloat32(0);
25643
+ }
25644
+ function bitsF32(value) {
25645
+ scratch.setFloat32(0, value);
25646
+ return BigInt(scratch.getUint32(0));
25647
+ }
25648
+ function parity(value) {
25649
+ let byte = Number(value & 0xffn);
25650
+ let bits = 0;
25651
+ while (byte) {
25652
+ bits ^= byte & 1;
25653
+ byte >>= 1;
25654
+ }
25655
+ return bits === 0;
25656
+ }
25657
+
25658
+ // src/runtime/x86/linux.ts
25659
+ init_mode();
25660
+ init_errno();
25661
+
25662
+ // src/runtime/x86/memory.ts
25663
+ var PAGE_SIZE = 4096;
25664
+ var PAGE_MASK = PAGE_SIZE - 1;
25665
+ var SegFault = class extends Error {
25666
+ constructor(address, kind) {
25667
+ super(`segmentation fault: ${kind} at 0x${address.toString(16)}`);
25668
+ this.address = address;
25669
+ this.kind = kind;
25670
+ this.name = "SegFault";
25671
+ }
25672
+ address;
25673
+ kind;
25674
+ };
25675
+ var Memory = class {
25676
+ pages = /* @__PURE__ */ new Map();
25677
+ /** Bump pointer for anonymous `mmap`, below the stack and above the heap. */
25678
+ mmapNext = 35184372088832;
25679
+ /** Current program break, set by the loader and moved by `brk`. */
25680
+ brk = 0;
25681
+ page(address, create, kind) {
25682
+ const index = Math.floor(address / PAGE_SIZE);
25683
+ let page = this.pages.get(index);
25684
+ if (page === void 0) {
25685
+ if (!create) throw new SegFault(address, kind);
25686
+ page = new Uint8Array(PAGE_SIZE);
25687
+ this.pages.set(index, page);
25688
+ }
25689
+ return page;
25690
+ }
25691
+ /** Make `[address, address+length)` readable and writable. */
25692
+ map(address, length) {
25693
+ const start = Math.floor(address / PAGE_SIZE);
25694
+ const end = Math.floor((address + Math.max(length, 1) - 1) / PAGE_SIZE);
25695
+ for (let i = start; i <= end; i++) {
25696
+ if (!this.pages.has(i)) this.pages.set(i, new Uint8Array(PAGE_SIZE));
25697
+ }
25698
+ }
25699
+ unmap(address, length) {
25700
+ const start = Math.floor(address / PAGE_SIZE);
25701
+ const end = Math.floor((address + Math.max(length, 1) - 1) / PAGE_SIZE);
25702
+ for (let i = start; i <= end; i++) this.pages.delete(i);
25703
+ }
25704
+ isMapped(address) {
25705
+ return this.pages.has(Math.floor(address / PAGE_SIZE));
25706
+ }
25707
+ /** Reserve `length` bytes of anonymous space, page-aligned. */
25708
+ mmapAnonymous(length) {
25709
+ const size = Math.ceil(length / PAGE_SIZE) * PAGE_SIZE;
25710
+ const address = this.mmapNext;
25711
+ this.mmapNext += size + PAGE_SIZE;
25712
+ this.map(address, size);
25713
+ return address;
25714
+ }
25715
+ read8(address) {
25716
+ return this.page(address, false, "read")[address & PAGE_MASK];
25717
+ }
25718
+ write8(address, value) {
25719
+ this.page(address, true, "write")[address & PAGE_MASK] = value & 255;
25720
+ }
25721
+ /**
25722
+ * Read `size` bytes little-endian. Split across pages byte by byte rather
25723
+ * than assuming an access stays inside one — unaligned loads that straddle a
25724
+ * page boundary are entirely legal on x86.
25725
+ */
25726
+ read(address, size) {
25727
+ let value = 0n;
25728
+ for (let i = size - 1; i >= 0; i--) {
25729
+ value = value << 8n | BigInt(this.read8(address + i));
25730
+ }
25731
+ return value;
25732
+ }
25733
+ write(address, size, value) {
25734
+ let remaining = value;
25735
+ for (let i = 0; i < size; i++) {
25736
+ this.write8(address + i, Number(remaining & 0xffn));
25737
+ remaining >>= 8n;
25738
+ }
25739
+ }
25740
+ readBytes(address, length) {
25741
+ const out = new Uint8Array(length);
25742
+ for (let i = 0; i < length; i++) out[i] = this.read8(address + i);
25743
+ return out;
25744
+ }
25745
+ writeBytes(address, bytes) {
25746
+ this.map(address, bytes.length);
25747
+ for (let i = 0; i < bytes.length; i++) this.write8(address + i, bytes[i]);
25748
+ }
25749
+ /** Read a NUL-terminated string, as every path argument to a syscall is. */
25750
+ readCString(address, limit = 4096) {
25751
+ const bytes = [];
25752
+ for (let i = 0; i < limit; i++) {
25753
+ const byte = this.read8(address + i);
25754
+ if (byte === 0) break;
25755
+ bytes.push(byte);
25756
+ }
25757
+ return new TextDecoder().decode(new Uint8Array(bytes));
25758
+ }
25759
+ writeCString(address, text) {
25760
+ const bytes = new TextEncoder().encode(text);
25761
+ this.writeBytes(address, bytes);
25762
+ this.write8(address + bytes.length, 0);
25763
+ return bytes.length + 1;
25764
+ }
25765
+ get pageCount() {
25766
+ return this.pages.size;
25767
+ }
25768
+ };
25769
+
25770
+ // src/runtime/x86/linux.ts
25771
+ init_path();
25772
+ var E2 = {
25773
+ PERM: 1,
25774
+ NOENT: 2,
25775
+ BADF: 9,
25776
+ NOMEM: 12,
25777
+ ACCES: 13,
25778
+ EXIST: 17,
25779
+ NOTDIR: 20,
25780
+ ISDIR: 21,
25781
+ INVAL: 22,
25782
+ MFILE: 24,
25783
+ NOTTY: 25,
25784
+ SPIPE: 29,
25785
+ ROFS: 30,
25786
+ NOSYS: 38,
25787
+ NOTEMPTY: 39,
25788
+ LOOP: 40,
25789
+ NAMETOOLONG: 36,
25790
+ XDEV: 18
25791
+ };
25792
+ var ERRNO_MAP2 = {
25793
+ EPERM: E2.PERM,
25794
+ ENOENT: E2.NOENT,
25795
+ EBADF: E2.BADF,
25796
+ ENOMEM: E2.NOMEM,
25797
+ EACCES: E2.ACCES,
25798
+ EEXIST: E2.EXIST,
25799
+ ENOTDIR: E2.NOTDIR,
25800
+ EISDIR: E2.ISDIR,
25801
+ EINVAL: E2.INVAL,
25802
+ EMFILE: E2.MFILE,
25803
+ ENFILE: E2.MFILE,
25804
+ ENOTTY: E2.NOTTY,
25805
+ ESPIPE: E2.SPIPE,
25806
+ EROFS: E2.ROFS,
25807
+ ENOSYS: E2.NOSYS,
25808
+ ENOTEMPTY: E2.NOTEMPTY,
25809
+ ELOOP: E2.LOOP,
25810
+ ENAMETOOLONG: E2.NAMETOOLONG,
25811
+ EXDEV: E2.XDEV,
25812
+ ENOTSUP: E2.NOSYS,
25813
+ EIO: 5
25814
+ };
25815
+ var AT_FDCWD = -100;
25816
+ var SIZEOF_STAT = 144;
25817
+ var LinuxSyscalls = class {
25818
+ files = /* @__PURE__ */ new Map();
25819
+ nextFd = 3;
25820
+ stdinPosition = 0;
25821
+ cwd;
25822
+ opts;
25823
+ /** Syscall numbers the guest asked for that are not implemented. */
25824
+ unimplemented = /* @__PURE__ */ new Set();
25825
+ /**
25826
+ * Descriptors with no file behind them — epoll sets and eventfds. Tracked so
25827
+ * `close` accepts them and `epoll_ctl` can tell a real one from a wrong one.
25828
+ */
25829
+ pseudoFds = /* @__PURE__ */ new Set();
25830
+ /** Threads the guest believes it started; see the `clone` case. */
25831
+ threadsRequested = 0;
25832
+ constructor(opts) {
25833
+ this.opts = opts;
25834
+ this.cwd = opts.cwd;
25835
+ }
25836
+ /** Thread scheduler, attached once the CPU exists. */
25837
+ scheduler = null;
25838
+ /**
25839
+ * Set by a syscall that switched threads. The result register then belongs to
25840
+ * the thread we switched *to*, and must not be overwritten with ours.
25841
+ */
25842
+ switched = false;
25843
+ /** Entry point handed to the CPU. */
25844
+ handle = (cpu) => {
25845
+ const number = Number(cpu.get(RAX) & 0xffffffffn);
25846
+ const args = [
25847
+ cpu.get(RDI),
25848
+ cpu.get(RSI),
25849
+ cpu.get(RDX),
25850
+ cpu.get(R10),
25851
+ cpu.get(R8),
25852
+ cpu.get(R9)
25853
+ ];
25854
+ this.switched = false;
25855
+ let result;
25856
+ try {
25857
+ result = this.dispatch(cpu, number, args);
25858
+ } catch (e) {
25859
+ if (e instanceof ProcessExit) throw e;
25860
+ result = BigInt(-this.errnoOf(e));
25861
+ }
25862
+ this.opts.trace?.(`syscall ${number}(${args.slice(0, 3).map((a) => a.toString()).join(", ")}) = ${result}`);
25863
+ if (!this.switched) cpu.set(RAX, result);
25864
+ };
25865
+ errnoOf(e) {
25866
+ if (isSysError(e)) return ERRNO_MAP2[e.code] ?? 5;
25867
+ return 5;
25868
+ }
25869
+ resolve(path, dirfd = AT_FDCWD) {
25870
+ if (isAbsolute(path)) return normalize(path);
25871
+ if (dirfd !== AT_FDCWD) {
25872
+ const dir3 = this.files.get(dirfd);
25873
+ if (dir3) return resolve(dir3.path, path);
25874
+ }
25875
+ return resolve(this.cwd, path);
25876
+ }
25877
+ dispatch(cpu, number, args) {
25878
+ const mem = this.opts.memory;
25879
+ const vfs = this.opts.vfs;
25880
+ const cred = this.opts.cred;
25881
+ const a = (i) => Number(BigInt.asIntN(64, args[i]));
25882
+ const u = (i) => Number(args[i]);
25883
+ switch (number) {
25884
+ /* ── file descriptors ─────────────────────────────────────────────── */
25885
+ case 0: {
25886
+ const fd = a(0);
25887
+ const buffer = u(1);
25888
+ const count = u(2);
25889
+ if (fd === 0) {
25890
+ const slice2 = this.opts.stdin.subarray(this.stdinPosition, this.stdinPosition + count);
25891
+ mem.writeBytes(buffer, slice2);
25892
+ this.stdinPosition += slice2.length;
25893
+ return BigInt(slice2.length);
25894
+ }
25895
+ const file3 = this.files.get(fd);
25896
+ if (!file3) return BigInt(-9);
25897
+ const slice = file3.data.subarray(file3.position, file3.position + count);
25898
+ mem.writeBytes(buffer, slice);
25899
+ file3.position += slice.length;
25900
+ return BigInt(slice.length);
25901
+ }
25902
+ case 1: {
25903
+ const fd = a(0);
25904
+ const bytes = mem.readBytes(u(1), u(2));
25905
+ return this.writeTo(fd, bytes);
25906
+ }
25907
+ case 20: {
25908
+ const fd = a(0);
25909
+ const iov = u(1);
25910
+ const count = u(2);
25911
+ let total = 0;
25912
+ for (let i = 0; i < count; i++) {
25913
+ const base = Number(mem.read(iov + i * 16, 8));
25914
+ const length = Number(mem.read(iov + i * 16 + 8, 8));
25915
+ if (length === 0) continue;
25916
+ const written = this.writeTo(fd, mem.readBytes(base, length));
25917
+ if (written < 0n) return written;
25918
+ total += Number(written);
25919
+ }
25920
+ return BigInt(total);
25921
+ }
25922
+ case 2:
25923
+ return this.open(this.resolve(mem.readCString(u(0))), u(1), u(2));
25924
+ case 257:
25925
+ return this.open(this.resolve(mem.readCString(u(1)), a(0)), u(2), u(3));
25926
+ case 3: {
25927
+ if (this.pseudoFds.delete(a(0))) return 0n;
25928
+ const file3 = this.files.get(a(0));
25929
+ if (!file3) return a(0) <= 2 ? 0n : BigInt(-9);
25930
+ this.flush(file3);
25931
+ this.files.delete(a(0));
25932
+ return 0n;
25933
+ }
25934
+ case 8: {
25935
+ const file3 = this.files.get(a(0));
25936
+ if (!file3) return BigInt(-29);
25937
+ const offset = a(1);
25938
+ const whence = a(2);
25939
+ const next = whence === 0 ? offset : whence === 1 ? file3.position + offset : file3.data.length + offset;
25940
+ if (next < 0) return BigInt(-22);
25941
+ file3.position = next;
25942
+ return BigInt(next);
25943
+ }
25944
+ case 4:
25945
+ return this.stat(this.resolve(mem.readCString(u(0))), u(1), true);
25946
+ case 6:
25947
+ return this.stat(this.resolve(mem.readCString(u(0))), u(1), false);
25948
+ case 5: {
25949
+ const fd = a(0);
25950
+ if (fd >= 0 && fd <= 2) return this.statCharDevice(u(1));
25951
+ const file3 = this.files.get(fd);
25952
+ if (!file3) return BigInt(-9);
25953
+ return this.stat(file3.path, u(1), true);
25954
+ }
25955
+ case 262: {
25956
+ const path = mem.readCString(u(1));
25957
+ if (path === "" && (u(3) & 4096) !== 0) {
25958
+ const file3 = this.files.get(a(0));
25959
+ return file3 ? this.stat(file3.path, u(2), true) : this.statCharDevice(u(2));
25960
+ }
25961
+ return this.stat(this.resolve(path, a(0)), u(2), (u(3) & 256) === 0);
25962
+ }
25963
+ case 217:
25964
+ return this.getdents64(a(0), u(1), u(2));
25965
+ case 21: {
25966
+ const path = this.resolve(mem.readCString(u(0)));
25967
+ return vfs.lexists(path) ? 0n : BigInt(-2);
25968
+ }
25969
+ case 269: {
25970
+ const path = this.resolve(mem.readCString(u(1)), a(0));
25971
+ return vfs.lexists(path) ? 0n : BigInt(-2);
25972
+ }
25973
+ case 87: {
25974
+ vfs.unlink(this.resolve(mem.readCString(u(0))), cred);
25975
+ return 0n;
25976
+ }
25977
+ case 263: {
25978
+ const path = this.resolve(mem.readCString(u(1)), a(0));
25979
+ if ((u(2) & 512) !== 0) vfs.rmdir(path, cred);
25980
+ else vfs.unlink(path, cred);
25981
+ return 0n;
25982
+ }
25983
+ case 83:
25984
+ vfs.mkdir(this.resolve(mem.readCString(u(0))), { cred, mode: u(1) & 511 });
25985
+ return 0n;
25986
+ case 258:
25987
+ vfs.mkdir(this.resolve(mem.readCString(u(1)), a(0)), { cred, mode: u(2) & 511 });
25988
+ return 0n;
25989
+ case 84:
25990
+ vfs.rmdir(this.resolve(mem.readCString(u(0))), cred);
25991
+ return 0n;
25992
+ case 82:
25993
+ vfs.rename(this.resolve(mem.readCString(u(0))), this.resolve(mem.readCString(u(1))), cred);
25994
+ return 0n;
25995
+ case 89: {
25996
+ const target = vfs.readlink(this.resolve(mem.readCString(u(0))), cred);
25997
+ return this.writeBounded(u(1), u(2), target);
25998
+ }
25999
+ case 267: {
26000
+ const target = vfs.readlink(this.resolve(mem.readCString(u(1)), a(0)), cred);
26001
+ return this.writeBounded(u(2), u(3), target);
26002
+ }
26003
+ case 79: {
26004
+ const bytes = new TextEncoder().encode(this.cwd);
26005
+ if (bytes.length + 1 > u(1)) return BigInt(-34);
26006
+ mem.writeCString(u(0), this.cwd);
26007
+ return BigInt(bytes.length + 1);
26008
+ }
26009
+ case 80: {
26010
+ const path = this.resolve(mem.readCString(u(0)));
26011
+ if (!vfs.stat(path, { cred }).isDirectory()) return BigInt(-20);
26012
+ this.cwd = path;
26013
+ return 0n;
26014
+ }
26015
+ case 16: {
26016
+ return BigInt(-25);
26017
+ }
26018
+ case 72: {
26019
+ const command = a(1);
26020
+ if (command === 1 || command === 3) return 0n;
26021
+ return 0n;
26022
+ }
26023
+ case 32:
26024
+ case 33:
26025
+ case 292:
26026
+ return BigInt(-38);
26027
+ /* ── memory ───────────────────────────────────────────────────────── */
26028
+ case 12: {
26029
+ const requested = u(0);
26030
+ if (requested === 0) return BigInt(mem.brk);
26031
+ if (requested > mem.brk) mem.map(mem.brk, requested - mem.brk);
26032
+ mem.brk = requested;
26033
+ return BigInt(requested);
26034
+ }
26035
+ case 9: {
26036
+ const addr = u(0);
26037
+ const length = u(1);
26038
+ const fd = a(4);
26039
+ const offset = u(5);
26040
+ const base = addr !== 0 && (u(3) & 16) !== 0 ? addr : mem.mmapAnonymous(length);
26041
+ if (addr !== 0 && (u(3) & 16) !== 0) mem.map(addr, length);
26042
+ if (fd >= 0) {
26043
+ const file3 = this.files.get(fd);
26044
+ if (file3) mem.writeBytes(base, file3.data.subarray(offset, offset + length));
26045
+ }
26046
+ return BigInt(base);
26047
+ }
26048
+ case 10:
26049
+ return 0n;
26050
+ case 11:
26051
+ mem.unmap(u(0), u(1));
26052
+ return 0n;
26053
+ case 25:
26054
+ return BigInt(-38);
26055
+ case 28:
26056
+ return 0n;
26057
+ /* ── process and identity ─────────────────────────────────────────── */
26058
+ case 39:
26059
+ return 1n;
26060
+ // getpid
26061
+ case 102:
26062
+ case 104:
26063
+ case 107:
26064
+ case 108:
26065
+ return 0n;
26066
+ // getuid/getgid/geteuid/getegid
26067
+ case 110:
26068
+ return 0n;
26069
+ // getppid
26070
+ case 186:
26071
+ return 1n;
26072
+ // gettid
26073
+ case 39 + 4096:
26074
+ return 0n;
26075
+ case 60: {
26076
+ if (this.scheduler && this.scheduler.count > 1) {
26077
+ if (this.scheduler.exit(this.opts.now())) {
26078
+ this.switched = true;
26079
+ return 0n;
26080
+ }
26081
+ }
26082
+ throw new ProcessExit(a(0) & 255);
26083
+ }
26084
+ case 231:
26085
+ throw new ProcessExit(a(0) & 255);
26086
+ case 63:
26087
+ return this.uname(u(0));
26088
+ case 158: {
26089
+ const code = a(0);
26090
+ if (code === 4098) {
26091
+ cpu.fsBase = u(1);
26092
+ return 0n;
26093
+ }
26094
+ if (code === 4097) {
26095
+ cpu.gsBase = u(1);
26096
+ return 0n;
26097
+ }
26098
+ if (code === 4099) {
26099
+ mem.write(u(1), 8, BigInt(cpu.fsBase));
26100
+ return 0n;
26101
+ }
26102
+ return BigInt(-22);
26103
+ }
26104
+ case 218:
26105
+ return 1n;
26106
+ // set_tid_address
26107
+ case 273:
26108
+ return 0n;
26109
+ // set_robust_list
26110
+ case 334:
26111
+ return 0n;
26112
+ // rseq
26113
+ case 302: {
26114
+ const out = u(3);
26115
+ if (out !== 0) {
26116
+ mem.write(out, 8, 8n * 1024n * 1024n);
26117
+ mem.write(out + 8, 8, 0xffffffffffffffffn);
26118
+ }
26119
+ return 0n;
26120
+ }
26121
+ /* ── signals: recorded and ignored, since nothing raises them ─────── */
26122
+ case 13:
26123
+ case 14:
26124
+ case 131:
26125
+ return 0n;
26126
+ // rt_sigaction, rt_sigprocmask, sigaltstack
26127
+ /* ── time ─────────────────────────────────────────────────────────── */
26128
+ case 96: {
26129
+ const now = this.opts.now();
26130
+ if (u(0) !== 0) {
26131
+ mem.write(u(0), 8, BigInt(Math.floor(now / 1e3)));
26132
+ mem.write(u(0) + 8, 8, BigInt(Math.floor(now % 1e3 * 1e3)));
26133
+ }
26134
+ return 0n;
26135
+ }
26136
+ case 201:
26137
+ return BigInt(Math.floor(this.opts.now() / 1e3));
26138
+ // time
26139
+ case 228:
26140
+ case 229: {
26141
+ const now = this.opts.now();
26142
+ mem.write(u(1), 8, BigInt(Math.floor(now / 1e3)));
26143
+ mem.write(u(1) + 8, 8, BigInt(Math.floor(now % 1e3 * 1e6)));
26144
+ return 0n;
26145
+ }
26146
+ case 35:
26147
+ case 230: {
26148
+ if (this.scheduler?.yieldNow(this.opts.now())) this.switched = true;
26149
+ return 0n;
26150
+ }
26151
+ case 318: {
26152
+ const length = u(1);
26153
+ const bytes = new Uint8Array(length);
26154
+ for (let i = 0; i < length; i++) bytes[i] = Math.floor(Math.random() * 256);
26155
+ mem.writeBytes(u(0), bytes);
26156
+ return BigInt(length);
26157
+ }
26158
+ case 56: {
26159
+ if (!this.scheduler) return BigInt(-38);
26160
+ const flags = u(0);
26161
+ const CLONE_PARENT_SETTID = 1048576;
26162
+ const CLONE_CHILD_SETTID = 16777216;
26163
+ const CLONE_SETTLS = 524288;
26164
+ const tid = this.scheduler.spawn(args[1], u(4), (flags & CLONE_SETTLS) !== 0);
26165
+ if ((flags & CLONE_PARENT_SETTID) !== 0 && u(2) !== 0) mem.write(u(2), 4, BigInt(tid));
26166
+ if ((flags & CLONE_CHILD_SETTID) !== 0 && u(3) !== 0) mem.write(u(3), 4, BigInt(tid));
26167
+ this.threadsRequested++;
26168
+ return BigInt(tid);
26169
+ }
26170
+ case 435:
26171
+ return BigInt(-38);
26172
+ // clone3: callers fall back to clone
26173
+ /* ── epoll ────────────────────────────────────────────────────────────
26174
+ * Go initialises its network poller the first time any file descriptor
26175
+ * is opened — including a plain file — and treats a failure as fatal. So
26176
+ * `os.ReadFile` on a static Go binary depends on epoll existing.
26177
+ *
26178
+ * Registration is accepted and no event is ever reported, which is
26179
+ * correct for this machine: regular files are always ready and are read
26180
+ * directly, and there are no sockets to become ready. */
26181
+ case 213:
26182
+ case 291: {
26183
+ const fd = this.nextFd++;
26184
+ this.pseudoFds.add(fd);
26185
+ return BigInt(fd);
26186
+ }
26187
+ case 233:
26188
+ return this.pseudoFds.has(a(0)) ? 0n : BigInt(-9);
26189
+ case 232:
26190
+ case 281:
26191
+ return 0n;
26192
+ case 284:
26193
+ case 290: {
26194
+ const fd = this.nextFd++;
26195
+ this.pseudoFds.add(fd);
26196
+ return BigInt(fd);
26197
+ }
26198
+ case 234:
26199
+ return 0n;
26200
+ // tgkill — signals are not delivered here
26201
+ case 157:
26202
+ return 0n;
26203
+ // prctl — thread names and similar, nothing to store
26204
+ case 202: {
26205
+ if (!this.scheduler) return 0n;
26206
+ const address = u(0);
26207
+ const operation = a(1) & 127;
26208
+ const FUTEX_WAIT = 0, FUTEX_WAKE = 1;
26209
+ if (operation === FUTEX_WAIT) {
26210
+ if (mem.read(address, 4) !== (args[2] & 0xffffffffn)) return BigInt(-11);
26211
+ const timeout = u(3) !== 0 ? Number(mem.read(u(3), 8)) * 1e3 + Number(mem.read(u(3) + 8, 8)) / 1e6 : 0;
26212
+ if (this.scheduler.wait(address, timeout, this.opts.now())) {
26213
+ this.switched = true;
26214
+ return 0n;
26215
+ }
26216
+ return BigInt(-11);
26217
+ }
26218
+ if (operation === FUTEX_WAKE) return BigInt(this.scheduler.wake(address, u(2)));
26219
+ return 0n;
26220
+ }
26221
+ case 24: {
26222
+ if (this.scheduler?.yieldNow(this.opts.now())) this.switched = true;
26223
+ return 0n;
26224
+ }
26225
+ case 204: {
26226
+ mem.write(u(2), 8, 1n);
26227
+ return 8n;
26228
+ }
26229
+ default:
26230
+ this.unimplemented.add(number);
26231
+ return BigInt(-38);
26232
+ }
26233
+ }
26234
+ /* ── helpers ────────────────────────────────────────────────────────────── */
26235
+ writeTo(fd, bytes) {
26236
+ if (fd === 1) {
26237
+ this.opts.stdout(bytes);
26238
+ return BigInt(bytes.length);
26239
+ }
26240
+ if (fd === 2) {
26241
+ this.opts.stderr(bytes);
26242
+ return BigInt(bytes.length);
26243
+ }
26244
+ const file3 = this.files.get(fd);
26245
+ if (!file3) return BigInt(-9);
26246
+ const position = file3.append ? file3.data.length : file3.position;
26247
+ const end = Math.max(file3.data.length, position + bytes.length);
26248
+ if (end > file3.data.length) {
26249
+ const grown = new Uint8Array(end);
26250
+ grown.set(file3.data);
26251
+ file3.data = grown;
26252
+ }
26253
+ file3.data.set(bytes, position);
26254
+ file3.position = position + bytes.length;
26255
+ file3.dirty = true;
26256
+ this.flush(file3);
26257
+ return BigInt(bytes.length);
26258
+ }
26259
+ flush(file3) {
26260
+ if (!file3.dirty || file3.isDirectory) return;
26261
+ this.opts.vfs.writeFile(file3.path, file3.data, { cred: this.opts.cred });
26262
+ file3.dirty = false;
26263
+ }
26264
+ open(path, flags, mode) {
26265
+ const vfs = this.opts.vfs;
26266
+ const cred = this.opts.cred;
26267
+ const O_CREAT2 = 64, O_EXCL2 = 128, O_TRUNC2 = 512, O_APPEND = 1024;
26268
+ const exists = vfs.lexists(path);
26269
+ if (!exists && (flags & O_CREAT2) === 0) return BigInt(-2);
26270
+ if (exists && (flags & O_CREAT2) !== 0 && (flags & O_EXCL2) !== 0) return BigInt(-17);
26271
+ if (!exists) vfs.writeFile(path, new Uint8Array(0), { cred, mode: mode & 511 || 420 });
26272
+ const stats = vfs.stat(path, { cred });
26273
+ const isDirectory = stats.isDirectory();
26274
+ let data = new Uint8Array(0);
26275
+ if (!isDirectory) {
26276
+ data = (flags & O_TRUNC2) !== 0 ? new Uint8Array(0) : vfs.readFile(path, cred);
26277
+ if ((flags & O_TRUNC2) !== 0) vfs.writeFile(path, data, { cred });
26278
+ }
26279
+ const fd = this.nextFd++;
26280
+ this.files.set(fd, {
26281
+ path,
26282
+ position: 0,
26283
+ data,
26284
+ dirty: false,
26285
+ isDirectory,
26286
+ append: (flags & O_APPEND) !== 0,
26287
+ dirOffset: 0
26288
+ });
26289
+ return BigInt(fd);
26290
+ }
26291
+ /** Fill a 144-byte `struct stat`. */
26292
+ stat(path, out, follow) {
26293
+ const stats = follow ? this.opts.vfs.stat(path, { cred: this.opts.cred }) : this.opts.vfs.lstat(path);
26294
+ const mem = this.opts.memory;
26295
+ mem.map(out, SIZEOF_STAT);
26296
+ for (let i = 0; i < SIZEOF_STAT; i++) mem.write8(out + i, 0);
26297
+ mem.write(out, 8, BigInt(stats.dev));
26298
+ mem.write(out + 8, 8, BigInt(stats.ino));
26299
+ mem.write(out + 16, 8, BigInt(stats.nlink));
26300
+ mem.write(out + 24, 4, BigInt(stats.mode));
26301
+ mem.write(out + 28, 4, BigInt(stats.uid));
26302
+ mem.write(out + 32, 4, BigInt(stats.gid));
26303
+ mem.write(out + 40, 8, BigInt(stats.rdev));
26304
+ mem.write(out + 48, 8, BigInt(stats.size));
26305
+ mem.write(out + 56, 8, 4096n);
26306
+ mem.write(out + 64, 8, BigInt(Math.ceil(stats.size / 512)));
26307
+ mem.write(out + 72, 8, BigInt(Math.floor(stats.atimeMs / 1e3)));
26308
+ mem.write(out + 88, 8, BigInt(Math.floor(stats.mtimeMs / 1e3)));
26309
+ mem.write(out + 104, 8, BigInt(Math.floor(stats.ctimeMs / 1e3)));
26310
+ return 0n;
26311
+ }
26312
+ /** `fstat` on stdin/stdout/stderr, which are character devices. */
26313
+ statCharDevice(out) {
26314
+ const mem = this.opts.memory;
26315
+ mem.map(out, SIZEOF_STAT);
26316
+ for (let i = 0; i < SIZEOF_STAT; i++) mem.write8(out + i, 0);
26317
+ mem.write(out + 24, 4, BigInt(8592));
26318
+ mem.write(out + 56, 8, 4096n);
26319
+ return 0n;
26320
+ }
26321
+ getdents64(fd, buffer, size) {
26322
+ const file3 = this.files.get(fd);
26323
+ if (!file3) return BigInt(-9);
26324
+ if (!file3.isDirectory) return BigInt(-20);
26325
+ const names = [".", "..", ...this.opts.vfs.readdir(file3.path, this.opts.cred)];
26326
+ const mem = this.opts.memory;
26327
+ let used = 0;
26328
+ while (file3.dirOffset < names.length) {
26329
+ const name = names[file3.dirOffset];
26330
+ const bytes = new TextEncoder().encode(name);
26331
+ const length = Math.ceil((19 + bytes.length + 1) / 8) * 8;
26332
+ if (used + length > size) break;
26333
+ let type = 0;
26334
+ try {
26335
+ const mode = this.opts.vfs.lstat(
26336
+ name === "." ? file3.path : name === ".." ? dirname(file3.path) : join(file3.path, name)
26337
+ ).mode;
26338
+ type = (mode & 61440) === S_IFDIR ? 4 : (mode & 61440) === S_IFREG ? 8 : (mode & 61440) === S_IFLNK ? 10 : 0;
26339
+ } catch {
26340
+ }
26341
+ const at = buffer + used;
26342
+ mem.write(at, 8, BigInt(file3.dirOffset + 1));
26343
+ mem.write(at + 8, 8, BigInt(file3.dirOffset + 1));
26344
+ mem.write(at + 16, 2, BigInt(length));
26345
+ mem.write8(at + 18, type);
26346
+ mem.writeBytes(at + 19, bytes);
26347
+ mem.write8(at + 19 + bytes.length, 0);
26348
+ used += length;
26349
+ file3.dirOffset++;
26350
+ }
26351
+ return BigInt(used);
26352
+ }
26353
+ writeBounded(buffer, size, text) {
26354
+ const bytes = new TextEncoder().encode(text);
26355
+ const length = Math.min(bytes.length, size);
26356
+ this.opts.memory.writeBytes(buffer, bytes.subarray(0, length));
26357
+ return BigInt(length);
26358
+ }
26359
+ /** `struct utsname`: six fixed 65-byte character arrays. */
26360
+ uname(out) {
26361
+ const fields = ["Linux", this.opts.hostname, "6.1.0-sandboxedjs", "#1 SMP SandboxedJS", "x86_64", "(none)"];
26362
+ const mem = this.opts.memory;
26363
+ mem.map(out, 65 * 6);
26364
+ fields.forEach((value, index) => {
26365
+ const at = out + index * 65;
26366
+ for (let i = 0; i < 65; i++) mem.write8(at + i, 0);
26367
+ mem.writeCString(at, value);
26368
+ });
26369
+ return 0n;
26370
+ }
26371
+ /** Persist everything still open. Called when the guest exits. */
26372
+ close() {
26373
+ for (const file3 of this.files.values()) this.flush(file3);
26374
+ this.files.clear();
26375
+ }
26376
+ };
26377
+
26378
+ // src/runtime/x86/threads.ts
26379
+ function blank(id) {
26380
+ return {
26381
+ id,
26382
+ regs: new BigUint64Array(16),
26383
+ xmmLo: new BigUint64Array(16),
26384
+ xmmHi: new BigUint64Array(16),
26385
+ rip: 0,
26386
+ fsBase: 0,
26387
+ gsBase: 0,
26388
+ cf: false,
26389
+ zf: false,
26390
+ sf: false,
26391
+ of: false,
26392
+ pf: false,
26393
+ af: false,
26394
+ df: false,
26395
+ state: "runnable",
26396
+ waitAddress: 0,
26397
+ waitUntil: 0
26398
+ };
26399
+ }
26400
+ function save(cpu, context) {
26401
+ context.regs.set(cpu.regs);
26402
+ context.xmmLo.set(cpu.xmmLo);
26403
+ context.xmmHi.set(cpu.xmmHi);
26404
+ context.rip = cpu.rip;
26405
+ context.fsBase = cpu.fsBase;
26406
+ context.gsBase = cpu.gsBase;
26407
+ context.cf = cpu.cf;
26408
+ context.zf = cpu.zf;
26409
+ context.sf = cpu.sf;
26410
+ context.of = cpu.of;
26411
+ context.pf = cpu.pf;
26412
+ context.af = cpu.af;
26413
+ context.df = cpu.df;
26414
+ }
26415
+ function restore(cpu, context) {
26416
+ cpu.regs.set(context.regs);
26417
+ cpu.xmmLo.set(context.xmmLo);
26418
+ cpu.xmmHi.set(context.xmmHi);
26419
+ cpu.rip = context.rip;
26420
+ cpu.fsBase = context.fsBase;
26421
+ cpu.gsBase = context.gsBase;
26422
+ cpu.cf = context.cf;
26423
+ cpu.zf = context.zf;
26424
+ cpu.sf = context.sf;
26425
+ cpu.of = context.of;
26426
+ cpu.pf = context.pf;
26427
+ cpu.af = context.af;
26428
+ cpu.df = context.df;
26429
+ }
26430
+ var Scheduler = class {
26431
+ constructor(cpu) {
26432
+ this.cpu = cpu;
26433
+ this.current = blank(1);
26434
+ this.threads.push(this.current);
26435
+ }
26436
+ cpu;
26437
+ threads = [];
26438
+ current;
26439
+ nextId = 2;
26440
+ get currentId() {
26441
+ return this.current.id;
26442
+ }
26443
+ get count() {
26444
+ return this.threads.filter((t) => t.state !== "done").length;
26445
+ }
26446
+ /**
26447
+ * Start a new thread at the caller's return address.
26448
+ *
26449
+ * A cloned thread resumes exactly where its parent is — immediately after the
26450
+ * `syscall` instruction — with its own stack and `rax` of zero, which is how
26451
+ * the child tells itself apart from the parent.
26452
+ */
26453
+ spawn(stackPointer, tlsBase, setTls) {
26454
+ save(this.cpu, this.current);
26455
+ const child = blank(this.nextId++);
26456
+ child.regs.set(this.cpu.regs);
26457
+ child.xmmLo.set(this.cpu.xmmLo);
26458
+ child.xmmHi.set(this.cpu.xmmHi);
26459
+ child.rip = this.cpu.rip;
26460
+ child.fsBase = setTls ? tlsBase : this.cpu.fsBase;
26461
+ child.gsBase = this.cpu.gsBase;
26462
+ if (stackPointer !== 0n) child.regs[4] = stackPointer;
26463
+ child.regs[0] = 0n;
26464
+ this.threads.push(child);
26465
+ return child.id;
26466
+ }
26467
+ /** Park the running thread on a futex, and run something else. */
26468
+ wait(address, timeoutMs, now) {
26469
+ this.current.state = "waiting";
26470
+ this.current.waitAddress = address;
26471
+ this.current.waitUntil = timeoutMs > 0 ? now + timeoutMs : 0;
26472
+ return this.yieldNow(now);
26473
+ }
26474
+ /** Make up to `count` threads parked on `address` runnable. Returns how many. */
26475
+ wake(address, count) {
26476
+ let woken = 0;
26477
+ for (const thread of this.threads) {
26478
+ if (woken >= count) break;
26479
+ if (thread.state === "waiting" && thread.waitAddress === address) {
26480
+ thread.state = "runnable";
26481
+ thread.waitAddress = 0;
26482
+ woken++;
26483
+ }
26484
+ }
26485
+ return woken;
26486
+ }
26487
+ /** Retire the running thread. Returns false when it was the last one. */
26488
+ exit(now) {
26489
+ this.current.state = "done";
26490
+ return this.yieldNow(now);
26491
+ }
26492
+ /**
26493
+ * Switch to the next runnable thread.
26494
+ *
26495
+ * Returns false when there is nothing else to run — the caller then has to
26496
+ * decide whether that is a finished program or a deadlock.
26497
+ */
26498
+ yieldNow(now) {
26499
+ for (const thread of this.threads) {
26500
+ if (thread.state === "waiting" && thread.waitUntil !== 0 && now >= thread.waitUntil) {
26501
+ thread.state = "runnable";
26502
+ thread.waitAddress = 0;
26503
+ thread.waitUntil = 0;
26504
+ }
26505
+ }
26506
+ const start = this.threads.indexOf(this.current);
26507
+ for (let i = 1; i <= this.threads.length; i++) {
26508
+ const candidate = this.threads[(start + i) % this.threads.length];
26509
+ if (candidate.state !== "runnable" || candidate === this.current) continue;
26510
+ if (this.current.state !== "done") save(this.cpu, this.current);
26511
+ restore(this.cpu, candidate);
26512
+ this.current = candidate;
26513
+ return true;
26514
+ }
26515
+ if (this.current.state === "waiting") {
26516
+ this.current.state = "runnable";
26517
+ this.current.waitAddress = 0;
26518
+ }
26519
+ return false;
26520
+ }
26521
+ /** True when every thread is parked — a genuine deadlock rather than a wait. */
26522
+ get allBlocked() {
26523
+ return this.threads.every((t) => t.state !== "runnable");
26524
+ }
26525
+ };
26526
+
26527
+ // src/runtime/x86/elf.ts
26528
+ var ELF_MAGIC = [127, 69, 76, 70];
26529
+ var PT_LOAD = 1;
26530
+ var PT_INTERP = 3;
26531
+ var PT_PHDR = 6;
26532
+ var AT_NULL = 0;
26533
+ var AT_PHDR = 3;
26534
+ var AT_PHENT = 4;
26535
+ var AT_PHNUM = 5;
26536
+ var AT_PAGESZ = 6;
26537
+ var AT_BASE = 7;
26538
+ var AT_FLAGS = 8;
26539
+ var AT_ENTRY = 9;
26540
+ var AT_UID = 11;
26541
+ var AT_EUID = 12;
26542
+ var AT_GID = 13;
26543
+ var AT_EGID = 14;
26544
+ var AT_HWCAP = 16;
26545
+ var AT_CLKTCK = 17;
26546
+ var AT_SECURE = 23;
26547
+ var AT_RANDOM = 25;
26548
+ var AT_HWCAP2 = 26;
26549
+ var AT_EXECFN = 31;
26550
+ function isElf(bytes) {
26551
+ return bytes.length >= 4 && ELF_MAGIC.every((byte, i) => bytes[i] === byte);
26552
+ }
26553
+ var EM_X86_64 = 62;
26554
+ function summarize(bytes) {
26555
+ if (!isElf(bytes) || bytes.length < 64) return null;
26556
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
26557
+ const little = bytes[5] !== 2;
26558
+ const type = view.getUint16(16, little);
26559
+ const machine = view.getUint16(18, little);
26560
+ let interpreter = null;
26561
+ try {
26562
+ const phoff = Number(view.getBigUint64(32, little));
26563
+ const phentsize = view.getUint16(54, little);
26564
+ const phnum = view.getUint16(56, little);
26565
+ for (let i = 0; i < phnum; i++) {
26566
+ const at = phoff + i * phentsize;
26567
+ if (view.getUint32(at, little) !== PT_INTERP) continue;
26568
+ const offset = Number(view.getBigUint64(at + 8, little));
26569
+ const size = Number(view.getBigUint64(at + 32, little));
26570
+ interpreter = new TextDecoder().decode(bytes.subarray(offset, offset + size)).replace(/\0.*$/, "");
26571
+ }
26572
+ } catch {
26573
+ }
26574
+ return {
26575
+ machine,
26576
+ type,
26577
+ is64Bit: bytes[4] === 2,
26578
+ isLittleEndian: little,
26579
+ isDynamic: interpreter !== null,
26580
+ interpreter
26581
+ };
26582
+ }
26583
+ function loadElf(memory, bytes, loadBias = 0) {
26584
+ if (!isElf(bytes)) throw new Error("not an ELF file");
26585
+ if (bytes[4] !== 2) throw new Error("not a 64-bit ELF file");
26586
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
26587
+ const little = bytes[5] !== 2;
26588
+ const type = view.getUint16(16, little);
26589
+ const machine = view.getUint16(18, little);
26590
+ const entry = Number(view.getBigUint64(24, little));
26591
+ const phoff = Number(view.getBigUint64(32, little));
26592
+ const phentSize = view.getUint16(54, little);
26593
+ const phnum = view.getUint16(56, little);
26594
+ const isPie = type === 3;
26595
+ const bias = isPie ? loadBias : 0;
26596
+ let brk = 0;
26597
+ let interpreter = null;
26598
+ let phdrAddress = 0;
26599
+ for (let i = 0; i < phnum; i++) {
26600
+ const at = phoff + i * phentSize;
26601
+ const pType = view.getUint32(at, little);
26602
+ const offset = Number(view.getBigUint64(at + 8, little));
26603
+ const vaddr = Number(view.getBigUint64(at + 16, little)) + bias;
26604
+ const filesz = Number(view.getBigUint64(at + 32, little));
26605
+ const memsz = Number(view.getBigUint64(at + 40, little));
26606
+ if (pType === PT_INTERP) {
26607
+ interpreter = new TextDecoder().decode(bytes.subarray(offset, offset + filesz)).replace(/\0.*$/, "");
26608
+ continue;
26609
+ }
26610
+ if (pType === PT_PHDR) {
26611
+ phdrAddress = vaddr;
26612
+ continue;
26613
+ }
26614
+ if (pType !== PT_LOAD) continue;
26615
+ memory.map(vaddr, Math.max(memsz, 1));
26616
+ if (filesz > 0) memory.writeBytes(vaddr, bytes.subarray(offset, offset + filesz));
26617
+ brk = Math.max(brk, vaddr + memsz);
26618
+ }
26619
+ if (phdrAddress === 0) phdrAddress = bias + phoff;
26620
+ brk = Math.ceil(brk / PAGE_SIZE) * PAGE_SIZE;
26621
+ memory.brk = brk;
26622
+ return {
26623
+ entry: entry + bias,
26624
+ base: bias,
26625
+ interpreter,
26626
+ phdrAddress,
26627
+ phentSize,
26628
+ phnum,
26629
+ brk,
26630
+ machine,
26631
+ isPie
26632
+ };
26633
+ }
26634
+ function buildStack(memory, elf, opts) {
26635
+ const top2 = opts.top ?? 140737488289792;
26636
+ const size = opts.size ?? 8 * 1024 * 1024;
26637
+ memory.map(top2 - size, size);
26638
+ let cursor = top2 - 4096;
26639
+ const putString = (text) => {
26640
+ const bytes = new TextEncoder().encode(text);
26641
+ cursor -= bytes.length + 1;
26642
+ memory.writeBytes(cursor, bytes);
26643
+ memory.write8(cursor + bytes.length, 0);
26644
+ return cursor;
26645
+ };
26646
+ const argvPointers = opts.argv.map(putString);
26647
+ const envPointers = Object.entries(opts.env).filter(([, value]) => value !== void 0).map(([key, value]) => putString(`${key}=${value}`));
26648
+ const execfn = argvPointers[0] ?? putString("");
26649
+ cursor -= 16;
26650
+ const randomAddress = cursor;
26651
+ for (let i = 0; i < 16; i++) memory.write8(randomAddress + i, Math.floor(Math.random() * 256));
26652
+ const auxv = [
26653
+ [AT_PHDR, elf.phdrAddress],
26654
+ [AT_PHENT, elf.phentSize],
26655
+ [AT_PHNUM, elf.phnum],
26656
+ [AT_PAGESZ, PAGE_SIZE],
26657
+ [AT_BASE, opts.interpreterBase ?? 0],
26658
+ [AT_FLAGS, 0],
26659
+ [AT_ENTRY, elf.entry],
26660
+ [AT_UID, 0],
26661
+ [AT_EUID, 0],
26662
+ [AT_GID, 0],
26663
+ [AT_EGID, 0],
26664
+ // Advertise only the baseline. Reporting SSE4/AVX would invite glibc's
26665
+ // IFUNC resolvers to select string routines this interpreter cannot run.
26666
+ [AT_HWCAP, 0],
26667
+ [AT_HWCAP2, 0],
26668
+ [AT_CLKTCK, 100],
26669
+ [AT_SECURE, 0],
26670
+ [AT_RANDOM, randomAddress],
26671
+ [AT_EXECFN, execfn],
26672
+ [AT_NULL, 0]
26673
+ ];
26674
+ const words = 1 + argvPointers.length + 1 + envPointers.length + 1 + auxv.length * 2;
26675
+ let rsp = cursor - words * 8;
26676
+ rsp -= rsp % 16;
26677
+ let at = rsp;
26678
+ const push = (value) => {
26679
+ memory.write(at, 8, BigInt(value));
26680
+ at += 8;
26681
+ };
26682
+ push(argvPointers.length);
26683
+ for (const pointer of argvPointers) push(pointer);
26684
+ push(0);
26685
+ for (const pointer of envPointers) push(pointer);
26686
+ push(0);
26687
+ for (const [key, value] of auxv) {
26688
+ push(key);
26689
+ push(value);
26690
+ }
26691
+ return rsp;
26692
+ }
26693
+
26694
+ // src/runtime/x86/index.ts
26695
+ init_path();
26696
+ var PIE_BASE = 93824992215040;
26697
+ var INTERPRETER_BASE = 139637976727552;
26698
+ var MACHINE_NAMES = {
26699
+ 3: "x86",
26700
+ 8: "MIPS",
26701
+ 20: "PowerPC",
26702
+ 21: "PowerPC 64-bit",
26703
+ 40: "ARM",
26704
+ 62: "x86-64",
26705
+ 183: "AArch64",
26706
+ 243: "RISC-V"
26707
+ };
26708
+ async function runElf(ctx, path, opts = {}) {
26709
+ const image = ctx.vfs.readFile(path, ctx.cred);
26710
+ const header = summarize(image);
26711
+ if (!header) return ctx.fail(`${path}: not a valid ELF file`, 126);
26712
+ if (!header.is64Bit || header.machine !== EM_X86_64) {
26713
+ const name = MACHINE_NAMES[header.machine] ?? `machine 0x${header.machine.toString(16)}`;
26714
+ ctx.warn(`${basename(path)}: ${name} binary; this machine emulates x86-64 only`);
26715
+ return 126;
26716
+ }
26717
+ const memory = new Memory();
26718
+ const elf = loadElf(memory, image, PIE_BASE);
26719
+ let entry = elf.entry;
26720
+ let interpreterBase = 0;
26721
+ if (elf.interpreter) {
26722
+ const loaderPath = elf.interpreter;
26723
+ if (!ctx.vfs.lexists(loaderPath)) {
26724
+ for (const line of missingLoader(path, loaderPath)) ctx.stderr.write(line + "\n");
26725
+ return 126;
26726
+ }
26727
+ const loaderImage = ctx.vfs.readFile(loaderPath, ctx.cred);
26728
+ const loader = loadElf(memory, loaderImage, INTERPRETER_BASE);
26729
+ interpreterBase = loader.base;
26730
+ entry = loader.entry;
26731
+ }
26732
+ let stdin = new Uint8Array(0);
26733
+ if (!ctx.stdin.isTTY && !ctx.stdin.interactive) stdin = await ctx.stdin.readAll();
26734
+ const syscalls = new LinuxSyscalls({
26735
+ vfs: ctx.vfs,
26736
+ cred: ctx.cred,
26737
+ cwd: ctx.cwd,
26738
+ memory,
26739
+ stdin,
26740
+ stdout: (bytes) => ctx.write(bytes),
26741
+ stderr: (bytes) => ctx.stderr.write(bytes),
26742
+ hostname: ctx.kernel.hostname,
26743
+ now: ctx.kernel.now,
26744
+ ...opts.trace ? { trace: opts.trace } : {}
26745
+ });
26746
+ const cpu = new Cpu({
26747
+ memory,
26748
+ onSyscall: syscalls.handle,
26749
+ ...opts.instructionLimit !== void 0 ? { instructionLimit: opts.instructionLimit } : {}
26750
+ });
26751
+ syscalls.scheduler = new Scheduler(cpu);
26752
+ cpu.rip = entry;
26753
+ cpu.set(
26754
+ RSP,
26755
+ BigInt(
26756
+ buildStack(memory, elf, {
26757
+ argv: [ctx.name, ...ctx.args],
26758
+ env: ctx.env,
26759
+ interpreterBase
26760
+ })
26761
+ )
26762
+ );
26763
+ cpu.set(RDX, 0n);
26764
+ try {
26765
+ return await cpu.run();
26766
+ } catch (e) {
26767
+ if (e instanceof ProcessExit) return e.code;
26768
+ if (e instanceof UnsupportedInstruction) {
26769
+ ctx.warn(`${basename(path)}: ${e.message}`);
26770
+ ctx.stderr.write(
26771
+ "This binary uses an instruction the emulator does not implement \u2014 most\noften a vector (SSE/AVX) routine. Register state at the fault:\n"
26772
+ );
26773
+ for (const line of cpu.describe().split("\n")) ctx.stderr.write(" " + line + "\n");
26774
+ return 132;
26775
+ }
26776
+ if (e instanceof SegFault) {
26777
+ ctx.warn(`${basename(path)}: ${e.message}`);
26778
+ return 139;
26779
+ }
26780
+ ctx.warn(e instanceof Error ? e.message : String(e));
26781
+ return 1;
26782
+ } finally {
26783
+ syscalls.close();
26784
+ if (syscalls.unimplemented.size > 0) {
26785
+ ctx.stderr.write(
26786
+ `note: unimplemented syscalls used: ${[...syscalls.unimplemented].sort((a, b) => a - b).join(", ")}
26787
+ `
26788
+ );
26789
+ }
26790
+ }
26791
+ }
26792
+ function missingLoader(path, loader) {
26793
+ return [
26794
+ `${basename(path)}: needs the dynamic linker ${loader}, which is not in this container.`,
26795
+ "",
26796
+ "This binary is linked against shared libraries, so it cannot start until the",
26797
+ "loader and the libraries it names are present. They are ordinary x86-64 files",
26798
+ "and the emulator runs them as-is \u2014 copy them in from a Linux system:",
26799
+ "",
26800
+ ` ${loader}`,
26801
+ " /lib/x86_64-linux-gnu/libc.so.6",
26802
+ "",
26803
+ "A statically linked build needs none of this. If you control the build:",
26804
+ " Go CGO_ENABLED=0 go build",
26805
+ " Rust cargo build --target x86_64-unknown-linux-musl"
26806
+ ];
26807
+ }
26808
+ var elfFormat = {
26809
+ name: "elf",
26810
+ priority: 20,
26811
+ matches: (head2) => isElf(head2),
26812
+ describe: (head2) => {
26813
+ const header = summarize(head2);
26814
+ if (!header) return "ELF object";
26815
+ const machine = MACHINE_NAMES[header.machine] ?? `machine 0x${header.machine.toString(16)}`;
26816
+ const kind = header.type === 3 ? header.isDynamic ? "pie executable" : "shared object" : header.type === 2 ? "executable" : "object";
26817
+ return `ELF ${header.is64Bit ? "64-bit" : "32-bit"} ${header.isLittleEndian ? "LSB" : "MSB"} ${kind}, ${machine}` + (header.isDynamic ? ", dynamically linked" : ", statically linked");
26818
+ },
26819
+ run: (ctx, path) => runElf(ctx, path)
26820
+ };
26821
+ var x86 = defineCommand({
26822
+ name: "x86",
26823
+ path: "/usr/bin/x86",
26824
+ summary: "run and inspect x86-64 Linux binaries",
26825
+ usage: "x86 run <binary> [args...] | x86 info <binary> | x86 trace <binary>",
26826
+ manual: `Runs a native x86-64 Linux executable on an emulated CPU, against the
26827
+ container's own filesystem.
26828
+
26829
+ Any ELF binary marked executable also runs directly, so './tool' does the
26830
+ same thing as 'x86 run tool'.
26831
+
26832
+ info report the architecture, linkage and required loader
26833
+ trace run with every syscall printed, for working out where a
26834
+ program stops`,
26835
+ async run(ctx) {
26836
+ const [subcommand, target, ...rest] = ctx.args;
26837
+ if (subcommand === void 0 || subcommand === "-h" || subcommand === "--help") {
26838
+ ctx.line("Usage: x86 run <binary> [args...]");
26839
+ ctx.line(" x86 info <binary>");
26840
+ ctx.line(" x86 trace <binary> [args...]");
26841
+ return subcommand === void 0 ? 2 : 0;
26842
+ }
26843
+ if (target === void 0) return ctx.fail(`${subcommand}: a binary is required`, 2);
26844
+ const path = ctx.path(target);
26845
+ if (!ctx.vfs.lexists(path)) return ctx.fail(`${target}: No such file or directory`, 1);
26846
+ if (subcommand === "info") {
26847
+ const header = summarize(ctx.vfs.readFile(path, ctx.cred).subarray(0, 4096));
26848
+ if (!header) return ctx.fail(`${target}: not an ELF file`, 1);
26849
+ const machine = MACHINE_NAMES[header.machine] ?? `0x${header.machine.toString(16)}`;
26850
+ ctx.line(`file: ${path}`);
26851
+ ctx.line(`class: ${header.is64Bit ? "ELF64" : "ELF32"}`);
26852
+ ctx.line(`machine: ${machine}`);
26853
+ ctx.line(`linkage: ${header.isDynamic ? "dynamic" : "static"}`);
26854
+ if (header.interpreter) {
26855
+ const present = ctx.vfs.lexists(header.interpreter);
26856
+ ctx.line(`loader: ${header.interpreter} ${present ? "(present)" : "(MISSING)"}`);
26857
+ }
26858
+ const runnable = header.is64Bit && header.machine === EM_X86_64 && (!header.interpreter || ctx.vfs.lexists(header.interpreter));
26859
+ ctx.line(`runnable: ${runnable ? "yes" : "no"}`);
26860
+ return 0;
26861
+ }
26862
+ const forwarded = { ...ctx, args: rest, argv: [basename(path), ...rest] };
26863
+ if (subcommand === "run") return await runElf(forwarded, path);
26864
+ if (subcommand === "trace") {
26865
+ return await runElf(forwarded, path, { trace: (line) => ctx.stderr.write(line + "\n") });
26866
+ }
26867
+ return ctx.fail(`unknown subcommand: ${subcommand}`, 2);
26868
+ }
26869
+ });
26870
+ function x86Commands() {
26871
+ return [x86];
26872
+ }
26873
+ var DECLARES_EXPORTS = /(?:^|[\s;{)])(?:var|let|const|function|class)\s+exports\b/;
26874
+ var NAME = "exports";
26875
+ function renameShadowedExports(source) {
26876
+ if (!DECLARES_EXPORTS.test(source)) return null;
26877
+ let ast;
26878
+ try {
26879
+ ast = acorn.parse(source, {
26880
+ ecmaVersion: "latest",
26881
+ sourceType: "module",
26882
+ allowAwaitOutsideFunction: true,
26883
+ allowHashBang: true
26884
+ });
26885
+ } catch {
26886
+ return null;
26887
+ }
26888
+ const body = ast.body;
26889
+ const isModule = body.some(
26890
+ (node2) => node2.type.startsWith("Import") || node2.type.startsWith("Export")
26891
+ );
26892
+ if (!isModule) return null;
26893
+ const programScope = { bindsExports: hoistedNames(body).has(NAME), parent: null };
26894
+ if (!programScope.bindsExports) return null;
26895
+ const targets = [];
26896
+ let bail = false;
26897
+ visit(ast, programScope);
26898
+ if (bail || targets.length === 0) return null;
26899
+ const replacement = freshName(source);
26900
+ let out = source;
26901
+ for (const target of [...targets].sort((a, b) => b.start - a.start)) {
26902
+ const text = target.shorthand ? `${NAME}: ${replacement}` : replacement;
26903
+ out = out.slice(0, target.start) + text + out.slice(target.end);
26904
+ }
26905
+ return out;
26906
+ function visit(node2, scope) {
26907
+ if (bail) return;
26908
+ let childScope = scope;
26909
+ let skip = NOTHING;
26910
+ switch (node2.type) {
26911
+ case "FunctionDeclaration":
26912
+ case "FunctionExpression":
26913
+ case "ArrowFunctionExpression": {
26914
+ const names = /* @__PURE__ */ new Set();
26915
+ for (const param of node2.params ?? []) collectPattern(param, names);
26916
+ if (isExports(node2.id) && node2.type === "FunctionExpression") names.add(NAME);
26917
+ const fnBody = node2.body;
26918
+ if (fnBody?.type === "BlockStatement") {
26919
+ for (const name of hoistedNames(fnBody.body)) names.add(name);
26920
+ }
26921
+ childScope = { bindsExports: names.has(NAME), parent: scope };
26922
+ break;
26923
+ }
26924
+ case "CatchClause": {
26925
+ const names = /* @__PURE__ */ new Set();
26926
+ if (node2.param) collectPattern(node2.param, names);
26927
+ childScope = { bindsExports: names.has(NAME), parent: scope };
26928
+ break;
26929
+ }
26930
+ case "ClassDeclaration":
26931
+ case "ClassExpression":
26932
+ if (isExports(node2.id) && node2.type === "ClassExpression") {
26933
+ childScope = { bindsExports: true, parent: scope };
26934
+ }
26935
+ break;
26936
+ case "BlockStatement":
26937
+ case "StaticBlock":
26938
+ if (node2 !== ast.body) {
26939
+ childScope = {
26940
+ bindsExports: blockNames(node2.body).has(NAME),
26941
+ parent: scope
26942
+ };
26943
+ }
26944
+ break;
26945
+ case "ForStatement":
26946
+ case "ForInStatement":
26947
+ case "ForOfStatement": {
26948
+ const head2 = node2.init ?? node2.left;
26949
+ if (head2?.type === "VariableDeclaration" && head2.kind !== "var") {
26950
+ const names = /* @__PURE__ */ new Set();
26951
+ for (const declarator of head2.declarations) {
26952
+ collectPattern(declarator.id, names);
26953
+ }
26954
+ childScope = { bindsExports: names.has(NAME), parent: scope };
26955
+ }
26956
+ break;
26957
+ }
26958
+ /* `export { exports }` would have to become `exports as exports`, and
26959
+ * `import { exports as x }` names someone else's binding. Neither is
26960
+ * worth handling; refuse rather than rewrite them wrongly. */
26961
+ case "ExportSpecifier":
26962
+ case "ImportSpecifier":
26963
+ if (isExports(node2.local) || isExports(node2.exported) || isExports(node2.imported)) {
26964
+ bail = true;
26965
+ }
26966
+ return;
26967
+ case "Identifier":
26968
+ if (node2.name === NAME && resolvesToProgram(scope)) {
26969
+ targets.push({ start: node2.start, end: node2.end, shorthand: false });
26970
+ }
26971
+ return;
26972
+ // Property positions are names, not references to the binding.
26973
+ case "MemberExpression":
26974
+ case "MethodDefinition":
26975
+ case "PropertyDefinition":
26976
+ skip = node2.computed ? NOTHING : PROPERTY;
26977
+ break;
26978
+ case "Property":
26979
+ if (node2.computed) break;
26980
+ if (node2.shorthand) {
26981
+ const value = node2.value;
26982
+ if (isExports(value) && resolvesToProgram(scope)) {
26983
+ targets.push({ start: value.start, end: value.end, shorthand: true });
26984
+ }
26985
+ return;
26986
+ }
26987
+ skip = PROPERTY;
26988
+ break;
26989
+ case "LabeledStatement":
26990
+ case "BreakStatement":
26991
+ case "ContinueStatement":
26992
+ skip = LABEL;
26993
+ break;
26994
+ }
26995
+ for (const [key, value] of Object.entries(node2)) {
26996
+ if (key === "type" || key === "start" || key === "end" || skip.includes(key)) continue;
26997
+ if (Array.isArray(value)) {
26998
+ for (const item of value) if (isNode2(item)) visit(item, childScope);
26999
+ } else if (isNode2(value)) {
27000
+ visit(value, childScope);
27001
+ }
27002
+ }
27003
+ }
27004
+ }
27005
+ function resolvesToProgram(scope) {
27006
+ for (let current = scope; current; current = current.parent) {
27007
+ if (current.bindsExports) return current.parent === null;
27008
+ }
27009
+ return false;
27010
+ }
23840
27011
  function hoistedNames(body) {
23841
27012
  const names = blockNames(body);
23842
27013
  collectVars(body, names);
@@ -24032,14 +27203,25 @@ function inspectPackage(ctx, packageDir, packageName) {
24032
27203
  const platformDeps = Object.keys(manifest.optionalDependencies ?? {}).filter(
24033
27204
  (dep) => /-(linux|darwin|win32|windows|freebsd)(-|$)|-(x64|arm64|ia32)(-|$)/.test(dep)
24034
27205
  );
24035
- if (nativeBins.length === 0 && platformDeps.length === 0) return { verdict: "ok", lines: [] };
27206
+ const cpuConstrained = Array.isArray(manifest.cpu) && manifest.cpu.length > 0;
27207
+ if (nativeBins.length === 0 && platformDeps.length === 0 && !cpuConstrained) {
27208
+ return { verdict: "ok", lines: [] };
27209
+ }
24036
27210
  const lines = [];
24037
27211
  const label = manifest.name ?? packageName;
24038
27212
  if (nativeBins.length > 0) {
24039
27213
  const [name, relative2] = nativeBins[0];
24040
27214
  lines.push(`${label}: '${name}' points at ${relative2}, which is a compiled executable, not JavaScript.`);
24041
- } else {
27215
+ } else if (platformDeps.length > 0) {
24042
27216
  lines.push(`${label}: ships its executable as a platform-specific package, not as JavaScript.`);
27217
+ } else {
27218
+ const targets = [
27219
+ ...manifest.os ?? [],
27220
+ ...manifest.cpu ?? []
27221
+ ].join(", ");
27222
+ lines.push(`${label}: declares a platform constraint (${targets}), so its payload is a native binary.`);
27223
+ lines.push("Packages shaped like this download a prebuilt executable during install and");
27224
+ lines.push("run it from a JavaScript launcher.");
24043
27225
  }
24044
27226
  if (platformDeps.length > 0) {
24045
27227
  const shown = platformDeps.slice(0, 3).join(", ");
@@ -24048,10 +27230,18 @@ function inspectPackage(ctx, packageDir, packageName) {
24048
27230
  );
24049
27231
  lines.push("each of which is native machine code for one OS and CPU.");
24050
27232
  }
27233
+ const hasLinuxBuild = platformDeps.some((dep) => /linux/.test(dep)) || platformDeps.length === 0;
24051
27234
  lines.push("");
24052
- lines.push("This container has no CPU to run machine code on \u2014 only JavaScript and");
24053
- lines.push("WebAssembly. The package is installed and its files are on disk, but the");
24054
- lines.push("command it provides cannot start.");
27235
+ if (hasLinuxBuild) {
27236
+ lines.push("This container emulates x86-64 Linux, so a linux-x64 build can run \u2014 but it");
27237
+ lines.push("has to be on disk, and a dynamically linked one also needs its loader:");
27238
+ lines.push(" x86 info <binary> what it is and whether it can start");
27239
+ lines.push(" /lib64/ld-linux-x86-64.so.2 copy in from any Linux system, with libc.so.6");
27240
+ lines.push("Builds for macOS or Windows will not run.");
27241
+ } else {
27242
+ lines.push("This container emulates x86-64 Linux only. None of the platform packages");
27243
+ lines.push("above target it, so there is no build here that can start.");
27244
+ }
24055
27245
  return { verdict: "native", lines };
24056
27246
  }
24057
27247
 
@@ -24188,8 +27378,6 @@ async function linkGlobalPackages(ctx, stagingRoot, specs, quiet, runScripts) {
24188
27378
  ctx.stderr.write("\n");
24189
27379
  for (const line of report.lines) ctx.stderr.write(`npm warn ${line}
24190
27380
  `);
24191
- status = report.verdict === "native" ? 1 : status;
24192
- continue;
24193
27381
  }
24194
27382
  if (!quiet) {
24195
27383
  if (linked.length > 0) {
@@ -24399,8 +27587,6 @@ var npm = defineCommand({
24399
27587
  if (report.verdict !== "ok") {
24400
27588
  for (const line of report.lines) ctx.stderr.write(`npm warn ${line}
24401
27589
  `);
24402
- status = 1;
24403
- continue;
24404
27590
  }
24405
27591
  for (const bin of linked) ctx.line(`${join(binDir, bin)} -> ${dir3}`);
24406
27592
  }
@@ -24826,11 +28012,12 @@ function allCommands() {
24826
28012
  ...pythonCommands(),
24827
28013
  ...ffmpegCommands(),
24828
28014
  ...wasmCommands(),
28015
+ ...x86Commands(),
24829
28016
  ...packageCommands()
24830
28017
  ];
24831
28018
  }
24832
28019
  function allFormats() {
24833
- return [wasmFormat, ...nativeFormats()];
28020
+ return [wasmFormat, elfFormat, ...nativeFormats()];
24834
28021
  }
24835
28022
  function installUserland(kernel) {
24836
28023
  kernel.installCommands(allCommands());
@@ -25947,12 +29134,16 @@ exports.CallbackSink = CallbackSink;
25947
29134
  exports.CommandRegistry = CommandRegistry;
25948
29135
  exports.Container = Container;
25949
29136
  exports.ContainerFs = ContainerFs;
29137
+ exports.ExecFormatRegistry = ExecFormatRegistry;
25950
29138
  exports.FileInput = FileInput;
25951
29139
  exports.FileOutput = FileOutput;
29140
+ exports.GuestMemory = Memory;
25952
29141
  exports.IncompleteInputError = IncompleteInputError;
25953
29142
  exports.KERNEL_NAME = KERNEL_NAME;
25954
29143
  exports.KERNEL_RELEASE = KERNEL_RELEASE;
25955
29144
  exports.Kernel = Kernel;
29145
+ exports.LinuxSyscalls = LinuxSyscalls;
29146
+ exports.MAGIC_BYTES = MAGIC_BYTES;
25956
29147
  exports.NODE_VERSION = NODE_VERSION;
25957
29148
  exports.NPM_VERSION = NPM_VERSION;
25958
29149
  exports.NetworkStack = NetworkStack;
@@ -25974,16 +29165,22 @@ exports.TeeOutput = TeeOutput;
25974
29165
  exports.Terminal = Terminal;
25975
29166
  exports.UserDatabase = UserDatabase;
25976
29167
  exports.Vfs = Vfs;
29168
+ exports.WasiExit = WasiExit;
29169
+ exports.WasiPreview1 = WasiPreview1;
29170
+ exports.X86Cpu = Cpu;
25977
29171
  exports.allCommands = allCommands;
29172
+ exports.analyseWasm = analyse;
25978
29173
  exports.applyChmod = applyChmod;
25979
29174
  exports.braceExpand = braceExpand;
25980
29175
  exports.buildRootfs = buildRootfs;
25981
29176
  exports.builtinNames = builtinNames;
25982
29177
  exports.captureStdio = captureStdio;
29178
+ exports.clearWasmCache = clearWasmCache;
25983
29179
  exports.createContainer = createContainer;
25984
29180
  exports.createContext = createContext;
25985
29181
  exports.default = src_default;
25986
29182
  exports.defineCommand = defineCommand;
29183
+ exports.elfFormat = elfFormat;
25987
29184
  exports.evalArith = evalArith;
25988
29185
  exports.exitCodeForSignal = exitCodeForSignal;
25989
29186
  exports.expandPrompt = expandPrompt;
@@ -25999,15 +29196,23 @@ exports.installUserland = installUserland;
25999
29196
  exports.isBuiltinName = isBuiltinName;
26000
29197
  exports.isPythonAvailable = isPythonAvailable;
26001
29198
  exports.isSysError = isSysError;
29199
+ exports.isWasmBinary = isWasmBinary;
29200
+ exports.machoFormat = machoFormat;
26002
29201
  exports.makeCred = makeCred;
29202
+ exports.nativeFormats = nativeFormats;
26003
29203
  exports.normalizeSignal = normalizeSignal;
26004
29204
  exports.octalMode = octalMode;
26005
29205
  exports.parseShell = parse;
26006
29206
  exports.parseUmask = parseUmask;
29207
+ exports.peFormat = peFormat;
26007
29208
  exports.posixPath = path_exports;
26008
29209
  exports.resetPidCounter = resetPidCounter;
29210
+ exports.runElf = runElf;
29211
+ exports.runWasi = runWasi;
26009
29212
  exports.shellQuote = shellQuote;
26010
29213
  exports.strerror = strerror;
26011
29214
  exports.unameInfo = unameInfo;
29215
+ exports.wasmFormat = wasmFormat;
29216
+ exports.x86Commands = x86Commands;
26012
29217
  //# sourceMappingURL=index.cjs.map
26013
29218
  //# sourceMappingURL=index.cjs.map