@ai-ecoverse/slicc-kernel 1.3.0 → 1.5.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.
@@ -59,34 +59,62 @@ function settle(transaction, value) {
59
59
  }
60
60
  var IndexedDbMeta = class {
61
61
  db;
62
+ cache = /* @__PURE__ */ new Map();
63
+ channel;
64
+ generation = 0;
62
65
  constructor(db) {
63
66
  this.db = db;
64
67
  db.onversionchange = () => db.close();
68
+ this.channel = new BroadcastChannel(`slicc-kernel-meta:${db.name}`);
69
+ this.channel.onmessage = ({ data }) => this.drop(data);
70
+ this.channel.unref?.();
71
+ }
72
+ drop({ paths = [], trees = [] }) {
73
+ this.generation++;
74
+ for (const path of paths) this.cache.delete(path);
75
+ for (const key of [...this.cache.keys()]) {
76
+ if (trees.some((tree) => under(key, tree))) this.cache.delete(key);
77
+ }
78
+ }
79
+ changed(invalidation) {
80
+ this.drop(invalidation);
81
+ this.channel.postMessage(invalidation);
65
82
  }
66
83
  transaction(mode) {
67
84
  const transaction = this.db.transaction(META_STORE, mode);
68
85
  return [transaction, transaction.objectStore(META_STORE)];
69
86
  }
70
87
  async get(paths) {
71
- const [transaction, store] = this.transaction("readonly");
72
- const found = paths.map(() => void 0);
73
- paths.forEach((path, i) => {
74
- const request2 = store.get(path);
75
- request2.onsuccess = () => {
76
- found[i] = request2.result;
77
- };
78
- });
79
- return settle(transaction, () => found);
88
+ const missing3 = paths.filter((path) => !this.cache.has(path));
89
+ if (missing3.length > 0) {
90
+ const generation = this.generation;
91
+ const [transaction, store] = this.transaction("readonly");
92
+ const found = /* @__PURE__ */ new Map();
93
+ for (const path of missing3) {
94
+ const request2 = store.get(path);
95
+ request2.onsuccess = () => void found.set(path, request2.result ?? null);
96
+ }
97
+ await settle(transaction, () => void 0);
98
+ if (generation === this.generation)
99
+ for (const [path, entry] of found) this.cache.set(path, entry);
100
+ return paths.map(
101
+ (path) => (found.has(path) ? found.get(path) : this.cache.get(path)) ?? void 0
102
+ );
103
+ }
104
+ return paths.map((path) => this.cache.get(path) ?? void 0);
80
105
  }
81
106
  async update(path, change) {
82
107
  const [transaction, store] = this.transaction("readwrite");
83
108
  const request2 = store.get(path);
109
+ let next;
84
110
  request2.onsuccess = () => {
85
- const next = change(request2.result);
111
+ next = change(request2.result);
86
112
  if (next) store.put(next);
87
113
  else store.delete(path);
88
114
  };
89
- return settle(transaction, () => void 0);
115
+ await settle(transaction, () => void 0);
116
+ this.changed({ paths: [path] });
117
+ this.cache.set(path, next ?? null);
90
118
  }
91
119
  async move(from, to, root) {
92
120
  const [transaction, store] = this.transaction("readwrite");
@@ -102,7 +130,8 @@ var IndexedDbMeta = class {
102
130
  }
103
131
  store.put(moved(root(source.result), to));
104
132
  };
105
- return settle(transaction, () => void 0);
133
+ await settle(transaction, () => void 0);
134
+ this.changed({ trees: [from, to] });
106
135
  }
107
136
  async remove(paths) {
108
137
  const [transaction, store] = this.transaction("readwrite");
@@ -110,7 +139,8 @@ var IndexedDbMeta = class {
110
139
  store.delete(path);
111
140
  store.delete(descendants(path));
112
141
  }
113
- return settle(transaction, () => void 0);
142
+ await settle(transaction, () => void 0);
143
+ this.changed({ trees: paths });
114
144
  }
