| ... |
... |
@@ -285,7 +285,10 @@ function originFromServerAddress({ address, family, port }) { |
|
285
|
285
|
}
|
|
286
|
286
|
|
|
287
|
287
|
// Browser/node portable code stays above this watermark.
|
|
288
|
|
-const isNode = Boolean(globalThis?.process?.versions?.node);
|
|
|
288
|
+let isNode = Boolean(globalThis?.process?.versions?.node);
|
|
|
289
|
+if (globalThis.Deno) {
|
|
|
290
|
+ isNode = false;
|
|
|
291
|
+}
|
|
289
|
292
|
|
|
290
|
293
|
// Too cumbersome to only import at use sites. Too troublesome to
|
|
291
|
294
|
// factor out browser-only/node-only logic into different modules. For
|
| ... |
... |
@@ -307,18 +310,22 @@ if (isNode) { |
|
307
|
310
|
ws = require("ws");
|
|
308
|
311
|
} catch {}
|
|
309
|
312
|
} else {
|
|
310
|
|
- wasi = await import(
|
|
311
|
|
- "https://cdn.jsdelivr.net/npm/@bjorn3/browser_wasi_shim@0.4.2/dist/index.js"
|
|
312
|
|
- );
|
|
|
313
|
+ wasi = await import("https://esm.sh/gh/haskell-wasm/browser_wasi_shim");
|
|
313
|
314
|
}
|
|
314
|
315
|
|
|
315
|
316
|
// A subset of dyld logic that can only be run in the host node
|
|
316
|
317
|
// process and has full access to local filesystem
|
|
317
|
|
-class DyLDHost {
|
|
|
318
|
+export class DyLDHost {
|
|
318
|
319
|
// Deduped absolute paths of directories where we lookup .so files
|
|
319
|
320
|
#rpaths = new Set();
|
|
320
|
321
|
|
|
321
|
322
|
constructor({ out_fd, in_fd }) {
|
|
|
323
|
+ // When running a non-iserv shared library with node, the DyLDHost
|
|
|
324
|
+ // instance is created without a pair of fds, so skip creation of
|
|
|
325
|
+ // readStream/writeStream, they won't be used anyway
|
|
|
326
|
+ if (!(typeof out_fd === "number" && typeof in_fd === "number")) {
|
|
|
327
|
+ return;
|
|
|
328
|
+ }
|
|
322
|
329
|
this.readStream = stream.Readable.toWeb(
|
|
323
|
330
|
fs.createReadStream(undefined, { fd: in_fd })
|
|
324
|
331
|
);
|
| ... |
... |
@@ -373,6 +380,77 @@ class DyLDHost { |
|
373
|
380
|
}
|
|
374
|
381
|
}
|
|
375
|
382
|
|
|
|
383
|
+// Runs in the browser and uses the in-memory vfs, doesn't do any RPC
|
|
|
384
|
+// calls
|
|
|
385
|
+export class DyLDBrowserHost {
|
|
|
386
|
+ // Deduped absolute paths of directories where we lookup .so files
|
|
|
387
|
+ #rpaths = new Set();
|
|
|
388
|
+ // The PreopenDirectory object of the root filesystem
|
|
|
389
|
+ rootfs;
|
|
|
390
|
+
|
|
|
391
|
+ // Given canonicalized absolute file path, return Uint8Array of file
|
|
|
392
|
+ // content, or null if absent
|
|
|
393
|
+ #readFile(p) {
|
|
|
394
|
+ const { ret, entry } = this.rootfs.dir.get_entry_for_path({
|
|
|
395
|
+ parts: p.split("/").filter((tok) => tok !== ""),
|
|
|
396
|
+ is_dir: false,
|
|
|
397
|
+ });
|
|
|
398
|
+ return ret === 0 ? entry.data : null;
|
|
|
399
|
+ }
|
|
|
400
|
+
|
|
|
401
|
+ constructor({ rootfs }) {
|
|
|
402
|
+ this.rootfs = rootfs;
|
|
|
403
|
+ }
|
|
|
404
|
+
|
|
|
405
|
+ close() {}
|
|
|
406
|
+
|
|
|
407
|
+ installSignalHandlers(_cb) {}
|
|
|
408
|
+
|
|
|
409
|
+ // p must be canonicalized absolute path
|
|
|
410
|
+ async addLibrarySearchPath(p) {
|
|
|
411
|
+ this.#rpaths.add(p);
|
|
|
412
|
+ return null;
|
|
|
413
|
+ }
|
|
|
414
|
+
|
|
|
415
|
+ async findSystemLibrary(f) {
|
|
|
416
|
+ if (f.startsWith("/")) {
|
|
|
417
|
+ if (this.#readFile(f)) {
|
|
|
418
|
+ return f;
|
|
|
419
|
+ }
|
|
|
420
|
+ throw new Error(`findSystemLibrary(${f}): not found in /`);
|
|
|
421
|
+ }
|
|
|
422
|
+
|
|
|
423
|
+ for (const rpath of this.#rpaths) {
|
|
|
424
|
+ const r = `${rpath}/${f}`;
|
|
|
425
|
+ if (this.#readFile(r)) {
|
|
|
426
|
+ return r;
|
|
|
427
|
+ }
|
|
|
428
|
+ }
|
|
|
429
|
+
|
|
|
430
|
+ throw new Error(
|
|
|
431
|
+ `findSystemLibrary(${f}): not found in ${[...this.#rpaths]}`
|
|
|
432
|
+ );
|
|
|
433
|
+ }
|
|
|
434
|
+
|
|
|
435
|
+ async fetchWasm(p) {
|
|
|
436
|
+ let buf = this.#readFile(p);
|
|
|
437
|
+ if (buf.buffer.resizable) {
|
|
|
438
|
+ buf = new Uint8Array(buf);
|
|
|
439
|
+ }
|
|
|
440
|
+ return new Response(buf, {
|
|
|
441
|
+ headers: { "Content-Type": "application/wasm" },
|
|
|
442
|
+ });
|
|
|
443
|
+ }
|
|
|
444
|
+
|
|
|
445
|
+ stdout(msg) {
|
|
|
446
|
+ console.info(msg);
|
|
|
447
|
+ }
|
|
|
448
|
+
|
|
|
449
|
+ stderr(msg) {
|
|
|
450
|
+ console.warn(msg);
|
|
|
451
|
+ }
|
|
|
452
|
+}
|
|
|
453
|
+
|
|
376
|
454
|
// Fulfill the same functionality as DyLDHost by doing fetch() calls
|
|
377
|
455
|
// to respective RPC endpoints of a host http server. Also manages
|
|
378
|
456
|
// WebSocket connections back to host.
|
| ... |
... |
@@ -540,7 +618,7 @@ class DyLDRPCServer { |
|
540
|
618
|
res.end(
|
|
541
|
619
|
`
|
|
542
|
620
|
import { DyLDRPC, main } from "./fs${dyldPath}";
|
|
543
|
|
-const args = ${JSON.stringify({ libdir, ghciSoPath, args })};
|
|
|
621
|
+const args = ${JSON.stringify({ libdirs: [libdir], ghciSoPath, args })};
|
|
544
|
622
|
args.rpc = new DyLDRPC({origin: "${origin}", redirectWasiConsole: ${redirectWasiConsole}});
|
|
545
|
623
|
args.rpc.opened.then(() => main(args));
|
|
546
|
624
|
`
|
| ... |
... |
@@ -832,6 +910,10 @@ class DyLD { |
|
832
|
910
|
],
|
|
833
|
911
|
{ debug: false }
|
|
834
|
912
|
);
|
|
|
913
|
+
|
|
|
914
|
+ if (this.#rpc instanceof DyLDBrowserHost) {
|
|
|
915
|
+ this.#wasi.fds[3] = this.#rpc.rootfs;
|
|
|
916
|
+ }
|
|
835
|
917
|
}
|
|
836
|
918
|
|
|
837
|
919
|
// Both wasi implementations we use provide
|
| ... |
... |
@@ -1218,15 +1300,39 @@ class DyLD { |
|
1218
|
1300
|
}
|
|
1219
|
1301
|
}
|
|
1220
|
1302
|
|
|
1221
|
|
-export async function main({ rpc, libdir, ghciSoPath, args }) {
|
|
|
1303
|
+// The main entry point of dyld that may be run on node/browser, and
|
|
|
1304
|
+// may run either iserv defaultMain from the ghci library or an
|
|
|
1305
|
+// alternative entry point from another shared library
|
|
|
1306
|
+export async function main({
|
|
|
1307
|
+ rpc, // Handle the side effects of DyLD
|
|
|
1308
|
+ libdirs, // Initial library search directories
|
|
|
1309
|
+ ghciSoPath, // Could also be another shared library that's actually not ghci
|
|
|
1310
|
+ args, // WASI argv without the executable name. +RTS etc will be respected
|
|
|
1311
|
+ altEntry, // Optional alternative entry point function name
|
|
|
1312
|
+ altArgs, // Argument array to pass to the alternative entry point function
|
|
|
1313
|
+}) {
|
|
1222
|
1314
|
try {
|
|
1223
|
1315
|
const dyld = new DyLD({
|
|
1224
|
1316
|
args: ["dyld.so", ...args],
|
|
1225
|
1317
|
rpc,
|
|
1226
|
1318
|
});
|
|
1227
|
|
- await dyld.addLibrarySearchPath(libdir);
|
|
|
1319
|
+ for (const libdir of libdirs) {
|
|
|
1320
|
+ await dyld.addLibrarySearchPath(libdir);
|
|
|
1321
|
+ }
|
|
1228
|
1322
|
await dyld.loadDLLs(ghciSoPath);
|
|
1229
|
1323
|
|
|
|
1324
|
+ // At this point, rts/ghc-internal are loaded, perform wasm shared
|
|
|
1325
|
+ // library specific RTS startup logic, see Note [JSFFI
|
|
|
1326
|
+ // initialization]
|
|
|
1327
|
+ dyld.exportFuncs.__ghc_wasm_jsffi_init();
|
|
|
1328
|
+
|
|
|
1329
|
+ // We're not running iserv, just invoke user-specified alternative
|
|
|
1330
|
+ // entry point and pass the arguments
|
|
|
1331
|
+ if (altEntry) {
|
|
|
1332
|
+ return await dyld.exportFuncs[altEntry](...altArgs);
|
|
|
1333
|
+ }
|
|
|
1334
|
+
|
|
|
1335
|
+ // iserv-specific logic follows
|
|
1230
|
1336
|
const reader = rpc.readStream.getReader();
|
|
1231
|
1337
|
const writer = rpc.writeStream.getWriter();
|
|
1232
|
1338
|
|
| ... |
... |
@@ -1245,19 +1351,19 @@ export async function main({ rpc, libdir, ghciSoPath, args }) { |
|
1245
|
1351
|
writer.write(new Uint8Array(buf));
|
|
1246
|
1352
|
};
|
|
1247
|
1353
|
|
|
1248
|
|
- dyld.exportFuncs.__ghc_wasm_jsffi_init();
|
|
1249
|
|
- await dyld.exportFuncs.defaultServer(cb_sig, cb_recv, cb_send);
|
|
|
1354
|
+ return await dyld.exportFuncs.defaultServer(cb_sig, cb_recv, cb_send);
|
|
1250
|
1355
|
} finally {
|
|
1251
|
1356
|
rpc.close();
|
|
1252
|
1357
|
}
|
|
1253
|
1358
|
}
|
|
1254
|
1359
|
|
|
1255
|
|
-export async function nodeMain({ libdir, ghciSoPath, out_fd, in_fd, args }) {
|
|
|
1360
|
+// node-specific iserv-specific logic
|
|
|
1361
|
+async function nodeMain({ libdir, ghciSoPath, out_fd, in_fd, args }) {
|
|
1256
|
1362
|
if (!process.env.GHCI_BROWSER) {
|
|
1257
|
1363
|
const rpc = new DyLDHost({ out_fd, in_fd });
|
|
1258
|
1364
|
await main({
|
|
1259
|
1365
|
rpc,
|
|
1260
|
|
- libdir,
|
|
|
1366
|
+ libdirs: [libdir],
|
|
1261
|
1367
|
ghciSoPath,
|
|
1262
|
1368
|
args,
|
|
1263
|
1369
|
});
|
| ... |
... |
@@ -1370,6 +1476,8 @@ export async function nodeMain({ libdir, ghciSoPath, out_fd, in_fd, args }) { |
|
1370
|
1476
|
);
|
|
1371
|
1477
|
}
|
|
1372
|
1478
|
|
|
|
1479
|
+// Can't be reused with isMain in post-link.mjs, import.meta.filename
|
|
|
1480
|
+// evaluates to filename of source location
|
|
1373
|
1481
|
function isNodeMain() {
|
|
1374
|
1482
|
if (!globalThis?.process?.versions?.node) {
|
|
1375
|
1483
|
return false;
|
| ... |
... |
@@ -1378,6 +1486,8 @@ function isNodeMain() { |
|
1378
|
1486
|
return import.meta.filename === process.argv[1];
|
|
1379
|
1487
|
}
|
|
1380
|
1488
|
|
|
|
1489
|
+// node iserv as invoked by
|
|
|
1490
|
+// GHC.Runtime.Interpreter.Wasm.spawnWasmInterp
|
|
1381
|
1491
|
if (isNodeMain()) {
|
|
1382
|
1492
|
const libdir = process.argv[2];
|
|
1383
|
1493
|
const ghciSoPath = process.argv[3];
|