@ai-ecoverse/slicc-kernel 1.3.0 → 1.4.0

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.
@@ -1206,12 +1206,12 @@ var Budget = class {
1206
1206
  while (this.free < n) {
1207
1207
  signal.throwIfAborted();
1208
1208
  await new Promise((resolve) => {
1209
- const done3 = () => {
1210
- signal.removeEventListener("abort", done3);
1209
+ const done4 = () => {
1210
+ signal.removeEventListener("abort", done4);
1211
1211
  resolve();
1212
1212
  };
1213
- this.waiters.push(done3);
1214
- signal.addEventListener("abort", done3, { once: true });
1213
+ this.waiters.push(done4);
1214
+ signal.addEventListener("abort", done4, { once: true });
1215
1215
  });
1216
1216
  }
1217
1217
  this.free -= n;
@@ -1422,11 +1422,11 @@ var RealmProxy = class {
1422
1422
  this.slots.release(1);
1423
1423
  break;
1424
1424
  }
1425
- const done3 = this.serve(conn).finally(() => {
1425
+ const done4 = this.serve(conn).finally(() => {
1426
1426
  this.slots.release(1);
1427
- served.delete(done3);
1427
+ served.delete(done4);
1428
1428
  });
1429
- served.add(done3);
1429
+ served.add(done4);
1430
1430
  }
1431
1431
  } catch {
1432
1432
  }
@@ -2450,44 +2450,83 @@ function envOf(pkg, raw) {
2450
2450
  const env = {};
2451
2451
  if (!raw || typeof raw !== "object") return env;
2452
2452
  for (const [key, value] of Object.entries(raw)) {
2453
- if (ENV_KEY.test(key) && typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
2453
+ if (!ENV_KEY.test(key)) continue;
2454
+ if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
2455
+ else if (value === null) env[key] = null;
2454
2456
  }
2455
2457
  return env;
2456
2458
  }
2459
+ function envFields(merged) {
2460
+ const env = {};
2461
+ const unset = [];
2462
+ for (const [key, value] of Object.entries(merged)) {
2463
+ if (value === null) unset.push(key);
2464
+ else env[key] = value;
2465
+ }
2466
+ return {
2467
+ ...Object.keys(env).length > 0 ? { env } : {},
2468
+ ...unset.length > 0 ? { unset } : {}
2469
+ };
2470
+ }
2457
2471
  function argsOf(pkg, raw) {
2458
2472
  if (!Array.isArray(raw) || raw.length === 0) return void 0;
2459
2473
  if (!raw.every((arg) => typeof arg === "string")) return void 0;
2460
2474
  return raw.map((arg) => arg.replace(PACKAGE, pkg));
2461
2475
  }
2476
+ function abiOf(raw, fallback) {
2477
+ const abi = raw ?? fallback;
2478
+ return abi === "emscripten" || abi === "wasi" ? abi : void 0;
2479
+ }
2480
+ function commandOf(pkg, name2, raw, packageAbi, shared) {
2481
+ const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
2482
+ const script = inside(pkg, raw.script);
2483
+ if (script) {
2484
+ return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
2485
+ }
2486
+ const abi = abiOf(raw.abi, packageAbi);
2487
+ const wasm = inside(pkg, raw.wasm);
2488
+ const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
2489
+ if (!abi || !glue || !wasm) return void 0;
2490
+ const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
2491
+ const args = argsOf(pkg, raw.args);
2492
+ const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
2493
+ return {
2494
+ name: name2,
2495
+ abi,
2496
+ glue,
2497
+ wasm,
2498
+ argv0,
2499
+ ...args ? { args } : {},
2500
+ ...withEnv,
2501
+ ...imports ? { imports } : {}
2502
+ };
2503
+ }
2462
2504
  function commandsOf(pkg, manifest) {
2463
2505
  const slicc = manifest.slicc;
2464
- if (!slicc || typeof slicc !== "object" || (slicc.abi ?? "emscripten") !== "emscripten")
2465
- return [];
2506
+ if (!slicc || typeof slicc !== "object") return [];
2507
+ const packageAbi = abiOf(slicc.abi, "emscripten");
2466
2508
  const entries = slicc.commands;
2467
- if (!entries || typeof entries !== "object") return [];
2509
+ if (!packageAbi || !entries || typeof entries !== "object") return [];
2468
2510
  const shared = envOf(pkg, slicc.env);
2469
2511
  const out = [];
2470
2512
  for (const [name2, raw] of Object.entries(entries)) {
2471
2513
  if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
2472
2514
  continue;
2473
2515
  }
2474
- const glue = inside(pkg, raw.glue);
2475
- const wasm = inside(pkg, raw.wasm);
2476
- if ((raw.abi ?? "emscripten") !== "emscripten" || !glue || !wasm) continue;
2477
- const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
2478
- const args = argsOf(pkg, raw.args);
2479
- const env = { ...shared, ...envOf(pkg, raw.env) };
2480
- out.push({
2481
- name: name2,
2482
- glue,
2483
- wasm,
2484
- argv0,
2485
- ...args ? { args } : {},
2486
- ...Object.keys(env).length > 0 ? { env } : {}
2487
- });
2516
+ const command = commandOf(pkg, name2, raw, packageAbi, shared);
2517
+ if (command) out.push(command);
2488
2518
  }
2489
2519
  return out;
2490
2520
  }
2521
+ async function withPackagePaths(fs, pkg, command) {
2522
+ if (!command.env) return command;
2523
+ const env = {};
2524
+ for (const [key, value] of Object.entries(command.env)) {
2525
+ const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
2526
+ env[key] = path && await fs.exists(path) ? path : value;
2527
+ }
2528
+ return { ...command, env };
2529
+ }
2491
2530
  async function packages(fs, modules) {
2492
2531
  const names = await fs.readdir(modules).catch(() => []);
2493
2532
  const dirs = [];
@@ -2513,7 +2552,8 @@ async function scanCommands(fs, modules) {
2513
2552
  continue;
2514
2553
  }
2515
2554
  for (const command of commandsOf(pkg, manifest)) {
2516
- if (!found.has(command.name)) found.set(command.name, command);
2555
+ if (!found.has(command.name))
2556
+ found.set(command.name, await withPackagePaths(fs, pkg, command));
2517
2557
  }
2518
2558
  }
2519
2559
  if (!found.has("sh") && found.has("bash")) {
@@ -2750,8 +2790,8 @@ var ChildTable = class {
2750
2790
  for (; ; ) {
2751
2791
  const candidates = this.candidates(pid, flags);
2752
2792
  if (candidates.length === 0) throw new KernelError("ECHILD");
2753
- const done3 = candidates.find(([, child]) => child.code !== void 0);
2754
- if (done3) return this.reap(done3[0], done3[1].code);
2793
+ const done4 = candidates.find(([, child]) => child.code !== void 0);
2794
+ if (done4) return this.reap(done4[0], done4[1].code);
2755
2795
  const changed = this.stateReport(candidates, flags);
2756
2796
  if (changed) return changed;
2757
2797
  if (nohang) return [0, 0];
@@ -3061,6 +3101,167 @@ function attachSyncSabResponder(port, sab, token, opts) {
3061
3101
  };
3062
3102
  }
3063
3103
 
3104
+ // src/kernel/host-ops.ts
3105
+ var HOST_OPS = [
3106
+ "lock",
3107
+ "unlock",
3108
+ "async-submit",
3109
+ "async-wait",
3110
+ "async-take",
3111
+ "async-resolve",
3112
+ "async-hold",
3113
+ "async-cancel",
3114
+ "async-close"
3115
+ ];
3116
+ var json2 = (value) => ({ ok: true, kind: "json", json: value });
3117
+ var done2 = { ok: true, kind: "void" };
3118
+ var LockTable = class {
3119
+ held = /* @__PURE__ */ new Map();
3120
+ lock(pid, path, exclusive, fd) {
3121
+ const holders = this.held.get(path) ?? /* @__PURE__ */ new Map();
3122
+ for (const [other, h] of holders) if (other !== pid && (exclusive || h.exclusive)) return false;
3123
+ const mine = holders.get(pid) ?? { exclusive, fds: /* @__PURE__ */ new Set() };
3124
+ mine.exclusive = exclusive;
3125
+ mine.fds.add(fd);
3126
+ holders.set(pid, mine);
3127
+ this.held.set(path, holders);
3128
+ return true;
3129
+ }
3130
+ unlock(pid, path) {
3131
+ const holders = this.held.get(path);
3132
+ holders?.delete(pid);
3133
+ if (holders?.size === 0) this.held.delete(path);
3134
+ }
3135
+ closed(pid, fd) {
3136
+ for (const [path, holders] of [...this.held]) {
3137
+ const mine = holders.get(pid);
3138
+ if (mine?.fds.delete(fd) && mine.fds.size === 0) this.unlock(pid, path);
3139
+ }
3140
+ }
3141
+ release(pid) {
3142
+ for (const path of [...this.held.keys()]) this.unlock(pid, path);
3143
+ }
3144
+ };
3145
+ var AsyncOps = class {
3146
+ next = 1;
3147
+ results = /* @__PURE__ */ new Map();
3148
+ ready = [];
3149
+ waiters = /* @__PURE__ */ new Set();
3150
+ closed = false;
3151
+ run;
3152
+ constructor(run2) {
3153
+ this.run = run2;
3154
+ }
3155
+ submit(req) {
3156
+ const id = this.hold();
3157
+ this.run(req).then(
3158
+ (result) => this.complete(id, result),
3159
+ (err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
3160
+ );
3161
+ return id;
3162
+ }
3163
+ resolve(value) {
3164
+ const id = this.hold();
3165
+ this.complete(id, json2(value));
3166
+ return id;
3167
+ }
3168
+ hold() {
3169
+ const id = this.next++;
3170
+ this.results.set(id, void 0);
3171
+ return id;
3172
+ }
3173
+ take(id) {
3174
+ if (!this.results.has(id)) throw new KernelError("EINVAL");
3175
+ const result = this.results.get(id);
3176
+ if (!result) throw new KernelError("EAGAIN");
3177
+ this.cancel(id);
3178
+ return result;
3179
+ }
3180
+ cancel(id) {
3181
+ this.results.delete(id);
3182
+ const at = this.ready.indexOf(id);
3183
+ if (at >= 0) this.ready.splice(at, 1);
3184
+ }
3185
+ async wait(timeoutMs, signal) {
3186
+ for (; ; ) {
3187
+ if (this.closed) return -1;
3188
+ const id = this.ready.shift();
3189
+ if (id !== void 0) return id;
3190
+ if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
3191
+ const woke = await this.sleep(timeoutMs, signal);
3192
+ if (!woke) return 0;
3193
+ }
3194
+ }
3195
+ close() {
3196
+ this.closed = true;
3197
+ this.wake();
3198
+ }
3199
+ sleep(timeoutMs, signal) {
3200
+ return new Promise((resolve, reject) => {
3201
+ let timer;
3202
+ const cleanup = () => {
3203
+ clearTimeout(timer);
3204
+ this.waiters.delete(wake);
3205
+ signal.removeEventListener("abort", abort);
3206
+ };
3207
+ const wake = () => {
3208
+ cleanup();
3209
+ resolve(true);
3210
+ };
3211
+ const abort = () => {
3212
+ cleanup();
3213
+ reject(new KernelError("EINTR"));
3214
+ };
3215
+ this.waiters.add(wake);
3216
+ signal.addEventListener("abort", abort, { once: true });
3217
+ if (timeoutMs !== void 0) {
3218
+ timer = setTimeout(() => {
3219
+ cleanup();
3220
+ resolve(false);
3221
+ }, timeoutMs);
3222
+ }
3223
+ });
3224
+ }
3225
+ complete(id, result) {
3226
+ if (!this.results.has(id)) return;
3227
+ this.results.set(id, result);
3228
+ this.ready.push(id);
3229
+ this.wake();
3230
+ }
3231
+ wake() {
3232
+ for (const wake of [...this.waiters]) wake();
3233
+ }
3234
+ };
3235
+ async function hostSyscall(req, ctx) {
3236
+ switch (req.op) {
3237
+ case "lock":
3238
+ if (!ctx.locks) throw new KernelError("ENOSYS");
3239
+ if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
3240
+ throw new KernelError("EAGAIN");
3241
+ return done2;
3242
+ case "unlock":
3243
+ ctx.locks?.unlock(ctx.pid, req.path);
3244
+ return done2;
3245
+ case "async-submit":
3246
+ if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
3247
+ return json2(ctx.ops.submit(req.req));
3248
+ case "async-wait":
3249
+ return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
3250
+ case "async-take":
3251
+ return ctx.ops.take(req.id);
3252
+ case "async-resolve":
3253
+ return json2(ctx.ops.resolve(req.value));
3254
+ case "async-hold":
3255
+ return json2(ctx.ops.hold());
3256
+ case "async-cancel":
3257
+ ctx.ops.cancel(req.id);
3258
+ return done2;
3259
+ case "async-close":
3260
+ ctx.ops.close();
3261
+ return done2;
3262
+ }
3263
+ }
3264
+
3064
3265
  // src/kernel/net/http-syscalls.ts
3065
3266
  var HTTP_OPS = ["net-request", "net-read", "net-close"];
3066
3267
  var EMPTY = new Uint8Array(0);
@@ -3571,41 +3772,41 @@ function terminalOf(ctx, fd) {
3571
3772
  if (!tty) throw new KernelError("ENOTTY");
3572
3773
  return tty;
3573
3774
  }
3574
- var json2 = (value) => ({ ok: true, kind: "json", json: value });
3575
- var done2 = { ok: true, kind: "void" };
3775
+ var json3 = (value) => ({ ok: true, kind: "json", json: value });
3776
+ var done3 = { ok: true, kind: "void" };
3576
3777
  function ptySyscall(req, ctx) {
3577
3778
  switch (req.op) {
3578
3779
  case "pty-open": {
3579
3780
  if (!ctx.ptys) throw new KernelError("ENOENT");
3580
- return json2(ctx.fds.install(ctx.ptys.open().master, 3));
3781
+ return json3(ctx.fds.install(ctx.ptys.open().master, 3));
3581
3782
  }
3582
3783
  case "pty-slave-open": {
3583
3784
  if (!ctx.ptys) throw new KernelError("ENOENT");
3584
3785
  const pair = ctx.ptys.get(req.n);
3585
3786
  const fd = ctx.fds.install(pair.openSlave(), 3);
3586
3787
  if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
3587
- return json2(fd);
3788
+ return json3(fd);
3588
3789
  }
3589
3790
  case "pty-number":
3590
- return json2(masterOf(ctx, req.fd).index);
3791
+ return json3(masterOf(ctx, req.fd).index);
3591
3792
  case "pty-lock":
3592
3793
  masterOf(ctx, req.fd).locked = req.lock;
3593
- return done2;
3794
+ return done3;
3594
3795
  case "pty-ctty": {
3595
3796
  const tty = terminalOf(ctx, req.fd);
3596
3797
  const leader = ctx.jobs?.getsid(ctx.pid, 0) === ctx.pid;
3597
- if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done2;
3798
+ if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done3;
3598
3799
  if (!ctx.jobs?.acquireTerminal(ctx.pid, tty)) throw new KernelError("EPERM");
3599
- return done2;
3800
+ return done3;
3600
3801
  }
3601
3802
  case "pty-list":
3602
- return json2(ctx.ptys?.numbers() ?? []);
3803
+ return json3(ctx.ptys?.numbers() ?? []);
3603
3804
  case "pty-packet":
3604
3805
  masterOf(ctx, req.fd).packet = req.on;
3605
- return done2;
3806
+ return done3;
3606
3807
  case "pty-winsz-set":
3607
3808
  terminalOf(ctx, req.fd).resize(req.cols, req.rows);
3608
- return done2;
3809
+ return done3;
3609
3810
  }
3610
3811
  }
3611
3812
 
@@ -3990,7 +4191,7 @@ function socketAt(fds, fd) {
3990
4191
  if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
3991
4192
  return file;
3992
4193
  }
3993
- var ok = (json3) => json3 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json3 };
4194
+ var ok = (json4) => json4 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json4 };
3994
4195
  async function socketSyscall(req, proc) {
3995
4196
  const { fds, net } = proc;
3996
4197
  switch (req.op) {
@@ -4316,6 +4517,9 @@ function isHttpSyscall(req) {
4316
4517
  function isSocketSyscall(req) {
4317
4518
  return req.op.startsWith("sock-");
4318
4519
  }
4520
+ function isHostSyscall(req) {
4521
+ return HOST_OPS.includes(req.op);
4522
+ }
4319
4523
  function isPtySyscall(req) {
4320
4524
  return req.op.startsWith("pty-");
4321
4525
  }
@@ -4353,6 +4557,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4353
4557
  "fd-cloexec",
4354
4558
  "fd-list",
4355
4559
  "proc-alarm",
4560
+ "dl-log",
4356
4561
  "fd-renumber",
4357
4562
  "fd-promote",
4358
4563
  "fd-open-tty",
@@ -4376,7 +4581,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4376
4581
  "sig-pause",
4377
4582
  ...SOCKET_OPS,
4378
4583
  ...HTTP_OPS,
4379
- ...PTY_OPS
4584
+ ...PTY_OPS,
4585
+ ...HOST_OPS
4380
4586
  ]);
4381
4587
  function isWasmSyscall(req) {
4382
4588
  const op = req.op;
@@ -4424,6 +4630,9 @@ var WasmProcess = class {
4424
4630
  const action = defaultAction(sig);
4425
4631
  return action === "stop" ? this.stop(sig) : action;
4426
4632
  }
4633
+ if (((this.options.pendingBits?.() ?? 0) & bit) !== 0 && defaultAction(sig) === "terminate") {
4634
+ return "terminate";
4635
+ }
4427
4636
  this.options.onPending?.(sig);
4428
4637
  const blocked = this.interrupt;
4429
4638
  this.interrupt = new AbortController();
@@ -4468,6 +4677,15 @@ var WasmProcess = class {
4468
4677
  if (isJobSyscall(req)) return this.jobSyscall(req);
4469
4678
  if (isSocketSyscall(req)) return await this.socketSyscall(req);
4470
4679
  if (isHttpSyscall(req)) return await this.httpSyscall(req);
4680
+ if (isHostSyscall(req)) {
4681
+ return await hostSyscall(req, {
4682
+ pid: this.pid,
4683
+ locks: this.options.locks,
4684
+ ops: this.asyncOps,
4685
+ blocking: () => this.blockingSignal(),
4686
+ valid: (inner) => isWasmSyscall(inner) && !isHostSyscall(inner)
4687
+ });
4688
+ }
4471
4689
  if (isPtySyscall(req)) {
4472
4690
  const { ptys, jobs } = this.options;
4473
4691
  return ptySyscall(req, { pid: this.pid, fds: this.fds, ptys, jobs });
@@ -4531,6 +4749,7 @@ var WasmProcess = class {
4531
4749
  case "fd-write":
4532
4750
  return { ok: true, kind: "json", json: await this.write(req.fd, req.body, req.nonblock) };
4533
4751
  case "fd-close":
4752
+ this.options.locks?.closed(this.pid, req.fd);
4534
4753
  await Promise.resolve(this.fds.close(req.fd));
4535
4754
  return { ok: true, kind: "void" };
4536
4755
  case "fd-pipe": {
@@ -4802,6 +5021,9 @@ var WasmProcess = class {
4802
5021
  case "proc-alarm":
4803
5022
  this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
4804
5023
  return { ok: true, kind: "void" };
5024
+ case "dl-log":
5025
+ if (req.append) this.dlLog.push(req.append);
5026
+ return { ok: true, kind: "json", json: this.dlLog.slice(req.from) };
4805
5027
  case "sig-mask":
4806
5028
  this.caught = req.caught;
4807
5029
  this.ignored = req.ignored;
@@ -4819,6 +5041,8 @@ var WasmProcess = class {
4819
5041
  });
4820
5042
  }
4821
5043
  alarm;
5044
+ dlLog = [];
5045
+ asyncOps = new AsyncOps((req) => this.syscall(req));
4822
5046
  alarmEvery;
4823
5047
  setAlarm(sig, first, every, timer) {
4824
5048
  if (!isSignal(sig)) throw new KernelError("EINVAL");
@@ -4847,6 +5071,8 @@ var WasmProcess = class {
4847
5071
  if (this.exited) return;
4848
5072
  this.exited = true;
4849
5073
  this.clearAlarm();
5074
+ this.options.locks?.release(this.pid);
5075
+ this.asyncOps.close();
4850
5076
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
4851
5077
  await this.fds.closeAll();
4852
5078
  await this.options.http?.closeAll();
@@ -4857,6 +5083,10 @@ var WasmProcess = class {
4857
5083
  var WASM_PROCESS_INIT = "wasm-process-init";
4858
5084
  var WASM_PROCESS_EXIT = "wasm-process-exit";
4859
5085
  var WASM_PROCESS_ERROR = "wasm-process-error";
5086
+ var WASM_THREAD_SPAWN = "wasm-thread-spawn";
5087
+ var WASM_THREAD_INIT = "wasm-thread-init";
5088
+ var WASM_THREAD_EXIT = "wasm-thread-exit";
5089
+ var WASM_MAX_THREADS = 64;
4860
5090
 
4861
5091
  // src/kernel/host.ts
4862
5092
  var CRASHED = 70;
@@ -4895,11 +5125,13 @@ function spawnWasmProcess(opts) {
4895
5125
  ptys: opts.ptys,
4896
5126
  net: opts.net,
4897
5127
  http: opts.http,
5128
+ locks: opts.locks,
4898
5129
  onReap: opts.onReap,
4899
5130
  onPending: (sig) => void Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig)),
4900
5131
  onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
4901
5132
  hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
4902
- raise: (sig) => signal(sig)
5133
+ raise: (sig) => signal(sig),
5134
+ ...opts.program.abi === "wasi" ? { pendingBits: () => Atomics.load(header2, SAB_I_SIGNALS) } : {}
4903
5135
  });
4904
5136
  const token = mintSyncFsToken({ fs: opts.fs, cwd: opts.cwd });
4905
5137
  const worker = opts.createWorker();
@@ -4910,14 +5142,15 @@ function spawnWasmProcess(opts) {
4910
5142
  const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
4911
5143
  let settle2;
4912
5144
  const exited = new Promise((resolve) => settle2 = resolve);
4913
- let done3 = false;
5145
+ let done4 = false;
4914
5146
  let endedBy;
4915
5147
  const finish = (code, sig) => {
4916
- if (done3) return;
4917
- done3 = true;
5148
+ if (done4) return;
5149
+ done4 = true;
4918
5150
  endedBy = sig ?? process.execTermsig;
4919
5151
  worker.removeEventListener("message", onMessage);
4920
5152
  worker.removeEventListener("error", onError);
5153
+ for (const tid of [...threads.keys()]) endThread(tid);
4921
5154
  responder.dispose();
4922
5155
  revokeSyncFsToken(token);
4923
5156
  worker.terminate();
@@ -4926,15 +5159,17 @@ function spawnWasmProcess(opts) {
4926
5159
  () => settle2(code)
4927
5160
  );
4928
5161
  };
4929
- const onMessage = (event) => {
4930
- const data = event.data;
5162
+ const handle = (data) => {
4931
5163
  if (data?.type === WASM_PROCESS_EXIT) {
4932
5164
  finish(typeof data.code === "number" ? data.code : CRASHED);
4933
5165
  } else if (data?.type === WASM_PROCESS_ERROR) {
4934
5166
  opts.onError?.(String(data.message));
4935
5167
  finish(CRASHED);
5168
+ } else if (data?.type === WASM_THREAD_SPAWN && data.thread) {
5169
+ spawnThread(data.thread);
4936
5170
  }
4937
5171
  };
5172
+ const onMessage = (event) => handle(event.data);
4938
5173
  const onError = (event) => {
4939
5174
  event.preventDefault();
4940
5175
  opts.onError?.(String(event.message ?? "worker error"));
@@ -4942,6 +5177,49 @@ function spawnWasmProcess(opts) {
4942
5177
  };
4943
5178
  worker.addEventListener("message", onMessage);
4944
5179
  worker.addEventListener("error", onError);
5180
+ const threads = /* @__PURE__ */ new Map();
5181
+ const endThread = (tid) => {
5182
+ const end = threads.get(tid);
5183
+ threads.delete(tid);
5184
+ end?.();
5185
+ };
5186
+ const spawnThread = (thread) => {
5187
+ if (done4) return;
5188
+ if (threads.size >= WASM_MAX_THREADS - 1) {
5189
+ opts.onError?.(`more than ${WASM_MAX_THREADS} threads`);
5190
+ finish(CRASHED);
5191
+ return;
5192
+ }
5193
+ const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
5194
+ const tw = opts.createWorker();
5195
+ const tresponder = attachSyncSabResponder(tw, tsab, token, { dispatch });
5196
+ const onThreadMessage = (event) => {
5197
+ const data = event.data;
5198
+ if (data?.type === WASM_THREAD_EXIT) endThread(thread.tid);
5199
+ else handle(data);
5200
+ };
5201
+ tw.addEventListener("message", onThreadMessage);
5202
+ tw.addEventListener("error", onError);
5203
+ threads.set(thread.tid, () => {
5204
+ tw.removeEventListener("message", onThreadMessage);
5205
+ tw.removeEventListener("error", onError);
5206
+ tresponder.dispose();
5207
+ tw.terminate();
5208
+ });
5209
+ const tinit = {
5210
+ type: WASM_THREAD_INIT,
5211
+ pid: opts.pid,
5212
+ program: opts.program,
5213
+ argv0: opts.argv0,
5214
+ args: opts.args,
5215
+ env: opts.env,
5216
+ cwd: opts.cwd,
5217
+ sab: tsab,
5218
+ ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
5219
+ thread
5220
+ };
5221
+ tw.postMessage(tinit);
5222
+ };
4945
5223
  const init = {
4946
5224
  type: WASM_PROCESS_INIT,
4947
5225
  pid: opts.pid,
@@ -5076,8 +5354,67 @@ var JobTable = class {
5076
5354
  }
5077
5355
  };
5078
5356
 
5357
+ // src/process/wasi/wasi-module.ts
5358
+ function importedMemory(bytes) {
5359
+ let at = 8;
5360
+ const u32 = () => {
5361
+ let result = 0;
5362
+ let shift = 0;
5363
+ for (; ; ) {
5364
+ const b = bytes[at++];
5365
+ result |= (b & 127) << shift;
5366
+ if (!(b & 128)) return result >>> 0;
5367
+ shift += 7;
5368
+ }
5369
+ };
5370
+ const name2 = () => {
5371
+ const len = u32();
5372
+ const s = new TextDecoder().decode(bytes.subarray(at, at + len));
5373
+ at += len;
5374
+ return s;
5375
+ };
5376
+ const limits = () => {
5377
+ const flags = bytes[at++];
5378
+ const initial = u32();
5379
+ const maximum = flags & 1 ? u32() : void 0;
5380
+ return { initial, maximum, shared: (flags & 2) !== 0 };
5381
+ };
5382
+ while (at < bytes.length) {
5383
+ const id = bytes[at++];
5384
+ const size = u32();
5385
+ const end = at + size;
5386
+ if (id !== 2) {
5387
+ at = end;
5388
+ continue;
5389
+ }
5390
+ const count = u32();
5391
+ for (let i = 0; i < count; i++) {
5392
+ const module = name2();
5393
+ const field = name2();
5394
+ const kind = bytes[at++];
5395
+ if (kind === 0) u32();
5396
+ else if (kind === 1) {
5397
+ at++;
5398
+ limits();
5399
+ } else if (kind === 2) {
5400
+ const l = limits();
5401
+ return { module, name: field, ...l };
5402
+ } else if (kind === 3) at += 2;
5403
+ else if (kind === 4) {
5404
+ at++;
5405
+ u32();
5406
+ }
5407
+ }
5408
+ return void 0;
5409
+ }
5410
+ return void 0;
5411
+ }
5412
+
5079
5413
  // src/launcher.ts
5080
5414
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
5415
+ var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
5416
+ var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
5417
+ var WASM_MAGIC = [0, 97, 115, 109];
5081
5418
  var SHEBANG_MAX = 256;
5082
5419
  var NOT_FOUND = 127;
5083
5420
  var SHARED_DIRS = ["/tmp", "/home"];
@@ -5088,13 +5425,43 @@ function baseName(path) {
5088
5425
  function modulePath(glue) {
5089
5426
  return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
5090
5427
  }
5428
+ function isWasm(bytes) {
5429
+ return WASM_MAGIC.every((b, i) => bytes[i] === b);
5430
+ }
5431
+ function shebang(head) {
5432
+ if (head[0] !== 35 || head[1] !== 33) return void 0;
5433
+ return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
5434
+ }
5435
+ function interpreter(head) {
5436
+ const words = shebang(head);
5437
+ if (!words) return "node";
5438
+ if (baseName(words[0]) === "env") words.shift();
5439
+ if (words[0] === "-S") words.shift();
5440
+ return baseName(words[0] ?? "");
5441
+ }
5442
+ function expandDefaults(defaults, env) {
5443
+ const out = {};
5444
+ for (const [key, value] of Object.entries(defaults ?? {})) {
5445
+ let missing3 = false;
5446
+ const expanded = value.replace(ENV_REFERENCE, (_, name2) => {
5447
+ const set2 = Object.hasOwn(env, name2) ? env[name2] : void 0;
5448
+ if (set2 === void 0) missing3 = true;
5449
+ return set2 ?? "";
5450
+ });
5451
+ if (!missing3) out[key] = expanded;
5452
+ }
5453
+ return out;
5454
+ }
5091
5455
  function targetOf(command) {
5092
5456
  return {
5457
+ abi: command.abi,
5093
5458
  glue: command.glue,
5094
5459
  wasm: command.wasm,
5095
5460
  argv0: command.argv0,
5096
5461
  ...command.args ? { prefix: command.args } : {},
5097
- ...command.env ? { env: command.env } : {}
5462
+ ...command.env ? { env: command.env } : {},
5463
+ ...command.unset ? { unset: command.unset } : {},
5464
+ ...command.imports ? { imports: command.imports } : {}
5098
5465
  };
5099
5466
  }
5100
5467
  function childHandle(handle) {
@@ -5126,6 +5493,7 @@ var Launcher = class {
5126
5493
  zombies = /* @__PURE__ */ new Set();
5127
5494
  orphans = /* @__PURE__ */ new Set();
5128
5495
  jobs = new JobTable();
5496
+ locks = new LockTable();
5129
5497
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
5130
5498
  net = new LoopbackNet();
5131
5499
  ca;
@@ -5154,35 +5522,62 @@ var Launcher = class {
5154
5522
  const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
5155
5523
  if (name2 !== void 0) {
5156
5524
  const command = (await this.commands()).get(name2);
5157
- return command && targetOf(command);
5525
+ return command && !command.script ? targetOf(command) : void 0;
5158
5526
  }
5159
5527
  const glue = await followLinks(this.base, this.fs.resolvePath(cwd, file));
5160
5528
  const linked = COMMAND2.exec(glue)?.[1];
5161
5529
  if (linked !== void 0) return this.resolve(`/bin/${linked}`, argv0, cwd);
5530
+ const head = await this.head(glue);
5531
+ if (!head) return void 0;
5532
+ if (isWasm(head)) {
5533
+ return {
5534
+ abi: "wasi",
5535
+ glue,
5536
+ wasm: glue,
5537
+ argv0: baseName(argv0 || file).replace(/\.wasm$/, "")
5538
+ };
5539
+ }
5540
+ if (interpreter(head) !== "node") return void 0;
5162
5541
  const wasm = modulePath(glue);
5163
- if (!await this.base.exists(glue) || !await this.base.exists(wasm)) return void 0;
5164
- return { glue, wasm, argv0: baseName(argv0 || file) };
5542
+ if (!await this.base.exists(wasm)) return void 0;
5543
+ return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file) };
5165
5544
  }
5166
- async interpreted(file, argv, cwd) {
5167
- let head;
5545
+ async head(path) {
5168
5546
  try {
5169
- head = (await this.base.readFileBuffer(this.fs.resolvePath(cwd, file))).subarray(
5170
- 0,
5171
- SHEBANG_MAX
5172
- );
5547
+ return (await this.base.readFileBuffer(path)).subarray(0, SHEBANG_MAX);
5173
5548
  } catch {
5174
5549
  return void 0;
5175
5550
  }
5176
- if (head[0] !== 35 || head[1] !== 33) return void 0;
5177
- const words = new TextDecoder().decode(head).slice(2).split("\n")[0].trim().split(/[ \t]+/);
5551
+ }
5552
+ async scriptCommand(file) {
5553
+ const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
5554
+ if (name2 === void 0) return void 0;
5555
+ const command = (await this.commands()).get(name2);
5556
+ return command?.script ? command : void 0;
5557
+ }
5558
+ async interpreted(file, argv, cwd) {
5559
+ const command = await this.scriptCommand(file);
5560
+ const script = command?.script ?? this.fs.resolvePath(cwd, file);
5561
+ const words = shebang(await this.head(script) ?? new Uint8Array());
5562
+ if (!words) return void 0;
5178
5563
  if (baseName(words[0]) === "env") words.shift();
5179
5564
  const [interp, ...rest] = words;
5180
- const target = interp ? await this.resolve(interp, interp, cwd) : void 0;
5181
- if (!target) return void 0;
5565
+ const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
5566
+ if (!found) return void 0;
5567
+ const target = command ? {
5568
+ ...found,
5569
+ env: { ...found.env, ...command.env },
5570
+ unset: [...found.unset ?? [], ...command.unset ?? []]
5571
+ } : found;
5182
5572
  const arg = rest.join(" ");
5183
5573
  return {
5184
5574
  target,
5185
- args: [...target.prefix ?? [], ...arg ? [arg] : [], file, ...argv.slice(1)]
5575
+ args: [
5576
+ ...target.prefix ?? [],
5577
+ ...arg ? [arg] : [],
5578
+ command ? script : file,
5579
+ ...argv.slice(1)
5580
+ ]
5186
5581
  };
5187
5582
  }
5188
5583
  async plan(file, argv, cwd) {
@@ -5199,33 +5594,58 @@ var Launcher = class {
5199
5594
  const key = `${path}:${st.size}:${st.mtime.getTime()}`;
5200
5595
  let module = this.compiled.get(key);
5201
5596
  if (!module) {
5202
- module = this.base.readFileBuffer(path).then((bytes) => WebAssembly.compile(bytes));
5597
+ module = this.base.readFileBuffer(path).then(async (bytes) => {
5598
+ const memory = importedMemory(bytes);
5599
+ const compiled = await WebAssembly.compile(bytes);
5600
+ return { module: compiled, ...memory ? { memory } : {} };
5601
+ });
5203
5602
  this.compiled.set(key, module);
5204
5603
  module.catch(() => this.compiled.delete(key));
5205
5604
  }
5206
5605
  return module;
5207
5606
  });
5208
5607
  }
5608
+ async names(path, module) {
5609
+ if (WebAssembly.Module.customSections(module, "name").length > 0) return void 0;
5610
+ const beside = `${path}.names`;
5611
+ if (await this.base.exists(beside)) return beside;
5612
+ const root = PACKAGE_ROOT.exec(path)?.[1];
5613
+ if (root === void 0) return void 0;
5614
+ const optional = `${root}-names${beside.slice(root.length)}`;
5615
+ return await this.base.exists(optional) ? optional : void 0;
5616
+ }
5617
+ async program(target, env) {
5618
+ if (target.abi !== "wasi") {
5619
+ const [glue, { module: module2 }] = await Promise.all([
5620
+ this.base.readFile(target.glue),
5621
+ this.module(target.wasm)
5622
+ ]);
5623
+ return { glue, module: module2 };
5624
+ }
5625
+ const { module, memory } = await this.module(target.wasm);
5626
+ const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
5627
+ const imports = target.imports ? await this.base.readFile(target.imports) : void 0;
5628
+ return {
5629
+ abi: "wasi",
5630
+ glue: "",
5631
+ module,
5632
+ ...memory ? { memory } : {},
5633
+ ...names ? { names } : {},
5634
+ ...imports !== void 0 ? { imports } : {}
5635
+ };
5636
+ }
5209
5637
  async launch(planned, req) {
5210
5638
  const { target, args } = planned;
5639
+ const env = { ...expandDefaults(target.env, { ...req.env, cwd: req.cwd }), ...req.env };
5640
+ for (const key of target.unset ?? []) delete env[key];
5211
5641
  let program;
5212
5642
  try {
5213
- const [glue, module] = await Promise.all([
5214
- this.base.readFile(target.glue),
5215
- this.module(target.wasm)
5216
- ]);
5217
- program = { glue, module };
5643
+ program = await this.program(target, env);
5218
5644
  } catch (err) {
5219
5645
  await req.fds.closeAll();
5220
5646
  throw err;
5221
5647
  }
5222
- return this.start({
5223
- ...req,
5224
- program,
5225
- argv0: target.argv0,
5226
- args,
5227
- env: { ...target.env, ...req.env }
5228
- });
5648
+ return this.start({ ...req, program, argv0: target.argv0, args, env });
5229
5649
  }
5230
5650
  start(req) {
5231
5651
  const pid = this.nextPid++;
@@ -5248,6 +5668,7 @@ var Launcher = class {
5248
5668
  ptys: this.ptys,
5249
5669
  net: this.net,
5250
5670
  http: new HttpHandles(this.transport),
5671
+ locks: this.locks,
5251
5672
  onReap: (child) => this.reaped(child),
5252
5673
  ...req.fork ? { fork: req.fork } : {},
5253
5674
  ...req.ppid !== void 0 ? { ppid: req.ppid } : {}