115
145
  async links(dir) {
116
146
  const [transaction, store] = this.transaction("readonly");
@@ -183,6 +213,7 @@ var S_IFLNK = 40960;
183
213
  var PERM_MASK = 4095;
184
214
  var READ_ATTEMPTS = 3;
185
215
  var MAX_LINKS = 40;
216
+ var CHECK_MS = 1e3;
186
217
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
187
218
  var ERRNO_BY_DOM_ERROR = {
188
219
  NotFoundError: "ENOENT",
@@ -222,37 +253,100 @@ function isHandle(child) {
222
253
  var OpfsFs = class {
223
254
  root;
224
255
  meta;
225
- constructor(root, meta = new MemoryMeta()) {
256
+ dirs = /* @__PURE__ */ new Map();
257
+ checked = /* @__PURE__ */ new WeakMap();
258
+ channel;
259
+ constructor(root, meta = new MemoryMeta(), channel) {
226
260
  this.root = root;
227
261
  this.meta = meta;
262
+ this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
263
+ if (this.channel) {
264
+ this.channel.onmessage = ({ data }) => {
265
+ for (const path of data) this.forget(path);
266
+ };
267
+ this.channel.unref?.();
268
+ }
269
+ }
270
+ changed(...paths) {
271
+ for (const path of paths) this.forget(path);
272
+ this.channel?.postMessage(paths);
228
273
  }
229
274
  resolvePath(base, path) {
230
275
  return resolvePath(base, path);
231
276
  }
277
+ async walk(parts, from, start) {
278
+ let dir = start;
279
+ for (let at = from; at < parts.length; at++) {
280
+ dir = await dir.getDirectoryHandle(parts[at]);
281
+ this.dirs.set(`/${parts.slice(0, at + 1).join("/")}`, dir);
282
+ this.checked.set(dir, performance.now());
283
+ }
284
+ return dir;
285
+ }
232
286
  async dir(path) {
233
- let dir = this.root;
287
+ const parts = split(path);
288
+ let from = parts.length;
289
+ let cached;
290
+ for (; from > 0 && !cached; from--)
291
+ cached = this.dirs.get(`/${parts.slice(0, from).join("/")}`);
292
+ if (cached) from++;
293
+ const at = `/${parts.slice(0, from).join("/")}`;
294
+ if (cached && !await this.current(at, cached)) {
295
+ this.forget(at);
296
+ cached = void 0;
297
+ from = 0;
298
+ }
234
299
  try {
235
- for (const part of split(path)) dir = await dir.getDirectoryHandle(part);
300
+ return await this.walk(parts, from, cached ?? this.root);
236
301
  } catch (err) {
237
302
  throw translate(err, path);
238
303
  }
239
- return dir;
304
+ }
305
+ async current(path, dir) {
306
+ if (performance.now() - (this.checked.get(dir) ?? -CHECK_MS) < CHECK_MS) return true;
307
+ const where = await this.root.resolve(dir).catch(() => null);
308
+ if (where === null || `/${where.join("/")}` !== path) return false;
309
+ this.checked.set(dir, performance.now());
310
+ return true;
311
+ }
312
+ forget(path) {
313
+ for (const key of [...this.dirs.keys()]) if (within(key, path)) this.dirs.delete(key);
314
+ }
315
+ async inDir(path, op) {
316
+ const dir = await this.dir(path);
317
+ try {
318
+ return await op(dir);
319
+ } catch (err) {
320
+ if (err?.name !== "NotFoundError" || await this.current(path, dir))
321
+ throw err;
322
+ this.forget(path);
323
+ return op(await this.dir(path));
324
+ }
325
+ }
326
+ async child(parent, name2, kind) {
327
+ return kind === "file" ? parent.getFileHandle(name2) : parent.getDirectoryHandle(name2);
240
328
  }
241
329
  async parent(path) {
242
330
  const parts = split(path);
243
331
  const name2 = parts.pop();
244
332
  if (name2 === void 0) throw fsError("EBUSY", path);
245
- return [await this.dir(`/${parts.join("/")}`), name2];
333
+ return [`/${parts.join("/")}`, name2];
246
334
  }
247
335
  async handle(path) {
248
336
  if (split(path).length === 0) return this.root;
249
337
  const [dir, name2] = await this.parent(path);
250
- try {
251
- return await dir.getFileHandle(name2);
252
- } catch (err) {
253
- if (err?.name !== "TypeMismatchError") throw translate(err, path);
254
- }
255
- return dir.getDirectoryHandle(name2);
338
+ const known = this.dirs.has(path);
339
+ return this.inDir(dir, async (parent) => {
340
+ const [first, second] = known ? ["directory", "file"] : ["file", "directory"];
341
+ try {
342
+ return await this.child(parent, name2, first);
343
+ } catch (err) {
344
+ if (err?.name !== "TypeMismatchError") throw err;
345
+ }
346
+ return this.child(parent, name2, second);
347
+ }).catch((err) => {
348
+ throw translate(err, path);
349
+ });
256
350
  }
257
351
  real(path) {
258
352
  return this.handle(path).then(
@@ -354,24 +448,25 @@ var OpfsFs = class {
354
448
  }
355
449
  async writeTarget(path) {
356
450
  try {
357
- return (await this.locate(path, true)).path;
451
+ return [(await this.locate(path, true)).path, true];
358
452
  } catch (err) {
359
453
  if (!missing(err)) throw err;
360
454
  }
361
455
  const [entry] = await this.meta.get([path]);
362
456
  const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
363
- return resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted));
457
+ return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
364
458
  }
365
459
  async writeFile(path, content) {
366
- const target = await this.writeTarget(path);
460
+ const [target, existed] = await this.writeTarget(path);
367
461
  const [dir, name2] = await this.parent(target);
368
462
  let file;
369
463
  try {
370
- file = await dir.getFileHandle(name2, { create: true });
464
+ file = await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true }));
371
465
  } catch (err) {
372
466
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
373
467
  throw code ? fsError(code, path) : translate(err, path);
374
468
  }
469
+ if (!existed && content.length === 0) return;
375
470
  const writable = await file.createWritable();
376
471
  await writable.write(content);
377
472
  await writable.close();
@@ -434,16 +529,21 @@ var OpfsFs = class {
434
529
  if (found.handle) {
435
530
  const [dir, name2] = await this.parent(found.path);
436
531
  try {
437
- await dir.removeEntry(name2, { recursive: options.recursive === true });
532
+ await this.inDir(
533
+ dir,
534
+ (parent) => parent.removeEntry(name2, { recursive: options.recursive === true })
535
+ );
438
536
  } catch (err) {
439
537
  throw translate(err, path);
440
538
  }
539
+ this.changed(found.path);
441
540
  }
442
541
  await this.meta.remove([found.path]);
443
542
  }
444
543
  async rename(from, to) {
445
544
  if (from === to) return;
446
545
  const source = await this.locate(from, false);
546
+ this.forget(source.path);
447
547
  const parent = await this.locate(parentOf2(to), true);
448
548
  const destination = resolvePath(parent.path, baseOf(to));
449
549
  const sourceStat = await this.describe(source);
@@ -476,6 +576,7 @@ var OpfsFs = class {
476
576
  const name2 = baseOf(destination);
477
577
  const swap = targetStat?.isDirectory === true;
478
578
  const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
579
+ this.forget(destination);
479
580
  const timed = await this.explicitTimes(source.path);
480
581
  const moved2 = await this.place(source.handle, dir, staging);
481
582
  if (swap) await this.rm(destination, { recursive: true });
@@ -489,6 +590,7 @@ var OpfsFs = class {
489
590
  copied ||= !placed;
490
591
  }
491
592
  if (copied) await this.restamp(destination, timed);
593
+ this.changed(source.path, destination);
492
594
  }
493
595
  async modified(path) {
494
596
  const handle = await this.handle(path);
@@ -1206,12 +1308,12 @@ var Budget = class {
1206
1308
  while (this.free < n) {
1207
1309
  signal.throwIfAborted();
1208
1310
  await new Promise((resolve) => {
1209
- const done3 = () => {
1210
- signal.removeEventListener("abort", done3);
1311
+ const done4 = () => {
1312
+ signal.removeEventListener("abort", done4);
1211
1313
  resolve();
1212
1314
  };
1213
- this.waiters.push(done3);
1214
- signal.addEventListener("abort", done3, { once: true });
1315
+ this.waiters.push(done4);
1316
+ signal.addEventListener("abort", done4, { once: true });
1215
1317
  });
1216
1318
  }
1217
1319
  this.free -= n;
@@ -1422,11 +1524,11 @@ var RealmProxy = class {
1422
1524
  this.slots.release(1);
1423
1525
  break;
1424
1526
  }
1425
- const done3 = this.serve(conn).finally(() => {
1527
+ const done4 = this.serve(conn).finally(() => {
1426
1528
  this.slots.release(1);
1427
- served.delete(done3);
1529
+ served.delete(done4);
1428
1530
  });
1429
- served.add(done3);
1531
+ served.add(done4);
1430
1532
  }
1431
1533
  } catch {
1432
1534
  }
@@ -2450,44 +2552,83 @@ function envOf(pkg, raw) {
2450
2552
  const env = {};
2451
2553
  if (!raw || typeof raw !== "object") return env;
2452
2554
  for (const [key, value] of Object.entries(raw)) {
2453
- if (ENV_KEY.test(key) && typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
2555
+ if (!ENV_KEY.test(key)) continue;
2556
+ if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
2557
+ else if (value === null) env[key] = null;
2454
2558
  }
2455
2559
  return env;
2456
2560
  }
2561
+ function envFields(merged) {
2562
+ const env = {};
2563
+ const unset = [];
2564
+ for (const [key, value] of Object.entries(merged)) {
2565
+ if (value === null) unset.push(key);
2566
+ else env[key] = value;
2567
+ }
2568
+ return {
2569
+ ...Object.keys(env).length > 0 ? { env } : {},
2570
+ ...unset.length > 0 ? { unset } : {}
2571
+ };
2572
+ }
2457
2573
  function argsOf(pkg, raw) {
2458
2574
  if (!Array.isArray(raw) || raw.length === 0) return void 0;
2459
2575
  if (!raw.every((arg) => typeof arg === "string")) return void 0;
2460
2576
  return raw.map((arg) => arg.replace(PACKAGE, pkg));
2461
2577
  }
2578
+ function abiOf(raw, fallback) {
2579
+ const abi = raw ?? fallback;
2580
+ return abi === "emscripten" || abi === "wasi" ? abi : void 0;
2581
+ }
2582
+ function commandOf(pkg, name2, raw, packageAbi, shared) {
2583
+ const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
2584
+ const script = inside(pkg, raw.script);
2585
+ if (script) {
2586
+ return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
2587
+ }
2588
+ const abi = abiOf(raw.abi, packageAbi);
2589
+ const wasm = inside(pkg, raw.wasm);
2590
+ const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
2591
+ if (!abi || !glue || !wasm) return void 0;
2592
+ const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
2593
+ const args = argsOf(pkg, raw.args);
2594
+ const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
2595
+ return {
2596
+ name: name2,
2597
+ abi,
2598
+ glue,
2599
+ wasm,
2600
+ argv0,
2601
+ ...args ? { args } : {},
2602
+ ...withEnv,
2603
+ ...imports ? { imports } : {}
2604
+ };
2605
+ }
2462
2606
  function commandsOf(pkg, manifest) {
2463
2607
  const slicc = manifest.slicc;
2464
- if (!slicc || typeof slicc !== "object" || (slicc.abi ?? "emscripten") !== "emscripten")
2465
- return [];
2608
+ if (!slicc || typeof slicc !== "object") return [];
2609
+ const packageAbi = abiOf(slicc.abi, "emscripten");
2466
2610
  const entries = slicc.commands;
2467
- if (!entries || typeof entries !== "object") return [];
2611
+ if (!packageAbi || !entries || typeof entries !== "object") return [];
2468
2612
  const shared = envOf(pkg, slicc.env);
2469
2613
  const out = [];
2470
2614
  for (const [name2, raw] of Object.entries(entries)) {
2471
2615
  if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
2472
2616
  continue;
2473
2617
  }
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
- });
2618
+ const command = commandOf(pkg, name2, raw, packageAbi, shared);
2619
+ if (command) out.push(command);
2488
2620
  }
2489
2621
  return out;
2490
2622
  }
2623
+ async function withPackagePaths(fs, pkg, command) {
2624
+ if (!command.env) return command;
2625
+ const env = {};
2626
+ for (const [key, value] of Object.entries(command.env)) {
2627
+ const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
2628
+ env[key] = path && await fs.exists(path) ? path : value;
2629
+ }
2630
+ return { ...command, env };
2631
+ }
2491
2632
  async function packages(fs, modules) {
2492
2633
  const names = await fs.readdir(modules).catch(() => []);
2493
2634
  const dirs = [];
@@ -2513,7 +2654,8 @@ async function scanCommands(fs, modules) {
2513
2654
  continue;
2514
2655
  }
2515
2656
  for (const command of commandsOf(pkg, manifest)) {
2516
- if (!found.has(command.name)) found.set(command.name, command);
2657
+ if (!found.has(command.name))
2658
+ found.set(command.name, await withPackagePaths(fs, pkg, command));
2517
2659
  }
2518
2660
  }
2519
2661
  if (!found.has("sh") && found.has("bash")) {
@@ -2750,8 +2892,8 @@ var ChildTable = class {
2750
2892
  for (; ; ) {
2751
2893
  const candidates = this.candidates(pid, flags);
2752
2894
  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);
2895
+ const done4 = candidates.find(([, child]) => child.code !== void 0);
2896
+ if (done4) return this.reap(done4[0], done4[1].code);
2755
2897
  const changed = this.stateReport(candidates, flags);
2756
2898
  if (changed) return changed;
2757
2899
  if (nohang) return [0, 0];
@@ -3061,6 +3203,167 @@ function attachSyncSabResponder(port, sab, token, opts) {
3061
3203
  };
3062
3204
  }
3063
3205
 
3206
+ // src/kernel/host-ops.ts
3207
+ var HOST_OPS = [
3208
+ "lock",
3209
+ "unlock",
3210
+ "async-submit",
3211
+ "async-wait",
3212
+ "async-take",
3213
+ "async-resolve",
3214
+ "async-hold",
3215
+ "async-cancel",
3216
+ "async-close"
3217
+ ];
3218
+ var json2 = (value) => ({ ok: true, kind: "json", json: value });
3219
+ var done2 = { ok: true, kind: "void" };
3220
+ var LockTable = class {
3221
+ held = /* @__PURE__ */ new Map();
3222
+ lock(pid, path, exclusive, fd) {
3223
+ const holders = this.held.get(path) ?? /* @__PURE__ */ new Map();
3224
+ for (const [other, h] of holders) if (other !== pid && (exclusive || h.exclusive)) return false;
3225
+ const mine = holders.get(pid) ?? { exclusive, fds: /* @__PURE__ */ new Set() };
3226
+ mine.exclusive = exclusive;
3227
+ mine.fds.add(fd);
3228
+ holders.set(pid, mine);
3229
+ this.held.set(path, holders);
3230
+ return true;
3231
+ }
3232
+ unlock(pid, path) {
3233
+ const holders = this.held.get(path);
3234
+ holders?.delete(pid);
3235
+ if (holders?.size === 0) this.held.delete(path);
3236
+ }
3237
+ closed(pid, fd) {
3238
+ for (const [path, holders] of [...this.held]) {
3239
+ const mine = holders.get(pid);
3240
+ if (mine?.fds.delete(fd) && mine.fds.size === 0) this.unlock(pid, path);
3241
+ }
3242
+ }
3243
+ release(pid) {
3244
+ for (const path of [...this.held.keys()]) this.unlock(pid, path);
3245
+ }
3246
+ };
3247
+ var AsyncOps = class {
3248
+ next = 1;
3249
+ results = /* @__PURE__ */ new Map();
3250
+ ready = [];
3251
+ waiters = /* @__PURE__ */ new Set();
3252
+ closed = false;
3253
+ run;
3254
+ constructor(run2) {
3255
+ this.run = run2;
3256
+ }
3257
+ submit(req) {
3258
+ const id = this.hold();
3259
+ this.run(req).then(
3260
+ (result) => this.complete(id, result),
3261
+ (err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
3262
+ );
3263
+ return id;
3264
+ }
3265
+ resolve(value) {
3266
+ const id = this.hold();
3267
+ this.complete(id, json2(value));
3268
+ return id;
3269
+ }
3270
+ hold() {
3271
+ const id = this.next++;
3272
+ this.results.set(id, void 0);
3273
+ return id;
3274
+ }
3275
+ take(id) {
3276
+ if (!this.results.has(id)) throw new KernelError("EINVAL");
3277
+ const result = this.results.get(id);
3278
+ if (!result) throw new KernelError("EAGAIN");
3279
+ this.cancel(id);
3280
+ return result;
3281
+ }
3282
+ cancel(id) {
3283
+ this.results.delete(id);
3284
+ const at = this.ready.indexOf(id);
3285
+ if (at >= 0) this.ready.splice(at, 1);
3286
+ }
3287
+ async wait(timeoutMs, signal) {
3288
+ for (; ; ) {
3289
+ if (this.closed) return -1;
3290
+ const id = this.ready.shift();
3291
+ if (id !== void 0) return id;
3292
+ if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
3293
+ const woke = await this.sleep(timeoutMs, signal);
3294
+ if (!woke) return 0;
3295
+ }
3296
+ }
3297
+ close() {
3298
+ this.closed = true;
3299
+ this.wake();
3300
+ }
3301
+ sleep(timeoutMs, signal) {
3302
+ return new Promise((resolve, reject) => {
3303
+ let timer;
3304
+ const cleanup = () => {
3305
+ clearTimeout(timer);
3306
+ this.waiters.delete(wake);
3307
+ signal.removeEventListener("abort", abort);
3308
+ };
3309
+ const wake = () => {
3310
+ cleanup();
3311
+ resolve(true);
3312
+ };
3313
+ const abort = () => {
3314
+ cleanup();
3315
+ reject(new KernelError("EINTR"));
3316
+ };
3317
+ this.waiters.add(wake);
3318
+ signal.addEventListener("abort", abort, { once: true });
3319
+ if (timeoutMs !== void 0) {
3320
+ timer = setTimeout(() => {
3321
+ cleanup();
3322
+ resolve(false);
3323
+ }, timeoutMs);
3324
+ }
3325
+ });
3326
+ }
3327
+ complete(id, result) {
3328
+ if (!this.results.has(id)) return;
3329
+ this.results.set(id, result);
3330
+ this.ready.push(id);
3331
+ this.wake();
3332
+ }
3333
+ wake() {
3334
+ for (const wake of [...this.waiters]) wake();
3335
+ }
3336
+ };
3337
+ async function hostSyscall(req, ctx) {
3338
+ switch (req.op) {
3339
+ case "lock":
3340
+ if (!ctx.locks) throw new KernelError("ENOSYS");
3341
+ if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
3342
+ throw new KernelError("EAGAIN");
3343
+ return done2;
3344
+ case "unlock":
3345
+ ctx.locks?.unlock(ctx.pid, req.path);
3346
+ return done2;
3347
+ case "async-submit":
3348
+ if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
3349
+ return json2(ctx.ops.submit(req.req));
3350
+ case "async-wait":
3351
+ return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
3352
+ case "async-take":
3353
+ return ctx.ops.take(req.id);
3354
+ case "async-resolve":
3355
+ return json2(ctx.ops.resolve(req.value));
3356
+ case "async-hold":
3357
+ return json2(ctx.ops.hold());
3358
+ case "async-cancel":
3359
+ ctx.ops.cancel(req.id);
3360
+ return done2;
3361
+ case "async-close":
3362
+ ctx.ops.close();
3363
+ return done2;
3364
+ }
3365
+ }
3366
+
3064
3367
  // src/kernel/net/http-syscalls.ts
3065
3368
  var HTTP_OPS = ["net-request", "net-read", "net-close"];
3066
3369
  var EMPTY = new Uint8Array(0);
@@ -3571,41 +3874,41 @@ function terminalOf(ctx, fd) {
3571
3874
  if (!tty) throw new KernelError("ENOTTY");
3572
3875
  return tty;
3573
3876
  }
3574
- var json2 = (value) => ({ ok: true, kind: "json", json: value });
3575
- var done2 = { ok: true, kind: "void" };
3877
+ var json3 = (value) => ({ ok: true, kind: "json", json: value });
3878
+ var done3 = { ok: true, kind: "void" };
3576
3879
  function ptySyscall(req, ctx) {
3577
3880
  switch (req.op) {
3578
3881
  case "pty-open": {
3579
3882
  if (!ctx.ptys) throw new KernelError("ENOENT");
3580
- return json2(ctx.fds.install(ctx.ptys.open().master, 3));
3883
+ return json3(ctx.fds.install(ctx.ptys.open().master, 3));
3581
3884
  }
3582
3885
  case "pty-slave-open": {
3583
3886
  if (!ctx.ptys) throw new KernelError("ENOENT");
3584
3887
  const pair = ctx.ptys.get(req.n);
3585
3888
  const fd = ctx.fds.install(pair.openSlave(), 3);
3586
3889
  if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
3587
- return json2(fd);
3890
+ return json3(fd);
3588
3891
  }
3589
3892
  case "pty-number":
3590
- return json2(masterOf(ctx, req.fd).index);
3893
+ return json3(masterOf(ctx, req.fd).index);
3591
3894
  case "pty-lock":
3592
3895
  masterOf(ctx, req.fd).locked = req.lock;
3593
- return done2;
3896
+ return done3;
3594
3897
  case "pty-ctty": {
3595
3898
  const tty = terminalOf(ctx, req.fd);
3596
3899
  const leader = ctx.jobs?.getsid(ctx.pid, 0) === ctx.pid;
3597
- if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done2;
3900
+ if (leader && ctx.jobs?.controllingTerminal(ctx.pid) === tty) return done3;
3598
3901
  if (!ctx.jobs?.acquireTerminal(ctx.pid, tty)) throw new KernelError("EPERM");
3599
- return done2;
3902
+ return done3;
3600
3903
  }
3601
3904
  case "pty-list":
3602
- return json2(ctx.ptys?.numbers() ?? []);
3905
+ return json3(ctx.ptys?.numbers() ?? []);
3603
3906
  case "pty-packet":
3604
3907
  masterOf(ctx, req.fd).packet = req.on;
3605
- return done2;
3908
+ return done3;
3606
3909
  case "pty-winsz-set":
3607
3910
  terminalOf(ctx, req.fd).resize(req.cols, req.rows);
3608
- return done2;
3911
+ return done3;
3609
3912
  }
3610
3913
  }
3611
3914
 
@@ -3990,7 +4293,7 @@ function socketAt(fds, fd) {
3990
4293
  if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
3991
4294
  return file;
3992
4295
  }
3993
- var ok = (json3) => json3 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json3 };
4296
+ var ok = (json4) => json4 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json4 };
3994
4297
  async function socketSyscall(req, proc) {
3995
4298
  const { fds, net } = proc;
3996
4299
  switch (req.op) {
@@ -4316,6 +4619,9 @@ function isHttpSyscall(req) {
4316
4619
  function isSocketSyscall(req) {
4317
4620
  return req.op.startsWith("sock-");
4318
4621
  }
4622
+ function isHostSyscall(req) {
4623
+ return HOST_OPS.includes(req.op);
4624
+ }
4319
4625
  function isPtySyscall(req) {
4320
4626
  return req.op.startsWith("pty-");
4321
4627
  }
@@ -4353,6 +4659,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4353
4659
  "fd-cloexec",
4354
4660
  "fd-list",
4355
4661
  "proc-alarm",
4662
+ "dl-log",
4356
4663
  "fd-renumber",
4357
4664
  "fd-promote",
4358
4665
  "fd-open-tty",
@@ -4376,7 +4683,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4376
4683
  "sig-pause",
4377
4684
  ...SOCKET_OPS,
4378
4685
  ...HTTP_OPS,
4379
- ...PTY_OPS
4686
+ ...PTY_OPS,
4687
+ ...HOST_OPS
4380
4688
  ]);
4381
4689
  function isWasmSyscall(req) {
4382
4690
  const op = req.op;
@@ -4424,6 +4732,9 @@ var WasmProcess = class {
4424
4732
  const action = defaultAction(sig);
4425
4733
  return action === "stop" ? this.stop(sig) : action;
4426
4734
  }
4735
+ if (((this.options.pendingBits?.() ?? 0) & bit) !== 0 && defaultAction(sig) === "terminate") {
4736
+ return "terminate";
4737
+ }
4427
4738
  this.options.onPending?.(sig);
4428
4739
  const blocked = this.interrupt;
4429
4740
  this.interrupt = new AbortController();
@@ -4468,6 +4779,15 @@ var WasmProcess = class {
4468
4779
  if (isJobSyscall(req)) return this.jobSyscall(req);
4469
4780
  if (isSocketSyscall(req)) return await this.socketSyscall(req);
4470
4781
  if (isHttpSyscall(req)) return await this.httpSyscall(req);
4782
+ if (isHostSyscall(req)) {
4783
+ return await hostSyscall(req, {
4784
+ pid: this.pid,
4785
+ locks: this.options.locks,
4786
+ ops: this.asyncOps,
4787
+ blocking: () => this.blockingSignal(),
4788
+ valid: (inner) => isWasmSyscall(inner) && !isHostSyscall(inner)
4789
+ });
4790
+ }
4471
4791
  if (isPtySyscall(req)) {
4472
4792
  const { ptys, jobs } = this.options;
4473
4793
  return ptySyscall(req, { pid: this.pid, fds: this.fds, ptys, jobs });
@@ -4531,6 +4851,7 @@ var WasmProcess = class {
4531
4851
  case "fd-write":
4532
4852
  return { ok: true, kind: "json", json: await this.write(req.fd, req.body, req.nonblock) };
4533
4853
  case "fd-close":
4854
+ this.options.locks?.closed(this.pid, req.fd);
4534
4855
  await Promise.resolve(this.fds.close(req.fd));
4535
4856
  return { ok: true, kind: "void" };
4536
4857
  case "fd-pipe": {
@@ -4802,6 +5123,9 @@ var WasmProcess = class {
4802
5123
  case "proc-alarm":
4803
5124
  this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
4804
5125
  return { ok: true, kind: "void" };
5126
+ case "dl-log":
5127
+ if (req.append) this.dlLog.push(req.append);
5128
+ return { ok: true, kind: "json", json: this.dlLog.slice(req.from) };
4805
5129
  case "sig-mask":
4806
5130
  this.caught = req.caught;
4807
5131
  this.ignored = req.ignored;
@@ -4819,6 +5143,8 @@ var WasmProcess = class {
4819
5143
  });
4820
5144
  }
4821
5145
  alarm;
5146
+ dlLog = [];
5147
+ asyncOps = new AsyncOps((req) => this.syscall(req));
4822
5148
  alarmEvery;
4823
5149
  setAlarm(sig, first, every, timer) {
4824
5150
  if (!isSignal(sig)) throw new KernelError("EINVAL");
@@ -4847,6 +5173,8 @@ var WasmProcess = class {
4847
5173
  if (this.exited) return;
4848
5174
  this.exited = true;
4849
5175
  this.clearAlarm();
5176
+ this.options.locks?.release(this.pid);
5177
+ this.asyncOps.close();
4850
5178
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
4851
5179
  await this.fds.closeAll();
4852
5180
  await this.options.http?.closeAll();
@@ -4857,6 +5185,10 @@ var WasmProcess = class {
4857
5185
  var WASM_PROCESS_INIT = "wasm-process-init";
4858
5186
  var WASM_PROCESS_EXIT = "wasm-process-exit";
4859
5187
  var WASM_PROCESS_ERROR = "wasm-process-error";
5188
+ var WASM_THREAD_SPAWN = "wasm-thread-spawn";
5189
+ var WASM_THREAD_INIT = "wasm-thread-init";
5190
+ var WASM_THREAD_EXIT = "wasm-thread-exit";
5191
+ var WASM_MAX_THREADS = 64;
4860
5192
 
4861
5193
  // src/kernel/host.ts
4862
5194
  var CRASHED = 70;
@@ -4895,11 +5227,13 @@ function spawnWasmProcess(opts) {
4895
5227
  ptys: opts.ptys,
4896
5228
  net: opts.net,
4897
5229
  http: opts.http,
5230
+ locks: opts.locks,
4898
5231
  onReap: opts.onReap,
4899
5232
  onPending: (sig) => void Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig)),
4900
5233
  onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
4901
5234
  hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
4902
- raise: (sig) => signal(sig)
5235
+ raise: (sig) => signal(sig),
5236
+ ...opts.program.abi === "wasi" ? { pendingBits: () => Atomics.load(header2, SAB_I_SIGNALS) } : {}
4903
5237
  });
4904
5238
  const token = mintSyncFsToken({ fs: opts.fs, cwd: opts.cwd });
4905
5239
  const worker = opts.createWorker();
@@ -4910,14 +5244,15 @@ function spawnWasmProcess(opts) {
4910
5244
  const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
4911
5245
  let settle2;
4912
5246
  const exited = new Promise((resolve) => settle2 = resolve);
4913
- let done3 = false;
5247
+ let done4 = false;
4914
5248
  let endedBy;
4915
5249
  const finish = (code, sig) => {
4916
- if (done3) return;
4917
- done3 = true;
5250
+ if (done4) return;
5251
+ done4 = true;
4918
5252
  endedBy = sig ?? process.execTermsig;
4919
5253
  worker.removeEventListener("message", onMessage);
4920
5254
  worker.removeEventListener("error", onError);
5255
+ for (const tid of [...threads.keys()]) endThread(tid);
4921
5256
  responder.dispose();
4922
5257
  revokeSyncFsToken(token);
4923
5258
  worker.terminate();
@@ -4926,15 +5261,17 @@ function spawnWasmProcess(opts) {
4926
5261
  () => settle2(code)
4927
5262
  );
4928
5263
  };
4929
- const onMessage = (event) => {
4930
- const data = event.data;
5264
+ const handle = (data) => {
4931
5265
  if (data?.type === WASM_PROCESS_EXIT) {
4932
5266
  finish(typeof data.code === "number" ? data.code : CRASHED);
4933
5267
  } else if (data?.type === WASM_PROCESS_ERROR) {
4934
5268
  opts.onError?.(String(data.message));
4935
5269
  finish(CRASHED);
5270
+ } else if (data?.type === WASM_THREAD_SPAWN && data.thread) {
5271
+ spawnThread(data.thread);
4936
5272
  }
4937
5273
  };
5274
+ const onMessage = (event) => handle(event.data);
4938
5275
  const onError = (event) => {
4939
5276
  event.preventDefault();
4940
5277
  opts.onError?.(String(event.message ?? "worker error"));
@@ -4942,6 +5279,49 @@ function spawnWasmProcess(opts) {
4942
5279
  };
4943
5280
  worker.addEventListener("message", onMessage);
4944
5281
  worker.addEventListener("error", onError);
5282
+ const threads = /* @__PURE__ */ new Map();
5283
+ const endThread = (tid) => {
5284
+ const end = threads.get(tid);
5285
+ threads.delete(tid);
5286
+ end?.();
5287
+ };
5288
+ const spawnThread = (thread) => {
5289
+ if (done4) return;
5290
+ if (threads.size >= WASM_MAX_THREADS - 1) {
5291
+ opts.onError?.(`more than ${WASM_MAX_THREADS} threads`);
5292
+ finish(CRASHED);
5293
+ return;
5294
+ }
5295
+ const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
5296
+ const tw = opts.createWorker();
5297
+ const tresponder = attachSyncSabResponder(tw, tsab, token, { dispatch });
5298
+ const onThreadMessage = (event) => {
5299
+ const data = event.data;
5300
+ if (data?.type === WASM_THREAD_EXIT) endThread(thread.tid);
5301
+ else handle(data);
5302
+ };
5303
+ tw.addEventListener("message", onThreadMessage);
5304
+ tw.addEventListener("error", onError);
5305
+ threads.set(thread.tid, () => {
5306
+ tw.removeEventListener("message", onThreadMessage);
5307
+ tw.removeEventListener("error", onError);
5308
+ tresponder.dispose();
5309
+ tw.terminate();
5310
+ });
5311
+ const tinit = {
5312
+ type: WASM_THREAD_INIT,
5313
+ pid: opts.pid,
5314
+ program: opts.program,
5315
+ argv0: opts.argv0,
5316
+ args: opts.args,
5317
+ env: opts.env,
5318
+ cwd: opts.cwd,
5319
+ sab: tsab,
5320
+ ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
5321
+ thread
5322
+ };
5323
+ tw.postMessage(tinit);
5324
+ };
4945
5325
  const init = {
4946
5326
  type: WASM_PROCESS_INIT,
4947
5327
  pid: opts.pid,
@@ -5076,8 +5456,67 @@ var JobTable = class {
5076
5456
  }
5077
5457
  };
5078
5458
 
5459
+ // src/process/wasi/wasi-module.ts
5460
+ function importedMemory(bytes) {
5461
+ let at = 8;
5462
+ const u32 = () => {
5463
+ let result = 0;
5464
+ let shift = 0;
5465
+ for (; ; ) {
5466
+ const b = bytes[at++];
5467
+ result |= (b & 127) << shift;
5468
+ if (!(b & 128)) return result >>> 0;
5469
+ shift += 7;
5470
+ }
5471
+ };
5472
+ const name2 = () => {
5473
+ const len = u32();
5474
+ const s = new TextDecoder().decode(bytes.subarray(at, at + len));
5475
+ at += len;
5476
+ return s;
5477
+ };
5478
+ const limits = () => {
5479
+ const flags = bytes[at++];
5480
+ const initial = u32();
5481
+ const maximum = flags & 1 ? u32() : void 0;
5482
+ return { initial, maximum, shared: (flags & 2) !== 0 };
5483
+ };
5484
+ while (at < bytes.length) {
5485
+ const id = bytes[at++];
5486
+ const size = u32();
5487
+ const end = at + size;
5488
+ if (id !== 2) {
5489
+ at = end;
5490
+ continue;
5491
+ }
5492
+ const count = u32();
5493
+ for (let i = 0; i < count; i++) {
5494
+ const module = name2();
5495
+ const field = name2();
5496
+ const kind = bytes[at++];
5497
+ if (kind === 0) u32();
5498
+ else if (kind === 1) {
5499
+ at++;
5500
+ limits();
5501
+ } else if (kind === 2) {
5502
+ const l = limits();
5503
+ return { module, name: field, ...l };
5504
+ } else if (kind === 3) at += 2;
5505
+ else if (kind === 4) {
5506
+ at++;
5507
+ u32();
5508
+ }
5509
+ }
5510
+ return void 0;
5511
+ }
5512
+ return void 0;
5513
+ }
5514
+
5079
5515
  // src/launcher.ts
5080
5516
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
5517
+ var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
5518
+ var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
5519
+ var WASM_MAGIC = [0, 97, 115, 109];
5081
5520
  var SHEBANG_MAX = 256;
5082
5521
  var NOT_FOUND = 127;
5083
5522
  var SHARED_DIRS = ["/tmp", "/home"];
@@ -5088,13 +5527,43 @@ function baseName(path) {
5088
5527
  function modulePath(glue) {
5089
5528
  return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
5090
5529
  }
5530
+ function isWasm(bytes) {
5531
+ return WASM_MAGIC.every((b, i) => bytes[i] === b);
5532
+ }
5533
+ function shebang(head) {
5534
+ if (head[0] !== 35 || head[1] !== 33) return void 0;
5535
+ return new TextDecoder().decode(head.subarray(0, SHEBANG_MAX)).slice(2).split("\n")[0].trim().split(/[ \t]+/);
5536
+ }
5537
+ function interpreter(head) {
5538
+ const words = shebang(head);
5539
+ if (!words) return "node";
5540
+ if (baseName(words[0]) === "env") words.shift();
5541
+ if (words[0] === "-S") words.shift();
5542
+ return baseName(words[0] ?? "");
5543
+ }
5544
+ function expandDefaults(defaults, env) {
5545
+ const out = {};
5546
+ for (const [key, value] of Object.entries(defaults ?? {})) {
5547
+ let missing3 = false;
5548
+ const expanded = value.replace(ENV_REFERENCE, (_, name2) => {
5549
+ const set2 = Object.hasOwn(env, name2) ? env[name2] : void 0;
5550
+ if (set2 === void 0) missing3 = true;
5551
+ return set2 ?? "";
5552
+ });
5553
+ if (!missing3) out[key] = expanded;
5554
+ }
5555
+ return out;
5556
+ }
5091
5557
  function targetOf(command) {
5092
5558
  return {
5559
+ abi: command.abi,
5093
5560
  glue: command.glue,
5094
5561
  wasm: command.wasm,
5095
5562
  argv0: command.argv0,
5096
5563
  ...command.args ? { prefix: command.args } : {},
5097
- ...command.env ? { env: command.env } : {}
5564
+ ...command.env ? { env: command.env } : {},
5565
+ ...command.unset ? { unset: command.unset } : {},
5566
+ ...command.imports ? { imports: command.imports } : {}
5098
5567
  };
5099
5568
  }
5100
5569
  function childHandle(handle) {
@@ -5126,6 +5595,7 @@ var Launcher = class {
5126
5595
  zombies = /* @__PURE__ */ new Set();
5127
5596
  orphans = /* @__PURE__ */ new Set();
5128
5597
  jobs = new JobTable();
5598
+ locks = new LockTable();
5129
5599
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
5130
5600
  net = new LoopbackNet();
5131
5601
  ca;
@@ -5154,35 +5624,62 @@ var Launcher = class {
5154
5624
  const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
5155
5625
  if (name2 !== void 0) {
5156
5626
  const command = (await this.commands()).get(name2);
5157
- return command && targetOf(command);
5627
+ return command && !command.script ? targetOf(command) : void 0;
5158
5628
  }
5159
5629
  const glue = await followLinks(this.base, this.fs.resolvePath(cwd, file));
5160
5630
  const linked = COMMAND2.exec(glue)?.[1];
5161
5631
  if (linked !== void 0) return this.resolve(`/bin/${linked}`, argv0, cwd);
5632
+ const head = await this.head(glue);
5633
+ if (!head) return void 0;
5634
+ if (isWasm(head)) {
5635
+ return {
5636
+ abi: "wasi",
5637
+ glue,
5638
+ wasm: glue,
5639
+ argv0: baseName(argv0 || file).replace(/\.wasm$/, "")
5640
+ };
5641
+ }
5642
+ if (interpreter(head) !== "node") return void 0;
5162
5643
  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) };
5644
+ if (!await this.base.exists(wasm)) return void 0;
5645
+ return { abi: "emscripten", glue, wasm, argv0: baseName(argv0 || file) };
5165
5646
  }
5166
- async interpreted(file, argv, cwd) {
5167
- let head;
5647
+ async head(path) {
5168
5648
  try {
5169
- head = (await this.base.readFileBuffer(this.fs.resolvePath(cwd, file))).subarray(
5170
- 0,
5171
- SHEBANG_MAX
5172
- );
5649
+ return (await this.base.readFileBuffer(path)).subarray(0, SHEBANG_MAX);
5173
5650
  } catch {
5174
5651
  return void 0;
5175
5652
  }
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]+/);
5653
+ }
5654
+ async scriptCommand(file) {
5655
+ const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
5656
+ if (name2 === void 0) return void 0;
5657
+ const command = (await this.commands()).get(name2);
5658
+ return command?.script ? command : void 0;
5659
+ }
5660
+ async interpreted(file, argv, cwd) {
5661
+ const command = await this.scriptCommand(file);
5662
+ const script = command?.script ?? this.fs.resolvePath(cwd, file);
5663
+ const words = shebang(await this.head(script) ?? new Uint8Array());
5664
+ if (!words) return void 0;
5178
5665
  if (baseName(words[0]) === "env") words.shift();
5179
5666
  const [interp, ...rest] = words;
5180
- const target = interp ? await this.resolve(interp, interp, cwd) : void 0;
5181
- if (!target) return void 0;
5667
+ const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
5668
+ if (!found) return void 0;
5669
+ const target = command ? {
5670
+ ...found,
5671
+ env: { ...found.env, ...command.env },
5672
+ unset: [...found.unset ?? [], ...command.unset ?? []]
5673
+ } : found;
5182
5674
  const arg = rest.join(" ");
5183
5675
  return {
5184
5676
  target,
5185
- args: [...target.prefix ?? [], ...arg ? [arg] : [], file, ...argv.slice(1)]
5677
+ args: [
5678
+ ...target.prefix ?? [],
5679
+ ...arg ? [arg] : [],
5680
+ command ? script : file,
5681
+ ...argv.slice(1)
5682
+ ]
5186
5683
  };
5187
5684
  }
5188
5685
  async plan(file, argv, cwd) {
@@ -5199,33 +5696,58 @@ var Launcher = class {
5199
5696
  const key = `${path}:${st.size}:${st.mtime.getTime()}`;
5200
5697
  let module = this.compiled.get(key);
5201
5698
  if (!module) {
5202
- module = this.base.readFileBuffer(path).then((bytes) => WebAssembly.compile(bytes));
5699
+ module = this.base.readFileBuffer(path).then(async (bytes) => {
5700
+ const memory = importedMemory(bytes);
5701
+ const compiled = await WebAssembly.compile(bytes);
5702
+ return { module: compiled, ...memory ? { memory } : {} };
5703
+ });
5203
5704
  this.compiled.set(key, module);
5204
5705
  module.catch(() => this.compiled.delete(key));
5205
5706
  }
5206
5707
  return module;
5207
5708
  });
5208
5709
  }
5710
+ async names(path, module) {
5711
+ if (WebAssembly.Module.customSections(module, "name").length > 0) return void 0;
5712
+ const beside = `${path}.names`;
5713
+ if (await this.base.exists(beside)) return beside;
5714
+ const root = PACKAGE_ROOT.exec(path)?.[1];
5715
+ if (root === void 0) return void 0;
5716
+ const optional = `${root}-names${beside.slice(root.length)}`;
5717
+ return await this.base.exists(optional) ? optional : void 0;
5718
+ }
5719
+ async program(target, env) {
5720
+ if (target.abi !== "wasi") {
5721
+ const [glue, { module: module2 }] = await Promise.all([
5722
+ this.base.readFile(target.glue),
5723
+ this.module(target.wasm)
5724
+ ]);
5725
+ return { glue, module: module2 };
5726
+ }
5727
+ const { module, memory } = await this.module(target.wasm);
5728
+ const names = env.SLICC_WASM_BACKTRACE === "1" ? await this.names(target.wasm, module) : void 0;
5729
+ const imports = target.imports ? await this.base.readFile(target.imports) : void 0;
5730
+ return {
5731
+ abi: "wasi",
5732
+ glue: "",
5733
+ module,
5734
+ ...memory ? { memory } : {},
5735
+ ...names ? { names } : {},
5736
+ ...imports !== void 0 ? { imports } : {}
5737
+ };
5738
+ }
5209
5739
  async launch(planned, req) {
5210
5740
  const { target, args } = planned;
5741
+ const env = { ...expandDefaults(target.env, { ...req.env, cwd: req.cwd }), ...req.env };
5742
+ for (const key of target.unset ?? []) delete env[key];
5211
5743
  let program;
5212
5744
  try {
5213
- const [glue, module] = await Promise.all([
5214
- this.base.readFile(target.glue),
5215
- this.module(target.wasm)
5216
- ]);
5217
- program = { glue, module };
5745
+ program = await this.program(target, env);
5218
5746
  } catch (err) {
5219
5747
  await req.fds.closeAll();
5220
5748
  throw err;
5221
5749
  }
5222
- return this.start({
5223
- ...req,
5224
- program,
5225
- argv0: target.argv0,
5226
- args,
5227
- env: { ...target.env, ...req.env }
5228
- });
5750
+ return this.start({ ...req, program, argv0: target.argv0, args, env });
5229
5751
  }
5230
5752
  start(req) {
5231
5753
  const pid = this.nextPid++;
@@ -5248,6 +5770,7 @@ var Launcher = class {
5248
5770
  ptys: this.ptys,
5249
5771
  net: this.net,
5250
5772
  http: new HttpHandles(this.transport),
5773
+ locks: this.locks,
5251
5774
  onReap: (child) => this.reaped(child),
5252
5775
  ...req.fork ? { fork: req.fork } : {},
5253
5776
  ...req.ppid !== void 0 ? { ppid: req.ppid } : {}
@@ -5377,6 +5900,9 @@ function act(session, req) {
5377
5900
  else if (req.action === "signal") session.signal(signalNumber(req.signal));
5378
5901
  else session.close();
5379
5902
  }
5903
+ function dirsChannel(name2) {
5904
+ return name2 === false ? void 0 : `slicc-kernel-dirs:${name2}`;
5905
+ }
5380
5906
  function serveKernel(port, deps) {
5381
5907
  let launcher;
5382
5908
  let remote;
@@ -5403,7 +5929,7 @@ function serveKernel(port, deps) {
5403
5929
  const name2 = req.metadata ?? META_DB;
5404
5930
  const meta = name2 === false ? void 0 : await deps.metadata?.(name2);
5405
5931
  remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
5406
- const fs = new OpfsFs(root, meta);
5932
+ const fs = new OpfsFs(root, meta, dirsChannel(name2));
5407
5933
  await fs.reconcile();
5408
5934
  const started = new Launcher({
5409
5935
  fs,