From e2705d2a118030a03a4e333c7681ac3cf571a858 Mon Sep 17 00:00:00 2001
From: Cheng Shao <terrorjack@type.dance>
Date: Tue, 1 Oct 2024 19:24:49 +0000
Subject: [PATCH] driver: add wasm backend iserv logic

This commit adds wasm backend iserv logic to the driver, see added
comments for explanation.

(cherry picked from commit 2d6107dc0e461f6d339ea14712b6f0cb9a619680)
---
 CODEOWNERS                                |  1 +
 compiler/GHC.hs                           | 30 +++++++++
 compiler/GHC/Runtime/Interpreter.hs       | 15 +++++
 compiler/GHC/Runtime/Interpreter/Types.hs | 19 ++++++
 compiler/GHC/Runtime/Interpreter/Wasm.hs  | 82 +++++++++++++++++++++++
 compiler/ghc.cabal.in                     |  1 +
 6 files changed, 148 insertions(+)
 create mode 100644 compiler/GHC/Runtime/Interpreter/Wasm.hs

diff --git a/CODEOWNERS b/CODEOWNERS
index 872965b92b1..bcb37703f93 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -55,6 +55,7 @@
 /compiler/GHC/Wasm/                @nrnrnr
 /compiler/GHC/JS/                  @luite @doyougnu @hsyl20 @JoshMeredith
 /compiler/GHC/StgToJS/             @luite @doyougnu @hsyl20 @JoshMeredith
+/compiler/GHC/Runtime/Interpreter/Wasm.hs @TerrorJack
 
 [Core libraries]
 /libraries/base/                  @hvr
diff --git a/compiler/GHC.hs b/compiler/GHC.hs
index 83388c6718f..129f8ecb078 100644
--- a/compiler/GHC.hs
+++ b/compiler/GHC.hs
@@ -407,6 +407,7 @@ import GHC.Unit.Module.ModDetails
 import GHC.Unit.Module.ModSummary
 import GHC.Unit.Module.Graph
 import GHC.Unit.Home.ModInfo
+import GHC.Settings
 
 import Control.Applicative ((<|>))
 import Control.Concurrent
@@ -679,6 +680,35 @@ setTopSessionDynFlags dflags = do
 
   -- Interpreter
   interp <- if
+    -- Wasm dynamic linker
+    | ArchWasm32 <- platformArch $ targetPlatform dflags
+    -> do
+        s <- liftIO $ newMVar InterpPending
+        loader <- liftIO Loader.uninitializedLoader
+        dyld <- liftIO $ makeAbsolute $ topDir dflags </> "dyld.mjs"
+#if defined(wasm32_HOST_ARCH)
+        let libdir = sorry "cannot spawn child process on wasm"
+#else
+        libdir <- liftIO $ do
+          libdirs <- Loader.getGccSearchDirectory logger dflags "libraries"
+          case libdirs of
+            [_, libdir] -> pure libdir
+            _ -> panic "corrupted wasi-sdk installation"
+#endif
+        let profiled = ways dflags `hasWay` WayProf
+            way_tag = if profiled then "_p" else ""
+        let cfg =
+              WasmInterpConfig
+                { wasmInterpDyLD = dyld,
+                  wasmInterpLibDir = libdir,
+                  wasmInterpOpts = getOpts dflags opt_i,
+                  wasmInterpTargetPlatform = targetPlatform dflags,
+                  wasmInterpProfiled = profiled,
+                  wasmInterpHsSoSuffix = way_tag ++ dynLibSuffix (ghcNameVersion dflags),
+                  wasmInterpUnitState = ue_units $ hsc_unit_env hsc_env
+                }
+        pure $ Just $ Interp (ExternalInterp $ ExtWasm $ ExtInterpState cfg s) loader lookup_cache
+
     -- JavaScript interpreter
     | ArchJavaScript <- platformArch (targetPlatform dflags)
     -> do
diff --git a/compiler/GHC/Runtime/Interpreter.hs b/compiler/GHC/Runtime/Interpreter.hs
index 7004a050cd0..13ab231e775 100644
--- a/compiler/GHC/Runtime/Interpreter.hs
+++ b/compiler/GHC/Runtime/Interpreter.hs
@@ -67,6 +67,7 @@ import GHC.Prelude
 
 import GHC.Runtime.Interpreter.Types
 import GHC.Runtime.Interpreter.JS
+import GHC.Runtime.Interpreter.Wasm
 import GHC.Runtime.Interpreter.Process
 import GHC.Runtime.Utils
 import GHCi.Message
@@ -196,11 +197,13 @@ interpCmd interp msg = case interpInstance interp of
 withExtInterp :: ExceptionMonad m => ExtInterp -> (forall d. ExtInterpInstance d -> m a) -> m a
 withExtInterp ext action = case ext of
   ExtJS    i -> withJSInterp i action
