summaryrefslogtreecommitdiff
path: root/ghc/GhciMonad.hs
diff options
context:
space:
mode:
authorSimon Marlow <marlowsd@gmail.com>2015-11-18 16:42:24 +0000
committerSimon Marlow <marlowsd@gmail.com>2015-12-17 09:39:52 +0000
commit4905b83a2d448c65ccced385343d4e8124548a3b (patch)
tree070cf9e48f6fce668cd01d888b8da8b3772d1f53 /ghc/GhciMonad.hs
parent7221ad70daa363d77f60d96c3f6e1baa1d9bec81 (diff)
downloadhaskell-4905b83a2d448c65ccced385343d4e8124548a3b.tar.gz
Remote GHCi, -fexternal-interpreter
Summary: (Apologies for the size of this patch, I couldn't make a smaller one that was validate-clean and also made sense independently) (Some of this code is derived from GHCJS.) This commit adds support for running interpreted code (for GHCi and TemplateHaskell) in a separate process. The functionality is experimental, so for now it is off by default and enabled by the flag -fexternal-interpreter. Reaosns we want this: * compiling Template Haskell code with -prof does not require building the code without -prof first * when GHC itself is profiled, it can interpret unprofiled code, and the same applies to dynamic linking. We would no longer need to force -dynamic-too with TemplateHaskell, and we can load ordinary objects into a dynamically-linked GHCi (and vice versa). * An unprofiled GHCi can load and run profiled code, which means it can use the stack-trace functionality provided by profiling without taking the performance hit on the compiler that profiling would entail. Amongst other things; see https://ghc.haskell.org/trac/ghc/wiki/RemoteGHCi for more details. Notes on the implementation are in Note [Remote GHCi] in the new module compiler/ghci/GHCi.hs. It probably needs more documenting, feel free to suggest things I could elaborate on. Things that are not currently implemented for -fexternal-interpreter: * The GHCi debugger * :set prog, :set args in GHCi * `recover` in Template Haskell * Redirecting stdin/stdout for the external process These are all doable, I just wanted to get to a working validate-clean patch first. I also haven't done any benchmarking yet. I expect there to be slight hit to link times for byte code and some penalty due to having to serialize/deserialize TH syntax, but I don't expect it to be a serious problem. There's also lots of low-hanging fruit in the byte code generator/linker that we could exploit to speed things up. Test Plan: * validate * I've run parts of the test suite with EXTRA_HC_OPTS=-fexternal-interpreter, notably tests/ghci and tests/th. There are a few failures due to the things not currently implemented (see above). Reviewers: simonpj, goldfire, ezyang, austin, alanz, hvr, niteria, bgamari, gibiansky, luite Subscribers: thomie Differential Revision: https://phabricator.haskell.org/D1562
Diffstat (limited to 'ghc/GhciMonad.hs')
-rw-r--r--ghc/GhciMonad.hs127
1 files changed, 57 insertions, 70 deletions
diff --git a/ghc/GhciMonad.hs b/ghc/GhciMonad.hs
index c1abe4f923..d8fa0e1146 100644
--- a/ghc/GhciMonad.hs
+++ b/ghc/GhciMonad.hs
@@ -22,7 +22,10 @@ module GhciMonad (
runStmt, runDecls, resume, timeIt, recordBreak, revertCAFs,
printForUser, printForUserPartWay, prettyLocations,
- initInterpBuffering, turnOffBuffering, flushInterpBuffers,
+ initInterpBuffering,
+ turnOffBuffering, turnOffBuffering_,
+ flushInterpBuffers,
+ mkEvalWrapper
) where
#include "HsVersions.h"
@@ -31,14 +34,13 @@ import qualified GHC
import GhcMonad hiding (liftIO)
import Outputable hiding (printForUser, printForUserPartWay)
import qualified Outputable
-import Util
import DynFlags
import FastString
import HscTypes
import SrcLoc
import Module
-import ObjLink
-import Linker
+import GHCi
+import GHCi.RemoteTypes
import Exception
import Numeric
@@ -48,7 +50,6 @@ import System.CPUTime
import System.Environment
import System.IO
import Control.Monad
-import GHC.Exts
import System.Console.Haskeline (CompletionFunc, InputT)
import qualified System.Console.Haskeline as Haskeline
@@ -62,6 +63,7 @@ data GHCiState = GHCiState
{
progname :: String,
args :: [String],
+ evalWrapper :: ForeignHValue, -- IO a -> IO a
prompt :: String,
prompt2 :: String,
editor :: String,
@@ -103,7 +105,12 @@ data GHCiState = GHCiState
-- help text to display to a user
short_help :: String,
long_help :: String,
- lastErrorLocations :: IORef [(FastString, Int)]
+ lastErrorLocations :: IORef [(FastString, Int)],
+
+ -- hFlush stdout; hFlush stderr in the interpreter
+ flushStdHandles :: ForeignHValue,
+ -- hSetBuffering NoBuffering for stdin/stdout/stderr
+ noBuffering :: ForeignHValue
}
type TickArray = Array Int [(BreakIndex,SrcSpan)]
@@ -282,18 +289,14 @@ printForUserPartWay doc = do
runStmt :: String -> GHC.SingleStep -> GHCi (Maybe GHC.ExecResult)
runStmt expr step = do
st <- getGHCiState
- reifyGHCi $ \x ->
- withProgName (progname st) $
- withArgs (args st) $
- reflectGHCi x $ do
- GHC.handleSourceError (\e -> do GHC.printException e;
- return Nothing) $ do
- let opts = GHC.execOptions
- { GHC.execSourceFile = progname st
- , GHC.execLineNumber = line_number st
- , GHC.execSingleStep = step }
- r <- GHC.execStmt expr opts
- return (Just r)
+ GHC.handleSourceError (\e -> do GHC.printException e; return Nothing) $ do
+ let opts = GHC.execOptions
+ { GHC.execSourceFile = progname st
+ , GHC.execLineNumber = line_number st
+ , GHC.execSingleStep = step
+ , GHC.execWrap = \fhv -> EvalApp (EvalThis (evalWrapper st))
+ (EvalThis fhv) }
+ Just <$> GHC.execStmt expr opts
runDecls :: String -> GHCi (Maybe [GHC.Name])
runDecls decls = do
@@ -355,9 +358,9 @@ revertCAFs :: GHCi ()
revertCAFs = do
liftIO rts_revertCAFs
s <- getGHCiState
- when (not (ghc_e s)) $ liftIO turnOffBuffering
- -- Have to turn off buffering again, because we just
- -- reverted stdout, stderr & stdin to their defaults.
+ when (not (ghc_e s)) turnOffBuffering
+ -- Have to turn off buffering again, because we just
+ -- reverted stdout, stderr & stdin to their defaults.
foreign import ccall "revertCAFs" rts_revertCAFs :: IO ()
-- Make it "safe", just in case
@@ -366,54 +369,38 @@ foreign import ccall "revertCAFs" rts_revertCAFs :: IO ()
-- To flush buffers for the *interpreted* computation we need
-- to refer to *its* stdout/stderr handles
-GLOBAL_VAR(stdin_ptr, error "no stdin_ptr", Ptr ())
-GLOBAL_VAR(stdout_ptr, error "no stdout_ptr", Ptr ())
-GLOBAL_VAR(stderr_ptr, error "no stderr_ptr", Ptr ())
-
--- After various attempts, I believe this is the least bad way to do
--- what we want. We know look up the address of the static stdin,
--- stdout, and stderr closures in the loaded base package, and each
--- time we need to refer to them we cast the pointer to a Handle.
--- This avoids any problems with the CAF having been reverted, because
--- we'll always get the current value.
---
--- The previous attempt that didn't work was to compile an expression
--- like "hSetBuffering stdout NoBuffering" into an expression of type
--- IO () and run this expression each time we needed it, but the
--- problem is that evaluating the expression might cache the contents
--- of the Handle rather than referring to it from its static address
--- each time. There's no safe workaround for this.
-
-initInterpBuffering :: Ghc ()
-initInterpBuffering = do -- make sure these are linked
- dflags <- GHC.getSessionDynFlags
- liftIO $ do
- initDynLinker dflags
-
- -- ToDo: we should really look up these names properly, but
- -- it's a fiddle and not all the bits are exposed via the GHC
- -- interface.
- mb_stdin_ptr <- ObjLink.lookupSymbol "base_GHCziIOziHandleziFD_stdin_closure"
- mb_stdout_ptr <- ObjLink.lookupSymbol "base_GHCziIOziHandleziFD_stdout_closure"
- mb_stderr_ptr <- ObjLink.lookupSymbol "base_GHCziIOziHandleziFD_stderr_closure"
-
- let f ref (Just ptr) = writeIORef ref ptr
- f _ Nothing = panic "interactiveUI:setBuffering2"
- zipWithM_ f [stdin_ptr,stdout_ptr,stderr_ptr]
- [mb_stdin_ptr,mb_stdout_ptr,mb_stderr_ptr]
-
+-- | Compile "hFlush stdout; hFlush stderr" once, so we can use it repeatedly
+initInterpBuffering :: Ghc (ForeignHValue, ForeignHValue)
+initInterpBuffering = do
+ nobuf <- GHC.compileExprRemote $
+ "do { System.IO.hSetBuffering System.IO.stdin System.IO.NoBuffering; " ++
+ " System.IO.hSetBuffering System.IO.stdout System.IO.NoBuffering; " ++
+ " System.IO.hSetBuffering System.IO.stderr System.IO.NoBuffering }"
+ flush <- GHC.compileExprRemote $
+ "do { System.IO.hFlush System.IO.stdout; " ++
+ " System.IO.hFlush System.IO.stderr }"
+ return (nobuf, flush)
+
+-- | Invoke "hFlush stdout; hFlush stderr" in the interpreter
flushInterpBuffers :: GHCi ()
-flushInterpBuffers
- = liftIO $ do getHandle stdout_ptr >>= hFlush
- getHandle stderr_ptr >>= hFlush
-
-turnOffBuffering :: IO ()
-turnOffBuffering
- = do hdls <- mapM getHandle [stdin_ptr,stdout_ptr,stderr_ptr]
- mapM_ (\h -> hSetBuffering h NoBuffering) hdls
-
-getHandle :: IORef (Ptr ()) -> IO Handle
-getHandle ref = do
- (Ptr addr) <- readIORef ref
- case addrToAny# addr of (# hval #) -> return (unsafeCoerce# hval)
+flushInterpBuffers = do
+ st <- getGHCiState
+ hsc_env <- GHC.getSession
+ liftIO $ evalIO hsc_env (flushStdHandles st)
+-- | Turn off buffering for stdin, stdout, and stderr in the interpreter
+turnOffBuffering :: GHCi ()
+turnOffBuffering = do
+ st <- getGHCiState
+ turnOffBuffering_ (noBuffering st)
+
+turnOffBuffering_ :: GhcMonad m => ForeignHValue -> m ()
+turnOffBuffering_ fhv = do
+ hsc_env <- getSession
+ liftIO $ evalIO hsc_env fhv
+
+mkEvalWrapper :: GhcMonad m => String -> [String] -> m ForeignHValue
+mkEvalWrapper progname args =
+ GHC.compileExprRemote $
+ "\\m -> System.Environment.withProgName " ++ show progname ++
+ "(System.Environment.withArgs " ++ show args ++ " m)"