sandboxedjs 0.1.8 → 0.1.9

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