+  ExtWasm  i -> withWasmInterp i action
   ExtIServ i -> withIServ    i action
 
 withExtInterpStatus :: ExtInterp -> (forall d. ExtInterpStatusVar d -> m a) -> m a
 withExtInterpStatus ext action = case ext of
   ExtJS    i -> action (interpStatus i)
+  ExtWasm  i -> action $ interpStatus i
   ExtIServ i -> action (interpStatus i)
 
 -- Note [uninterruptibleMask_ and interpCmd]
@@ -229,6 +232,11 @@ withJSInterp (ExtInterpState cfg mstate) action = do
   inst <- spawnInterpMaybe cfg spawnJSInterp mstate
   action inst
 
+withWasmInterp :: ExceptionMonad m => WasmInterp -> (ExtInterpInstance () -> m a) -> m a
+withWasmInterp (ExtInterpState cfg mstate) action = do
+  inst <- spawnInterpMaybe cfg spawnWasmInterp mstate
+  action inst
+
 -- | Spawn an interpreter if not already running according to the status in the
 -- MVar. Update the status, free pending heap references, and return the
 -- interpreter instance.
@@ -452,6 +460,9 @@ lookupSymbol interp str = withSymbolCache interp str $
         uninterruptibleMask_ $
           sendMessage inst (LookupSymbol (unpackFS str))
       ExtJS {} -> pprPanic "lookupSymbol not supported by the JS interpreter" (ppr str)
+      ExtWasm i -> withWasmInterp i $ \inst -> fmap fromRemotePtr <$> do
+        uninterruptibleMask_ $
+          sendMessage inst (LookupSymbol (unpackFS str))
 
 lookupSymbolInDLL :: Interp -> RemotePtr LoadedDLL -> FastString -> IO (Maybe (Ptr ()))
 lookupSymbolInDLL interp dll str = withSymbolCache interp str $
@@ -464,6 +475,8 @@ lookupSymbolInDLL interp dll str = withSymbolCache interp str $
         uninterruptibleMask_ $
           sendMessage inst (LookupSymbolInDLL dll (unpackFS str))
       ExtJS {} -> pprPanic "lookupSymbol not supported by the JS interpreter" (ppr str)
+      -- wasm dyld doesn't track which symbol comes from which .so
+      ExtWasm {} -> lookupSymbol interp str
 
 lookupClosure :: Interp -> String -> IO (Maybe HValueRef)
 lookupClosure interp str =
@@ -704,6 +717,7 @@ interpreterProfiled interp = case interpInstance interp of
   ExternalInterp ext -> case ext of
     ExtIServ i -> iservConfProfiled (interpConfig i)
     ExtJS {}   -> False -- we don't support profiling yet in the JS backend
+    ExtWasm i -> wasmInterpProfiled $ interpConfig i
 
 -- | Interpreter uses Dynamic way
 interpreterDynamic :: Interp -> Bool
@@ -714,3 +728,4 @@ interpreterDynamic interp = case interpInstance interp of
   ExternalInterp ext -> case ext of
     ExtIServ i -> iservConfDynamic (interpConfig i)
     ExtJS {}   -> False -- dynamic doesn't make sense for JS
+    ExtWasm {} -> True  -- wasm dyld can only load dynamic code
diff --git a/compiler/GHC/Runtime/Interpreter/Types.hs b/compiler/GHC/Runtime/Interpreter/Types.hs
index 53575f164d4..8c92d5ea16e 100644
--- a/compiler/GHC/Runtime/Interpreter/Types.hs
+++ b/compiler/GHC/Runtime/Interpreter/Types.hs
@@ -20,6 +20,8 @@ module GHC.Runtime.Interpreter.Types
    , JSState (..)
    , NodeJsSettings (..)
    , defaultNodeJsSettings
+   , WasmInterp
+   , WasmInterpConfig (..)
    )
 where
 
@@ -32,9 +34,11 @@ import GHC.Types.Unique.FM
 import GHC.Data.FastString ( FastString )
 import Foreign
 
+import GHC.Platform
 import GHC.Utils.TmpFs
 import GHC.Utils.Logger
 import GHC.Unit.Env
+import GHC.Unit.State
 import GHC.Unit.Types
 import GHC.StgToJS.Types
 import GHC.StgToJS.Linker.Types
@@ -65,6 +69,7 @@ data InterpInstance
 data ExtInterp
   = ExtIServ !IServ
   | ExtJS !JSInterp
+  | ExtWasm !WasmInterp
 
 -- | External interpreter
 --
@@ -80,6 +85,7 @@ type ExtInterpStatusVar d = MVar (InterpStatus (ExtInterpInstance d))
 
 type IServ    = ExtInterpState IServConfig    ()
 type JSInterp = ExtInterpState JSInterpConfig JSInterpExtra
+type WasmInterp = ExtInterpState WasmInterpConfig ()
 
 data InterpProcess = InterpProcess
   { interpPipe   :: !Pipe           -- ^ Pipe to communicate with the server
@@ -161,3 +167,16 @@ data JSInterpConfig = JSInterpConfig
   , jsInterpFinderCache :: !FinderCache
   }
 
