From 5c17afcac5e1609f442496712d533981abcbb2ae Mon Sep 17 00:00:00 2001
From: Cheng Shao <terrorjack@type.dance>
Date: Mon, 10 Mar 2025 19:58:23 +0000
Subject: [PATCH] ghci: make the Pipe type opaque

This commit makes the Pipe type in ghci opaque, and introduce the
mkPipeFromHandles constructor for creating a Pipe from a pair of
Handles. Pipe is now just a pair of receiver/sender continuations
under the hood. This allows a Pipe to be potentially backed by other
IPC mechanisms (e.g. WebSockets) which is essential for wasm ghci
browser mode.

(cherry picked from commit 7d18c19b4c6a55d1fad3ca65c5a0754cf865bc26)
(cherry picked from commit 4148bc13cc02986428ad716635ed8600471b4d50)
---
 compiler/GHC/Runtime/Interpreter.hs      |  6 ++--
 compiler/GHC/Runtime/Interpreter/JS.hs   |  5 ++-
 compiler/GHC/Runtime/Interpreter/Wasm.hs |  5 ++-
 libraries/ghci/GHCi/Message.hs           | 44 ++++++++++++++----------
 libraries/ghci/GHCi/Server.hs            |  4 +--
 5 files changed, 32 insertions(+), 32 deletions(-)

diff --git a/compiler/GHC/Runtime/Interpreter.hs b/compiler/GHC/Runtime/Interpreter.hs
index d0b4175ce87..b34c0f5e6e2 100644
--- a/compiler/GHC/Runtime/Interpreter.hs
+++ b/compiler/GHC/Runtime/Interpreter.hs
@@ -108,7 +108,6 @@ import Control.Monad.Catch as MC (mask)
 import Data.Binary
 import Data.ByteString (ByteString)
 import Data.Array ((!))
-import Data.IORef
 import Foreign hiding (void)
 import qualified GHC.Exts.Heap as Heap
 import GHC.Stack.CCS (CostCentre,CostCentreStack)
@@ -577,12 +576,11 @@ spawnIServ conf = do
   (ph, rh, wh) <- runWithPipes createProc (iservConfProgram conf)
                                           []
                                           (iservConfOpts    conf)
-  lo_ref <- newIORef Nothing
+  interpPipe <- mkPipeFromHandles rh wh
   lock <- newMVar ()
-  let pipe = Pipe { pipeRead = rh, pipeWrite = wh, pipeLeftovers = lo_ref }
   let process = InterpProcess
                   { interpHandle = ph
-                  , interpPipe   = pipe
+                  , interpPipe
                   , interpLock   = lock
                   }
 
diff --git a/compiler/GHC/Runtime/Interpreter/JS.hs b/compiler/GHC/Runtime/Interpreter/JS.hs
index 4d6f3460ba1..843d98751e2 100644
--- a/compiler/GHC/Runtime/Interpreter/JS.hs
+++ b/compiler/GHC/Runtime/Interpreter/JS.hs
@@ -129,12 +129,11 @@ startTHRunnerProcess interp_js settings = do
                                            (nodeExtraArgs settings)
   std_in <- readIORef interp_in
 
-  lo_ref <- newIORef Nothing
+  interpPipe <- mkPipeFromHandles rh wh
   lock <- newMVar ()
-  let pipe = Pipe { pipeRead = rh, pipeWrite = wh, pipeLeftovers = lo_ref }
   let proc = InterpProcess
               { interpHandle = hdl
-              , interpPipe   = pipe
+              , interpPipe
               , interpLock   = lock
               }
   pure (std_in, proc)
diff --git a/compiler/GHC/Runtime/Interpreter/Wasm.hs b/compiler/GHC/Runtime/Interpreter/Wasm.hs
index e3a6231cf70..c89423e5cf2 100644
--- a/compiler/GHC/Runtime/Interpreter/Wasm.hs
+++ b/compiler/GHC/Runtime/Interpreter/Wasm.hs
@@ -10,7 +10,6 @@ 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
@@ -60,7 +59,7 @@ spawnWasmInterp WasmInterpConfig {..} = do
   wh <- Posix.fdToHandle wfd2
   hSetBuffering wh NoBuffering
   hSetBuffering rh NoBuffering
-  lo_ref <- newIORef Nothing
+  interpPipe <- mkPipeFromHandles rh wh
   lock <- newMVar ()
   pending_frees <- newMVar []
   pure
