Remote GHCi: comments only
authorSimon Marlow <marlowsd@gmail.com>
Thu, 23 Jun 2016 08:22:32 +0000 (09:22 +0100)
committerSimon Marlow <marlowsd@gmail.com>
Fri, 24 Jun 2016 10:29:33 +0000 (11:29 +0100)
Summary: Add more Notes and signposts across the codebase to help navigation.

Test Plan: validate

Reviewers: goldfire, simonpj, austin, ezyang, hvr, bgamari, erikd

Subscribers: thomie

Differential Revision: https://phabricator.haskell.org/D2358

compiler/ghci/GHCi.hs
compiler/typecheck/TcSplice.hs
iserv/src/Main.hs
libraries/ghci/GHCi/CreateBCO.hs
libraries/ghci/GHCi/Message.hs
libraries/ghci/GHCi/RemoteTypes.hs
libraries/ghci/GHCi/Run.hs
libraries/ghci/GHCi/TH.hs

index 7097e66..b4777a3 100644 (file)
@@ -137,6 +137,13 @@ Things that do not work with -fexternal-interpreter
 dynCompileExpr cannot work, because we have no way to run code of an
 unknown type in the remote process.  This API fails with an error
 message if it is used with -fexternal-interpreter.
+
+Other Notes on Remote GHCi
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+  * This wiki page has an implementation overview:
+    https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/ExternalInterpreter
+  * Note [External GHCi pointers] in compiler/ghci/GHCi.hs
+  * Note [Remote Template Haskell] in libraries/ghci/GHCi/TH.hs
 -}
 
 -- | Run a command in the interpreter's context.  With
index 69cacd5..44bc299 100644 (file)
@@ -947,12 +947,14 @@ runTH ty fhv = do
   dflags <- getDynFlags
   if not (gopt Opt_ExternalInterpreter dflags)
     then do
-       -- just run it in the local TcM
+       -- Run it in the local TcM
       hv <- liftIO $ wormhole dflags fhv
       r <- runQuasi (unsafeCoerce# hv :: TH.Q a)
       return r
     else
-      -- run it on the server
+      -- Run it on the server.  For an overview of how TH works with
+      -- Remote GHCi, see Note [Remote Template Haskell] in
+      -- libraries/ghci/GHCi/TH.hs.
       withIServ hsc_env $ \i -> do
         rstate <- getTHState i
         loc <- TH.qLocation
@@ -965,7 +967,8 @@ runTH ty fhv = do
         return $! runGet get (LB.fromStrict bs)
 
 
--- | communicate with a remotely-running TH computation until it finishes
+-- | communicate with a remotely-running TH computation until it finishes.
+-- See Note [Remote Template Haskell] in libraries/ghci/GHCi/TH.hs.
 runRemoteTH
   :: IServ
   -> [Messages]   --  saved from nested calls to qRecover
@@ -1030,6 +1033,13 @@ Back in GHC, when we receive:
     and merge in the new messages if caught_error is false.
 -}
 
