diff --git a/compiler/GHC.hs b/compiler/GHC.hs index 776fb7e7632181c456a22d778e7d4fcd7c3d1bcc..53ac414835ee0fdc696ef83f8438b3d99549b5cf 100644 --- a/compiler/GHC.hs +++ b/compiler/GHC.hs @@ -722,6 +722,13 @@ setTopSessionDynFlags dflags = do { wasmInterpDyLD = dyld, wasmInterpLibDir = libdir, wasmInterpOpts = getOpts dflags opt_i, + wasmInterpBrowser = gopt Opt_GhciBrowser dflags, + wasmInterpBrowserHost = ghciBrowserHost dflags, + wasmInterpBrowserPort = ghciBrowserPort dflags, + wasmInterpBrowserRedirectWasiConsole = gopt Opt_GhciBrowserRedirectWasiConsole dflags, + wasmInterpBrowserPuppeteerLaunchOpts = ghciBrowserPuppeteerLaunchOpts dflags, + wasmInterpBrowserPlaywrightBrowserType = ghciBrowserPlaywrightBrowserType dflags, + wasmInterpBrowserPlaywrightLaunchOpts = ghciBrowserPlaywrightLaunchOpts dflags, wasmInterpTargetPlatform = targetPlatform dflags, wasmInterpProfiled = profiled, wasmInterpHsSoSuffix = way_tag ++ dynLibSuffix (ghcNameVersion dflags), diff --git a/compiler/GHC/Driver/DynFlags.hs b/compiler/GHC/Driver/DynFlags.hs index a24ea13e5caaeeb4bed68452efca6ebd39b25fc1..226bc94c8301118cd99ba570c988e84925fddd72 100644 --- a/compiler/GHC/Driver/DynFlags.hs +++ b/compiler/GHC/Driver/DynFlags.hs @@ -399,6 +399,13 @@ data DynFlags = DynFlags { ghciHistSize :: Int, + -- wasm ghci browser mode + ghciBrowserHost :: !String, + ghciBrowserPort :: !Int, + ghciBrowserPuppeteerLaunchOpts :: !(Maybe String), + ghciBrowserPlaywrightBrowserType :: !(Maybe String), + ghciBrowserPlaywrightLaunchOpts :: !(Maybe String), + flushOut :: FlushOut, ghcVersionFile :: Maybe FilePath, @@ -683,6 +690,12 @@ defaultDynFlags mySettings = ghciHistSize = 50, -- keep a log of length 50 by default + ghciBrowserHost = "127.0.0.1", + ghciBrowserPort = 0, + ghciBrowserPuppeteerLaunchOpts = Nothing, + ghciBrowserPlaywrightBrowserType = Nothing, + ghciBrowserPlaywrightLaunchOpts = Nothing, + flushOut = defaultFlushOut, pprUserLength = 5, pprCols = 100, diff --git a/compiler/GHC/Driver/Flags.hs b/compiler/GHC/Driver/Flags.hs index bce427d95697e353eecf8f5c9ea0da0057de9d9a..41c4b31a4d317669c6f012da9476252351267c4d 100644 --- a/compiler/GHC/Driver/Flags.hs +++ b/compiler/GHC/Driver/Flags.hs @@ -729,6 +729,11 @@ data GeneralFlag | Opt_ValidateHie | Opt_LocalGhciHistory | Opt_NoIt + + -- wasm ghci browser mode + | Opt_GhciBrowser + | Opt_GhciBrowserRedirectWasiConsole + | Opt_HelpfulErrors | Opt_DeferTypeErrors -- Since 7.6 | Opt_DeferTypedHoles -- Since 7.10 diff --git a/compiler/GHC/Driver/Session.hs b/compiler/GHC/Driver/Session.hs index 74490ba91f9bb57416433305ebb95c2c0c57f347..44fcd8881fd1d2e0d6347b05821d8bf3bcafdbdf 100644 --- a/compiler/GHC/Driver/Session.hs +++ b/compiler/GHC/Driver/Session.hs @@ -1825,6 +1825,19 @@ dynamic_flags_deps = [ (intSuffix (\n d -> d {maxForcedSpecArgs = n})) , make_ord_flag defGhciFlag "fghci-hist-size" (intSuffix (\n d -> d {ghciHistSize = n})) + + -- wasm ghci browser mode + , make_ord_flag defGhciFlag "fghci-browser-host" + $ hasArg $ \f d -> d { ghciBrowserHost = f } + , make_ord_flag defGhciFlag "fghci-browser-port" + $ intSuffix $ \n d -> d { ghciBrowserPort = n } + , make_ord_flag defGhciFlag "fghci-browser-puppeteer-launch-opts" + $ hasArg $ \f d -> d { ghciBrowserPuppeteerLaunchOpts = Just f } + , make_ord_flag defGhciFlag "fghci-browser-playwright-browser-type" + $ hasArg $ \f d -> d { ghciBrowserPlaywrightBrowserType = Just f } + , make_ord_flag defGhciFlag "fghci-browser-playwright-launch-opts" + $ hasArg $ \f d -> d { ghciBrowserPlaywrightLaunchOpts = Just f } + , make_ord_flag defGhcFlag "fmax-inline-alloc-size" (intSuffix (\n d -> d { maxInlineAllocSize = n })) , make_ord_flag defGhcFlag "fmax-inline-memcpy-insns" @@ -2457,6 +2470,11 @@ fFlagsDeps = [ flagGhciSpec "local-ghci-history" Opt_LocalGhciHistory, flagGhciSpec "no-it" Opt_NoIt, flagSpec "ghci-sandbox" Opt_GhciSandbox, + + -- wasm ghci browser mode + flagGhciSpec "ghci-browser" Opt_GhciBrowser, + flagGhciSpec "ghci-browser-redirect-wasi-console" Opt_GhciBrowserRedirectWasiConsole, + flagSpec "helpful-errors" Opt_HelpfulErrors, flagSpec "hpc" Opt_Hpc, flagSpec "ignore-asserts" Opt_IgnoreAsserts, diff --git a/compiler/GHC/Runtime/Interpreter/Types.hs b/compiler/GHC/Runtime/Interpreter/Types.hs index ca51d612c8e8405b587d25648d92398e9a03b45c..73aed5ca5db364cc9edd8909be5492680b08b824 100644 --- a/compiler/GHC/Runtime/Interpreter/Types.hs +++ b/compiler/GHC/Runtime/Interpreter/Types.hs @@ -176,6 +176,16 @@ 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 + + -- wasm ghci browser mode + , wasmInterpBrowser :: !Bool + , wasmInterpBrowserHost :: !String + , wasmInterpBrowserPort :: !Int + , wasmInterpBrowserRedirectWasiConsole :: !Bool + , wasmInterpBrowserPuppeteerLaunchOpts :: !(Maybe String) + , wasmInterpBrowserPlaywrightBrowserType :: !(Maybe String) + , wasmInterpBrowserPlaywrightLaunchOpts :: !(Maybe String) + , wasmInterpTargetPlatform :: !Platform , wasmInterpProfiled :: !Bool -- ^ Are we profiling yet? , wasmInterpHsSoSuffix :: !String -- ^ Shared lib filename common suffix sans .so, e.g. p-ghc9.13.20241001 diff --git a/compiler/GHC/Runtime/Interpreter/Wasm.hs b/compiler/GHC/Runtime/Interpreter/Wasm.hs index a6dd9513bc7e8f0fa1da9b9cc7469508421fe376..645aba927181a296891b4eb76fdca8118e4f4f1b 100644 --- a/compiler/GHC/Runtime/Interpreter/Wasm.hs +++ b/compiler/GHC/Runtime/Interpreter/Wasm.hs @@ -10,12 +10,14 @@ import GHC.Runtime.Interpreter.Types #if !defined(mingw32_HOST_OS) import Control.Concurrent.MVar +import Data.Maybe 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.Environment.Blank import System.IO import qualified System.Posix.IO as Posix import System.Process @@ -46,13 +48,22 @@ spawnWasmInterp WasmInterpConfig {..} = do (rfd2, wfd2) <- Posix.createPipe Posix.setFdOption rfd1 Posix.CloseOnExec True Posix.setFdOption wfd2 Posix.CloseOnExec True + ghc_env <- getEnvironment + let dyld_env = + [("GHCI_BROWSER", "1") | wasmInterpBrowser] + ++ [("GHCI_BROWSER_HOST", wasmInterpBrowserHost), ("GHCI_BROWSER_PORT", show wasmInterpBrowserPort)] + ++ [("GHCI_BROWSER_REDIRECT_WASI_CONSOLE", "1") | wasmInterpBrowserRedirectWasiConsole] + ++ [("GHCI_BROWSER_PUPPETEER_LAUNCH_OPTS", f) | f <- maybeToList wasmInterpBrowserPuppeteerLaunchOpts] + ++ [("GHCI_BROWSER_PLAYWRIGHT_BROWSER_TYPE", f) | f <- maybeToList wasmInterpBrowserPlaywrightBrowserType] + ++ [("GHCI_BROWSER_PLAYWRIGHT_LAUNCH_OPTS", f) | f <- maybeToList wasmInterpBrowserPlaywrightLaunchOpts] + ++ ghc_env (_, _, _, ph) <- createProcess ( proc wasmInterpDyLD $ [wasmInterpLibDir, ghci_so_path, show wfd1, show rfd2] ++ wasmInterpOpts ++ ["+RTS", "-H64m", "-RTS"] - ) + ) { env = Just dyld_env } Posix.closeFd wfd1 Posix.closeFd rfd2 rh <- Posix.fdToHandle rfd1 diff --git a/docs/users_guide/wasm.rst b/docs/users_guide/wasm.rst index b5585e66be6b8e4c26a3d4eb64a4b94e923c1fd5..ce572759f1a0660466286cd1c680d62ca6a07aec 100644 --- a/docs/users_guide/wasm.rst +++ b/docs/users_guide/wasm.rst @@ -124,13 +124,19 @@ your wasm backend installation is supplied by ``ghc-wasm-meta``, the right ``node`` installation and ``NODE_PATH`` with all the optional npm dependencies are automatically provided out of the box. -To get started with the browser mode, set the ``GHCI_BROWSER`` -environment variable: +To start GHCi with the browser mode, use the following GHC flag: + +.. ghc-flag:: -fghci-browser + :shortdesc: Enable wasm ghci browser mode. + :type: dynamic + + :default: off + + Enable wasm ghci browser mode, see :ref:`wasm-ghci`. :: - $ export GHCI_BROWSER=1 - $ wasm32-wasi-ghc --interactive + $ wasm32-wasi-ghc --interactive -fghci-browser GHCi, version 9.13.20250320: https://www.haskell.org/ghc/ :? for help Open http://127.0.0.1:37517/main.html or import http://127.0.0.1:37517/main.js to boot ghci @@ -164,21 +170,41 @@ GHCi browser mode: prompt, the messages are written to the F12 devtools console in a line-buffered manner. -There are other options that can be specified as environment variables: - -- ``GHCI_BROWSER_HOST``: specify the host address that the ``dyld`` HTTP - server should bind to, supports IPv4/IPv6. Defaults to ``127.0.0.1``. - Be careful when changing it and exposing the ``dyld`` HTTP server to - other networks, some endpoints of the server allow downloading files - from the host filesystem! -- ``GHCI_BROWSER_PORT``: specify the port that the ``dyld`` HTTP server - should listen on. Defaults to a random idle port. -- ``GHCI_BROWSER_REDIRECT_WASI_CONSOLE``: if set to ``1``, the wasi - stdout/stderr output messages are redirected back to the host GHCi - terminal instead of outputing to the F12 devtools console. The main - intended use case is mobile browsers which likely don’t have F12 - devtools readily available. Also note that this only redirects wasi - console messages, not ``console.log`` invocations in the browser. +See below for other optional GHC flags of wasm ghci browser mode: + +.. ghc-flag:: -fghci-browser-host + :shortdesc: Wasm ghci browser mode host address. + :type: dynamic + + :default: ``127.0.0.1`` + + Specify the host address that the ``dyld`` HTTP server should bind + to, supports IPv4/IPv6. Defaults to ``127.0.0.1``. Be careful when + changing it and exposing the ``dyld`` HTTP server to other + networks, some endpoints of the server allow downloading files + from the host filesystem! + +.. ghc-flag:: -fghci-browser-port + :shortdesc: Wasm ghci browser mode host port. + :type: dynamic + + :default: ``0`` + + Specify the port that the ``dyld`` HTTP server should listen on. + Defaults to a random idle port. + +.. ghc-flag:: -fghci-browser-redirect-wasi-console + :shortdesc: Redirect wasi console stdout/stderr back to host ghci. + :type: dynamic + + :default: off + + If this flag is on, the wasi stdout/stderr output messages are + redirected back to the host GHCi terminal instead of outputing to + the F12 devtools console. The main intended use case is mobile + browsers which likely don’t have F12 devtools readily available. + Also note that this only redirects wasi console messages, not + ``console.log`` invocations in the browser. For testing purposes, there is also support for using `Puppeteer <https://pptr.dev>`__ or @@ -188,15 +214,26 @@ dependencies need to be supplied via ``NODE_PATH``, either ``puppeteer``/``puppeteer-core`` or ``playwright``/``playwright-core``, then the following options can be used: -- ``GHCI_BROWSER_PUPPETEER_LAUNCH_OPTS``: JSON-formatted arguments to - be passed to `puppeteer.launch() - <https://pptr.dev/api/puppeteer.puppeteernode.launch>`__. -- ``GHCI_BROWSER_PLAYWRIGHT_BROWSER_TYPE``: one of - ``chromium``/``firefox``/``webkit``, the kind of browser to be - launched by ``playwright``. -- ``GHCI_BROWSER_PLAYWRIGHT_LAUNCH_OPTS``: optional, JSON-formatted - arguments to be passed to `browser.launch() - <https://playwright.dev/docs/api/class-browsertype#browser-type-launch>`__. +.. ghc-flag:: -fghci-browser-puppeteer-launch-opts + :shortdesc: puppeteer.launch() options. + :type: dynamic + + JSON-formatted options to be passed to `puppeteer.launch() + <https://pptr.dev/api/puppeteer.puppeteernode.launch>`__. + +.. ghc-flag:: -fghci-browser-playwright-browser-type + :shortdesc: Playwright browser type. + :type: dynamic + + One of ``chromium``/``firefox``/``webkit``, the type of browser to + be launched by ``playwright``. + +.. ghc-flag:: -fghci-browser-playwright-launch-opts + :shortdesc: Playwright browser.launch() options. + :type: dynamic + + Optional, JSON-formatted options to be passed to `browser.launch() + <https://playwright.dev/docs/api/class-browsertype#browser-type-launch>`__. .. _wasm-jsffi: diff --git a/testsuite/tests/ghci-browser/all.T b/testsuite/tests/ghci-browser/all.T index 243d8acbcea2a37f863996308a35db589d3bb912..73bcd4db0ff96007a3130bc8aefa472ff42a4778 100644 --- a/testsuite/tests/ghci-browser/all.T +++ b/testsuite/tests/ghci-browser/all.T @@ -61,14 +61,14 @@ async def ghci_browser(name_with_browser, way): if browser == "firefox": o = ghc_env["FIREFOX_LAUNCH_OPTS"] - env_flags = f"GHCI_BROWSER=1 GHCI_BROWSER_PUPPETEER_LAUNCH_OPTS='{o}'" + browser_flags = f"-fghci-browser -fghci-browser-puppeteer-launch-opts='{o}'" elif browser == "chrome": o = ghc_env["CHROME_LAUNCH_OPTS"] - env_flags = f"GHCI_BROWSER=1 GHCI_BROWSER_PUPPETEER_LAUNCH_OPTS='{o}'" + browser_flags = f"-fghci-browser -fghci-browser-puppeteer-launch-opts='{o}'" else: assert browser == "webkit" o = ghc_env["WEBKIT_LAUNCH_OPTS"] - env_flags = f"GHCI_BROWSER=1 GHCI_BROWSER_PLAYWRIGHT_BROWSER_TYPE=webkit GHCI_BROWSER_PLAYWRIGHT_LAUNCH_OPTS='{o}'" + browser_flags = f"-fghci-browser -fghci-browser-playwright-browser-type=webkit -fghci-browser-playwright-launch-opts='{o}'" # ghci_script formats the command then calls simple_run. simple_run # formats the command then calls runCmdPerf. runCmdPerf formats the @@ -78,8 +78,8 @@ async def ghci_browser(name_with_browser, way): # payload to survive how many layers of pure insanity. cmd = ( f"cd '{opts.testdir}' && " - + env_flags.replace("{", "{{").replace("}", "}}") + f" {{compiler}} {way_flags} {flags} {opts.extra_run_opts}" + + browser_flags.replace("{", "{{").replace("}", "}}") ) exit_code = await runCmd(