@@ -68,7 +67,7 @@ spawnWasmInterp WasmInterpConfig {..} = do
       { instProcess =
           InterpProcess
             { interpHandle = ph,
-              interpPipe = Pipe {pipeRead = rh, pipeWrite = wh, pipeLeftovers = lo_ref},
+              interpPipe,
               interpLock = lock
             },
         instPendingFrees = pending_frees,
diff --git a/libraries/ghci/GHCi/Message.hs b/libraries/ghci/GHCi/Message.hs
index 63b9894afe7..14512eb62d4 100644
--- a/libraries/ghci/GHCi/Message.hs
+++ b/libraries/ghci/GHCi/Message.hs
@@ -1,6 +1,6 @@
 {-# LANGUAGE GADTs, DeriveGeneric, StandaloneDeriving, ScopedTypeVariables,
     GeneralizedNewtypeDeriving, ExistentialQuantification, RecordWildCards,
-    CPP #-}
+    CPP, NamedFieldPuns #-}
 {-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}
 
 -- |
@@ -21,7 +21,7 @@ module GHCi.Message
   , ResumeContext(..)
   , QState(..)
   , getMessage, putMessage, getTHMessage, putTHMessage
-  , Pipe(..), remoteCall, remoteTHCall, readPipe, writePipe
+  , Pipe, mkPipeFromHandles, remoteCall, remoteTHCall, readPipe, writePipe
   , BreakModule
   , LoadedDLL
   ) where
@@ -45,6 +45,7 @@ import Data.Binary.Get
 import Data.Binary.Put
 import Data.ByteString (ByteString)
 import qualified Data.ByteString as B
+import qualified Data.ByteString.Builder as B
 import qualified Data.ByteString.Lazy as LB
 import Data.Dynamic
 import Data.Typeable (TypeRep)
@@ -634,47 +635,52 @@ serializeBCOs rbcos = parMap doChunk (chunkList 100 rbcos)
 -- -----------------------------------------------------------------------------
 -- Reading/writing messages
 
+-- | An opaque pipe for bidirectional binary data transmission.
 data Pipe = Pipe
-  { pipeRead :: Handle
-  , pipeWrite ::  Handle
-  , pipeLeftovers :: IORef (Maybe ByteString)
+  { getSome :: !(IO ByteString)
+  , putAll :: !(B.Builder -> IO ())
+  , pipeLeftovers :: !(IORef (Maybe ByteString))
   }
 
+-- | Make a 'Pipe' from a 'Handle' to read and a 'Handle' to write.
+mkPipeFromHandles :: Handle -> Handle -> IO Pipe
+mkPipeFromHandles pipeRead pipeWrite = do
+  let getSome = B.hGetSome pipeRead (32*1024)
+      putAll b = do
+        B.hPutBuilder pipeWrite b
+        hFlush pipeWrite
+  pipeLeftovers <- newIORef Nothing
+  pure $ Pipe { getSome, putAll, pipeLeftovers }
+
 remoteCall :: Binary a => Pipe -> Message a -> IO a
 remoteCall pipe msg = do
   writePipe pipe (putMessage msg)
   readPipe pipe get
 
+writePipe :: Pipe -> Put -> IO ()
+writePipe Pipe{..} put = putAll $ execPut put
+
 remoteTHCall :: Binary a => Pipe -> THMessage a -> IO a
 remoteTHCall pipe msg = do
   writePipe pipe (putTHMessage msg)
   readPipe pipe get
 
-writePipe :: Pipe -> Put -> IO ()
-writePipe Pipe{..} put
-  | LB.null bs = return ()
-  | otherwise  = do
-    LB.hPut pipeWrite bs
-    hFlush pipeWrite
- where
-  bs = runPut put
-
 readPipe :: Pipe -> Get a -> IO a
 readPipe Pipe{..} get = do
   leftovers <- readIORef pipeLeftovers
-  m <- getBin pipeRead get leftovers
+  m <- getBin getSome get leftovers
   case m of
     Nothing -> throw $
-      mkIOError eofErrorType "GHCi.Message.remoteCall" (Just pipeRead) Nothing
+      mkIOError eofErrorType "GHCi.Message.readPipe" Nothing Nothing
     Just (result, new_leftovers) -> do
       writeIORef pipeLeftovers new_leftovers
       return result
 
 getBin
-  :: Handle -> Get a -> Maybe ByteString
+  :: (IO ByteString) -> Get a -> Maybe ByteString
   -> IO (Maybe (a, Maybe ByteString))
 
-getBin h get leftover = go leftover (runGetIncremental get)
+getBin getsome get leftover = go leftover (runGetIncremental get)
  where
    go Nothing (Done leftover _ msg) =
      return (Just (msg, if B.null leftover then Nothing else Just leftover))
@@ -683,7 +689,7 @@ getBin h get leftover = go leftover (runGetIncremental get)
      go Nothing (fun (Just leftover))
    go Nothing (Partial fun) = do
      -- putStrLn "before hGetSome"
-     b <- B.hGetSome h (32*1024)
+     b <- getsome
      -- putStrLn $ "hGetSome: " ++ show (B.length b)
      if B.null b
         then return Nothing
diff --git a/libraries/ghci/GHCi/Server.hs b/libraries/ghci/GHCi/Server.hs
index 69a5ea7b597..c5c661da90a 100644
--- a/libraries/ghci/GHCi/Server.hs
+++ b/libraries/ghci/GHCi/Server.hs
@@ -19,7 +19,6 @@ import Control.Exception
 import Control.Monad
 import Control.Concurrent (threadDelay)
 import Data.Binary
-import Data.IORef
 
 import Text.Printf
 import System.Environment (getProgName, getArgs)
@@ -127,8 +126,7 @@ defaultServer = do
   installSignalHandlers
 #endif
 
-  lo_ref <- newIORef Nothing
-  let pipe = Pipe{pipeRead = inh, pipeWrite = outh, pipeLeftovers = lo_ref}
+  pipe <- mkPipeFromHandles inh outh
 
   when wait $ do
     when verbose $
-- 
GitLab