+------------------------
+-- Wasm Stuff
+------------------------
+
+data WasmInterpConfig = WasmInterpConfig
+  { wasmInterpDyLD           :: !FilePath  -- ^ Location of dyld.mjs script
+  , wasmInterpLibDir         ::  FilePath  -- ^ wasi-sdk sysroot libdir containing libc.so, etc
+  , wasmInterpOpts           :: ![String]  -- ^ Additional command line arguments for iserv
+  , wasmInterpTargetPlatform :: !Platform
+  , wasmInterpProfiled       :: !Bool      -- ^ Are we profiling yet?
+  , wasmInterpHsSoSuffix     :: !String    -- ^ Shared lib filename common suffix sans .so, e.g. p-ghc9.13.20241001
+  , wasmInterpUnitState      :: !UnitState
+  }
diff --git a/compiler/GHC/Runtime/Interpreter/Wasm.hs b/compiler/GHC/Runtime/Interpreter/Wasm.hs
new file mode 100644
index 00000000000..12624da4fdf
--- /dev/null
+++ b/compiler/GHC/Runtime/Interpreter/Wasm.hs
@@ -0,0 +1,82 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
+
+module GHC.Runtime.Interpreter.Wasm (spawnWasmInterp) where
+
+import GHC.Prelude
+import GHC.Runtime.Interpreter.Types
+
+#if !defined(mingw32_HOST_OS)
+
+import Control.Concurrent.MVar
+import Data.IORef
+import GHC.Data.FastString
+import qualified GHC.Data.ShortText as ST
+import GHC.Platform
+import GHC.Unit
+import GHCi.Message
+import System.Directory
+import System.IO
+import qualified System.Posix.IO as Posix
+import System.Process
+
+#else
+
+import GHC.Utils.Panic
+
+#endif
+
+spawnWasmInterp :: WasmInterpConfig -> IO (ExtInterpInstance ())
+
+#if !defined(mingw32_HOST_OS)
+
+-- See Note [The Wasm Dynamic Linker] for details
+spawnWasmInterp WasmInterpConfig {..} = do
+  let Just ghci_unit_id =
+        lookupPackageName
+          wasmInterpUnitState
+          (PackageName $ fsLit "ghci")
+      ghci_unit_info = unsafeLookupUnitId wasmInterpUnitState ghci_unit_id
+      ghci_so_dirs = map ST.unpack $ unitLibraryDynDirs ghci_unit_info
+      [ghci_lib_name] = map ST.unpack $ unitLibraries ghci_unit_info
+      ghci_so_name = ghci_lib_name ++ wasmInterpHsSoSuffix
+      ghci_so_file = platformHsSOName wasmInterpTargetPlatform ghci_so_name
+  Just ghci_so_path <- findFile ghci_so_dirs ghci_so_file
+  (rfd1, wfd1) <- Posix.createPipe
+  (rfd2, wfd2) <- Posix.createPipe
+  Posix.setFdOption rfd1 Posix.CloseOnExec True
+  Posix.setFdOption wfd2 Posix.CloseOnExec True
+  (_, _, _, ph) <-
+    createProcess
+      ( proc wasmInterpDyLD $
+          [wasmInterpLibDir, ghci_so_path, show wfd1, show rfd2]
+            ++ wasmInterpOpts
+            ++ ["+RTS", "-H64m", "-RTS"]
+      )
+  Posix.closeFd wfd1
+  Posix.closeFd rfd2
+  rh <- Posix.fdToHandle rfd1
+  wh <- Posix.fdToHandle wfd2
+  hSetBuffering wh NoBuffering
+  hSetBuffering rh NoBuffering
+  lo_ref <- newIORef Nothing
+  pending_frees <- newMVar []
+  pure
+    $ ExtInterpInstance
+      { instProcess =
+          InterpProcess
+            { interpHandle = ph,
+              interpPipe = Pipe {pipeRead = rh, pipeWrite = wh, pipeLeftovers = lo_ref}
+            },
+        instPendingFrees = pending_frees,
+        instExtra = ()
+      }
+
+#else
+
+-- Due to difficulty of using inherited pipe file descriptor in
+-- nodejs, unfortunately we don't support Win32 host yet
+spawnWasmInterp _ = sorry "Wasm iserv doesn't work on Win32 host yet"
+
+#endif
diff --git a/compiler/ghc.cabal.in b/compiler/ghc.cabal.in
index 5d2dbb8084f..c9bc20e4957 100644
--- a/compiler/ghc.cabal.in
+++ b/compiler/ghc.cabal.in
@@ -676,6 +676,7 @@ Library
         GHC.Runtime.Interpreter.JS
         GHC.Runtime.Interpreter.Process
         GHC.Runtime.Interpreter.Types
+        GHC.Runtime.Interpreter.Wasm
         GHC.Runtime.Loader
         GHC.Runtime.Utils
         GHC.Settings
-- 
GitLab