From 1cb735f2a58f82659766cbdfeead743fda7d686b Mon Sep 17 00:00:00 2001
From: Cheng Shao <terrorjack@type.dance>
Date: Sun, 16 Mar 2025 22:14:20 +0000
Subject: [PATCH] wasm: isolate nodejs-specific logic with the isNode flag in
 dyld

As we move towards supporting running the dyld script in the browser,
this commit implements the isNode module-level binding which is true
if dyld is running in nodejs. The nodejs-specific bits are gated under
isNode.

For the browser case, this commit introduces @bjorn3/browser_wasi_shim
as the wasi implementation; we already use it in quite a few projects
and it simply works.

(cherry picked from commit 7003a39901d180d13723e1cec5f2972068fe5e26)
(cherry picked from commit 9156318fab8723c6bae562d2b431fe3d533078c9)
---
 utils/jsffi/dyld.mjs | 63 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 48 insertions(+), 15 deletions(-)

diff --git a/utils/jsffi/dyld.mjs b/utils/jsffi/dyld.mjs
index 3820d78a555..caa699ee1cb 100755
--- a/utils/jsffi/dyld.mjs
+++ b/utils/jsffi/dyld.mjs
@@ -87,10 +87,6 @@
 // -opti GHC flag to pass process arguments, like RTS flags or -opti-v
 // to dump the iserv messages.
 
-import fs from "node:fs";
-import path from "node:path";
-import stream from "node:stream";
-import { WASI } from "node:wasi";
 import { JSValManager, setImmediate } from "./prelude.mjs";
 import { parseRecord, parseSections } from "./post-link.mjs";
 
@@ -265,6 +261,28 @@ async function parseDyLink0(reader) {
   return r;
 }
 
+// Browser/node portable code stays above this watermark.
+const isNode = Boolean(globalThis?.process?.versions?.node);
+
+// Too cumbersome to only import at use sites. Too troublesome to
+// factor out browser-only/node-only logic into different modules. For
+// now, just make these global let bindings optionally initialized if
+// isNode and be careful to not use them in browser-only logic.
+let fs, path, require, stream, wasi;
+
+if (isNode) {
+  require = (await import("node:module")).createRequire(import.meta.url);
+
+  fs = require("fs");
+  path = require("path");
+  stream = require("stream");
+  wasi = require("wasi");
+} else {
+  wasi = await import(
+    "https://cdn.jsdelivr.net/npm/@bjorn3/browser_wasi_shim@0.4.1/dist/index.js"
+  );
+}
+
 // The real stuff
 class DyLD {
   // Wasm page size.
@@ -342,12 +360,31 @@ class DyLD {
   #regs = {};
 
   constructor({ args }) {
-    this.#wasi = new WASI({
-      version: "preview1",
-      args,
-      env: { PATH: "", PWD: process.cwd() },
-      preopens: { "/": "/" },
-    });
+    if (isNode) {
+      this.#wasi = new wasi.WASI({
+        version: "preview1",
+        args,
+        env: { PATH: "", PWD: process.cwd() },
+        preopens: { "/": "/" },
+      });
+    } else {
+      this.#wasi = new wasi.WASI(
+        args,
+        [],
+        [
+          new wasi.OpenFile(
+            new wasi.File(new Uint8Array(), { readonly: true })
+          ),
+          wasi.ConsoleStdout.lineBuffered((msg) =>
+            console.log(`[WASI stdout] ${msg}`)
+          ),
+          wasi.ConsoleStdout.lineBuffered((msg) =>
+            console.warn(`[WASI stderr] ${msg}`)
+          ),
+        ],
+        { debug: false }
+      );
+    }
 
     // Keep this in sync with rts/wasm/Wasm.S!
     for (let i = 1; i <= 10; ++i) {
@@ -740,11 +777,7 @@ class DyLD {
   }
 }
 
-function isMain() {
-  return import.meta.filename === process.argv[1];
-}
-
-if (isMain()) {
+if (isNode) {
   // sysroot libdir that contains libc.so etc
   const libdir = process.argv[2],
     ghci_so_path = process.argv[3];
-- 
GitLab