+-- | Retrieve (or create, if it hasn't been created already), the
+-- remote TH state.  The TH state is a remote reference to an IORef
+-- QState living on the server, and we have to pass this to each RunTH
+-- call we make.
+--
+-- The TH state is stored in tcg_th_remote_state in the TcGblEnv.
+--
 getTHState :: IServ -> TcM (ForeignRef (IORef QState))
 getTHState i = do
   tcg <- getGblEnv
index 2e4555b..3fcd49f 100644 (file)
@@ -1,4 +1,11 @@
 {-# LANGUAGE RecordWildCards, GADTs, ScopedTypeVariables, RankNTypes #-}
+
+-- |
+-- The Remote GHCi server.
+--
+-- For details on Remote GHCi, see Note [Remote GHCi] in
+-- compiler/ghci/GHCi.hs.
+--
 module Main (main) where
 
 import GHCi.Run
@@ -55,6 +62,10 @@ serv verbose pipe@Pipe{..} restore = loop
     writePipe pipe (put r)
     loop
 
+  -- Run some TH code, which may interact with GHC by sending
+  -- THMessage requests, and then finally send RunTHDone followed by a
+  -- QResult.  For an overview of how TH works with Remote GHCi, see
+  -- Note [Remote Template Haskell] in libraries/ghci/GHCi/TH.hs.
   wrapRunTH :: forall a. (Binary a, Show a) => IO a -> IO ()
   wrapRunTH io = do
     r <- try io
index 9501b5f..f42c975 100644 (file)
@@ -10,6 +10,7 @@
 --  (c) The University of Glasgow 2002-2006
 --
 
+-- | Create real byte-code objects from 'ResolvedBCO's.
 module GHCi.CreateBCO (createBCOs) where
 
 import GHCi.ResolvedBCO
index b46030f..b14fca4 100644 (file)
@@ -2,6 +2,12 @@
     GeneralizedNewtypeDeriving, ExistentialQuantification, RecordWildCards #-}
 {-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}
 
+-- |
+-- Remote GHCi message types and serialization.
+--
+-- For details on Remote GHCi, see Note [Remote GHCi] in
+-- compiler/ghci/GHCi.hs.
+--
 module GHCi.Message
   ( Message(..), Msg(..)
   , THMessage(..), THMsg(..)
@@ -44,7 +50,8 @@ import System.IO.Error
 -- -----------------------------------------------------------------------------
 -- The RPC protocol between GHC and the interactive server
 
--- | A @Message a@ is a message that returns a value of type @a@
+-- | A @Message a@ is a message that returns a value of type @a@.
+-- These are requests sent from GHC to the server.
 data Message a where
   -- | Exit the iserv process
   Shutdown :: Message ()
@@ -159,6 +166,8 @@ data Message a where
    -> Message (Maybe HValueRef)
 
   -- Template Haskell -------------------------------------------
+  -- For more details on how TH works with Remote GHCi, see
+  -- Note [Remote Template Haskell] in libraries/ghci/GHCi/TH.hs.
 
   -- | Start a new TH module, return a state token that should be
   StartTH :: Message (RemoteRef (IORef QState))
@@ -198,7 +207,8 @@ instance Binary a => Binary (QResult a)
 
 
 -- | Messages sent back to GHC from GHCi.TH, to implement the methods
--- of 'Quasi'.
+-- of 'Quasi'.  For an overview of how TH works with Remote GHCi, see
+-- Note [Remote Template Haskell] in GHCi.TH.
 data THMessage a where
   NewName :: String -> THMessage (THResult TH.Name)
   Report :: Bool -> String -> THMessage (THResult ())
@@ -352,6 +362,9 @@ data THResultType = THExp | THPat | THType | THDec | THAnnWrapper
 
 instance Binary THResultType
 
+-- | The server-side Template Haskell state.  This is created by the
+-- StartTH message.  A new one is created per module that GHC
+-- typechecks.
 data QState = QState
   { qsMap        :: Map TypeRep Dynamic
        -- ^ persistent data between splices in a module
index ea91f19..5bc0136 100644 (file)
@@ -1,4 +1,12 @@
 {-# LANGUAGE CPP, StandaloneDeriving, GeneralizedNewtypeDeriving #-}
+
+-- |
+-- Types for referring to remote objects in Remote GHCi.  For more
+-- details, see Note [External GHCi pointers] in compiler/ghci/GHCi.hs
+--
+-- For details on Remote GHCi, see Note [Remote GHCi] in
+-- compiler/ghci/GHCi.hs.
+--
 module GHCi.RemoteTypes
   ( RemotePtr(..), toRemotePtr, fromRemotePtr, castRemotePtr
   , HValue(..)
index a2ea4e2..542fe55 100644 (file)
@@ -3,7 +3,10 @@
 {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
 
 -- |
--- Execute GHCi messages
+-- Execute GHCi messages.
+--
+-- For details on Remote GHCi, see Note [Remote GHCi] in
+-- compiler/ghci/GHCi.hs.
 --
 module GHCi.Run
   ( run, redirectInterrupts
index 6d6158f..3495162 100644 (file)
@@ -7,6 +7,85 @@
 --
 module GHCi.TH (startTH, finishTH, runTH, GHCiQException(..)) where
 
+{- Note [Remote Template Haskell]
+
+Here is an overview of how TH works with -fexternal-interpreter.
+
+Initialisation
+~~~~~~~~~~~~~~
+
+GHC sends a StartTH message to the server (see TcSplice.getTHState):
+
+   StartTH :: Message (RemoteRef (IORef QState))
+
+The server creates an initial QState object, makes an IORef to it, and
+returns a RemoteRef to this to GHC. (see GHCi.TH.startTH below).
+
+This happens once per module, the first time we need to run a TH
+splice.  The reference that GHC gets back is kept in
+tcg_th_remote_state in the TcGblEnv, and passed to each RunTH call
+that follows.
+
+
+For each splice
+~~~~~~~~~~~~~~~
+
+1. GHC compiles a splice to byte code, and sends it to the server: in
+   a CreateBCOs message:
+
+   CreateBCOs :: [LB.ByteString] -> Message [HValueRef]
+
+2. The server creates the real byte-code objects in its heap, and
+   returns HValueRefs to GHC.  HValueRef is the same as RemoteRef
+   HValue.
+
+3. GHC sends a RunTH message to the server:
+
+  RunTH
+   :: RemoteRef (IORef QState)
+        -- The state returned by StartTH in step1
+   -> HValueRef
+        -- The HValueRef we got in step 4, points to the code for the splice
+   -> THResultType
+        -- Tells us what kind of splice this is (decl, expr, type, etc.)
+   -> Maybe TH.Loc
+        -- Source location
+   -> Message (QResult ByteString)
+        -- Eventually it will return a QResult back to GHC.  The
+        -- ByteString here is the (encoded) result of the splice.
+
+4. The server runs the splice code.
+
+5. Each time the splice code calls a method of the Quasi class, such
+   as qReify, a message is sent from the server to GHC.  These
+   messages are defined by the THMessage type.  GHC responds with the
+   result of the request, e.g. in the case of qReify it would be the
+   TH.Info for the requested entity.
+
+6. When the splice has been fully evaluated, the server sends
+   RunTHDone back to GHC.  This tells GHC that the server has finished
+   sending THMessages and will send the QResult next.
+
+8. The server then sends a QResult back to GHC, which is notionally
+   the response to the original RunTH message.  The QResult indicates
+   whether the splice succeeded, failed, or threw an exception.
+
+
+After typechecking
+~~~~~~~~~~~~~~~~~~
+
+GHC sends a FinishTH message to the server (see TcSplice.finishTH).
+The server runs any finalizers that were added by addModuleFinalizer.
+
+
+Other Notes on TH / Remote GHCi
+
+  * Note [Remote GHCi] in compiler/ghci/GHCi.hs
+  * Note [External GHCi pointers] in compiler/ghci/GHCi.hs
+  * Note [TH recover with -fexternal-interpreter] in
+    compiler/typecheck/TcSplice.hs
+-}
+
 import GHCi.Message
 import GHCi.RemoteTypes
 import GHC.Serialized
@@ -29,6 +108,7 @@ import qualified Language.Haskell.TH        as TH
 import qualified Language.Haskell.TH.Syntax as TH
 import Unsafe.Coerce
 
+-- | Create a new instance of 'QState'
 initQState :: Pipe -> QState
 initQState p = QState M.empty [] Nothing p
 
@@ -39,8 +119,10 @@ runModFinalizers = go =<< getState
       putState (s { qsFinalizers = ff}) >> TH.runQ f >> getState >>= go
     go _ = return ()
 
+-- | The monad in which we run TH computations on the server
 newtype GHCiQ a = GHCiQ { runGHCiQ :: QState -> IO (a, QState) }
 
+-- | The exception thrown by "fail" in the GHCiQ monad
 data GHCiQException = GHCiQException QState String
   deriving Show
 
@@ -75,6 +157,7 @@ putState s = GHCiQ $ \_ -> return ((),s)
 noLoc :: TH.Loc
 noLoc = TH.Loc "<no file>" "<no package>" "<no module>" (0,0) (0,0)
 
+-- | Send a 'THMessage' to GHC and return the result.
 ghcCmd :: Binary a => THMessage (THResult a) -> GHCiQ a
 ghcCmd m = GHCiQ $ \s -> do
   r <- remoteTHCall (qsPipe s) m
@@ -126,11 +209,14 @@ instance TH.Quasi GHCiQ where
   qIsExtEnabled x = ghcCmd (IsExtEnabled x)
   qExtsEnabled = ghcCmd ExtsEnabled
 
+-- | The implementation of the 'StartTH' message: create
+-- a new IORef QState, and return a RemoteRef to it.
 startTH :: IO (RemoteRef (IORef QState))
 startTH = do
   r <- newIORef (initQState (error "startTH: no pipe"))
   mkRemoteRef r
 
+-- | The implementation of the 'FinishTH' message.
 finishTH :: Pipe -> RemoteRef (IORef QState) -> IO ()
 finishTH pipe rstate = do
   qstateref <- localRef rstate
@@ -138,11 +224,20 @@ finishTH pipe rstate = do
   _ <- runGHCiQ runModFinalizers qstate { qsPipe = pipe }
   return ()
 
+-- | The implementation of the 'RunTH' message
 runTH
-  :: Pipe -> RemoteRef (IORef QState) -> HValueRef
+  :: Pipe
+  -> RemoteRef (IORef QState)
+      -- ^ The TH state, created by 'startTH'
+  -> HValueRef
+      -- ^ The splice to run
   -> THResultType
+      -- ^ What kind of splice it is
   -> Maybe TH.Loc
+      -- ^ The source location
   -> IO ByteString
+      -- ^ Returns an (encoded) result that depends on the THResultType
+
 runTH pipe rstate rhv ty mb_loc = do
   hv <- localRef rhv
   case ty of
@@ -156,6 +251,7 @@ runTH pipe rstate rhv ty mb_loc = do
         AnnotationWrapper thing -> return $!
           LB.toStrict (runPut (put (toSerialized serializeWithData thing)))
 
+-- | Run a Q computation.
 runTHQ
   :: Binary a => Pipe -> RemoteRef (IORef QState) -> Maybe TH.Loc -> TH.Q a
   -> IO ByteString