diff --git a/compiler/GHC.hs b/compiler/GHC.hs index 49c94930ae7eca4c2cde352baf9d90084621c914..66432aa1634352e1c814c993910dcfd85eee846a 100644 --- a/compiler/GHC.hs +++ b/compiler/GHC.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE CPP #-} {-# LANGUAGE NondecreasingIndentation, ScopedTypeVariables #-} {-# LANGUAGE TupleSections, NamedFieldPuns #-} @@ -315,6 +316,7 @@ import GHC.Driver.Backend import GHC.Driver.Config.Finder (initFinderOpts) import GHC.Driver.Config.Parser (initParserOpts) import GHC.Driver.Config.Logger (initLogFlags) +import GHC.Driver.Config.StgToJS (initStgToJSConfig) import GHC.Driver.Config.Diagnostic import GHC.Driver.Main import GHC.Driver.Make @@ -676,8 +678,10 @@ setTopSessionDynFlags dflags = do logger <- getLogger -- Interpreter - interp <- if gopt Opt_ExternalInterpreter dflags - then do + interp <- if + -- external interpreter + | gopt Opt_ExternalInterpreter dflags + -> do let prog = pgm_i dflags ++ flavour profiled = ways dflags `hasWay` WayProf @@ -699,10 +703,31 @@ setTopSessionDynFlags dflags = do , iservConfHook = createIservProcessHook (hsc_hooks hsc_env) , iservConfTrace = tr } - s <- liftIO $ newMVar IServPending + s <- liftIO $ newMVar InterpPending + loader <- liftIO Loader.uninitializedLoader + return (Just (Interp (ExternalInterp (ExtIServ (ExtInterpState conf s))) loader)) + + -- JavaScript interpreter + | ArchJavaScript <- platformArch (targetPlatform dflags) + -> do + s <- liftIO $ newMVar InterpPending loader <- liftIO Loader.uninitializedLoader - return (Just (Interp (ExternalInterp conf (IServ s)) loader)) - else + let cfg = JSInterpConfig + { jsInterpNodeConfig = defaultNodeJsSettings + , jsInterpScript = topDir dflags </> "ghc-interp.js" + , jsInterpTmpFs = hsc_tmpfs hsc_env + , jsInterpTmpDir = tmpDir dflags + , jsInterpLogger = hsc_logger hsc_env + , jsInterpCodegenCfg = initStgToJSConfig dflags + , jsInterpUnitEnv = hsc_unit_env hsc_env + , jsInterpFinderOpts = initFinderOpts dflags + , jsInterpFinderCache = hsc_FC hsc_env + } + return (Just (Interp (ExternalInterp (ExtJS (ExtInterpState cfg s))) loader)) + + -- Internal interpreter + | otherwise + -> #if defined(HAVE_INTERNAL_INTERPRETER) do loader <- liftIO Loader.uninitializedLoader diff --git a/compiler/GHC/Driver/Main.hs b/compiler/GHC/Driver/Main.hs index 2c5b23a637cff70c6f25f03e18e127eb5097ee30..fd02fbdb0cd72c0ebc44f3ac535fe4fda0e1ebcb 100644 --- a/compiler/GHC/Driver/Main.hs +++ b/compiler/GHC/Driver/Main.hs @@ -140,9 +140,10 @@ import GHC.Driver.Hooks import GHC.Driver.GenerateCgIPEStub (generateCgIPEStub) import GHC.Runtime.Context -import GHC.Runtime.Interpreter ( addSptEntry ) +import GHC.Runtime.Interpreter +import GHC.Runtime.Interpreter.JS import GHC.Runtime.Loader ( initializePlugins ) -import GHCi.RemoteTypes ( ForeignHValue ) +import GHCi.RemoteTypes import GHC.ByteCode.Types import GHC.Linker.Loader @@ -156,6 +157,9 @@ import GHC.HsToCore import GHC.StgToByteCode ( byteCodeGen ) import GHC.StgToJS ( stgToJS ) +import GHC.StgToJS.Ids +import GHC.StgToJS.Types +import GHC.JS.Syntax import GHC.IfaceToCore ( typecheckIface, typecheckWholeCoreBindings ) @@ -172,7 +176,6 @@ import GHC.Core import GHC.Core.Lint.Interactive ( interactiveInScope ) import GHC.Core.Tidy ( tidyExpr ) import GHC.Core.Type ( Type, Kind ) -import GHC.Core.Multiplicity import GHC.Core.Utils ( exprType ) import GHC.Core.ConLike import GHC.Core.Opt.Pipeline @@ -201,7 +204,6 @@ import GHC.Stg.Pipeline ( stg2stg, StgCgInfos ) import GHC.Builtin.Utils import GHC.Builtin.Names -import GHC.Builtin.Uniques ( mkPseudoUniqueE ) import qualified GHC.StgToCmm as StgToCmm ( codeGen ) import GHC.StgToCmm.Types (CmmCgInfos (..), ModuleLFInfos, LambdaFormInfo(..)) @@ -231,7 +233,7 @@ import GHC.Types.SourceError import GHC.Types.SafeHaskell import GHC.Types.ForeignStubs import GHC.Types.Name.Env ( mkNameEnv ) -import GHC.Types.Var.Env ( emptyTidyEnv ) +import GHC.Types.Var.Env ( mkEmptyTidyEnv ) import GHC.Types.Error import GHC.Types.Fixity.Env import GHC.Types.CostCentre @@ -245,6 +247,8 @@ import GHC.Types.Name.Ppr import GHC.Types.Name.Set (NonCaffySet) import GHC.Types.TyThing import GHC.Types.HpcInfo +import GHC.Types.Unique.Supply (uniqFromMask) +import GHC.Types.Unique (getKey) import GHC.Utils.Fingerprint ( Fingerprint ) import GHC.Utils.Panic @@ -289,6 +293,7 @@ import System.IO.Unsafe ( unsafeInterleaveIO ) import GHC.Iface.Env ( trace_if ) import GHC.Stg.InferTags.TagSig (seqTagSig) import GHC.Types.Unique.FM +import GHC.Types.Unique.DFM {- ********************************************************************** @@ -1853,7 +1858,7 @@ hscGenHardCode hsc_env cgguts location output_filename = do c `seqList` d `seqList` (seqEltsUFM (seqTagSig) tag_env)) - (myCoreToStg logger dflags (hsc_IC hsc_env) False this_mod location prepd_binds) + (myCoreToStg logger dflags (interactiveInScope (hsc_IC hsc_env)) False this_mod location prepd_binds) let cost_centre_info = (late_local_ccs ++ caf_ccs, caf_cc_stacks) @@ -1975,7 +1980,7 @@ hscInteractive hsc_env cgguts location = do -- omit it here (stg_binds, _infotable_prov, _caf_ccs__caf_cc_stacks, _ignore_stg_cg_infos) <- {-# SCC "CoreToStg" #-} - myCoreToStg logger dflags (hsc_IC hsc_env) True this_mod location prepd_binds + myCoreToStg logger dflags (interactiveInScope (hsc_IC hsc_env)) True this_mod location prepd_binds ----------------- Generate byte code ------------------ comp_bc <- byteCodeGen hsc_env this_mod stg_binds data_tycons mod_breaks ------------------ Create f-x-dynamic C-side stuff ----- @@ -2150,46 +2155,21 @@ doCodeGen hsc_env this_mod denv data_tycons return $ Stream.mapM dump2 $ generateCgIPEStub hsc_env this_mod denv pipeline_stream -myCoreToStgExpr :: Logger -> DynFlags -> InteractiveContext - -> Bool - -> Module -> ModLocation -> CoreExpr - -> IO ( Id - , [CgStgTopBinding] - , InfoTableProvMap - , CollectedCCs - , StgCgInfos ) -myCoreToStgExpr logger dflags ictxt for_bytecode this_mod ml prepd_expr = do - {- Create a temporary binding (just because myCoreToStg needs a - binding for the stg2stg step) -} - let bco_tmp_id = mkSysLocal (fsLit "BCO_toplevel") - (mkPseudoUniqueE 0) - ManyTy - (exprType prepd_expr) - (stg_binds, prov_map, collected_ccs, stg_cg_infos) <- - myCoreToStg logger - dflags - ictxt - for_bytecode - this_mod - ml - [NonRec bco_tmp_id prepd_expr] - return (bco_tmp_id, stg_binds, prov_map, collected_ccs, stg_cg_infos) - -myCoreToStg :: Logger -> DynFlags -> InteractiveContext +myCoreToStg :: Logger -> DynFlags -> [Var] -> Bool -> Module -> ModLocation -> CoreProgram -> IO ( [CgStgTopBinding] -- output program , InfoTableProvMap , CollectedCCs -- CAF cost centre info (declared and used) , StgCgInfos ) -myCoreToStg logger dflags ictxt for_bytecode this_mod ml prepd_binds = do +myCoreToStg logger dflags ic_inscope for_bytecode this_mod ml prepd_binds = do let (stg_binds, denv, cost_centre_info) = {-# SCC "Core2Stg" #-} coreToStg (initCoreToStgOpts dflags) this_mod ml prepd_binds (stg_binds_with_fvs,stg_cg_info) <- {-# SCC "Stg2Stg" #-} - stg2stg logger (interactiveInScope ictxt) (initStgPipelineOpts dflags for_bytecode) + stg2stg logger ic_inscope (initStgPipelineOpts dflags for_bytecode) this_mod stg_binds putDumpFileMaybe logger Opt_D_dump_stg_cg "CodeGenInput STG:" FormatSTG @@ -2350,7 +2330,7 @@ hscParsedDecls hsc_env decls = runInteractiveHsc hsc_env $ do <- {-# SCC "CoreToStg" #-} liftIO $ myCoreToStg (hsc_logger hsc_env) (hsc_dflags hsc_env) - (hsc_IC hsc_env) + (interactiveInScope (hsc_IC hsc_env)) True this_mod iNTERACTIVELoc @@ -2558,56 +2538,135 @@ hscCompileCoreExpr hsc_env loc expr = Just h -> h hsc_env loc expr hscCompileCoreExpr' :: HscEnv -> SrcSpan -> CoreExpr -> IO (ForeignHValue, [Linkable], PkgsLoaded) -hscCompileCoreExpr' hsc_env srcspan ds_expr - = do { {- Simplify it -} - -- Question: should we call SimpleOpt.simpleOptExpr here instead? - -- It is, well, simpler, and does less inlining etc. - let dflags = hsc_dflags hsc_env - ; let logger = hsc_logger hsc_env - ; let ic = hsc_IC hsc_env - ; let unit_env = hsc_unit_env hsc_env - ; let simplify_expr_opts = initSimplifyExprOpts dflags ic - ; simpl_expr <- simplifyExpr logger (ue_eps unit_env) simplify_expr_opts ds_expr - - {- Tidy it (temporary, until coreSat does cloning) -} - ; let tidy_expr = tidyExpr emptyTidyEnv simpl_expr - - {- Prepare for codegen -} - ; cp_cfg <- initCorePrepConfig hsc_env - ; prepd_expr <- corePrepExpr - logger cp_cfg - tidy_expr - - {- Lint if necessary -} - ; lintInteractiveExpr (text "hscCompileExpr") hsc_env prepd_expr - ; let iNTERACTIVELoc = ModLocation{ ml_hs_file = Nothing, - ml_hi_file = panic "hscCompileCoreExpr':ml_hi_file", - ml_obj_file = panic "hscCompileCoreExpr':ml_obj_file", - ml_dyn_obj_file = panic "hscCompileCoreExpr': ml_obj_file", - ml_dyn_hi_file = panic "hscCompileCoreExpr': ml_dyn_hi_file", - ml_hie_file = panic "hscCompileCoreExpr':ml_hie_file" } - - ; let ictxt = hsc_IC hsc_env - ; (binding_id, stg_expr, _, _, _stg_cg_info) <- - myCoreToStgExpr logger - dflags - ictxt - True - (icInteractiveModule ictxt) - iNTERACTIVELoc - prepd_expr - - {- Convert to BCOs -} - ; bcos <- byteCodeGen hsc_env - (icInteractiveModule ictxt) - stg_expr - [] Nothing - - {- load it -} - ; (fv_hvs, mods_needed, units_needed) <- loadDecls (hscInterp hsc_env) hsc_env srcspan bcos - {- Get the HValue for the root -} - ; return (expectJust "hscCompileCoreExpr'" - $ lookup (idName binding_id) fv_hvs, mods_needed, units_needed) } +hscCompileCoreExpr' hsc_env srcspan ds_expr = do + {- Simplify it -} + -- Question: should we call SimpleOpt.simpleOptExpr here instead? + -- It is, well, simpler, and does less inlining etc. + let dflags = hsc_dflags hsc_env + let logger = hsc_logger hsc_env + let ic = hsc_IC hsc_env + let unit_env = hsc_unit_env hsc_env + let simplify_expr_opts = initSimplifyExprOpts dflags ic + + simpl_expr <- simplifyExpr logger (ue_eps unit_env) simplify_expr_opts ds_expr + + -- Create a unique temporary binding + -- + -- The id has to be exported for the JS backend. This isn't required for the + -- byte-code interpreter but it does no harm to always do it. + u <- uniqFromMask 'I' + let binding_name = mkSystemVarName u (fsLit ("BCO_toplevel")) + let binding_id = mkExportedVanillaId binding_name (exprType simpl_expr) + + {- Tidy it (temporary, until coreSat does cloning) -} + let tidy_occ_env = initTidyOccEnv [occName binding_id] + let tidy_env = mkEmptyTidyEnv tidy_occ_env + let tidy_expr = tidyExpr tidy_env simpl_expr + + {- Prepare for codegen -} + cp_cfg <- initCorePrepConfig hsc_env + prepd_expr <- corePrepExpr + logger cp_cfg + tidy_expr + + {- Lint if necessary -} + lintInteractiveExpr (text "hscCompileExpr") hsc_env prepd_expr + let this_loc = ModLocation{ ml_hs_file = Nothing, + ml_hi_file = panic "hscCompileCoreExpr':ml_hi_file", + ml_obj_file = panic "hscCompileCoreExpr':ml_obj_file", + ml_dyn_obj_file = panic "hscCompileCoreExpr': ml_obj_file", + ml_dyn_hi_file = panic "hscCompileCoreExpr': ml_dyn_hi_file", + ml_hie_file = panic "hscCompileCoreExpr':ml_hie_file" } + + -- Ensure module uniqueness by giving it a name like "GhciNNNN". + -- This uniqueness is needed by the JS linker. Without it we break the 1-1 + -- relationship between modules and object files, i.e. we get different object + -- files for the same module and the JS linker doesn't support this. + -- + -- Note that we can't use icInteractiveModule because the ic_mod_index value + -- isn't bumped between invocations of hscCompileExpr, so uniqueness isn't + -- guaranteed. + -- + -- We reuse the unique we obtained for the binding, but any unique would do. + let this_mod = mkInteractiveModule (getKey u) + let for_bytecode = True + + (stg_binds, _prov_map, _collected_ccs, _stg_cg_infos) <- + myCoreToStg logger + dflags + (interactiveInScope (hsc_IC hsc_env)) + for_bytecode + this_mod + this_loc + [NonRec binding_id prepd_expr] + + let interp = hscInterp hsc_env + let tmpfs = hsc_tmpfs hsc_env + let tmp_dir = tmpDir dflags + + case interp of + -- always generate JS code for the JS interpreter (no bytecode!) + Interp (ExternalInterp (ExtJS i)) _ -> + jsCodeGen logger tmpfs tmp_dir unit_env (initStgToJSConfig dflags) interp i + this_mod stg_binds binding_id + + _ -> do + {- Convert to BCOs -} + bcos <- byteCodeGen hsc_env + this_mod + stg_binds + [] Nothing + + {- load it -} + (fv_hvs, mods_needed, units_needed) <- loadDecls interp hsc_env srcspan bcos + {- Get the HValue for the root -} + return (expectJust "hscCompileCoreExpr'" + $ lookup (idName binding_id) fv_hvs, mods_needed, units_needed) + + + +-- | Generate JS code for the given bindings and return the HValue for the given id +jsCodeGen + :: Logger + -> TmpFs + -> TempDir + -> UnitEnv + -> StgToJSConfig + -> Interp + -> JSInterp + -> Module + -> [CgStgTopBinding] + -> Id + -> IO (ForeignHValue, [Linkable], PkgsLoaded) +jsCodeGen logger tmpfs tmp_dir unit_env js_config interp i this_mod stg_binds binding_id = do + let foreign_stubs = NoStubs + spt_entries = mempty + cost_centre_info = mempty + + -- codegen into object file whose path is in out_obj + out_obj <- newTempName logger tmpfs tmp_dir TFL_CurrentModule "o" + stgToJS logger js_config stg_binds this_mod spt_entries foreign_stubs cost_centre_info out_obj + + let TxtI id_sym = makeIdentForId binding_id Nothing IdPlain this_mod + -- link code containing binding "id_sym = expr", using id_sym as root + withJSInterp i $ \inst -> do + let roots = mkExportedModFuns this_mod [id_sym] + jsLinkObject logger tmpfs tmp_dir js_config unit_env inst out_obj roots + + -- look up "id_sym" closure and create a StablePtr (HValue) from it + href <- lookupClosure interp (unpackFS id_sym) >>= \case + Nothing -> pprPanic "Couldn't find just linked TH closure" (ppr id_sym) + Just r -> pure r + + binding_fref <- withJSInterp i $ \inst -> + mkForeignRef href (freeReallyRemoteRef inst href) + + -- FIXME (#23013): the JS linker doesn't use the LoaderState. + -- The state is only maintained in the interpreter instance (jsLinkState field) for now. + let linkables = mempty + let loaded_pkgs = emptyUDFM + + return (castForeignRef binding_fref, linkables, loaded_pkgs) {- ********************************************************************** diff --git a/compiler/GHC/Driver/Make.hs b/compiler/GHC/Driver/Make.hs index 08d89aede971832e3f2f73416ac8e2024a336b9d..47e6a24b7258abc908148ff0fa43f814329038a3 100644 --- a/compiler/GHC/Driver/Make.hs +++ b/compiler/GHC/Driver/Make.hs @@ -2778,6 +2778,7 @@ executeLinkNode hug kn uid deps = do link (ghcLink dflags) (hsc_logger hsc_env') (hsc_tmpfs hsc_env') + (hsc_FC hsc_env') (hsc_hooks hsc_env') dflags (hsc_unit_env hsc_env') diff --git a/compiler/GHC/Driver/Pipeline.hs b/compiler/GHC/Driver/Pipeline.hs index 9e246347f62b8c6de36801822b28001b28390d71..ea5f716272ff9bc7164fa7fe3c0caf2a4ce3ca57 100644 --- a/compiler/GHC/Driver/Pipeline.hs +++ b/compiler/GHC/Driver/Pipeline.hs @@ -111,7 +111,7 @@ import GHC.Types.SourceError import GHC.Unit import GHC.Unit.Env ---import GHC.Unit.Finder +import GHC.Unit.Finder --import GHC.Unit.State import GHC.Unit.Module.ModSummary import GHC.Unit.Module.ModIface @@ -351,6 +351,7 @@ compileOne' mHscMessage link :: GhcLink -- ^ interactive or batch -> Logger -- ^ Logger -> TmpFs + -> FinderCache -> Hooks -> DynFlags -- ^ dynamic flags -> UnitEnv -- ^ unit environment @@ -366,7 +367,7 @@ link :: GhcLink -- ^ interactive or batch -- exports main, i.e., we have good reason to believe that linking -- will succeed. -link ghcLink logger tmpfs hooks dflags unit_env batch_attempt_linking mHscMessage hpt = +link ghcLink logger tmpfs fc hooks dflags unit_env batch_attempt_linking mHscMessage hpt = case linkHook hooks of Nothing -> case ghcLink of NoLink -> return Succeeded @@ -382,7 +383,7 @@ link ghcLink logger tmpfs hooks dflags unit_env batch_attempt_linking mHscMessag -> panicBadLink LinkInMemory Just h -> h ghcLink dflags batch_attempt_linking hpt where - normal_link = link' logger tmpfs dflags unit_env batch_attempt_linking mHscMessage hpt + normal_link = link' logger tmpfs fc dflags unit_env batch_attempt_linking mHscMessage hpt panicBadLink :: GhcLink -> a @@ -391,6 +392,7 @@ panicBadLink other = panic ("link: GHC not built to link this way: " ++ link' :: Logger -> TmpFs + -> FinderCache -> DynFlags -- ^ dynamic flags -> UnitEnv -- ^ unit environment -> Bool -- ^ attempt linking in batch mode? @@ -398,7 +400,7 @@ link' :: Logger -> HomePackageTable -- ^ what to link -> IO SuccessFlag -link' logger tmpfs dflags unit_env batch_attempt_linking mHscMessager hpt +link' logger tmpfs fc dflags unit_env batch_attempt_linking mHscMessager hpt | batch_attempt_linking = do let @@ -445,7 +447,7 @@ link' logger tmpfs dflags unit_env batch_attempt_linking mHscMessager hpt -- Don't showPass in Batch mode; doLink will do that for us. case ghcLink dflags of LinkBinary - | backendUseJSLinker (backend dflags) -> linkJSBinary logger dflags unit_env obj_files pkg_deps + | backendUseJSLinker (backend dflags) -> linkJSBinary logger fc dflags unit_env obj_files pkg_deps | otherwise -> linkBinary logger tmpfs dflags unit_env obj_files pkg_deps LinkStaticLib -> linkStaticLib logger dflags unit_env obj_files pkg_deps LinkDynLib -> linkDynLibCheck logger tmpfs dflags unit_env obj_files pkg_deps @@ -462,14 +464,14 @@ link' logger tmpfs dflags unit_env batch_attempt_linking mHscMessager hpt return Succeeded -linkJSBinary :: Logger -> DynFlags -> UnitEnv -> [FilePath] -> [UnitId] -> IO () -linkJSBinary logger dflags unit_env obj_files pkg_deps = do +linkJSBinary :: Logger -> FinderCache -> DynFlags -> UnitEnv -> [FilePath] -> [UnitId] -> IO () +linkJSBinary logger fc dflags unit_env obj_files pkg_deps = do -- we use the default configuration for now. In the future we may expose -- settings to the user via DynFlags. let lc_cfg = defaultJSLinkConfig let cfg = initStgToJSConfig dflags let extra_js = mempty - jsLinkBinary lc_cfg cfg extra_js logger dflags unit_env obj_files pkg_deps + jsLinkBinary fc lc_cfg cfg extra_js logger dflags unit_env obj_files pkg_deps linkingNeeded :: Logger -> DynFlags -> UnitEnv -> Bool -> [Linkable] -> [UnitId] -> IO RecompileRequired linkingNeeded logger dflags unit_env staticLink linkables pkg_deps = do @@ -574,12 +576,13 @@ doLink hsc_env o_files = do logger = hsc_logger hsc_env unit_env = hsc_unit_env hsc_env tmpfs = hsc_tmpfs hsc_env + fc = hsc_FC hsc_env case ghcLink dflags of NoLink -> return () LinkBinary | backendUseJSLinker (backend dflags) - -> linkJSBinary logger dflags unit_env o_files [] + -> linkJSBinary logger fc dflags unit_env o_files [] | otherwise -> linkBinary logger tmpfs dflags unit_env o_files [] LinkStaticLib -> linkStaticLib logger dflags unit_env o_files [] LinkDynLib -> linkDynLibCheck logger tmpfs dflags unit_env o_files [] diff --git a/compiler/GHC/Runtime/Interpreter.hs b/compiler/GHC/Runtime/Interpreter.hs index c40e9cd1f6f1b0d7f4857187673750f473b21338..554f86bef4013175b820de4d377e47490c8cf0b9 100644 --- a/compiler/GHC/Runtime/Interpreter.hs +++ b/compiler/GHC/Runtime/Interpreter.hs @@ -3,6 +3,8 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE RankNTypes #-} -- | Interacting with the iserv interpreter, whether it is running on an -- external process or in the current process. @@ -45,22 +47,30 @@ module GHC.Runtime.Interpreter , resolveObjs , findSystemLibrary - -- * Lower-level API using messages - , interpCmd, Message(..), withIServ, withIServ_ + , interpCmd + , withExtInterp + , withExtInterpStatus + , withIServ + , withJSInterp , stopInterp - , iservCall, readIServ, writeIServ , purgeLookupSymbolCache + , freeReallyRemoteRef , freeHValueRefs , mkFinalizedHValue , wormhole, wormholeRef , fromEvalResult + + -- * Reexport for convenience + , Message (..) + , module GHC.Runtime.Interpreter.Process ) where import GHC.Prelude -import GHC.IO (catchException) - import GHC.Runtime.Interpreter.Types +import GHC.Runtime.Interpreter.JS +import GHC.Runtime.Interpreter.Process +import GHC.Runtime.Utils import GHCi.Message import GHCi.RemoteTypes import GHCi.ResolvedBCO @@ -97,7 +107,7 @@ import GHC.Platform.Ways import Control.Concurrent import Control.Monad import Control.Monad.IO.Class -import Control.Monad.Catch as MC (mask, onException) +import Control.Monad.Catch as MC (mask) import Data.Binary import Data.Binary.Put import Data.ByteString (ByteString) @@ -107,19 +117,6 @@ import Data.IORef import Foreign hiding (void) import qualified GHC.Exts.Heap as Heap import GHC.Stack.CCS (CostCentre,CostCentreStack) -import System.Exit -import GHC.IO.Handle.Types (Handle) -#if defined(mingw32_HOST_OS) -import Foreign.C -import GHC.IO.Handle.FD (fdToHandle) -# if defined(__IO_MANAGER_WINIO__) -import GHC.IO.SubSystem ((<!>)) -import GHC.IO.Handle.Windows (handleToHANDLE) -import GHC.Event.Windows (associateHandle') -# endif -#else -import System.Posix as Posix -#endif import System.Directory import System.Process import GHC.Conc (pseq, par) @@ -198,11 +195,21 @@ interpCmd interp msg = case interpInstance interp of #if defined(HAVE_INTERNAL_INTERPRETER) InternalInterp -> run msg -- Just run it directly #endif - ExternalInterp c i -> withIServ_ c i $ \iserv -> + ExternalInterp ext -> withExtInterp ext $ \inst -> uninterruptibleMask_ $ -- Note [uninterruptibleMask_ and interpCmd] - iservCall iserv msg + sendMessage inst msg +withExtInterp :: ExceptionMonad m => ExtInterp -> (forall d. ExtInterpInstance d -> m a) -> m a +withExtInterp ext action = case ext of + ExtJS i -> withJSInterp 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) + ExtIServ i -> action (interpStatus i) + -- Note [uninterruptibleMask_ and interpCmd] -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- If we receive an async exception, such as ^C, while communicating @@ -216,37 +223,51 @@ interpCmd interp msg = case interpInstance interp of -- Overloaded because this is used from TcM as well as IO. withIServ :: (ExceptionMonad m) - => IServConfig -> IServ -> (IServInstance -> m (IServInstance, a)) -> m a -withIServ conf (IServ mIServState) action = - MC.mask $ \restore -> do - state <- liftIO $ takeMVar mIServState - - iserv <- case state of - -- start the external iserv process if we haven't done so yet - IServPending -> - liftIO (spawnIServ conf) - `MC.onException` (liftIO $ putMVar mIServState state) - - IServRunning inst -> return inst - - - let iserv' = iserv{ iservPendingFrees = [] } - - (iserv'',a) <- (do - -- free any ForeignHValues that have been garbage collected. - liftIO $ when (not (null (iservPendingFrees iserv))) $ - iservCall iserv (FreeHValueRefs (iservPendingFrees iserv)) - -- run the inner action - restore $ action iserv') - `MC.onException` (liftIO $ putMVar mIServState (IServRunning iserv')) - liftIO $ putMVar mIServState (IServRunning iserv'') - return a - -withIServ_ - :: (MonadIO m, ExceptionMonad m) - => IServConfig -> IServ -> (IServInstance -> m a) -> m a -withIServ_ conf iserv action = withIServ conf iserv $ \inst -> - (inst,) <$> action inst + => IServ -> (ExtInterpInstance () -> m a) -> m a +withIServ (ExtInterpState cfg mstate) action = do + inst <- spawnInterpMaybe cfg spawnIServ mstate + action inst + +-- | Spawn JS interpreter if it isn't already running and execute the given action +-- +-- Update the interpreter state. +withJSInterp :: ExceptionMonad m => JSInterp -> (ExtInterpInstance JSInterpExtra -> m a) -> m a +withJSInterp (ExtInterpState cfg mstate) action = do + inst <- spawnInterpMaybe cfg spawnJSInterp 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. +-- +-- This function is generic to support both the native external interpreter and +-- the JS one. +spawnInterpMaybe :: ExceptionMonad m => cfg -> (cfg -> IO (ExtInterpInstance d)) -> ExtInterpStatusVar d -> m (ExtInterpInstance d) +spawnInterpMaybe cfg spawn mstatus = do + inst <- liftIO $ modifyMVarMasked mstatus $ \case + -- start the external iserv process if we haven't done so yet + InterpPending -> do + inst <- spawn cfg + pure (InterpRunning inst, inst) + + InterpRunning inst -> do + pure (InterpRunning inst, inst) + + -- free any ForeignRef that have been garbage collected. + pending_frees <- liftIO $ swapMVar (instPendingFrees inst) [] + liftIO $ when (not (null (pending_frees))) $ + sendMessage inst (FreeHValueRefs pending_frees) + + -- run the inner action + pure inst + +withExtInterpMaybe + :: (ExceptionMonad m) + => ExtInterp -> (forall d. Maybe (ExtInterpInstance d) -> m a) -> m a +withExtInterpMaybe ext action = withExtInterpStatus ext $ \mstate -> do + liftIO (readMVar mstate) >>= \case + InterpPending {} -> action Nothing -- already shut down or never launched + InterpRunning inst -> action (Just inst) -- ----------------------------------------------------------------------------- -- Wrappers around messages @@ -435,24 +456,27 @@ lookupSymbol interp str = case interpInstance interp of InternalInterp -> fmap fromRemotePtr <$> run (LookupSymbol (unpackFS str)) #endif - ExternalInterp c i -> withIServ c i $ \iserv -> do - -- Profiling of GHCi showed a lot of time and allocation spent - -- making cross-process LookupSymbol calls, so I added a GHC-side - -- cache which sped things up quite a lot. We have to be careful - -- to purge this cache when unloading code though. - let cache = iservLookupSymbolCache iserv - case lookupUFM cache str of - Just p -> return (iserv, Just p) - Nothing -> do - m <- uninterruptibleMask_ $ - iservCall iserv (LookupSymbol (unpackFS str)) - case m of - Nothing -> return (iserv, Nothing) - Just r -> do - let p = fromRemotePtr r - cache' = addToUFM cache str p - iserv' = iserv {iservLookupSymbolCache = cache'} - return (iserv', Just p) + ExternalInterp ext -> case ext of + ExtIServ i -> withIServ i $ \inst -> do + -- Profiling of GHCi showed a lot of time and allocation spent + -- making cross-process LookupSymbol calls, so I added a GHC-side + -- cache which sped things up quite a lot. We have to be careful + -- to purge this cache when unloading code though. + cache <- readMVar (instLookupSymbolCache inst) + case lookupUFM cache str of + Just p -> return (Just p) + Nothing -> do + m <- uninterruptibleMask_ $ + sendMessage inst (LookupSymbol (unpackFS str)) + case m of + Nothing -> return Nothing + Just r -> do + let p = fromRemotePtr r + cache' = addToUFM cache str p + modifyMVar_ (instLookupSymbolCache inst) (const (pure cache')) + return (Just p) + + ExtJS {} -> pprPanic "lookupSymbol not supported by the JS interpreter" (ppr str) lookupClosure :: Interp -> String -> IO (Maybe HValueRef) lookupClosure interp str = @@ -463,12 +487,9 @@ purgeLookupSymbolCache interp = case interpInstance interp of #if defined(HAVE_INTERNAL_INTERPRETER) InternalInterp -> pure () #endif - ExternalInterp _ (IServ mstate) -> - modifyMVar_ mstate $ \state -> pure $ case state of - IServPending -> state - IServRunning iserv -> IServRunning - (iserv { iservLookupSymbolCache = emptyUFM }) - + ExternalInterp ext -> withExtInterpMaybe ext $ \case + Nothing -> pure () -- interpreter stopped, nothing to do + Just inst -> modifyMVar_ (instLookupSymbolCache inst) (const (pure emptyUFM)) -- | loadDLL loads a dynamic library using the OS's native linker -- (i.e. dlopen() on Unix, LoadLibrary() on Windows). It takes either @@ -518,56 +539,35 @@ resolveObjs interp = successIf <$> interpCmd interp ResolveObjs findSystemLibrary :: Interp -> String -> IO (Maybe String) findSystemLibrary interp str = interpCmd interp (FindSystemLibrary str) - -- ----------------------------------------------------------------------------- --- Raw calls and messages - --- | Send a 'Message' and receive the response from the iserv process -iservCall :: Binary a => IServInstance -> Message a -> IO a -iservCall iserv msg = - remoteCall (iservPipe iserv) msg - `catchException` \(e :: SomeException) -> handleIServFailure iserv e - --- | Read a value from the iserv process -readIServ :: IServInstance -> Get a -> IO a -readIServ iserv get = - readPipe (iservPipe iserv) get - `catchException` \(e :: SomeException) -> handleIServFailure iserv e - --- | Send a value to the iserv process -writeIServ :: IServInstance -> Put -> IO () -writeIServ iserv put = - writePipe (iservPipe iserv) put - `catchException` \(e :: SomeException) -> handleIServFailure iserv e - -handleIServFailure :: IServInstance -> SomeException -> IO a -handleIServFailure iserv e = do - let proc = iservProcess iserv - ex <- getProcessExitCode proc - case ex of - Just (ExitFailure n) -> - throwIO (InstallationError ("ghc-iserv terminated (" ++ show n ++ ")")) - _ -> do - terminateProcess proc - _ <- waitForProcess proc - throw e +-- IServ specific calls and messages -- | Spawn an external interpreter -spawnIServ :: IServConfig -> IO IServInstance +spawnIServ :: IServConfig -> IO (ExtInterpInstance ()) spawnIServ conf = do iservConfTrace conf let createProc = fromMaybe (\cp -> do { (_,_,_,ph) <- createProcess cp ; return ph }) (iservConfHook conf) (ph, rh, wh) <- runWithPipes createProc (iservConfProgram conf) + [] (iservConfOpts conf) lo_ref <- newIORef Nothing - return $ IServInstance - { iservPipe = Pipe { pipeRead = rh, pipeWrite = wh, pipeLeftovers = lo_ref } - , iservProcess = ph - , iservLookupSymbolCache = emptyUFM - , iservPendingFrees = [] - } + let pipe = Pipe { pipeRead = rh, pipeWrite = wh, pipeLeftovers = lo_ref } + let process = InterpProcess + { interpHandle = ph + , interpPipe = pipe + } + + pending_frees <- newMVar [] + lookup_cache <- newMVar emptyUFM + let inst = ExtInterpInstance + { instProcess = process + , instPendingFrees = pending_frees + , instLookupSymbolCache = lookup_cache + , instExtra = () + } + pure inst -- | Stop the interpreter stopInterp :: Interp -> IO () @@ -575,76 +575,16 @@ stopInterp interp = case interpInstance interp of #if defined(HAVE_INTERNAL_INTERPRETER) InternalInterp -> pure () #endif - ExternalInterp _ (IServ mstate) -> + ExternalInterp ext -> withExtInterpStatus ext $ \mstate -> do MC.mask $ \_restore -> modifyMVar_ mstate $ \state -> do case state of - IServPending -> pure state -- already stopped - IServRunning i -> do - ex <- getProcessExitCode (iservProcess i) + InterpPending -> pure state -- already stopped + InterpRunning i -> do + ex <- getProcessExitCode (interpHandle (instProcess i)) if isJust ex then pure () - else iservCall i Shutdown - pure IServPending - -runWithPipes :: (CreateProcess -> IO ProcessHandle) - -> FilePath -> [String] -> IO (ProcessHandle, Handle, Handle) -#if defined(mingw32_HOST_OS) -foreign import ccall "io.h _close" - c__close :: CInt -> IO CInt - -foreign import ccall unsafe "io.h _get_osfhandle" - _get_osfhandle :: CInt -> IO CInt - -runWithPipesPOSIX :: (CreateProcess -> IO ProcessHandle) - -> FilePath -> [String] -> IO (ProcessHandle, Handle, Handle) -runWithPipesPOSIX createProc prog opts = do - (rfd1, wfd1) <- createPipeFd -- we read on rfd1 - (rfd2, wfd2) <- createPipeFd -- we write on wfd2 - wh_client <- _get_osfhandle wfd1 - rh_client <- _get_osfhandle rfd2 - let args = show wh_client : show rh_client : opts - ph <- createProc (proc prog args) - rh <- mkHandle rfd1 - wh <- mkHandle wfd2 - return (ph, rh, wh) - where mkHandle :: CInt -> IO Handle - mkHandle fd = (fdToHandle fd) `Ex.onException` (c__close fd) - -# if defined (__IO_MANAGER_WINIO__) -runWithPipesNative :: (CreateProcess -> IO ProcessHandle) - -> FilePath -> [String] -> IO (ProcessHandle, Handle, Handle) -runWithPipesNative createProc prog opts = do - (rh, wfd1) <- createPipe -- we read on rfd1 - (rfd2, wh) <- createPipe -- we write on wfd2 - wh_client <- handleToHANDLE wfd1 - rh_client <- handleToHANDLE rfd2 - -- Associate the handle with the current manager - -- but don't touch the ones we're passing to the child - -- since it needs to register the handle with its own manager. - associateHandle' =<< handleToHANDLE rh - associateHandle' =<< handleToHANDLE wh - let args = show wh_client : show rh_client : opts - ph <- createProc (proc prog args) - return (ph, rh, wh) - -runWithPipes = runWithPipesPOSIX <!> runWithPipesNative -# else -runWithPipes = runWithPipesPOSIX -# endif -#else -runWithPipes createProc prog opts = do - (rfd1, wfd1) <- Posix.createPipe -- we read on rfd1 - (rfd2, wfd2) <- Posix.createPipe -- we write on wfd2 - setFdOption rfd1 CloseOnExec True - setFdOption wfd2 CloseOnExec True - let args = show wfd1 : show rfd2 : opts - ph <- createProc (proc prog args) - closeFd wfd1 - closeFd rfd2 - rh <- fdToHandle rfd1 - wh <- fdToHandle wfd2 - return (ph, rh, wh) -#endif + else sendMessage i Shutdown + pure InterpPending -- ----------------------------------------------------------------------------- {- Note [External GHCi pointers] @@ -661,10 +601,10 @@ we cannot use this to refer to things in the external process. RemoteRef --------- -RemoteRef is a StablePtr to a heap-resident value. When --fexternal-interpreter is used, this value resides in the external -process's heap. RemoteRefs are mostly used to send pointers in -messages between GHC and iserv. +RemoteRef is a StablePtr to a heap-resident value. When -fexternal-interpreter +or the JS interpreter is used, this value resides in the external process's +heap. RemoteRefs are mostly used to send pointers in messages between GHC and +iserv. A RemoteRef must be explicitly freed when no longer required, using freeHValueRefs, or by attaching a finalizer with mkForeignHValue. @@ -690,18 +630,18 @@ principle it would probably be ok, but it seems less hairy this way. -- 'RemoteRef' when it is no longer referenced. mkFinalizedHValue :: Interp -> RemoteRef a -> IO (ForeignRef a) mkFinalizedHValue interp rref = do - free <- case interpInstance interp of + case interpInstance interp of #if defined(HAVE_INTERNAL_INTERPRETER) - InternalInterp -> return (freeRemoteRef rref) + InternalInterp -> mkForeignRef rref (freeRemoteRef rref) #endif - ExternalInterp _ (IServ i) -> return $ modifyMVar_ i $ \state -> - case state of - IServPending {} -> pure state -- already shut down - IServRunning inst -> do - let !inst' = inst {iservPendingFrees = castRemoteRef rref : iservPendingFrees inst} - pure (IServRunning inst') + ExternalInterp ext -> withExtInterpMaybe ext $ \case + Nothing -> mkForeignRef rref (pure ()) -- nothing to do, interpreter already stopped + Just inst -> mkForeignRef rref (freeReallyRemoteRef inst rref) - mkForeignRef rref free +freeReallyRemoteRef :: ExtInterpInstance d -> RemoteRef a -> IO () +freeReallyRemoteRef inst rref = + -- add to the list of HValues to free + modifyMVar_ (instPendingFrees inst) (\xs -> pure (castRemoteRef rref : xs)) freeHValueRefs :: Interp -> [HValueRef] -> IO () @@ -751,7 +691,9 @@ interpreterProfiled interp = case interpInstance interp of #if defined(HAVE_INTERNAL_INTERPRETER) InternalInterp -> hostIsProfiled #endif - ExternalInterp c _ -> iservConfProfiled c + ExternalInterp ext -> case ext of + ExtIServ i -> iservConfProfiled (interpConfig i) + ExtJS {} -> False -- we don't support profiling yet in the JS backend -- | Interpreter uses Dynamic way interpreterDynamic :: Interp -> Bool @@ -759,4 +701,6 @@ interpreterDynamic interp = case interpInstance interp of #if defined(HAVE_INTERNAL_INTERPRETER) InternalInterp -> hostIsDynamic #endif - ExternalInterp c _ -> iservConfDynamic c + ExternalInterp ext -> case ext of + ExtIServ i -> iservConfDynamic (interpConfig i) + ExtJS {} -> False -- dynamic doesn't make sense for JS diff --git a/compiler/GHC/Runtime/Interpreter/JS.hs b/compiler/GHC/Runtime/Interpreter/JS.hs new file mode 100644 index 0000000000000000000000000000000000000000..3dce1204fa444e53365b73d3ae7bf5be13bc9c2f --- /dev/null +++ b/compiler/GHC/Runtime/Interpreter/JS.hs @@ -0,0 +1,403 @@ +{-# LANGUAGE GADTs #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE LambdaCase #-} + +-- | JavaScript interpreter +-- +-- See Note [The JS interpreter] +-- +module GHC.Runtime.Interpreter.JS + ( spawnJSInterp + , jsLinkRts + , jsLinkInterp + , jsLinkObject + , jsLinkObjects + , jsLoadFile + , jsRunServer + -- * Reexported for convenience + , mkExportedModFuns + ) +where + +import GHC.Prelude +import GHC.Runtime.Interpreter.Types +import GHC.Runtime.Interpreter.Process +import GHC.Runtime.Utils +import GHCi.Message + +import GHC.StgToJS.Linker.Types +import GHC.StgToJS.Linker.Linker +import GHC.StgToJS.Types +import GHC.StgToJS.Object + +import GHC.Unit.Env +import GHC.Unit.Types +import GHC.Unit.State + +import GHC.Utils.Logger +import GHC.Utils.TmpFs +import GHC.Utils.Panic +import GHC.Utils.Error (logInfo) +import GHC.Utils.Outputable (text) +import GHC.Data.FastString +import GHC.Types.Unique.FM + +import Control.Concurrent +import Control.Monad + +import System.Process +import System.IO +import System.FilePath + +import Data.IORef +import qualified Data.Set as Set +import qualified Data.ByteString as B + +import Foreign.C.String + + +-- Note [The JS interpreter] +-- ~~~~~~~~~~~~~~~~~~~~~~~~~ +-- +-- The JS interpreter works as follows: +-- +-- ghc-interp.js is a simple JS script used to bootstrap the external +-- interpreter server (iserv) that is written in Haskell. This script waits for +-- commands on stdin: +-- +-- LOAD foo.js +-- +-- load a JS file in the current JS environment +-- +-- RUN_SERVER ghci_unit_id +-- +-- execute h$main(h$ghci_unit_idZCGHCiziServerzidefaultServer), +-- the entry point of the interpreter server +-- +-- On the GHC side, when we need the interpreter we do the following: +-- +-- 1. spawn nodejs with $topdir/ghc-interp.js script +-- 2. link the JS rts and send a LOAD command to load it +-- 3. link iserv (i.e. use GHCi.Server.defaultServer as root) and LOAD it +-- 4. send a RUN_SERVER command to execute the JS iserv +-- +-- From this moment on, everything happens as with the native iserv, using a +-- pipe for communication, with the following differences: +-- +-- - the JS iserv only supports the LoadObj linking command which has been +-- repurposed to load a JS source file. The JS iserv doesn't deal with +-- libraries (.a) and with object files (.o). The linker state is maintained on +-- the GHC side and GHC only sends the appropriate chunks of JS code to link. +-- +-- - the JS iserv doesn't support ByteCode (i.e. it doesn't support CreateBCOs +-- messages). JS iserv clients should use the usual JS compilation pipeline and +-- send JS code instead. See GHC.Driver.Main.hscCompileCoreExpr for an example. +-- +-- GHC keeps track of JS blocks (JS unit of linking corresponding to top-level +-- binding groups) that have already been linked by the JS interpreter. It only +-- links new ones when necessary. +-- +-- Note that the JS interpreter isn't subject to staging issues: we can use it +-- in a Stage1 GHC. +-- + +--------------------------------------------------------- +-- Running node +--------------------------------------------------------- + +-- | Start NodeJS interactively with "ghc-interp.js" script loaded in +startTHRunnerProcess :: FilePath -> NodeJsSettings -> IO (Handle,InterpProcess) +startTHRunnerProcess interp_js settings = do + interp_in <- newIORef undefined + + let createProc cp = do + let cp' = cp + { std_in = CreatePipe + , std_out = Inherit + , std_err = Inherit + } + (mb_in, _mb_out, _mb_err, hdl) <- createProcess cp' + -- we can't directly return stdin for the process given the current + -- implementation of runWithPipes. So we just use an IORef for this... + case mb_in of + Nothing -> panic "startTHRunnerProcess: expected stdin for interpreter" + Just i -> writeIORef interp_in i + return hdl + + (hdl, rh, wh) <- runWithPipes createProc (nodeProgram settings) + [interp_js] + (nodeExtraArgs settings) + std_in <- readIORef interp_in + + lo_ref <- newIORef Nothing + let pipe = Pipe { pipeRead = rh, pipeWrite = wh, pipeLeftovers = lo_ref } + let proc = InterpProcess + { interpHandle = hdl + , interpPipe = pipe + } + pure (std_in, proc) + +-- | Spawn a JS interpreter +-- +-- Run NodeJS with "ghc-interp.js" loaded in. Then load GHCi.Server and its deps +-- (including the rts) and run GHCi.Server.defaultServer. +spawnJSInterp :: JSInterpConfig -> IO (ExtInterpInstance JSInterpExtra) +spawnJSInterp cfg = do + let logger= jsInterpLogger cfg + when (logVerbAtLeast logger 2) $ + logInfo logger (text "Spawning JS interpreter") + + let tmpfs = jsInterpTmpFs cfg + tmp_dir = jsInterpTmpDir cfg + logger = jsInterpLogger cfg + codegen_cfg = jsInterpCodegenCfg cfg + unit_env = jsInterpUnitEnv cfg + finder_opts = jsInterpFinderOpts cfg + finder_cache = jsInterpFinderCache cfg + + (std_in, proc) <- startTHRunnerProcess (jsInterpScript cfg) (jsInterpNodeConfig cfg) + + js_state <- newMVar (JSState + { jsLinkState = emptyLinkPlan + , jsServerStarted = False + }) + + -- get the unit-id of the ghci package. We need this to load the + -- interpreter code. + ghci_unit_id <- case lookupPackageName (ue_units unit_env) (PackageName (fsLit "ghci")) of + Nothing -> cmdLineErrorIO "JS interpreter: couldn't find \"ghci\" package" + Just i -> pure i + + let extra = JSInterpExtra + { instStdIn = std_in + , instJSState = js_state + , instFinderCache = finder_cache + , instFinderOpts = finder_opts + , instGhciUnitId = ghci_unit_id + } + + pending_frees <- newMVar [] + lookup_cache <- newMVar emptyUFM + let inst = ExtInterpInstance + { instProcess = proc + , instPendingFrees = pending_frees + , instLookupSymbolCache = lookup_cache + , instExtra = extra + } + + -- link rts and its deps + jsLinkRts logger tmpfs tmp_dir codegen_cfg unit_env inst + + -- link interpreter and its deps + jsLinkInterp logger tmpfs tmp_dir codegen_cfg unit_env inst + + -- run interpreter main loop + jsRunServer inst + + pure inst + + + +--------------------------------------------------------- +-- Interpreter commands +--------------------------------------------------------- + +-- | Link JS RTS +jsLinkRts :: Logger -> TmpFs -> TempDir -> StgToJSConfig -> UnitEnv -> ExtInterpInstance JSInterpExtra -> IO () +jsLinkRts logger tmpfs tmp_dir cfg unit_env inst = do + let link_cfg = JSLinkConfig + { lcNoStats = True -- we don't need the stats + , lcNoRts = False -- we need the RTS + , lcCombineAll = False -- we don't need the combined all.js, we'll link each part independently below + , lcForeignRefs = False -- we don't need foreign references + , lcNoJSExecutables = True -- we don't need executables + , lcNoHsMain = True -- nor HsMain + } + + -- link the RTS and its dependencies (things it uses from `base`, etc.) + let link_spec = LinkSpec + { lks_unit_ids = [rtsUnitId, baseUnitId, primUnitId] + , lks_obj_files = mempty + , lks_obj_root_filter = const False + , lks_extra_roots = mempty + , lks_extra_js = mempty + } + + let finder_opts = instFinderOpts (instExtra inst) + finder_cache = instFinderCache (instExtra inst) + + link_plan <- computeLinkDependencies cfg unit_env link_spec finder_opts finder_cache + jsLinkPlan logger tmpfs tmp_dir link_cfg cfg inst link_plan + +-- | Link JS interpreter +jsLinkInterp :: Logger -> TmpFs -> TempDir -> StgToJSConfig -> UnitEnv -> ExtInterpInstance JSInterpExtra -> IO () +jsLinkInterp logger tmpfs tmp_dir cfg unit_env inst = do + + let link_cfg = JSLinkConfig + { lcNoStats = True -- we don't need the stats + , lcNoRts = True -- we don't need the RTS + , lcCombineAll = False -- we don't need the combined all.js, we'll link each part independently below + , lcForeignRefs = False -- we don't need foreign references + , lcNoJSExecutables = True -- we don't need executables + , lcNoHsMain = True -- nor HsMain + } + + let is_root _ = True -- FIXME: we shouldn't consider every function as a root + + let ghci_unit_id = instGhciUnitId (instExtra inst) + + -- compute unit dependencies of ghc_unit_id + let unit_map = unitInfoMap (ue_units unit_env) + dep_units <- mayThrowUnitErr $ closeUnitDeps unit_map [(ghci_unit_id,Nothing)] + let units = dep_units ++ [ghci_unit_id] + + -- indicate that our root function is GHCi.Server.defaultServer + let root_deps = Set.fromList $ mkExportedFuns ghci_unit_id (fsLit "GHCi.Server") [fsLit "defaultServer"] + + -- link the interpreter and its dependencies + let link_spec = LinkSpec + { lks_unit_ids = units + , lks_obj_files = mempty + , lks_obj_root_filter = is_root + , lks_extra_roots = root_deps + , lks_extra_js = mempty + } + + let finder_cache = instFinderCache (instExtra inst) + finder_opts = instFinderOpts (instExtra inst) + + link_plan <- computeLinkDependencies cfg unit_env link_spec finder_opts finder_cache + + jsLinkPlan logger tmpfs tmp_dir link_cfg cfg inst link_plan + + +-- | Link object files +jsLinkObjects :: Logger -> TmpFs -> TempDir -> StgToJSConfig -> UnitEnv -> ExtInterpInstance JSInterpExtra -> [FilePath] -> (ExportedFun -> Bool) -> IO () +jsLinkObjects logger tmpfs tmp_dir cfg unit_env inst objs is_root = do + let link_cfg = JSLinkConfig + { lcNoStats = True -- we don't need the stats + , lcNoRts = True -- we don't need the RTS (already linked) + , lcCombineAll = False -- we don't need the combined all.js, we'll link each part independently below + , lcForeignRefs = False -- we don't need foreign references + , lcNoJSExecutables = True -- we don't need executables + , lcNoHsMain = True -- nor HsMain + } + + let units = preloadUnits (ue_units unit_env) + ++ [thUnitId] -- don't forget TH which is an implicit dep + + -- compute dependencies + let link_spec = LinkSpec + { lks_unit_ids = units + , lks_obj_files = fmap ObjFile objs + , lks_obj_root_filter = is_root + , lks_extra_roots = mempty + , lks_extra_js = mempty + } + + let finder_opts = instFinderOpts (instExtra inst) + finder_cache = instFinderCache (instExtra inst) + + link_plan <- computeLinkDependencies cfg unit_env link_spec finder_opts finder_cache + + -- link + jsLinkPlan logger tmpfs tmp_dir link_cfg cfg inst link_plan + + + +-- | Link an object file using the given functions as roots +jsLinkObject :: Logger -> TmpFs -> TempDir -> StgToJSConfig -> UnitEnv -> ExtInterpInstance JSInterpExtra -> FilePath -> [ExportedFun] -> IO () +jsLinkObject logger tmpfs tmp_dir cfg unit_env inst obj roots = do + let is_root f = Set.member f (Set.fromList roots) + let objs = [obj] + jsLinkObjects logger tmpfs tmp_dir cfg unit_env inst objs is_root + + +-- | Link the given link plan +-- +-- Perform incremental linking by removing what is already linked from the plan +jsLinkPlan :: Logger -> TmpFs -> TempDir -> JSLinkConfig -> StgToJSConfig -> ExtInterpInstance JSInterpExtra -> LinkPlan -> IO () +jsLinkPlan logger tmpfs tmp_dir link_cfg cfg inst link_plan = do + ---------------------------------------------------------------- + -- Get already linked stuff and compute incremental plan + ---------------------------------------------------------------- + + old_plan <- jsLinkState <$> readMVar (instJSState (instExtra inst)) + + -- compute new plan discarding what's already linked + let (diff_plan, total_plan) = incrementLinkPlan old_plan link_plan + + ---------------------------------------------------------------- + -- Generate JS code for the incremental plan + ---------------------------------------------------------------- + + tmp_out <- newTempSubDir logger tmpfs tmp_dir + void $ jsLink link_cfg cfg logger tmp_out diff_plan + + -- Code has been linked into the following files: + -- - generated rts from tmp_out/rts.js (depends on link options) + -- - raw js files from tmp_out/lib.js + -- - Haskell generated JS from tmp_out/out.js + + -- We need to combine at least rts.js and lib.js for the RTS because they + -- depend on each other. We might as well combine them all, so that's what we + -- do. + let filenames + | lcNoRts link_cfg = ["lib.js", "out.js"] + | otherwise = ["rts.js", "lib.js", "out.js"] + let files = map (tmp_out </>) filenames + let all_js = tmp_out </> "all.js" + let all_files = all_js : files + withBinaryFile all_js WriteMode $ \h -> do + let cpy i = B.readFile i >>= B.hPut h + mapM_ cpy files + + -- add files to clean + addFilesToClean tmpfs TFL_CurrentModule all_files + + ---------------------------------------------------------------- + -- Link JS code + ---------------------------------------------------------------- + + -- linking JS code depends on the phase we're in: + -- - during in the initialization phase, we send a LoadFile message to the + -- JS server; + -- - once the Haskell server is started, we send a LoadObj message to the + -- Haskell server. + server_started <- jsServerStarted <$> readMVar (instJSState (instExtra inst)) + if server_started + then sendMessageNoResponse inst $ LoadObj all_js + else jsLoadFile inst all_js + + ---------------------------------------------------------------- + -- update linker state + ---------------------------------------------------------------- + modifyMVar_ (instJSState (instExtra inst)) $ \state -> pure state { jsLinkState = total_plan } + + +-- | Send a command to the JS interpreter +jsSendCommand :: ExtInterpInstance JSInterpExtra -> String -> IO () +jsSendCommand inst cmd = send_cmd cmd + where + extra = instExtra inst + handle = instStdIn extra + send_cmd s = do + withCStringLen s \(p,n) -> hPutBuf handle p n + hFlush handle + +-- | Load a JS file in the interpreter +jsLoadFile :: ExtInterpInstance JSInterpExtra -> FilePath -> IO () +jsLoadFile inst path = jsSendCommand inst ("LOAD " ++ path ++ "\n") + +-- | Run JS server +jsRunServer :: ExtInterpInstance JSInterpExtra -> IO () +jsRunServer inst = do + let ghci_unit_id = instGhciUnitId (instExtra inst) + let zghci_unit_id = zString (zEncodeFS (unitIdFS ghci_unit_id)) + + -- Run `GHCi.Server.defaultServer` + jsSendCommand inst ("RUN_SERVER " ++ zghci_unit_id ++ "\n") + + -- indicate that the Haskell server is now started + modifyMVar_ (instJSState (instExtra inst)) $ \state -> pure state { jsServerStarted = True } diff --git a/compiler/GHC/Runtime/Interpreter/Process.hs b/compiler/GHC/Runtime/Interpreter/Process.hs new file mode 100644 index 0000000000000000000000000000000000000000..a93d00d7bc7216f391ce6fcd6f237753608bce8f --- /dev/null +++ b/compiler/GHC/Runtime/Interpreter/Process.hs @@ -0,0 +1,102 @@ +module GHC.Runtime.Interpreter.Process + ( + -- * Low-level API + callInterpProcess + , readInterpProcess + , writeInterpProcess + + -- * Message API + , Message(..) + , DelayedResponse (..) + , sendMessage + , sendMessageNoResponse + , sendMessageDelayedResponse + , sendAnyValue + , receiveAnyValue + , receiveDelayedResponse + , receiveTHMessage + + ) +where + +import GHC.Prelude + +import GHC.Runtime.Interpreter.Types +import GHCi.Message + +import GHC.IO (catchException) +import GHC.Utils.Panic +import GHC.Utils.Exception as Ex + +import Data.Binary +import System.Exit +import System.Process + +data DelayedResponse a = DelayedResponse + +-- | Send a message to the interpreter process that doesn't expect a response +sendMessageNoResponse :: ExtInterpInstance d -> Message () -> IO () +sendMessageNoResponse i m = writeInterpProcess (instProcess i) (putMessage m) + +-- | Send a message to the interpreter that excepts a response +sendMessage :: Binary a => ExtInterpInstance d -> Message a -> IO a +sendMessage i m = callInterpProcess (instProcess i) m + +-- | Send a message to the interpreter process whose response is expected later +-- +-- This is useful to avoid forgetting to receive the value and to ensure that +-- the type of the response isn't lost. Use receiveDelayedResponse to read it. +sendMessageDelayedResponse :: ExtInterpInstance d -> Message a -> IO (DelayedResponse a) +sendMessageDelayedResponse i m = do + writeInterpProcess (instProcess i) (putMessage m) + pure DelayedResponse + +-- | Send any value +sendAnyValue :: Binary a => ExtInterpInstance d -> a -> IO () +sendAnyValue i m = writeInterpProcess (instProcess i) (put m) + +-- | Expect a value to be received +receiveAnyValue :: ExtInterpInstance d -> Get a -> IO a +receiveAnyValue i get = readInterpProcess (instProcess i) get + +-- | Expect a delayed result to be received now +receiveDelayedResponse :: Binary a => ExtInterpInstance d -> DelayedResponse a -> IO a +receiveDelayedResponse i DelayedResponse = readInterpProcess (instProcess i) get + +-- | Expect a value to be received +receiveTHMessage :: ExtInterpInstance d -> IO THMsg +receiveTHMessage i = receiveAnyValue i getTHMessage + + +-- ----------------------------------------------------------------------------- +-- Low-level API + +-- | Send a 'Message' and receive the response from the interpreter process +callInterpProcess :: Binary a => InterpProcess -> Message a -> IO a +callInterpProcess i msg = + remoteCall (interpPipe i) msg + `catchException` \(e :: SomeException) -> handleInterpProcessFailure i e + +-- | Read a value from the interpreter process +readInterpProcess :: InterpProcess -> Get a -> IO a +readInterpProcess i get = + readPipe (interpPipe i) get + `catchException` \(e :: SomeException) -> handleInterpProcessFailure i e + +-- | Send a value to the interpreter process +writeInterpProcess :: InterpProcess -> Put -> IO () +writeInterpProcess i put = + writePipe (interpPipe i) put + `catchException` \(e :: SomeException) -> handleInterpProcessFailure i e + +handleInterpProcessFailure :: InterpProcess -> SomeException -> IO a +handleInterpProcessFailure i e = do + let hdl = interpHandle i + ex <- getProcessExitCode hdl + case ex of + Just (ExitFailure n) -> + throwIO (InstallationError ("External interpreter terminated (" ++ show n ++ ")")) + _ -> do + terminateProcess hdl + _ <- waitForProcess hdl + throw e diff --git a/compiler/GHC/Runtime/Interpreter/Types.hs b/compiler/GHC/Runtime/Interpreter/Types.hs index e1b33198d0e587a2322ef39327676999265a48cf..962c21491fd1eca1f9445fdfee98d61b1ef9ae3c 100644 --- a/compiler/GHC/Runtime/Interpreter/Types.hs +++ b/compiler/GHC/Runtime/Interpreter/Types.hs @@ -4,10 +4,22 @@ module GHC.Runtime.Interpreter.Types ( Interp(..) , InterpInstance(..) - , IServ(..) - , IServInstance(..) + , InterpProcess (..) + , ExtInterp (..) + , ExtInterpStatusVar + , ExtInterpInstance (..) + , ExtInterpState (..) + , InterpStatus(..) + -- * IServ + , IServ , IServConfig(..) - , IServState(..) + -- * JSInterp + , JSInterp + , JSInterpExtra (..) + , JSInterpConfig (..) + , JSState (..) + , NodeJsSettings (..) + , defaultNodeJsSettings ) where @@ -20,8 +32,17 @@ import GHC.Types.Unique.FM import GHC.Data.FastString ( FastString ) import Foreign +import GHC.Utils.TmpFs +import GHC.Utils.Logger +import GHC.Unit.Env +import GHC.Unit.Types +import GHC.StgToJS.Types +import GHC.StgToJS.Linker.Types + import Control.Concurrent import System.Process ( ProcessHandle, CreateProcess ) +import System.IO +import GHC.Unit.Finder.Types (FinderCache, FinderOpts) -- | Interpreter data Interp = Interp @@ -32,24 +53,40 @@ data Interp = Interp -- ^ Interpreter loader } - data InterpInstance - = ExternalInterp !IServConfig !IServ -- ^ External interpreter + = ExternalInterp !ExtInterp -- ^ External interpreter #if defined(HAVE_INTERNAL_INTERPRETER) - | InternalInterp -- ^ Internal interpreter + | InternalInterp -- ^ Internal interpreter #endif +data ExtInterp + = ExtIServ !IServ + | ExtJS !JSInterp + -- | External interpreter -- -- The external interpreter is spawned lazily (on first use) to avoid slowing -- down sessions that don't require it. The contents of the MVar reflects the -- state of the interpreter (running or not). -newtype IServ = IServ (MVar IServState) +data ExtInterpState cfg details = ExtInterpState + { interpConfig :: !cfg + , interpStatus :: !(ExtInterpStatusVar details) + } + +type ExtInterpStatusVar d = MVar (InterpStatus (ExtInterpInstance d)) + +type IServ = ExtInterpState IServConfig () +type JSInterp = ExtInterpState JSInterpConfig JSInterpExtra --- | State of an external interpreter -data IServState - = IServPending -- ^ Not spawned yet - | IServRunning !IServInstance -- ^ Running +data InterpProcess = InterpProcess + { interpPipe :: !Pipe -- ^ Pipe to communicate with the server + , interpHandle :: !ProcessHandle -- ^ Process handle of the server + } + +-- | Status of an external interpreter +data InterpStatus inst + = InterpPending -- ^ Not spawned yet + | InterpRunning !inst -- ^ Running -- | Configuration needed to spawn an external interpreter data IServConfig = IServConfig @@ -61,14 +98,66 @@ data IServConfig = IServConfig , iservConfTrace :: IO () -- ^ Trace action executed after spawn } --- | External interpreter instance -data IServInstance = IServInstance - { iservPipe :: !Pipe - , iservProcess :: !ProcessHandle - , iservLookupSymbolCache :: !(UniqFM FastString (Ptr ())) - , iservPendingFrees :: ![HValueRef] +-- | Common field between native external interpreter and the JS one +data ExtInterpInstance c = ExtInterpInstance + { instProcess :: {-# UNPACK #-} !InterpProcess + -- ^ External interpreter process and its pipe (communication channel) + + , instPendingFrees :: !(MVar [HValueRef]) -- ^ Values that need to be freed before the next command is sent. - -- Threads can append values to this list asynchronously (by modifying the - -- IServ state MVar). + -- Finalizers for ForeignRefs can append values to this list + -- asynchronously. + + , instLookupSymbolCache :: !(MVar (UniqFM FastString (Ptr ()))) + -- ^ LookupSymbol cache + + , instExtra :: !c + -- ^ Instance specific extra fields + } + +------------------------ +-- JS Stuff +------------------------ + +data JSInterpExtra = JSInterpExtra + { instStdIn :: !Handle -- ^ Stdin for the process + , instFinderCache :: !FinderCache + , instFinderOpts :: !FinderOpts + , instJSState :: !(MVar JSState) -- ^ Mutable state + , instGhciUnitId :: !UnitId -- ^ GHCi unit-id + } + +data JSState = JSState + { jsLinkState :: !LinkPlan -- ^ Linker state of the interpreter + , jsServerStarted :: !Bool -- ^ Is the Haskell server started? + } + +-- | NodeJs configuration +data NodeJsSettings = NodeJsSettings + { nodeProgram :: FilePath -- ^ location of node.js program + , nodePath :: Maybe FilePath -- ^ value of NODE_PATH environment variable (search path for Node modules; GHCJS used to provide some) + , nodeExtraArgs :: [String] -- ^ extra arguments to pass to node.js + , nodeKeepAliveMaxMem :: Integer -- ^ keep node.js (TH, GHCJSi) processes alive if they don't use more than this + } + +defaultNodeJsSettings :: NodeJsSettings +defaultNodeJsSettings = NodeJsSettings + { nodeProgram = "node" + , nodePath = Nothing + , nodeExtraArgs = [] + , nodeKeepAliveMaxMem = 536870912 + } + + +data JSInterpConfig = JSInterpConfig + { jsInterpNodeConfig :: !NodeJsSettings -- ^ NodeJS settings + , jsInterpScript :: !FilePath -- ^ Path to "ghc-interp.js" script + , jsInterpTmpFs :: !TmpFs + , jsInterpTmpDir :: !TempDir + , jsInterpLogger :: !Logger + , jsInterpCodegenCfg :: !StgToJSConfig + , jsInterpUnitEnv :: !UnitEnv + , jsInterpFinderOpts :: !FinderOpts + , jsInterpFinderCache :: !FinderCache } diff --git a/compiler/GHC/Runtime/Utils.hs b/compiler/GHC/Runtime/Utils.hs new file mode 100644 index 0000000000000000000000000000000000000000..083d592990639f08b15aedc8dbc59e778383ec6d --- /dev/null +++ b/compiler/GHC/Runtime/Utils.hs @@ -0,0 +1,84 @@ +{-# LANGUAGE CPP #-} + +module GHC.Runtime.Utils + ( runWithPipes + ) +where + +import GHC.Prelude + +#if defined(mingw32_HOST_OS) +import Foreign.C +import GHC.IO.Handle.FD (fdToHandle) +import GHC.Utils.Exception as Ex +# if defined(__IO_MANAGER_WINIO__) +import GHC.IO.SubSystem ((<!>)) +import GHC.IO.Handle.Windows (handleToHANDLE) +import GHC.Event.Windows (associateHandle') +# endif +#else +import System.Posix as Posix +#endif +import System.Process +import System.IO + +runWithPipes :: (CreateProcess -> IO ProcessHandle) + -> FilePath -> [String] -> [String] -> IO (ProcessHandle, Handle, Handle) +#if defined(mingw32_HOST_OS) +foreign import ccall "io.h _close" + c__close :: CInt -> IO CInt + +foreign import ccall unsafe "io.h _get_osfhandle" + _get_osfhandle :: CInt -> IO CInt + +runWithPipesPOSIX :: (CreateProcess -> IO ProcessHandle) + -> FilePath -> [String] -> [String] -> IO (ProcessHandle, Handle, Handle) +runWithPipesPOSIX createProc prog pre_opts opts = do + (rfd1, wfd1) <- createPipeFd -- we read on rfd1 + (rfd2, wfd2) <- createPipeFd -- we write on wfd2 + wh_client <- _get_osfhandle wfd1 + rh_client <- _get_osfhandle rfd2 + let args = pre_opts ++ (show wh_client : show rh_client : opts) + ph <- createProc (proc prog args) + rh <- mkHandle rfd1 + wh <- mkHandle wfd2 + return (ph, rh, wh) + where mkHandle :: CInt -> IO Handle + mkHandle fd = (fdToHandle fd) `Ex.onException` (c__close fd) + +# if defined (__IO_MANAGER_WINIO__) +runWithPipesNative :: (CreateProcess -> IO ProcessHandle) + -> FilePath -> [String] -> [String] -> IO (ProcessHandle, Handle, Handle) +runWithPipesNative createProc prog pre_opts opts = do + (rh, wfd1) <- createPipe -- we read on rfd1 + (rfd2, wh) <- createPipe -- we write on wfd2 + wh_client <- handleToHANDLE wfd1 + rh_client <- handleToHANDLE rfd2 + -- Associate the handle with the current manager + -- but don't touch the ones we're passing to the child + -- since it needs to register the handle with its own manager. + associateHandle' =<< handleToHANDLE rh + associateHandle' =<< handleToHANDLE wh + let args = pre_opts ++ (show wh_client : show rh_client : opts) + ph <- createProc (proc prog args) + return (ph, rh, wh) + +runWithPipes = runWithPipesPOSIX <!> runWithPipesNative +# else +runWithPipes = runWithPipesPOSIX +# endif +#else +runWithPipes createProc prog pre_opts opts = do + (rfd1, wfd1) <- Posix.createPipe -- we read on rfd1 + (rfd2, wfd2) <- Posix.createPipe -- we write on wfd2 + setFdOption rfd1 CloseOnExec True + setFdOption wfd2 CloseOnExec True + let args = pre_opts ++ (show wfd1 : show rfd2 : opts) + ph <- createProc (proc prog args) + closeFd wfd1 + closeFd rfd2 + rh <- fdToHandle rfd1 + wh <- fdToHandle wfd2 + return (ph, rh, wh) +#endif + diff --git a/compiler/GHC/StgToJS/CodeGen.hs b/compiler/GHC/StgToJS/CodeGen.hs index 8af6215c7c75cde7f781b6641d2bc180d07ef76d..8b04f9bb5f4fb13475382c70d833199d4b77a66b 100644 --- a/compiler/GHC/StgToJS/CodeGen.hs +++ b/compiler/GHC/StgToJS/CodeGen.hs @@ -90,11 +90,11 @@ stgToJS logger config stg_binds0 this_mod spt_entries foreign_stubs cccs output_ -- Doc to dump when -ddump-js is enabled when (logHasDumpFlag logger Opt_D_dump_js) $ do putDumpFileMaybe logger Opt_D_dump_js "JavaScript code" FormatJS - $ vcat (fmap (jsToDoc . oiStat . luObjUnit) lus) + $ vcat (fmap (jsToDoc . oiStat . luObjBlock) lus) -- Write the object file bh <- openBinMem (4 * 1024 * 1000) -- a bit less than 4kB - Object.putObject bh (moduleName this_mod) deps (map luObjUnit lus) + Object.putObject bh (moduleName this_mod) deps (map luObjBlock lus) createDirectoryIfMissing True (takeDirectory output_fn) writeBinMem bh output_fn @@ -137,7 +137,7 @@ genUnits m ss spt_entries foreign_stubs = do satJStat (Just $ modulePrefix m 1) $ mconcat (reverse glbl) <> staticInit) let syms = [moduleGlobalSymbol m] - let oi = ObjUnit + let oi = ObjBlock { oiSymbols = syms , oiClInfo = [] , oiStatic = [] @@ -147,7 +147,7 @@ genUnits m ss spt_entries foreign_stubs = do , oiFImports = [] } let lu = LinkableUnit - { luObjUnit = oi + { luObjBlock = oi , luIdExports = [] , luOtherExports = syms , luIdDeps = [] @@ -169,7 +169,7 @@ genUnits m ss spt_entries foreign_stubs = do let syms = [moduleExportsSymbol m] let raw = utf8EncodeByteString $ renderWithContext defaultSDocContext f_c - let oi = ObjUnit + let oi = ObjBlock { oiSymbols = syms , oiClInfo = [] , oiStatic = [] @@ -179,7 +179,7 @@ genUnits m ss spt_entries foreign_stubs = do , oiFImports = [] } let lu = LinkableUnit - { luObjUnit = oi + { luObjBlock = oi , luIdExports = [] , luOtherExports = syms , luIdDeps = [] @@ -211,7 +211,7 @@ genUnits m ss spt_entries foreign_stubs = do $ satJStat (Just $ modulePrefix m n) body let ids = [bnd] syms <- (\(TxtI i) -> [i]) <$> identForId bnd - let oi = ObjUnit + let oi = ObjBlock { oiSymbols = syms , oiClInfo = [] , oiStatic = si @@ -221,7 +221,7 @@ genUnits m ss spt_entries foreign_stubs = do , oiFImports = [] } let lu = LinkableUnit - { luObjUnit = oi + { luObjBlock = oi , luIdExports = ids , luOtherExports = [] , luIdDeps = [] @@ -249,7 +249,7 @@ genUnits m ss spt_entries foreign_stubs = do . satJStat (Just $ modulePrefix m n) $ mconcat (reverse extraTl) <> tl syms <- mapM (fmap (\(TxtI i) -> i) . identForId) topDeps - let oi = ObjUnit + let oi = ObjBlock { oiSymbols = syms , oiClInfo = ci , oiStatic = si @@ -259,7 +259,7 @@ genUnits m ss spt_entries foreign_stubs = do , oiFImports = fRefs } let lu = LinkableUnit - { luObjUnit = oi + { luObjBlock = oi , luIdExports = topDeps , luOtherExports = [] , luIdDeps = allDeps diff --git a/compiler/GHC/StgToJS/Deps.hs b/compiler/GHC/StgToJS/Deps.hs index e76d3afee14fb29aaad5402116ebbf6662127a85..52628b3eee98798af6f54299cd57dd33a5340b80 100644 --- a/compiler/GHC/StgToJS/Deps.hs +++ b/compiler/GHC/StgToJS/Deps.hs @@ -22,7 +22,7 @@ where import GHC.Prelude -import GHC.StgToJS.Object as Object +import GHC.StgToJS.Object import GHC.StgToJS.Types import GHC.StgToJS.Ids @@ -55,9 +55,9 @@ import Control.Monad.Trans.Class import Control.Monad.Trans.State data DependencyDataCache = DDC - { ddcModule :: !(IntMap Unit) -- ^ Unique Module -> Unit - , ddcId :: !(IntMap Object.ExportedFun) -- ^ Unique Id -> Object.ExportedFun (only to other modules) - , ddcOther :: !(Map OtherSymb Object.ExportedFun) + { ddcModule :: !(IntMap Unit) -- ^ Unique Module -> Unit + , ddcId :: !(IntMap ExportedFun) -- ^ Unique Id -> ExportedFun (only to other modules) + , ddcOther :: !(Map OtherSymb ExportedFun) } -- | Generate module dependency data @@ -68,16 +68,15 @@ genDependencyData :: HasDebugCallStack => Module -> [LinkableUnit] - -> G Object.Deps + -> G BlockInfo genDependencyData mod units = do - -- [(blockindex, blockdeps, required, exported)] ds <- evalStateT (mapM (uncurry oneDep) blocks) (DDC IM.empty IM.empty M.empty) - return $ Object.Deps - { depsModule = mod - , depsRequired = IS.fromList [ n | (n, _, True, _) <- ds ] - , depsHaskellExported = M.fromList $ (\(n,_,_,es) -> map (,n) es) =<< ds - , depsBlocks = listArray (0, length blocks-1) (map (\(_,deps,_,_) -> deps) ds) + return $ BlockInfo + { bi_module = mod + , bi_must_link = IS.fromList [ n | (n, _, True, _) <- ds ] + , bi_exports = M.fromList $ (\(n,_,_,es) -> map (,n) es) =<< ds + , bi_block_deps = listArray (0, length blocks-1) (map (\(_,deps,_,_) -> deps) ds) } where -- Id -> Block @@ -99,7 +98,7 @@ genDependencyData mod units = do -- generate the list of exports and set of dependencies for one unit oneDep :: LinkableUnit -> Int - -> StateT DependencyDataCache G (Int, Object.BlockDeps, Bool, [Object.ExportedFun]) + -> StateT DependencyDataCache G (Int, BlockDeps, Bool, [ExportedFun]) oneDep (LinkableUnit _ idExports otherExports idDeps pseudoIdDeps otherDeps req _frefs) n = do (edi, bdi) <- partitionEithers <$> mapM (lookupIdFun n) idDeps (edo, bdo) <- partitionEithers <$> mapM lookupOtherFun otherDeps @@ -107,9 +106,10 @@ genDependencyData mod units = do expi <- mapM lookupExportedId (filter isExportedId idExports) expo <- mapM lookupExportedOther otherExports -- fixme thin deps, remove all transitive dependencies! - let bdeps = Object.BlockDeps - (IS.toList . IS.fromList . filter (/=n) $ bdi++bdo++bdp) - (S.toList . S.fromList $ edi++edo++edp) + let bdeps = BlockDeps + { blockBlockDeps = IS.toList . IS.fromList . filter (/=n) $ bdi++bdo++bdp + , blockFunDeps = S.toList . S.fromList $ edi++edo++edp + } return (n, bdeps, req, expi++expo) idModule :: Id -> Maybe Module @@ -117,7 +117,7 @@ genDependencyData mod units = do guard (m /= mod) >> return m lookupPseudoIdFun :: Int -> Unique - -> StateT DependencyDataCache G (Either Object.ExportedFun Int) + -> StateT DependencyDataCache G (Either ExportedFun Int) lookupPseudoIdFun _n u = case lookupUFM_Directly unitIdExports u of Just k -> return (Right k) @@ -130,14 +130,14 @@ genDependencyData mod units = do -- assumes function is internal to the current block if it's -- from teh current module and not in the unitIdExports map. lookupIdFun :: Int -> Id - -> StateT DependencyDataCache G (Either Object.ExportedFun Int) + -> StateT DependencyDataCache G (Either ExportedFun Int) lookupIdFun n i = case lookupUFM unitIdExports i of Just k -> return (Right k) Nothing -> case idModule i of Nothing -> return (Right n) Just m -> let k = getKey . getUnique $ i - addEntry :: StateT DependencyDataCache G Object.ExportedFun + addEntry :: StateT DependencyDataCache G ExportedFun addEntry = do (TxtI idTxt) <- lift (identForId i) lookupExternalFun (Just k) (OtherSymb m idTxt) @@ -149,7 +149,7 @@ genDependencyData mod units = do -- get the function for an OtherSymb from the cache, add it if necessary lookupOtherFun :: OtherSymb - -> StateT DependencyDataCache G (Either Object.ExportedFun Int) + -> StateT DependencyDataCache G (Either ExportedFun Int) lookupOtherFun od@(OtherSymb m idTxt) = case M.lookup od unitOtherExports of Just n -> return (Right n) @@ -157,22 +157,22 @@ genDependencyData mod units = do Nothing -> Left <$> (maybe (lookupExternalFun Nothing od) return =<< gets (M.lookup od . ddcOther)) - lookupExportedId :: Id -> StateT DependencyDataCache G Object.ExportedFun + lookupExportedId :: Id -> StateT DependencyDataCache G ExportedFun lookupExportedId i = do (TxtI idTxt) <- lift (identForId i) lookupExternalFun (Just . getKey . getUnique $ i) (OtherSymb mod idTxt) - lookupExportedOther :: FastString -> StateT DependencyDataCache G Object.ExportedFun + lookupExportedOther :: FastString -> StateT DependencyDataCache G ExportedFun lookupExportedOther = lookupExternalFun Nothing . OtherSymb mod -- lookup a dependency to another module, add to the id cache if there's -- an id key, otherwise add to other cache lookupExternalFun :: Maybe Int - -> OtherSymb -> StateT DependencyDataCache G Object.ExportedFun + -> OtherSymb -> StateT DependencyDataCache G ExportedFun lookupExternalFun mbIdKey od@(OtherSymb m idTxt) = do let mk = getKey . getUnique $ m mpk = moduleUnit m - exp_fun = Object.ExportedFun m (LexicalFastString idTxt) + exp_fun = ExportedFun m (LexicalFastString idTxt) addCache = do ms <- gets ddcModule let !cache' = IM.insert mk mpk ms diff --git a/compiler/GHC/StgToJS/Linker/Linker.hs b/compiler/GHC/StgToJS/Linker/Linker.hs index 635b2b659f08ea81de714162e7b2c2f7553fa71f..be6f222fda41281981c8562d79f595602f198872 100644 --- a/compiler/GHC/StgToJS/Linker/Linker.hs +++ b/compiler/GHC/StgToJS/Linker/Linker.hs @@ -21,7 +21,17 @@ module GHC.StgToJS.Linker.Linker ( jsLinkBinary + , jsLink , embedJsFile + , staticInitStat + , staticDeclStat + , mkExportedFuns + , mkExportedModFuns + , computeLinkDependencies + , LinkSpec (..) + , LinkPlan (..) + , emptyLinkPlan + , incrementLinkPlan ) where @@ -41,6 +51,7 @@ import GHC.SysTools.Cpp import GHC.SysTools import GHC.Linker.Static.Utils (exeFileName) +import GHC.Linker.Types (Unlinked(..), linkableUnlinked) import GHC.StgToJS.Linker.Types import GHC.StgToJS.Linker.Utils @@ -54,7 +65,7 @@ import GHC.StgToJS.Closure import GHC.Unit.State import GHC.Unit.Env -import GHC.Unit.Home +import GHC.Unit.Home.ModInfo import GHC.Unit.Types import GHC.Unit.Module (moduleStableString) @@ -75,7 +86,6 @@ import qualified GHC.SysTools.Ar as Ar import qualified GHC.Data.ShortText as ST import GHC.Data.FastString -import Control.Concurrent.MVar import Control.Monad import Data.Array @@ -84,13 +94,9 @@ import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString as BS import Data.Function (on) -import Data.IntSet (IntSet) import qualified Data.IntSet as IS import Data.IORef -import Data.List ( partition, nub, intercalate, sort - , groupBy, intersperse, - ) -import qualified Data.List.NonEmpty as NE +import Data.List ( nub, intercalate, groupBy, intersperse, sortBy) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe @@ -108,6 +114,10 @@ import System.Directory ( createDirectoryIfMissing , getPermissions ) +import GHC.Unit.Finder.Types +import GHC.Unit.Finder (findObjectLinkableMaybe, findHomeModule) +import GHC.Driver.Config.Finder (initFinderOpts) + data LinkerStats = LinkerStats { bytesPerModule :: !(Map Module Word64) -- ^ number of bytes linked per module , packedMetaDataSize :: !Word64 -- ^ number of bytes for metadata @@ -122,7 +132,8 @@ defaultJsContext :: SDocContext defaultJsContext = defaultSDocContext{sdocStyle = PprCode} jsLinkBinary - :: JSLinkConfig + :: FinderCache + -> JSLinkConfig -> StgToJSConfig -> [FilePath] -> Logger @@ -131,7 +142,7 @@ jsLinkBinary -> [FilePath] -> [UnitId] -> IO () -jsLinkBinary lc_cfg cfg js_srcs logger dflags u_env objs dep_pkgs +jsLinkBinary finder_cache lc_cfg cfg js_srcs logger dflags unit_env objs dep_units | lcNoJSExecutables lc_cfg = return () | otherwise = do -- additional objects to link are passed as FileOption ldInputs... @@ -141,47 +152,56 @@ jsLinkBinary lc_cfg cfg js_srcs logger dflags u_env objs dep_pkgs let objs' = map ObjFile (objs ++ cmdline_js_objs) js_srcs' = js_srcs ++ cmdline_js_srcs - isRoot _ = True + is_root _ = True -- FIXME: we shouldn't consider every function as a root, + -- but only the program entry point (main), either the + -- generated one or coming from an object exe = jsExeFileName dflags - void $ link lc_cfg cfg logger u_env exe mempty dep_pkgs objs' js_srcs' isRoot mempty + -- compute dependencies + let link_spec = LinkSpec + { lks_unit_ids = dep_units + , lks_obj_files = objs' + , lks_obj_root_filter = is_root + , lks_extra_roots = mempty + , lks_extra_js = js_srcs' + } + + let finder_opts = initFinderOpts dflags + + link_plan <- computeLinkDependencies cfg unit_env link_spec finder_opts finder_cache + + void $ jsLink lc_cfg cfg logger exe link_plan -- | link and write result to disk (jsexe directory) -link :: JSLinkConfig +jsLink + :: JSLinkConfig -> StgToJSConfig -> Logger - -> UnitEnv -> FilePath -- ^ output file/directory - -> [FilePath] -- ^ include path for home package - -> [UnitId] -- ^ packages to link - -> [LinkedObj] -- ^ the object files we're linking - -> [FilePath] -- ^ extra js files to include - -> (ExportedFun -> Bool) -- ^ functions from the objects to use as roots (include all their deps) - -> Set ExportedFun -- ^ extra symbols to link in + -> LinkPlan -> IO () -link lc_cfg cfg logger unit_env out _include units objFiles jsFiles isRootFun extraStaticDeps = do +jsLink lc_cfg cfg logger out link_plan = do -- create output directory createDirectoryIfMissing False out + when (logVerbAtLeast logger 2) $ + logInfo logger $ hang (text "jsLink:") 2 (ppr link_plan) + ------------------------------------------------------------- -- link all Haskell code (program + dependencies) into out.js - -- compute dependencies - (dep_map, dep_units, all_deps, _rts_wired_functions, dep_archives) - <- computeLinkDependencies cfg logger out unit_env units objFiles extraStaticDeps isRootFun - - -- retrieve code for dependencies - mods <- collectDeps dep_map dep_units all_deps + -- retrieve code for Haskell dependencies + mods <- collectModuleCodes link_plan -- LTO + rendering of JS code link_stats <- withBinaryFile (out </> "out.js") WriteMode $ \h -> - renderLinker h (csPrettyRender cfg) mods jsFiles + renderLinker h (csPrettyRender cfg) mods (lkp_extra_js link_plan) ------------------------------------------------------------- -- dump foreign references file (.frefs) - unless (lcOnlyOut lc_cfg) $ do + when (lcForeignRefs lc_cfg) $ do let frefsFile = "out.frefs" -- frefs = concatMap mc_frefs mods jsonFrefs = mempty -- FIXME: toJson frefs @@ -202,7 +222,7 @@ link lc_cfg cfg logger unit_env out _include units objFiles jsFiles isRootFun ex -- link dependencies' JS files into lib.js withBinaryFile (out </> "lib.js") WriteMode $ \h -> do - forM_ dep_archives $ \archive_file -> do + forM_ (lkp_archives link_plan) $ \archive_file -> do Ar.Archive entries <- Ar.loadAr archive_file forM_ entries $ \entry -> do case getJsArchiveEntry entry of @@ -211,47 +231,106 @@ link lc_cfg cfg logger unit_env out _include units objFiles jsFiles isRootFun ex B.hPut h bs hPutChar h '\n' - -- link everything together into all.js - when (generateAllJs lc_cfg) $ do + -- link everything together into a runnable all.js + -- only if we link a complete application, + -- no incremental linking and no skipped parts + when (lcCombineAll lc_cfg && not (lcNoRts lc_cfg)) $ do _ <- combineFiles lc_cfg out writeHtml out writeRunMain out writeRunner lc_cfg out writeExterns out +data LinkSpec = LinkSpec + { lks_unit_ids :: [UnitId] + + , lks_obj_files :: [LinkedObj] + + , lks_obj_root_filter :: ExportedFun -> Bool + -- ^ Predicate for exported functions in objects to declare as root + + , lks_extra_roots :: Set ExportedFun + -- ^ Extra root functions from loaded units + + , lks_extra_js :: [FilePath] + -- ^ Extra JS files to link + } + +instance Outputable LinkSpec where + ppr s = hang (text "LinkSpec") 2 $ vcat + [ hcat [text "Unit ids: ", ppr (lks_unit_ids s)] + , hcat [text "Object files:", ppr (lks_obj_files s)] + , text "Object root filter: <function>" + , hcat [text "Extra roots: ", ppr (lks_extra_roots s)] + , hang (text "Extra JS:") 2 (vcat (fmap text (lks_extra_js s))) + ] + +emptyLinkPlan :: LinkPlan +emptyLinkPlan = LinkPlan + { lkp_block_info = mempty + , lkp_dep_blocks = mempty + , lkp_archives = mempty + , lkp_extra_js = mempty + } + +-- | Given a `base` link plan (assumed to be already linked) and a `new` link +-- plan, compute `(diff, total)` link plans. +-- +-- - `diff` is the incremental link plan to get from `base` to `total` +-- - `total` is the total link plan as if `base` and `new` were linked at once +incrementLinkPlan :: LinkPlan -> LinkPlan -> (LinkPlan, LinkPlan) +incrementLinkPlan base new = (diff,total) + where + total = LinkPlan + { lkp_block_info = M.union (lkp_block_info base) (lkp_block_info new) + , lkp_dep_blocks = S.union (lkp_dep_blocks base) (lkp_dep_blocks new) + , lkp_archives = S.union (lkp_archives base) (lkp_archives new) + , lkp_extra_js = S.union (lkp_extra_js base) (lkp_extra_js new) + } + diff = LinkPlan + { lkp_block_info = lkp_block_info new -- block info from "new" contains all we need to load new blocks + , lkp_dep_blocks = S.difference (lkp_dep_blocks new) (lkp_dep_blocks base) + , lkp_archives = S.difference (lkp_archives new) (lkp_archives base) + , lkp_extra_js = S.difference (lkp_extra_js new) (lkp_extra_js base) + } + computeLinkDependencies :: StgToJSConfig - -> Logger - -> String -> UnitEnv - -> [UnitId] - -> [LinkedObj] - -> Set ExportedFun - -> (ExportedFun -> Bool) - -> IO (Map Module (Deps, DepsLocation), [UnitId], Set LinkableUnit, Set ExportedFun, [FilePath]) -computeLinkDependencies cfg logger target unit_env units objFiles extraStaticDeps isRootFun = do - - (objDepsMap, objRequiredUnits) <- loadObjDeps objFiles - - let roots = S.fromList . filter isRootFun $ concatMap (M.keys . depsHaskellExported . fst) (M.elems objDepsMap) - rootMods = map (moduleNameString . moduleName . NE.head) . NE.group . sort . map funModule . S.toList $ roots - objPkgs = map moduleUnitId $ nub (M.keys objDepsMap) - - when (logVerbAtLeast logger 2) $ void $ do - compilationProgressMsg logger $ hcat - [ text "Linking ", text target, text " (", text (intercalate "," rootMods), char ')' ] - compilationProgressMsg logger $ hcat - [ text "objDepsMap ", ppr objDepsMap ] - compilationProgressMsg logger $ hcat - [ text "objFiles ", ppr objFiles ] + -> LinkSpec + -> FinderOpts + -> FinderCache + -> IO LinkPlan +computeLinkDependencies cfg unit_env link_spec finder_opts finder_cache = do + + let units = lks_unit_ids link_spec + let obj_files = lks_obj_files link_spec + let extra_roots = lks_extra_roots link_spec + let obj_is_root = lks_obj_root_filter link_spec + + -- Process: + -- 1) Find new required linkables (object files, libraries, etc.) for all + -- transitive dependencies + -- 2) Load ObjBlockInfo from them and cache them + -- 3) Compute ObjBlock dependencies and return the link plan + + -- TODO (#23013): currently we directly compute the ObjBlock dependencies and + -- find/load linkable on-demand when a module is missing. + + + (objs_block_info, objs_required_blocks) <- loadObjBlockInfo obj_files + + let obj_roots = S.fromList . filter obj_is_root $ concatMap (M.keys . bi_exports . lbi_info) (M.elems objs_block_info) + obj_units = map moduleUnitId $ nub (M.keys objs_block_info) let (rts_wired_units, rts_wired_functions) = rtsDeps units -- all the units we want to link together, without their dependencies let root_units = filter (/= mainUnitId) + $ filter (/= interactiveUnitId) $ nub - $ rts_wired_units ++ reverse objPkgs ++ reverse units + $ rts_wired_units ++ reverse obj_units ++ reverse units -- all the units we want to link together, including their dependencies, -- preload units, and backpack instantiations @@ -260,26 +339,72 @@ computeLinkDependencies cfg logger target unit_env units objFiles extraStaticDep let all_units = fmap unitId all_units_infos dep_archives <- getPackageArchives cfg unit_env all_units - env <- newGhcjsEnv - (archsDepsMap, archsRequiredUnits) <- loadArchiveDeps env dep_archives - - when (logVerbAtLeast logger 2) $ - logInfo logger $ hang (text "Linking with archives:") 2 (vcat (fmap text dep_archives)) + (archives_block_info, archives_required_blocks) <- loadArchiveBlockInfo dep_archives -- compute dependencies - let dep_units = all_units ++ [homeUnitId (ue_unsafeHomeUnit $ unit_env)] - dep_map = objDepsMap `M.union` archsDepsMap - excluded_units = S.empty - dep_fun_roots = roots `S.union` rts_wired_functions `S.union` extraStaticDeps - dep_unit_roots = archsRequiredUnits ++ objRequiredUnits - - all_deps <- getDeps (fmap fst dep_map) excluded_units dep_fun_roots dep_unit_roots - - when (logVerbAtLeast logger 2) $ - logInfo logger $ hang (text "Units to link:") 2 (vcat (fmap ppr dep_units)) - -- logInfo logger $ hang (text "All deps:") 2 (vcat (fmap ppr (S.toList all_deps))) + let block_info = objs_block_info `M.union` archives_block_info + dep_fun_roots = obj_roots `S.union` rts_wired_functions `S.union` extra_roots + + -- read transitive dependencies + new_required_blocks_var <- newIORef [] + let load_info mod = do + -- Adapted from the tangled code in GHC.Linker.Loader.getLinkDeps. + linkable <- case lookupHugByModule mod (ue_home_unit_graph unit_env) of + Nothing -> + -- It's not in the HPT because we are in one shot mode, + -- so use the Finder to get a ModLocation... + case ue_homeUnit unit_env of + Nothing -> pprPanic "getDeps: No home-unit: " (pprModule mod) + Just home_unit -> do + mb_stuff <- findHomeModule finder_cache finder_opts home_unit (moduleName mod) + case mb_stuff of + Found loc mod -> found loc mod + _ -> pprPanic "getDeps: Couldn't find home-module: " (pprModule mod) + where + found loc mod = do { + mb_lnk <- findObjectLinkableMaybe mod loc ; + case mb_lnk of { + Nothing -> pprPanic "getDeps: Couldn't find linkable for module: " (pprModule mod) ; + Just lnk -> pure lnk + }} + + Just mod_info -> case homeModInfoObject mod_info of + Nothing -> pprPanic "getDeps: Couldn't find object file for home-module: " (pprModule mod) + Just lnk -> pure lnk + + case linkableUnlinked linkable of + [DotO p] -> do + (bis, req_b) <- loadObjBlockInfo [ObjFile p] + -- Store new required blocks in IORef + modifyIORef new_required_blocks_var ((++) req_b) + case M.lookup mod bis of + Nothing -> pprPanic "getDeps: Didn't load any block info for home-module: " (pprModule mod) + Just bi -> pure bi + ul -> pprPanic "getDeps: Unrecognized linkable for home-module: " + (vcat [ pprModule mod + , ppr ul]) + + -- required blocks have no dependencies, so don't have to use them as roots in + -- the traversal + (updated_block_info, transitive_deps) <- getDeps block_info load_info dep_fun_roots mempty + + new_required_blocks <- readIORef new_required_blocks_var + let required_blocks = S.fromList $ mconcat + [ archives_required_blocks + , objs_required_blocks + , new_required_blocks + ] + + let all_deps = S.union transitive_deps required_blocks + + let plan = LinkPlan + { lkp_block_info = updated_block_info + , lkp_dep_blocks = all_deps + , lkp_archives = S.fromList dep_archives + , lkp_extra_js = S.fromList (lks_extra_js link_spec) + } - return (dep_map, dep_units, all_deps, rts_wired_functions, dep_archives) + return plan -- | Compiled module @@ -325,9 +450,9 @@ renderLinker :: Handle -> Bool -- ^ should we render readable JS for debugging? -> [ModuleCode] -- ^ linked code per module - -> [FilePath] -- ^ additional JS files + -> Set FilePath -- ^ additional JS files -> IO LinkerStats -renderLinker h render_pretty mods jsFiles = do +renderLinker h render_pretty mods js_files = do -- link modules let (compacted_mods, meta) = linkModules mods @@ -356,7 +481,7 @@ renderLinker h render_pretty mods jsFiles = do mapM_ (putBS . cmc_exports) compacted_mods -- explicit additional JS files - mapM_ (\i -> B.readFile i >>= putBS) jsFiles + mapM_ (\i -> B.readFile i >>= putBS) (S.toList js_files) -- stats let link_stats = LinkerStats @@ -489,99 +614,147 @@ writeExterns :: FilePath -> IO () writeExterns out = writeFile (out </> "all.js.externs") $ unpackFS rtsExterns --- | get all dependencies for a given set of roots -getDeps :: Map Module Deps -- ^ loaded deps - -> Set LinkableUnit -- ^ don't link these blocks - -> Set ExportedFun -- ^ start here - -> [LinkableUnit] -- ^ and also link these - -> IO (Set LinkableUnit) -getDeps loaded_deps base fun startlu = go' S.empty (S.fromList startlu) (S.toList fun) +-- | Get all block dependencies for a given set of roots +-- +-- Returns the update block info map and the blocks. +getDeps :: Map Module LocatedBlockInfo -- ^ Block info per module + -> (Module -> IO LocatedBlockInfo) -- ^ Used to load block info if missing + -> Set ExportedFun -- ^ start here + -> Set BlockRef -- ^ and also link these + -> IO (Map Module LocatedBlockInfo, Set BlockRef) +getDeps init_infos load_info root_funs root_blocks = traverse_funs init_infos S.empty root_blocks (S.toList root_funs) where - go :: Set LinkableUnit - -> Set LinkableUnit - -> IO (Set LinkableUnit) - go result open = case S.minView open of - Nothing -> return result - Just (lu@(lmod,n), open') -> - case M.lookup lmod loaded_deps of - Nothing -> pprPanic "getDeps.go: object file not loaded for: " (pprModule lmod) - Just (Deps _ _ _ b) -> - let block = b!n - result' = S.insert lu result - in go' result' - (addOpen result' open' $ - map (lmod,) (blockBlockDeps block)) (blockFunDeps block) - - go' :: Set LinkableUnit - -> Set LinkableUnit - -> [ExportedFun] - -> IO (Set LinkableUnit) - go' result open [] = go result open - go' result open (f:fs) = - let key = funModule f - in case M.lookup key loaded_deps of - Nothing -> pprPanic "getDeps.go': object file not loaded for: " $ pprModule key - Just (Deps _m _r e _b) -> - let lun :: Int - lun = fromMaybe (pprPanic "exported function not found: " $ ppr f) - (M.lookup f e) - lu = (key, lun) - in go' result (addOpen result open [lu]) fs - - addOpen :: Set LinkableUnit -> Set LinkableUnit -> [LinkableUnit] - -> Set LinkableUnit - addOpen result open newUnits = - let alreadyLinked s = S.member s result || - S.member s open || - S.member s base - in open `S.union` S.fromList (filter (not . alreadyLinked) newUnits) + -- A block may depend on: + -- 1. other blocks from the same module + -- 2. exported functions from another module + -- + -- Process: + -- 1. We use the BlockInfos to find the block corresponding to every + -- exported root functions. + -- + -- 2. We had these blocks to the set of root_blocks if they aren't already + -- added to the result. + -- + -- 3. Then we traverse the root_blocks to find their dependencies and we + -- add them to root_blocks (if they aren't already added to the result) and + -- to root_funs. + -- + -- 4. back to 1 + + lookup_info infos mod = case M.lookup mod infos of + Just info -> pure (infos, lbi_info info) + Nothing -> do + -- load info and update cache with it + info <- load_info mod + pure (M.insert mod info infos, lbi_info info) + + traverse_blocks + :: Map Module LocatedBlockInfo + -> Set BlockRef + -> Set BlockRef + -> IO (Map Module LocatedBlockInfo, Set BlockRef) + traverse_blocks infos result open = case S.minView open of + Nothing -> return (infos, result) + Just (ref, open') -> do + let mod = block_ref_mod ref + !(infos',info) <- lookup_info infos mod + let block = bi_block_deps info ! block_ref_idx ref + result' = S.insert ref result + to_block_ref i = BlockRef + { block_ref_mod = mod + , block_ref_idx = i + } + traverse_funs infos' result' + (addOpen result' open' $ + map to_block_ref (blockBlockDeps block)) (blockFunDeps block) + + traverse_funs + :: Map Module LocatedBlockInfo + -> Set BlockRef + -> Set BlockRef + -> [ExportedFun] + -> IO (Map Module LocatedBlockInfo, Set BlockRef) + traverse_funs infos result open = \case + [] -> traverse_blocks infos result open + (f:fs) -> do + let mod = funModule f + -- lookup module block info for the module that exports the function + !(infos',info) <- lookup_info infos mod + -- lookup block index associated to the function in the block info + case M.lookup f (bi_exports info) of + Nothing -> pprPanic "exported function not found: " $ ppr f + Just idx -> do + let fun_block_ref = BlockRef + { block_ref_mod = mod + , block_ref_idx = idx + } + -- always add the module "global block" when we link a module + let global_block_ref = BlockRef + { block_ref_mod = mod + , block_ref_idx = 0 + } + traverse_funs infos' result (addOpen result open [fun_block_ref,global_block_ref]) fs + + -- extend the open block set with new blocks that are not already in the + -- result block set nor in the open block set. + addOpen + :: Set BlockRef + -> Set BlockRef + -> [BlockRef] + -> Set BlockRef + addOpen result open new_blocks = + let alreadyLinked s = S.member s result || S.member s open + in open `S.union` S.fromList (filter (not . alreadyLinked) new_blocks) -- | collect dependencies for a set of roots -collectDeps :: Map Module (Deps, DepsLocation) -- ^ Dependency map - -> [UnitId] -- ^ packages, code linked in this order - -> Set LinkableUnit -- ^ All dependencides - -> IO [ModuleCode] -collectDeps mod_deps packages all_deps = do - - -- read ghc-prim first, since we depend on that for static initialization - let packages' = uncurry (++) $ partition (== primUnitId) (nub packages) - - units_by_module :: Map Module IntSet - units_by_module = M.fromListWith IS.union $ - map (\(m,n) -> (m, IS.singleton n)) (S.toList all_deps) - - mod_deps_bypkg :: Map UnitId [(Deps, DepsLocation)] - mod_deps_bypkg = M.fromListWith (++) - (map (\(m,v) -> (moduleUnitId m,[v])) (M.toList mod_deps)) - +collectModuleCodes :: LinkPlan -> IO [ModuleCode] +collectModuleCodes link_plan = do + + let block_info = lkp_block_info link_plan + let blocks = lkp_dep_blocks link_plan + + -- we're going to load all the blocks. Instead of doing this randomly, we + -- group them by module first. + let module_blocks :: Map Module BlockIds + module_blocks = M.fromListWith IS.union $ + map (\ref -> (block_ref_mod ref, IS.singleton (block_ref_idx ref))) (S.toList blocks) + + -- GHCJS had this comment: "read ghc-prim first, since we depend on that for + -- static initialization". Not sure if it's still true as we haven't ported + -- the compactor yet. Still we sort to read ghc-prim blocks first just in + -- case. + let pred x = moduleUnitId (fst x) == primUnitId + cmp x y = case (pred x, pred y) of + (True,False) -> LT + (False,True) -> GT + (True,True) -> EQ + (False,False) -> EQ + + sorted_module_blocks :: [(Module,BlockIds)] + sorted_module_blocks = sortBy cmp (M.toList module_blocks) + + -- load blocks ar_state <- emptyArchiveState - fmap (catMaybes . concat) . forM packages' $ \pkg -> - mapM (uncurry $ extractDeps ar_state units_by_module) - (fromMaybe [] $ M.lookup pkg mod_deps_bypkg) - -extractDeps :: ArchiveState - -> Map Module IntSet - -> Deps - -> DepsLocation - -> IO (Maybe ModuleCode) -extractDeps ar_state units deps loc = - case M.lookup mod units of - Nothing -> return Nothing - Just mod_units -> Just <$> do - let selector n _ = fromIntegral n `IS.member` mod_units || isGlobalUnit (fromIntegral n) - case loc of - ObjectFile fp -> do - us <- readObjectUnits fp selector - pure (collectCode us) - ArchiveFile a -> do - obj <- readArObject ar_state mod a - us <- getObjectUnits obj selector - pure (collectCode us) - InMemory _n obj -> do - us <- getObjectUnits obj selector - pure (collectCode us) + forM sorted_module_blocks $ \(mod,bids) -> do + case M.lookup mod block_info of + Nothing -> pprPanic "collectModuleCodes: couldn't find block info for module" (ppr mod) + Just lbi -> extractBlocks ar_state lbi bids + +extractBlocks :: ArchiveState -> LocatedBlockInfo -> BlockIds -> IO ModuleCode +extractBlocks ar_state lbi blocks = do + case lbi_loc lbi of + ObjectFile fp -> do + us <- readObjectBlocks fp blocks + pure (collectCode us) + ArchiveFile a -> do + obj <- readArObject ar_state mod a + us <- getObjectBlocks obj blocks + pure (collectCode us) + InMemory _n obj -> do + us <- getObjectBlocks obj blocks + pure (collectCode us) where - mod = depsModule deps + mod = bi_module (lbi_info lbi) newline = BC.pack "\n" mk_exports = mconcat . intersperse newline . filter (not . BS.null) . map oiRaw mk_js_code = mconcat . map oiStat @@ -713,40 +886,32 @@ mkPrimFuns = mkExportedFuns primUnitId -- | Given a @UnitId@, a module name, and a set of symbols in the module, -- package these into an @ExportedFun@. mkExportedFuns :: UnitId -> FastString -> [FastString] -> [ExportedFun] -mkExportedFuns uid mod_name symbols = map mk_fun symbols +mkExportedFuns uid mod_name symbols = mkExportedModFuns mod names where mod = mkModule (RealUnit (Definite uid)) (mkModuleNameFS mod_name) - mk_fun sym = ExportedFun mod (LexicalFastString (mkJsSymbol True mod sym)) + names = map (mkJsSymbol True mod) symbols + +-- | Given a @Module@ and a set of symbols in the module, package these into an +-- @ExportedFun@. +mkExportedModFuns :: Module -> [FastString] -> [ExportedFun] +mkExportedModFuns mod symbols = map mk_fun symbols + where + mk_fun sym = ExportedFun mod (LexicalFastString sym) -- | read all dependency data from the to-be-linked files -loadObjDeps :: [LinkedObj] -- ^ object files to link - -> IO (Map Module (Deps, DepsLocation), [LinkableUnit]) -loadObjDeps objs = (prepareLoadedDeps . catMaybes) <$> mapM readDepsFromObj objs +loadObjBlockInfo :: [LinkedObj] -- ^ object files to link + -> IO (Map Module LocatedBlockInfo, [BlockRef]) +loadObjBlockInfo objs = (prepareLoadedDeps . catMaybes) <$> mapM readBlockInfoFromObj objs -- | Load dependencies for the Linker from Ar -loadArchiveDeps :: GhcjsEnv - -> [FilePath] - -> IO ( Map Module (Deps, DepsLocation) - , [LinkableUnit] - ) -loadArchiveDeps env archives = modifyMVar (linkerArchiveDeps env) $ \m -> - case M.lookup archives' m of - Just r -> return (m, r) - Nothing -> loadArchiveDeps' archives >>= \r -> return (M.insert archives' r m, r) - where - archives' = S.fromList archives - -loadArchiveDeps' :: [FilePath] - -> IO ( Map Module (Deps, DepsLocation) - , [LinkableUnit] - ) -loadArchiveDeps' archives = do +loadArchiveBlockInfo :: [FilePath] -> IO (Map Module LocatedBlockInfo, [BlockRef]) +loadArchiveBlockInfo archives = do archDeps <- forM archives $ \file -> do (Ar.Archive entries) <- Ar.loadAr file catMaybes <$> mapM (readEntry file) entries return (prepareLoadedDeps $ concat archDeps) where - readEntry :: FilePath -> Ar.ArchiveEntry -> IO (Maybe (Deps, DepsLocation)) + readEntry :: FilePath -> Ar.ArchiveEntry -> IO (Maybe LocatedBlockInfo) readEntry ar_file ar_entry = do let bs = Ar.filedata ar_entry bh <- unsafeUnpackBinBuffer bs @@ -754,8 +919,8 @@ loadArchiveDeps' archives = do Left _ -> pure Nothing -- not a valid object entry Right mod_name -> do obj <- getObjectBody bh mod_name - let !deps = objDeps obj - pure $ Just (deps, ArchiveFile ar_file) + let !info = objBlockInfo obj + pure $ Just (LocatedBlockInfo (ArchiveFile ar_file) info) -- | Predicate to check that an entry in Ar is a JS source -- and to return it without its header @@ -785,29 +950,32 @@ jsHeaderLength = B.length jsHeader -prepareLoadedDeps :: [(Deps, DepsLocation)] - -> ( Map Module (Deps, DepsLocation) - , [LinkableUnit] - ) -prepareLoadedDeps deps = - let req = concatMap (requiredUnits . fst) deps - depsMap = M.fromList $ map (\d -> (depsModule (fst d), d)) deps - in (depsMap, req) +prepareLoadedDeps :: [LocatedBlockInfo] + -> (Map Module LocatedBlockInfo, [BlockRef]) +prepareLoadedDeps lbis = (module_blocks, must_link) + where + must_link = concatMap (requiredBlocks . lbi_info) lbis + module_blocks = M.fromList $ map (\d -> (bi_module (lbi_info d), d)) lbis -requiredUnits :: Deps -> [LinkableUnit] -requiredUnits d = map (depsModule d,) (IS.toList $ depsRequired d) +requiredBlocks :: BlockInfo -> [BlockRef] +requiredBlocks d = map mk_block_ref (IS.toList $ bi_must_link d) + where + mk_block_ref i = BlockRef + { block_ref_mod = bi_module d + , block_ref_idx = i + } --- | read dependencies from an object that might have already been into memory +-- | read block info from an object that might have already been into memory -- pulls in all Deps from an archive -readDepsFromObj :: LinkedObj -> IO (Maybe (Deps, DepsLocation)) -readDepsFromObj = \case +readBlockInfoFromObj :: LinkedObj -> IO (Maybe LocatedBlockInfo) +readBlockInfoFromObj = \case ObjLoaded name obj -> do - let !deps = objDeps obj - pure $ Just (deps,InMemory name obj) + let !info = objBlockInfo obj + pure $ Just (LocatedBlockInfo (InMemory name obj) info) ObjFile file -> do - readObjectDeps file >>= \case + readObjectBlockInfo file >>= \case Nothing -> pure Nothing - Just deps -> pure $ Just (deps,ObjectFile file) + Just info -> pure $ Just (LocatedBlockInfo (ObjectFile file) info) -- | Embed a JS file into a .o file diff --git a/compiler/GHC/StgToJS/Linker/Types.hs b/compiler/GHC/StgToJS/Linker/Types.hs index 9e1714fc00d35bf6daf32ee805fb5e007f534a57..7f4cc683b96d49aa58193ecd940d444b49498aa4 100644 --- a/compiler/GHC/StgToJS/Linker/Types.hs +++ b/compiler/GHC/StgToJS/Linker/Types.hs @@ -2,8 +2,6 @@ {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE LambdaCase #-} -{-# OPTIONS_GHC -Wno-orphans #-} -- for Ident's Binary instance - ----------------------------------------------------------------------------- -- | -- Module : GHC.StgToJS.Linker.Types @@ -19,26 +17,21 @@ ----------------------------------------------------------------------------- module GHC.StgToJS.Linker.Types - ( GhcjsEnv (..) - , newGhcjsEnv - , JSLinkConfig (..) + ( JSLinkConfig (..) , defaultJSLinkConfig - , generateAllJs , LinkedObj (..) - , LinkableUnit + , LinkPlan (..) ) where import GHC.StgToJS.Object import GHC.Unit.Types -import GHC.Utils.Outputable (hsep,Outputable(..),text,ppr) +import GHC.Utils.Outputable (hsep,Outputable(..),text,ppr, hang, IsDoc (vcat), IsLine (hcat)) import Data.Map.Strict (Map) -import qualified Data.Map.Strict as M import Data.Set (Set) - -import Control.Concurrent.MVar +import qualified Data.Set as S import System.IO @@ -49,35 +42,53 @@ import Prelude -------------------------------------------------------------------------------- data JSLinkConfig = JSLinkConfig - { lcNoJSExecutables :: Bool - , lcNoHsMain :: Bool - , lcOnlyOut :: Bool - , lcNoRts :: Bool - , lcNoStats :: Bool + { lcNoJSExecutables :: !Bool -- ^ Dont' build JS executables + , lcNoHsMain :: !Bool -- ^ Don't generate Haskell main entry + , lcNoRts :: !Bool -- ^ Don't dump the generated RTS + , lcNoStats :: !Bool -- ^ Disable .stats file generation + , lcForeignRefs :: !Bool -- ^ Dump .frefs (foreign references) files + , lcCombineAll :: !Bool -- ^ Generate all.js (combined js) + wrappers } --- | we generate a runnable all.js only if we link a complete application, --- no incremental linking and no skipped parts -generateAllJs :: JSLinkConfig -> Bool -generateAllJs s = not (lcOnlyOut s) && not (lcNoRts s) - +-- | Default linker configuration defaultJSLinkConfig :: JSLinkConfig defaultJSLinkConfig = JSLinkConfig { lcNoJSExecutables = False , lcNoHsMain = False - , lcOnlyOut = False , lcNoRts = False , lcNoStats = False + , lcCombineAll = True + , lcForeignRefs = True } +data LinkPlan = LinkPlan + { lkp_block_info :: Map Module LocatedBlockInfo + -- ^ Block information + + , lkp_dep_blocks :: Set BlockRef + -- ^ Blocks to link + + , lkp_archives :: Set FilePath + -- ^ Archives to load JS sources from + + , lkp_extra_js :: Set FilePath + -- ^ Extra JS files to link + } + +instance Outputable LinkPlan where + ppr s = hang (text "LinkPlan") 2 $ vcat + -- Hidden because it's too verbose and it's not really part of the + -- plan, just meta info used to retrieve actual block contents + -- [ hcat [ text "Block info: ", ppr (lkp_block_info s)] + [ hcat [ text "Blocks: ", ppr (S.size (lkp_dep_blocks s))] + , hang (text "JS files from archives:") 2 (vcat (fmap text (S.toList (lkp_archives s)))) + , hang (text "Extra JS:") 2 (vcat (fmap text (S.toList (lkp_extra_js s)))) + ] + -------------------------------------------------------------------------------- -- Linker Environment -------------------------------------------------------------------------------- --- | A @LinkableUnit@ is a pair of a module and the index of the block in the --- object file -type LinkableUnit = (Module, Int) - -- | An object file that's either already in memory (with name) or on disk data LinkedObj = ObjFile FilePath -- ^ load from this file @@ -87,15 +98,3 @@ instance Outputable LinkedObj where ppr = \case ObjFile fp -> hsep [text "ObjFile", text fp] ObjLoaded s o -> hsep [text "ObjLoaded", text s, ppr (objModuleName o)] - -data GhcjsEnv = GhcjsEnv - { linkerArchiveDeps :: MVar (Map (Set FilePath) - (Map Module (Deps, DepsLocation) - , [LinkableUnit] - ) - ) - } - --- | return a fresh @GhcjsEnv@ -newGhcjsEnv :: IO GhcjsEnv -newGhcjsEnv = GhcjsEnv <$> newMVar M.empty diff --git a/compiler/GHC/StgToJS/Object.hs b/compiler/GHC/StgToJS/Object.hs index f9fd37b0115179b0f9fc13cd74ca477690c1a371..f986859ea0cd5ac9f39de1b2d55248ac104aa6c3 100644 --- a/compiler/GHC/StgToJS/Object.hs +++ b/compiler/GHC/StgToJS/Object.hs @@ -46,14 +46,20 @@ module GHC.StgToJS.Object , getObjectBody , getObject , readObject - , getObjectUnits - , readObjectUnits - , readObjectDeps - , isGlobalUnit + , getObjectBlocks + , readObjectBlocks + , readObjectBlockInfo + , isGlobalBlock , isJsObjectFile , Object(..) , IndexEntry(..) - , Deps (..), BlockDeps (..), DepsLocation (..) + , LocatedBlockInfo (..) + , BlockInfo (..) + , BlockDeps (..) + , BlockLocation (..) + , BlockId + , BlockIds + , BlockRef (..) , ExportedFun (..) ) where @@ -96,63 +102,75 @@ data Object = Object { objModuleName :: !ModuleName -- ^ name of the module , objHandle :: !BinHandle - -- ^ BinHandle that can be used to read the ObjUnits - , objPayloadOffset :: !(Bin ObjUnit) + -- ^ BinHandle that can be used to read the ObjBlocks + , objPayloadOffset :: !(Bin ObjBlock) -- ^ Offset of the payload (units) - , objDeps :: !Deps - -- ^ Dependencies + , objBlockInfo :: !BlockInfo + -- ^ Information about blocks , objIndex :: !Index - -- ^ The Index, serialed unit indices and their linkable units + -- ^ Block index: symbols per block and block offset in the object file } type BlockId = Int type BlockIds = IntSet --- | dependencies for a single module -data Deps = Deps - { depsModule :: !Module - -- ^ module - , depsRequired :: !BlockIds +-- | Information about blocks (linkable units) +data BlockInfo = BlockInfo + { bi_module :: !Module + -- ^ Module they were generated from + , bi_must_link :: !BlockIds -- ^ blocks that always need to be linked when this object is loaded (e.g. -- everything that contains initializer code or foreign exports) - , depsHaskellExported :: !(Map ExportedFun BlockId) + , bi_exports :: !(Map ExportedFun BlockId) -- ^ exported Haskell functions -> block - , depsBlocks :: !(Array BlockId BlockDeps) - -- ^ info about each block + , bi_block_deps :: !(Array BlockId BlockDeps) + -- ^ dependencies of each block } -instance Outputable Deps where +data LocatedBlockInfo = LocatedBlockInfo + { lbi_loc :: !BlockLocation -- ^ Where to find the blocks + , lbi_info :: !BlockInfo -- ^ Block information + } + +instance Outputable BlockInfo where ppr d = vcat - [ hcat [ text "module: ", pprModule (depsModule d) ] - , hcat [ text "exports: ", ppr (M.keys (depsHaskellExported d)) ] + [ hcat [ text "module: ", pprModule (bi_module d) ] + , hcat [ text "exports: ", ppr (M.keys (bi_exports d)) ] ] --- | Where are the dependencies -data DepsLocation +-- | Where are the blocks +data BlockLocation = ObjectFile FilePath -- ^ In an object file at path | ArchiveFile FilePath -- ^ In a Ar file at path | InMemory String Object -- ^ In memory -instance Outputable DepsLocation where +instance Outputable BlockLocation where ppr = \case ObjectFile fp -> hsep [text "ObjectFile", text fp] ArchiveFile fp -> hsep [text "ArchiveFile", text fp] InMemory s o -> hsep [text "InMemory", text s, ppr (objModuleName o)] +-- | A @BlockRef@ is a pair of a module and the index of the block in the +-- object file +data BlockRef = BlockRef + { block_ref_mod :: !Module -- ^ Module + , block_ref_idx :: !BlockId -- ^ Block index in the object file + } + deriving (Eq,Ord) + data BlockDeps = BlockDeps - { blockBlockDeps :: [Int] -- ^ dependencies on blocks in this object + { blockBlockDeps :: [BlockId] -- ^ dependencies on blocks in this object , blockFunDeps :: [ExportedFun] -- ^ dependencies on exported symbols in other objects -- , blockForeignExported :: [ExpFun] -- , blockForeignImported :: [ForeignRef] } -{- | we use the convention that the first unit (0) is a module-global - unit that's always included when something from the module - is loaded. everything in a module implicitly depends on the - global block. the global unit itself can't have dependencies - -} -isGlobalUnit :: Int -> Bool -isGlobalUnit n = n == 0 +-- | we use the convention that the first block (0) is a module-global block +-- that's always included when something from the module is loaded. everything +-- in a module implicitly depends on the global block. The global block itself +-- can't have dependencies +isGlobalBlock :: BlockId -> Bool +isGlobalBlock n = n == 0 -- | Exported Functions data ExportedFun = ExportedFun @@ -166,10 +184,10 @@ instance Outputable ExportedFun where , hcat [ text "symbol: ", ppr f ] ] --- | Write an ObjUnit, except for the top level symbols which are stored in the +-- | Write an ObjBlock, except for the top level symbols which are stored in the -- index -putObjUnit :: BinHandle -> ObjUnit -> IO () -putObjUnit bh (ObjUnit _syms b c d e f g) = do +putObjBlock :: BinHandle -> ObjBlock -> IO () +putObjBlock bh (ObjBlock _syms b c d e f g) = do put_ bh b put_ bh c lazyPut bh d @@ -177,17 +195,17 @@ putObjUnit bh (ObjUnit _syms b c d e f g) = do put_ bh f put_ bh g --- | Read an ObjUnit and associate it to the given symbols (that must have been +-- | Read an ObjBlock and associate it to the given symbols (that must have been -- read from the index) -getObjUnit :: [FastString] -> BinHandle -> IO ObjUnit -getObjUnit syms bh = do +getObjBlock :: [FastString] -> BinHandle -> IO ObjBlock +getObjBlock syms bh = do b <- get bh c <- get bh d <- lazyGet bh e <- get bh f <- get bh g <- get bh - pure $ ObjUnit + pure $ ObjBlock { oiSymbols = syms , oiClInfo = b , oiStatic = c @@ -203,12 +221,12 @@ getObjUnit syms bh = do magic :: String magic = "GHCJSOBJ" --- | Serialized unit indexes and their exported symbols --- (the first unit is module-global) +-- | Serialized block indexes and their exported symbols +-- (the first block is module-global) type Index = [IndexEntry] data IndexEntry = IndexEntry - { idxSymbols :: ![FastString] -- ^ Symbols exported by a unit - , idxOffset :: !(Bin ObjUnit) -- ^ Offset of the unit in the object file + { idxSymbols :: ![FastString] -- ^ Symbols exported by a block + , idxOffset :: !(Bin ObjBlock) -- ^ Offset of the block in the object file } @@ -221,8 +239,8 @@ data IndexEntry = IndexEntry putObject :: BinHandle -> ModuleName -- ^ module - -> Deps -- ^ dependencies - -> [ObjUnit] -- ^ linkable units and their symbols + -> BlockInfo -- ^ block infos + -> [ObjBlock] -- ^ linkable units and their symbols -> IO () putObject bh mod_name deps os = do forM_ magic (putByte bh . fromIntegral . ord) @@ -243,7 +261,7 @@ putObject bh mod_name deps os = do idx <- forM os $ \o -> do p <- tellBin bh_fs -- write units without their symbols - putObjUnit bh_fs o + putObjBlock bh_fs o -- return symbols and offset to store in the index pure (oiSymbols o,p) pure idx @@ -295,15 +313,15 @@ getObjectBody bh0 mod_name = do dict <- forwardGet bh0 (getDictionary bh0) let bh = setUserData bh0 $ noUserData { ud_get_fs = getDictFastString dict } - deps <- get bh - idx <- forwardGet bh (get bh) + block_info <- get bh + idx <- forwardGet bh (get bh) payload_pos <- tellBin bh pure $ Object { objModuleName = mod_name , objHandle = bh , objPayloadOffset = payload_pos - , objDeps = deps + , objBlockInfo = block_info , objIndex = idx } @@ -322,31 +340,31 @@ readObject file = do bh <- readBinMem file getObject bh --- | Reads only the part necessary to get the dependencies -readObjectDeps :: FilePath -> IO (Maybe Deps) -readObjectDeps file = do +-- | Reads only the part necessary to get the block info +readObjectBlockInfo :: FilePath -> IO (Maybe BlockInfo) +readObjectBlockInfo file = do bh <- readBinMem file getObject bh >>= \case - Just obj -> pure $! Just $! objDeps obj + Just obj -> pure $! Just $! objBlockInfo obj Nothing -> pure Nothing --- | Get units in the object file, using the given filtering function -getObjectUnits :: Object -> (Word -> IndexEntry -> Bool) -> IO [ObjUnit] -getObjectUnits obj pred = mapMaybeM read_entry (zip (objIndex obj) [0..]) +-- | Get blocks in the object file, using the given filtering function +getObjectBlocks :: Object -> BlockIds -> IO [ObjBlock] +getObjectBlocks obj bids = mapMaybeM read_entry (zip (objIndex obj) [0..]) where bh = objHandle obj - read_entry (e@(IndexEntry syms offset),i) - | pred i e = do + read_entry (IndexEntry syms offset,i) + | IS.member i bids = do seekBin bh offset - Just <$> getObjUnit syms bh + Just <$> getObjBlock syms bh | otherwise = pure Nothing --- | Read units in the object file, using the given filtering function -readObjectUnits :: FilePath -> (Word -> IndexEntry -> Bool) -> IO [ObjUnit] -readObjectUnits file pred = do +-- | Read blocks in the object file, using the given filtering function +readObjectBlocks :: FilePath -> BlockIds -> IO [ObjBlock] +readObjectBlocks file bids = do readObject file >>= \case Nothing -> pure [] - Just obj -> getObjectUnits obj pred + Just obj -> getObjectBlocks obj bids -------------------------------------------------------------------------------- @@ -378,13 +396,13 @@ instance Binary IndexEntry where put_ bh (IndexEntry a b) = put_ bh a >> put_ bh b get bh = IndexEntry <$> get bh <*> get bh -instance Binary Deps where - put_ bh (Deps m r e b) = do +instance Binary BlockInfo where + put_ bh (BlockInfo m r e b) = do put_ bh m put_ bh (map toI32 $ IS.toList r) put_ bh (map (\(x,y) -> (x, toI32 y)) $ M.toList e) put_ bh (elems b) - get bh = Deps <$> get bh + get bh = BlockInfo <$> get bh <*> (IS.fromList . map fromI32 <$> get bh) <*> (M.fromList . map (\(x,y) -> (x, fromI32 y)) <$> get bh) <*> ((\xs -> listArray (0, length xs - 1) xs) <$> get bh) diff --git a/compiler/GHC/StgToJS/Rts/Rts.hs b/compiler/GHC/StgToJS/Rts/Rts.hs index 8dd0272ae9df7e259fa5df70e8d948ff359bd045..707f6a64f45f1680de70b56d10e9b4fc51b2ff6e 100644 --- a/compiler/GHC/StgToJS/Rts/Rts.hs +++ b/compiler/GHC/StgToJS/Rts/Rts.hs @@ -52,6 +52,7 @@ import Data.Array import Data.Monoid import Data.Char (toLower, toUpper) import qualified Data.Bits as Bits +import qualified Data.ByteString.Lazy.Char8 as BLC -- | The garbageCollector resets registers and result variables. garbageCollector :: JStat diff --git a/compiler/GHC/StgToJS/Types.hs b/compiler/GHC/StgToJS/Types.hs index 7293ab504c8db64fc8524b5166bb1b3d484367d2..00f04ff0ad85e704a9d96ea29deca449b396a938 100644 --- a/compiler/GHC/StgToJS/Types.hs +++ b/compiler/GHC/StgToJS/Types.hs @@ -288,9 +288,9 @@ data ForeignJSRef = ForeignJSRef , foreignRefResult :: !FastString } --- | data used to generate one ObjUnit in our object file +-- | data used to generate one ObjBlock in our object file data LinkableUnit = LinkableUnit - { luObjUnit :: ObjUnit -- ^ serializable unit info + { luObjBlock :: ObjBlock -- ^ serializable unit info , luIdExports :: [Id] -- ^ exported names from haskell identifiers , luOtherExports :: [FastString] -- ^ other exports , luIdDeps :: [Id] -- ^ identifiers this unit depends on @@ -301,7 +301,7 @@ data LinkableUnit = LinkableUnit } -- | one toplevel block in the object file -data ObjUnit = ObjUnit +data ObjBlock = ObjBlock { oiSymbols :: ![FastString] -- ^ toplevel symbols (stored in index) , oiClInfo :: ![ClosureInfo] -- ^ closure information of all closures in block , oiStatic :: ![StaticInfo] -- ^ static closure data diff --git a/compiler/GHC/Tc/Gen/Splice.hs b/compiler/GHC/Tc/Gen/Splice.hs index f8817c000ef17acd4c6d07b0fadb094942a93dd6..d42e5366e6ee96fa14b479b78604b95af550af60 100644 --- a/compiler/GHC/Tc/Gen/Splice.hs +++ b/compiler/GHC/Tc/Gen/Splice.hs @@ -154,6 +154,7 @@ import qualified Data.Map as Map import Data.Typeable ( typeOf, Typeable, TypeRep, typeRep ) import Data.Data (Data) import Data.Proxy ( Proxy (..) ) +import Data.IORef import GHC.Parser.HaddockLex (lexHsDoc) import GHC.Parser (parseIdentifier) import GHC.Rename.Doc (rnHsDoc) @@ -1058,6 +1059,7 @@ runRemoteModFinalizers (ThModFinalizers finRefs) = do withForeignRefs (x : xs) f = withForeignRef x $ \r -> withForeignRefs xs $ \rs -> f (r : rs) interp <- tcGetInterp + case interpInstance interp of #if defined(HAVE_INTERNAL_INTERPRETER) InternalInterp -> do @@ -1065,17 +1067,18 @@ runRemoteModFinalizers (ThModFinalizers finRefs) = do runQuasi $ sequence_ qs #endif - ExternalInterp conf iserv -> withIServ_ conf iserv $ \i -> do + ExternalInterp ext -> withExtInterp ext $ \inst -> do tcg <- getGblEnv th_state <- readTcRef (tcg_th_remote_state tcg) case th_state of Nothing -> return () -- TH was not started, nothing to do Just fhv -> do - liftIO $ withForeignRef fhv $ \st -> + r <- liftIO $ withForeignRef fhv $ \st -> withForeignRefs finRefs $ \qrefs -> - writeIServ i (putMessage (RunModFinalizers st qrefs)) - () <- runRemoteTH i [] - readQResult i + sendMessageDelayedResponse inst (RunModFinalizers st qrefs) + () <- runRemoteTH inst [] + qr <- liftIO $ receiveDelayedResponse inst r + checkQResult qr runQResult :: (a -> String) @@ -1692,37 +1695,40 @@ runTH ty fhv = do return r #endif - ExternalInterp conf iserv -> + ExternalInterp ext -> withExtInterp ext $ \inst -> do -- 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_ conf iserv $ \i -> do - rstate <- getTHState i - loc <- TH.qLocation - liftIO $ - withForeignRef rstate $ \state_hv -> - withForeignRef fhv $ \q_hv -> - writeIServ i (putMessage (RunTH state_hv q_hv ty (Just loc))) - runRemoteTH i [] - bs <- readQResult i - return $! runGet get (LB.fromStrict bs) + rstate <- getTHState inst + loc <- TH.qLocation + -- run a remote TH request + r <- liftIO $ + withForeignRef rstate $ \state_hv -> + withForeignRef fhv $ \q_hv -> + sendMessageDelayedResponse inst (RunTH state_hv q_hv ty (Just loc)) + -- respond to requests from the interpreter + runRemoteTH inst [] + -- get the final result + qr <- liftIO $ receiveDelayedResponse inst r + bs <- checkQResult qr + return $! runGet get (LB.fromStrict bs) -- | communicate with a remotely-running TH computation until it finishes. -- See Note [Remote Template Haskell] in libraries/ghci/GHCi/TH.hs. runRemoteTH - :: IServInstance + :: ExtInterpInstance d -> [Messages TcRnMessage] -- saved from nested calls to qRecover -> TcM () -runRemoteTH iserv recovers = do - THMsg msg <- liftIO $ readIServ iserv getTHMessage +runRemoteTH inst recovers = do + THMsg msg <- liftIO $ receiveTHMessage inst case msg of RunTHDone -> return () StartRecover -> do -- Note [TH recover with -fexternal-interpreter] v <- getErrsVar msgs <- readTcRef v writeTcRef v emptyMessages - runRemoteTH iserv (msgs : recovers) + runRemoteTH inst (msgs : recovers) EndRecover caught_error -> do let (prev_msgs, rest) = case recovers of [] -> panic "EndRecover" @@ -1733,16 +1739,15 @@ runRemoteTH iserv recovers = do writeTcRef v $ if caught_error then prev_msgs else mkMessages warn_msgs `unionMessages` prev_msgs - runRemoteTH iserv rest + runRemoteTH inst rest _other -> do r <- handleTHMessage msg - liftIO $ writeIServ iserv (put r) - runRemoteTH iserv recovers + liftIO $ sendAnyValue inst r + runRemoteTH inst recovers --- | Read a value of type QResult from the iserv -readQResult :: Binary a => IServInstance -> TcM a -readQResult i = do - qr <- liftIO $ readIServ i get +-- | Check a QResult +checkQResult :: QResult a -> TcM a +checkQResult qr = case qr of QDone a -> return a QException str -> liftIO $ throwIO (ErrorCall str) @@ -1789,17 +1794,18 @@ Back in GHC, when we receive: -- -- The TH state is stored in tcg_th_remote_state in the TcGblEnv. -- -getTHState :: IServInstance -> TcM (ForeignRef (IORef QState)) -getTHState i = do - tcg <- getGblEnv - th_state <- readTcRef (tcg_th_remote_state tcg) - case th_state of - Just rhv -> return rhv - Nothing -> do - interp <- tcGetInterp - fhv <- liftIO $ mkFinalizedHValue interp =<< iservCall i StartTH - writeTcRef (tcg_th_remote_state tcg) (Just fhv) - return fhv +getTHState :: ExtInterpInstance d -> TcM (ForeignRef (IORef QState)) +getTHState inst = do + th_state_var <- tcg_th_remote_state <$> getGblEnv + liftIO $ do + th_state <- readIORef th_state_var + case th_state of + Just rhv -> return rhv + Nothing -> do + rref <- sendMessage inst StartTH + fhv <- mkForeignRef rref (freeReallyRemoteRef inst rref) + writeIORef th_state_var (Just fhv) + return fhv wrapTHResult :: TcM a -> TcM (THResult a) wrapTHResult tcm = do diff --git a/compiler/ghc.cabal.in b/compiler/ghc.cabal.in index 3add1bbf764370ae56a3942aa52588422cd580b7..3483effe3ab29cb68a640fcd700b65f2fd1a3312 100644 --- a/compiler/ghc.cabal.in +++ b/compiler/ghc.cabal.in @@ -626,8 +626,11 @@ Library GHC.Runtime.Heap.Inspect GHC.Runtime.Heap.Layout GHC.Runtime.Interpreter + GHC.Runtime.Interpreter.JS + GHC.Runtime.Interpreter.Process GHC.Runtime.Interpreter.Types GHC.Runtime.Loader + GHC.Runtime.Utils GHC.Settings GHC.Settings.Config GHC.Settings.Constants diff --git a/ghc-interp.js b/ghc-interp.js new file mode 100644 index 0000000000000000000000000000000000000000..27c9c55ea43486c1d865460820ee221c3a763b29 --- /dev/null +++ b/ghc-interp.js @@ -0,0 +1,101 @@ +/* + GHC JS Interpreter + + See Note [The JS interpreter] in GHC.Runtime.Interpreter.JS + + Read commands on stdin (ending with \n): + LOAD foo.js : load foo.js file + RUN_SERVER ghci_unit_id : run ghci_unit_id:GHCi.Server.defaultServer + + Once the Haskell server is started with RUN_SERVER, the JS server no longer + reads commands on stdin. Everything must go through the Haskell server (which + uses pipes for communication) +*/ + +var h$THfs = require('fs'); +var h$THvm = require('vm'); + +function h$debug_log(s) { + // uncomment the following line to enable some debug messages + // console.log("[JS interpreter] " + s); +} + +// load and exec JS file +function h$loadJS(path) { + h$debug_log("Loading file: " + path); + var data = h$THfs.readFileSync(path); + const script = new h$THvm.Script(data); + script.runInThisContext(); +} + +// Lookup a static closure by its name +function h$lookupClosure(v) { + h$debug_log("Looking up closure: " + v); + const r = eval(v); + h$debug_log(" -> Result: " + r); + if (!r) return 0; + // a RemoteRef is just the offset of a stable pointer + return h$makeStablePtr(r); +} + +// give access to these functions to the dynamically linked code +globalThis.h$loadJS = h$loadJS; +globalThis.h$lookupClosure = h$lookupClosure; +global.require = require; +global.module = module; + + +function h$initInterp() { + h$debug_log("Welcome to GHC's JS interpreter"); + + function stdin_end() { + h$debug_log('GHC disconnected: goodbye.'); + process.exit(1); + }; + + // read until we find '\n' + // Accumulate bytes in "bytes" array + let bytes = []; + let decoder = new TextDecoder('utf8'); + + function stdin_readable() { + // read until we find '\n' + while (null !== (bs = process.stdin.read(1))) { + let b = bs[0]; + switch(b) { + case 10: // `\n` found. `bytes` must contain a command + let cmd = decoder.decode(new Uint8Array(bytes)); + bytes = []; + // we only supports 2 commands: LOAD, RUN_SERVER + if (cmd.startsWith("LOAD ")) { + h$loadJS(cmd.slice(5)); + } + else if (cmd.startsWith("RUN_SERVER ")) { + let uid = cmd.slice(11); + let root = eval("h$" + uid + "ZCGHCiziServerzidefaultServer"); + // remove listeners + process.stdin.removeListener('end', stdin_end); + process.stdin.removeListener('readable', stdin_readable); + // run the server + h$debug_log("Run server"); + h$main(root); + // break the loop + return; + } + else { + console.log("[JS interpreter] Invalid command received: " + cmd); + process.exit(1); + } + break; + default: + bytes.push(b); + } + } + }; + + // read commands on STDIN + process.stdin.on('end', stdin_end); + process.stdin.on('readable', stdin_readable); +} + +h$initInterp(); diff --git a/hadrian/src/Base.hs b/hadrian/src/Base.hs index 623a5bae8bf8bcedc9c9a68702140eb75407fff5..2ef6ed9ddcc4df0a1c863e0b120a5f0cfe84119f 100644 --- a/hadrian/src/Base.hs +++ b/hadrian/src/Base.hs @@ -130,6 +130,7 @@ ghcLibDeps stage iplace = do ps <- mapM (\f -> stageLibPath stage <&> (-/- f)) [ "llvm-targets" , "llvm-passes" + , "ghc-interp.js" , "settings" ] cxxStdLib <- systemCxxStdLibConfPath (PackageDbLoc stage iplace) diff --git a/hadrian/src/Rules/Generate.hs b/hadrian/src/Rules/Generate.hs index 6f8fc9511da2f2fc82191a6e1fdfca24c03d0322..d7a41a20ae4914d2fb4fd812a6db5fba54667390 100644 --- a/hadrian/src/Rules/Generate.hs +++ b/hadrian/src/Rules/Generate.hs @@ -226,6 +226,7 @@ copyRules = do prefix -/- "ghci-usage.txt" <~ return "driver" prefix -/- "llvm-targets" <~ return "." prefix -/- "llvm-passes" <~ return "." + prefix -/- "ghc-interp.js" <~ return "." prefix -/- "template-hsc.h" <~ return (pkgPath hsc2hs -/- "data") prefix -/- "html/**" <~ return "utils/haddock/haddock-api/resources" diff --git a/libraries/ghci/GHCi/Run.hs b/libraries/ghci/GHCi/Run.hs index 4ecb64620a4e95198b558ac8cd32beadee6d9a4b..cae13010fe86b41f73081900e410354e5c326c87 100644 --- a/libraries/ghci/GHCi/Run.hs +++ b/libraries/ghci/GHCi/Run.hs @@ -1,5 +1,5 @@ {-# LANGUAGE GADTs, RecordWildCards, MagicHash, ScopedTypeVariables, CPP, - UnboxedTuples #-} + UnboxedTuples, LambdaCase #-} {-# OPTIONS_GHC -fno-warn-name-shadowing #-} -- | @@ -13,8 +13,14 @@ module GHCi.Run ) where import Prelude -- See note [Why do we import Prelude here?] + +#if !defined(javascript_HOST_ARCH) import GHCi.CreateBCO import GHCi.InfoTable +import Data.Binary +import Data.Binary.Get +#endif + import GHCi.FFI import GHCi.Message import GHCi.ObjLink @@ -27,8 +33,6 @@ import Control.Concurrent import Control.DeepSeq import Control.Exception import Control.Monad -import Data.Binary -import Data.Binary.Get import Data.ByteString (ByteString) import qualified Data.ByteString.Unsafe as B import GHC.Exts @@ -49,19 +53,36 @@ foreign import ccall "revertCAFs" rts_revertCAFs :: IO () run :: Message a -> IO a run m = case m of +#if defined(javascript_HOST_ARCH) + LoadObj p -> withCString p loadJS + InitLinker -> notSupportedJS m + LoadDLL {} -> notSupportedJS m + LoadArchive {} -> notSupportedJS m + UnloadObj {} -> notSupportedJS m + AddLibrarySearchPath {} -> notSupportedJS m + RemoveLibrarySearchPath {} -> notSupportedJS m + MkConInfoTable {} -> notSupportedJS m + ResolveObjs -> notSupportedJS m + FindSystemLibrary {} -> notSupportedJS m + CreateBCOs {} -> notSupportedJS m + LookupClosure str -> lookupJSClosure str +#else InitLinker -> initObjLinker RetainCAFs - RtsRevertCAFs -> rts_revertCAFs - LookupSymbol str -> fmap toRemotePtr <$> lookupSymbol str - LookupClosure str -> lookupClosure str LoadDLL str -> loadDLL str LoadArchive str -> loadArchive str LoadObj str -> loadObj str UnloadObj str -> unloadObj str AddLibrarySearchPath str -> toRemotePtr <$> addLibrarySearchPath str RemoveLibrarySearchPath ptr -> removeLibrarySearchPath (fromRemotePtr ptr) + MkConInfoTable tc ptrs nptrs tag ptrtag desc -> + toRemotePtr <$> mkConInfoTable tc ptrs nptrs tag ptrtag desc ResolveObjs -> resolveObjs FindSystemLibrary str -> findSystemLibrary str CreateBCOs bcos -> createBCOs (concatMap (runGet get) bcos) + LookupClosure str -> lookupClosure str +#endif + RtsRevertCAFs -> rts_revertCAFs + LookupSymbol str -> fmap toRemotePtr <$> lookupSymbol str FreeHValueRefs rs -> mapM_ freeRemoteRef rs AddSptEntry fpr r -> localRef r >>= sptAddEntry fpr EvalStmt opts r -> evalStmt opts r @@ -89,15 +110,38 @@ run m = case m of MallocStrings bss -> mapM mkString0 bss PrepFFI conv args res -> toRemotePtr <$> prepForeignCall conv args res FreeFFI p -> freeForeignCallInfo (fromRemotePtr p) - MkConInfoTable tc ptrs nptrs tag ptrtag desc -> - toRemotePtr <$> mkConInfoTable tc ptrs nptrs tag ptrtag desc StartTH -> startTH GetClosure ref -> do clos <- Heap.getClosureData =<< localRef ref mapM (\(Heap.Box x) -> mkRemoteRef (HValue x)) clos Seq ref -> doSeq ref ResumeSeq ref -> resumeSeq ref - _other -> error "GHCi.Run.run" + + Shutdown -> unexpectedMessage m + RunTH {} -> unexpectedMessage m + RunModFinalizers {} -> unexpectedMessage m + +unexpectedMessage :: Message a -> b +unexpectedMessage m = error ("GHCi.Run.Run: unexpected message: " ++ show m) + +#if defined(javascript_HOST_ARCH) +foreign import javascript "((ptr,off) => globalThis.h$loadJS(h$decodeUtf8z(ptr,off)))" loadJS :: CString -> IO () + +foreign import javascript "((ptr,off) => globalThis.h$lookupClosure(h$decodeUtf8z(ptr,off)))" lookupJSClosure# :: CString -> State# RealWorld -> (# State# RealWorld, Int# #) + +lookupJSClosure' :: String -> IO Int +lookupJSClosure' str = withCString str $ \cstr -> IO (\s -> + case lookupJSClosure# cstr s of + (# s', r #) -> (# s', I# r #)) + +lookupJSClosure :: String -> IO (Maybe HValueRef) +lookupJSClosure str = lookupJSClosure' str >>= \case + 0 -> pure Nothing + r -> pure (Just (RemoteRef (RemotePtr (fromIntegral r)))) + +notSupportedJS :: Message a -> b +notSupportedJS m = error ("Message not supported with the JavaScript interpreter: " ++ show m) +#endif evalStmt :: EvalOpts -> EvalExpr HValueRef -> IO (EvalStatus [HValueRef]) evalStmt opts expr = do diff --git a/libraries/ghci/ghci.cabal.in b/libraries/ghci/ghci.cabal.in index 700b7d62ead69b03601cf02c505a8f97aeed5f69..ce7dc10a3e6dc3ca66d10136177b29a409dad11e 100644 --- a/libraries/ghci/ghci.cabal.in +++ b/libraries/ghci/ghci.cabal.in @@ -50,7 +50,6 @@ library if flag(internal-interpreter) CPP-Options: -DHAVE_INTERNAL_INTERPRETER exposed-modules: - GHCi.InfoTable GHCi.Run GHCi.CreateBCO GHCi.ObjLink @@ -59,6 +58,10 @@ library GHCi.TH GHCi.Server + if !arch(javascript) + exposed-modules: + GHCi.InfoTable + exposed-modules: GHCi.BreakArray GHCi.BinaryArray diff --git a/libraries/template-haskell/tests/all.T b/libraries/template-haskell/tests/all.T index 48f05c64fa64355da180d1a7717dee9f9918fe81..6fb09fc3c7027d7f0b5dce62d709ffe4f58da83c 100644 --- a/libraries/template-haskell/tests/all.T +++ b/libraries/template-haskell/tests/all.T @@ -1,3 +1,3 @@ # difficult to test TH with profiling, because we have to build twice -test('dataToExpQUnit', [omit_ways(prof_ways), req_interp], compile, ['-v0']) -test('pragCompletePpr', [omit_ways(prof_ways), req_interp], compile_and_run, ['']) +test('dataToExpQUnit', [omit_ways(prof_ways), req_th], compile, ['-v0']) +test('pragCompletePpr', [omit_ways(prof_ways), req_th], compile_and_run, ['']) diff --git a/testsuite/driver/testlib.py b/testsuite/driver/testlib.py index 3ead379b5b8cf701904b7569bafaf004715c1524..955461dcdb2457290556bc7d844b255f2493ecd5 100644 --- a/testsuite/driver/testlib.py +++ b/testsuite/driver/testlib.py @@ -237,6 +237,10 @@ def req_profiling( name, opts ): if not config.have_profiling: opts.expect = 'fail' + # JS backend doesn't support profiling yet + if arch("js"): + opts.expect = 'fail' + def req_dynamic_lib_support( name, opts ): ''' Require that the platform have shared object support (N.B. this doesn't @@ -259,7 +263,16 @@ def req_interp( name, opts ): # skip on wasm32, otherwise they show up as unexpected passes if arch('wasm32'): skip(name, opts) - # JS backend doesn't provide an interpreter yet + +def req_bco( name, opts ): + ''' + Require support for ByteCode + ''' + + # Requires the interpreter + req_interp(name, opts) + + # JS backend doesn't support ByteCode js_skip(name, opts) def req_rts_linker( name, opts ): @@ -290,6 +303,13 @@ def req_ffi_exports( name, opts): # JS backend doesn't support FFI exports (yet) js_skip(name, opts) +def req_asm( name, opts): + """ + Mark a test as requiring LangAsm support + """ + # JS backend doesn't support asm + js_skip(name, opts) + def req_th( name, opts ): """ Mark a test as requiring TemplateHaskell. In addition to having interpreter @@ -297,7 +317,15 @@ def req_th( name, opts ): when GHC is dynamically-linked since we can't load profiled objects in this case. """ - req_interp(name, opts) + + # The JS target always supports TH, even in the stage1 compiler + # However it doesn't support the "Interpreter" yet (GHCi). + # So specifically enables TH here for JS. + if js_arch(): + return normal; + + req_interp(name, opts); + if ghc_dynamic(): return _omit_ways(name, opts, ['profasm', 'profthreaded']) diff --git a/testsuite/tests/annotations/should_compile/all.T b/testsuite/tests/annotations/should_compile/all.T index 8861d1baa3d51b7c4ade1cd0c99c91b5cb7d8a33..0ec1a9d1bca121057c4642f0fd3c76a31552db9e 100644 --- a/testsuite/tests/annotations/should_compile/all.T +++ b/testsuite/tests/annotations/should_compile/all.T @@ -2,9 +2,9 @@ # order for this to work with profiling, we would have to build the # program twice and use -osuf p_o (see the TH_spliceE5_prof test). For # now, just disable the profiling ways. -test('ann01', [req_interp, omit_ways(prof_ways)], compile, ['-v0']) -test('T14129', [req_interp, omit_ways(prof_ways)], compile, ['-v0']) -test('T19374a', [req_interp, omit_ways(prof_ways)], compile, ['-v0']) +test('ann01', [req_th, omit_ways(prof_ways)], compile, ['-v0']) +test('T14129', [req_th, omit_ways(prof_ways)], compile, ['-v0']) +test('T19374a', [req_th, omit_ways(prof_ways)], compile, ['-v0']) """" Helpful things to C+P: diff --git a/testsuite/tests/annotations/should_fail/all.T b/testsuite/tests/annotations/should_fail/all.T index 29f6c7488d766e9b4a5fb6f2e721b28289fb13ad..555e98ac6ece625fa8ae1c3b1fdfec57ac920665 100644 --- a/testsuite/tests/annotations/should_fail/all.T +++ b/testsuite/tests/annotations/should_fail/all.T @@ -1,23 +1,23 @@ -test('annfail01', req_interp, compile_fail, ['']) -test('annfail02', req_interp, compile_fail, ['']) -test('annfail03', req_interp, compile_fail, ['']) +test('annfail01', req_th, compile_fail, ['']) +test('annfail02', req_th, compile_fail, ['']) +test('annfail03', req_th, compile_fail, ['']) test('annfail04', [extra_files(['Annfail04_Help.hs']), - req_interp], multimod_compile_fail, ['annfail04', '-v0']) + req_th], multimod_compile_fail, ['annfail04', '-v0']) test('annfail05', [extra_files(['Annfail05_Help.hs']), - req_interp], multimod_compile_fail, ['annfail05', '-v0']) + req_th], multimod_compile_fail, ['annfail05', '-v0']) test('annfail06', [extra_files(['Annfail06_Help.hs']), - req_interp], multimod_compile_fail, ['annfail06', '-v0']) -test('annfail07', req_interp, compile_fail, ['']) -test('annfail08', req_interp, compile_fail, ['']) -test('annfail09', req_interp, compile_fail, ['']) -test('annfail10', req_interp, compile_fail, ['']) -test('annfail11', req_interp, compile_fail, ['']) -test('annfail12', req_interp, compile_fail, ['-v0']) -test('annfail13', req_interp, compile_fail, ['']) -test('T10826', req_interp, compile_fail, ['']) -test('T19374b', req_interp, compile_fail, ['']) -test('T19374c', req_interp, compile_fail, ['']) + req_th], multimod_compile_fail, ['annfail06', '-v0']) +test('annfail07', req_th, compile_fail, ['']) +test('annfail08', req_th, compile_fail, ['']) +test('annfail09', req_th, compile_fail, ['']) +test('annfail10', req_th, compile_fail, ['']) +test('annfail11', req_th, compile_fail, ['']) +test('annfail12', req_th, compile_fail, ['-v0']) +test('annfail13', req_th, compile_fail, ['']) +test('T10826', req_th, compile_fail, ['']) +test('T19374b', req_th, compile_fail, ['']) +test('T19374c', req_th, compile_fail, ['']) """" Helpful things to C+P: diff --git a/testsuite/tests/annotations/should_run/all.T b/testsuite/tests/annotations/should_run/all.T index e0fd311dcd063ad45dd94d3959dd84e015cd6d34..a0d19cacaea606a56e158076433a4e7ad425557f 100644 --- a/testsuite/tests/annotations/should_run/all.T +++ b/testsuite/tests/annotations/should_run/all.T @@ -8,7 +8,8 @@ setTestOpts(when(fast(), skip)) # config.ghc_th_way_flags. test('annrun01', [extra_files(['Annrun01_Help.hs']), - req_interp, + req_th, + when(js_arch(), compile_timeout_multiplier(5)), pre_cmd('$MAKE -s --no-print-directory config'), omit_ways(['dyn'] + prof_ways)], multimod_compile_and_run, diff --git a/testsuite/tests/driver/T20604/all.T b/testsuite/tests/driver/T20604/all.T index 79cb2dd256f5ae0f0ae3bc82ad74e5bc24c212c9..fec1b36681343b815e3ec2226ea020be086ecb92 100644 --- a/testsuite/tests/driver/T20604/all.T +++ b/testsuite/tests/driver/T20604/all.T @@ -10,5 +10,6 @@ def normalise_paths(s): test('T20604', [ req_th + , js_broken(23013) , extra_files(['A.hs', 'A1.hs']) , normalise_fun(normalise_paths)], makefile_test, []) diff --git a/testsuite/tests/driver/all.T b/testsuite/tests/driver/all.T index c78b94774a1255f15f681543d370fdb4cbaa415e..4196dad0e9ddb2ffe1da8ce53d4f0a916a67ceb2 100644 --- a/testsuite/tests/driver/all.T +++ b/testsuite/tests/driver/all.T @@ -288,6 +288,7 @@ test('T15396', normal, compile_and_run, ['-package ghc']) test('T16737', [extra_files(['T16737include/']), req_th, + req_c, expect_broken_for(16541, ghci_ways)], compile_and_run, ['-optP=-isystem -optP=T16737include']) diff --git a/testsuite/tests/driver/fat-iface/T22405/all.T b/testsuite/tests/driver/fat-iface/T22405/all.T index 9d2f6ada470e34c3ef5866a5337810e637f1e204..0f62f637b12577ff2543429b4b0927536e0f5b49 100644 --- a/testsuite/tests/driver/fat-iface/T22405/all.T +++ b/testsuite/tests/driver/fat-iface/T22405/all.T @@ -1,2 +1,2 @@ -test('T22405', [extra_files(['Main.hs']), js_broken(22576)], makefile_test, ['T22405']) -test('T22405b', [extra_files(['Main2.hs']), js_broken(22576)], makefile_test, ['T22405b']) +test('T22405', [extra_files(['Main.hs']), req_bco], makefile_test, ['T22405']) +test('T22405b', [extra_files(['Main2.hs']), req_bco], makefile_test, ['T22405b']) diff --git a/testsuite/tests/driver/fat-iface/all.T b/testsuite/tests/driver/fat-iface/all.T index 18837711de6fd614822812c50fc4d6d6b26b0a34..399b360d437ef0afc4960eab877369c6844a3e93 100644 --- a/testsuite/tests/driver/fat-iface/all.T +++ b/testsuite/tests/driver/fat-iface/all.T @@ -1,17 +1,17 @@ test('fat001', [extra_files(['Fat.hs'])], makefile_test, ['fat001']) -test('fat005', [extra_files(['Fat.hs']), filter_stdout_lines(r'= Proto-BCOs'), js_broken(22261)], makefile_test, ['fat005']) +test('fat005', [extra_files(['Fat.hs']), filter_stdout_lines(r'= Proto-BCOs')], makefile_test, ['fat005']) test('fat006', [extra_files(['Fat.hs'])], makefile_test, ['fat006']) test('fat006a', [extra_files(['Fat.hs'])], makefile_test, ['fat006a']) test('fat007', [extra_files(['Fat.hs'])], makefile_test, ['fat007']) test('fat008', [req_th, unless(ghc_dynamic(), skip), extra_files(['FatTH.hs', 'FatQuote.hs']), copy_files], makefile_test, ['fat008']) -test('fat009', [req_th, extra_files(['FatTH.hs', 'FatQuote.hs']), copy_files], makefile_test, ['fat009']) +test('fat009', [req_interp, extra_files(['FatTH.hs', 'FatQuote.hs']), copy_files], makefile_test, ['fat009']) test('fat010', [req_th,extra_files(['THA.hs', 'THB.hs', 'THC.hs']), copy_files], makefile_test, ['fat010']) # Check linking works when using -fbyte-code-and-object-code test('fat011', [req_th, extra_files(['FatMain.hs', 'FatTH.hs', 'FatQuote.hs'])], multimod_compile, ['FatMain', '-fbyte-code-and-object-code -fprefer-byte-code']) # Check that we use interpreter rather than enable dynamic-too if needed for TH test('fat012', [req_th, unless(ghc_dynamic(), skip), extra_files(['FatTH.hs', 'FatQuote.hs'])], multimod_compile, ['FatTH', '-fprefer-byte-code']) # Check that no objects are generated if using -fno-code and -fprefer-byte-code -test('fat013', [req_th, extra_files(['FatTH.hs', 'FatQuote.hs'])], multimod_compile, ['FatTH', '-fno-code -fprefer-byte-code']) +test('fat013', [req_th, req_bco, extra_files(['FatTH.hs', 'FatQuote.hs'])], multimod_compile, ['FatTH', '-fno-code -fprefer-byte-code']) # When using interpreter should not produce objects test('fat014', [req_th, extra_files(['FatTH.hs', 'FatQuote.hs']), extra_run_opts('-fno-code')], ghci_script, ['fat014.script']) test('fat015', [req_th, unless(ghc_dynamic(), skip), extra_files(['FatQuote.hs', 'FatQuote1.hs', 'FatQuote2.hs', 'FatTH1.hs', 'FatTH2.hs', 'FatTHTop.hs'])], multimod_compile, ['FatTHTop', '-fno-code -fwrite-interface']) diff --git a/testsuite/tests/driver/recomp009/all.T b/testsuite/tests/driver/recomp009/all.T index 33c36f7ea3d8ba7dd13871bdb5013383015a8a5d..83a811ca68d551f738453f095e312f37001bf8eb 100644 --- a/testsuite/tests/driver/recomp009/all.T +++ b/testsuite/tests/driver/recomp009/all.T @@ -1,3 +1,3 @@ # Test for #481, a recompilation bug with Template Haskell -test('recomp009', [req_th, extra_files(['Main.hs', 'Sub1.hs', 'Sub2.hs'])], makefile_test, []) +test('recomp009', [req_th, js_broken(23013), extra_files(['Main.hs', 'Sub1.hs', 'Sub2.hs'])], makefile_test, []) diff --git a/testsuite/tests/driver/recompTH/all.T b/testsuite/tests/driver/recompTH/all.T index 8d75ed0308ffaeb513e34a0ec052ca98d717420c..238f7aa2749248ab8187ad2f5868b7fa72d64a5d 100644 --- a/testsuite/tests/driver/recompTH/all.T +++ b/testsuite/tests/driver/recompTH/all.T @@ -1,4 +1,4 @@ -test('recompTH', [req_th, extra_files(['A.hs', 'B1.hs', 'B2.hs' ]), +test('recompTH', [req_th, js_broken(23013), extra_files(['A.hs', 'B1.hs', 'B2.hs' ]), when(fast(), skip) , normalise_slashes], makefile_test, []) diff --git a/testsuite/tests/driver/th-new-test/all.T b/testsuite/tests/driver/th-new-test/all.T index 54dd7852beb639b3587a7882be1ca1509ae4890f..a5072178b4be450677c99af63ebe4794e532be18 100644 --- a/testsuite/tests/driver/th-new-test/all.T +++ b/testsuite/tests/driver/th-new-test/all.T @@ -1,4 +1,4 @@ -test('th-new-test', [req_th, extra_files(['A.hs', 'B.hs', 'C.hs', 'D.hs', 'B1.hs', 'D1.hs', 'Main.hs']), +test('th-new-test', [req_th, js_broken(23013), extra_files(['A.hs', 'B.hs', 'C.hs', 'D.hs', 'B1.hs', 'D1.hs', 'Main.hs']), when(fast(), skip) , normalise_slashes], makefile_test, []) diff --git a/testsuite/tests/ghci/should_run/PackedDataCon/packeddatacon.T b/testsuite/tests/ghci/should_run/PackedDataCon/packeddatacon.T index 4bf55043fc4e09d37841dfbe37941328f7315cc9..bb253c74685460fdac7166dfdfe1ac683adbd1d5 100644 --- a/testsuite/tests/ghci/should_run/PackedDataCon/packeddatacon.T +++ b/testsuite/tests/ghci/should_run/PackedDataCon/packeddatacon.T @@ -1,6 +1,7 @@ test('PackedDataCon', [ extra_files(['Obj.hs', 'ByteCode.hs', 'Types.hs', 'Common.hs-incl']), req_interp, + req_bco, extra_ways(['ghci']), when(config.have_ext_interp, extra_ways(['ghci', 'ghci-ext'])), when(config.have_ext_interp and config.have_profiling, extra_ways(['ghci', 'ghci-ext', 'ghci-ext-prof'])) diff --git a/testsuite/tests/ghci/should_run/T21052.stdout b/testsuite/tests/ghci/should_run/T21052.stdout index cb1a1a16d49e92d060d0f1caee1dbe569c6d15e4..74dcb37c593c2441b8c8a1e6aa604b682d5143e3 100644 --- a/testsuite/tests/ghci/should_run/T21052.stdout +++ b/testsuite/tests/ghci/should_run/T21052.stdout @@ -1,7 +1,7 @@ ==================== CodeGenInput STG: ==================== BCO_toplevel :: GHC.Types.IO [GHC.Types.Any] -[LclId] = +[LclIdX] = {} \u [] let { sat :: [GHC.Types.Any] diff --git a/testsuite/tests/ghci/should_run/UnboxedTuples/unboxedtuples.T b/testsuite/tests/ghci/should_run/UnboxedTuples/unboxedtuples.T index 4166c82f7fe2c3bbfdd1a28a6d49b8b7e421fc76..22f678b57d93708b93462df0b1af5cf4378cf90d 100644 --- a/testsuite/tests/ghci/should_run/UnboxedTuples/unboxedtuples.T +++ b/testsuite/tests/ghci/should_run/UnboxedTuples/unboxedtuples.T @@ -1,6 +1,7 @@ test('UnboxedTuples', [ extra_files(['Obj.hs', 'ByteCode.hs', 'Common.hs-incl']), req_interp, + req_bco, extra_ways(['ghci']), when(config.have_ext_interp, extra_ways(['ghci', 'ghci-ext'])), when(config.have_ext_interp and config.have_profiling, extra_ways(['ghci', 'ghci-ext', 'ghci-ext-prof'])) diff --git a/testsuite/tests/ghci/should_run/UnliftedDataTypeInterp/unlifteddatatypeinterp.T b/testsuite/tests/ghci/should_run/UnliftedDataTypeInterp/unlifteddatatypeinterp.T index d31c394e9e3414a9d4bc1612d0b49512b3128f38..774d99d6fe0aa13fda1222be08ed5aac1664c3d9 100644 --- a/testsuite/tests/ghci/should_run/UnliftedDataTypeInterp/unlifteddatatypeinterp.T +++ b/testsuite/tests/ghci/should_run/UnliftedDataTypeInterp/unlifteddatatypeinterp.T @@ -1,6 +1,7 @@ test('UnliftedDataTypeInterp', [ extra_files(['Obj.hs', 'ByteCode.hs', 'Types.hs', 'Common.hs-incl']), req_interp, + req_bco, extra_ways(['ghci']), when(config.have_ext_interp, extra_ways(['ghci', 'ghci-ext'])), when(config.have_ext_interp and config.have_profiling, extra_ways(['ghci', 'ghci-ext', 'ghci-ext-prof'])) diff --git a/testsuite/tests/layout/all.T b/testsuite/tests/layout/all.T index 8826bf9f7b731d4eed2c82eb6cbf40be8104176c..15e44fd0569791cc8e3a7d0d5f8b8f1111bb5b3b 100644 --- a/testsuite/tests/layout/all.T +++ b/testsuite/tests/layout/all.T @@ -11,7 +11,7 @@ test('layout005', [], makefile_test, ['layout005']) test('layout006', [], makefile_test, ['layout006']) -test('layout007', [req_interp], makefile_test, ['layout007']) +test('layout007', [req_th], makefile_test, ['layout007']) test('layout008', [], makefile_test, ['layout008']) diff --git a/testsuite/tests/overloadedrecflds/should_compile/all.T b/testsuite/tests/overloadedrecflds/should_compile/all.T index 485aa3f51c73a9a1cd42619438c6ed7a4cc1e22c..4e5a06d90d39d782ab495c16fa8e00ce9baa9efe 100644 --- a/testsuite/tests/overloadedrecflds/should_compile/all.T +++ b/testsuite/tests/overloadedrecflds/should_compile/all.T @@ -3,7 +3,7 @@ test('T12609', normal, compile, ['']) test('T16597', [], multimod_compile, ['T16597', '-v0']) test('T17176', normal, compile, ['']) test('DRFPatSynExport', [], makefile_test, ['DRFPatSynExport']) -test('NoFieldSelectors', req_interp, compile, ['']) +test('NoFieldSelectors', req_th, compile, ['']) test('NFSDRF', normal, compile, ['']) test('NFSImport', [extra_files(['NFSExport.hs'])], multimod_compile, ['NFSImport NFSExport', '-v0']) test('T18999_NoFieldSelectors', normal, compile, ['']) diff --git a/testsuite/tests/partial-sigs/should_compile/all.T b/testsuite/tests/partial-sigs/should_compile/all.T index d421491b5a28640d94af524f59c2aee8d2f08614..62a3a282e32af9776e0ee255d94aacc7aaabed94 100644 --- a/testsuite/tests/partial-sigs/should_compile/all.T +++ b/testsuite/tests/partial-sigs/should_compile/all.T @@ -49,7 +49,7 @@ test('PatternSig', normal, compile, ['-ddump-types -fno-warn-partial-type-signat # (1) Use -fexternal-interpreter, or # (2) Build the program twice: once with -dynamic, and then # with -prof using -osuf to set a different object file suffix. -test('PatternSplice', [req_interp, omit_ways(['profasm'])], compile, ['-fno-warn-partial-type-signatures']) +test('PatternSplice', [req_th, omit_ways(['profasm'])], compile, ['-fno-warn-partial-type-signatures']) test('Recursive', normal, compile, ['-ddump-types -fno-warn-partial-type-signatures']) test('ScopedNamedWildcards', normal, compile, ['-ddump-types -fno-warn-partial-type-signatures']) test('ScopedNamedWildcardsGood', normal, compile, ['-ddump-types -fno-warn-partial-type-signatures']) @@ -62,7 +62,7 @@ test('Uncurry', normal, compile, ['-ddump-types -fno-warn-partial-type-signature test('UncurryNamed', normal, compile, ['-ddump-types -fno-warn-partial-type-signatures']) test('WarningWildcardInstantiations', normal, compile, ['-ddump-types']) test('SplicesUsed', [extra_files(['Splices.hs']), - req_interp, omit_ways(prof_ways)], multimod_compile, + req_th, omit_ways(prof_ways)], multimod_compile, ['SplicesUsed', config.ghc_th_way_flags]) test('TypedSplice', normal, compile, ['']) test('T10403', normal, compile, ['']) diff --git a/testsuite/tests/partial-sigs/should_fail/all.T b/testsuite/tests/partial-sigs/should_fail/all.T index 01baa58d1fee6104cd60e3d50448e8d371ff701a..26dbc5aff8b33c3902431a4d085dd0a0f836732a 100644 --- a/testsuite/tests/partial-sigs/should_fail/all.T +++ b/testsuite/tests/partial-sigs/should_fail/all.T @@ -3,9 +3,9 @@ test('AnnotatedConstraintNotForgotten', normal, compile_fail, ['']) test('Defaulting1MROff', expect_broken(23232), compile, ['']) test('ExtraConstraintsWildcardInExpressionSignature', normal, compile, ['']) test('ExtraConstraintsWildcardInPatternSignature', normal, compile_fail, ['']) -test('ExtraConstraintsWildcardInPatternSplice', [req_interp, normal], compile_fail, ['']) +test('ExtraConstraintsWildcardInPatternSplice', [req_th, normal], compile_fail, ['']) test('ExtraConstraintsWildcardInTypeSpliceUsed', [extra_files(['ExtraConstraintsWildcardInTypeSplice.hs']), - req_interp], + req_th], multimod_compile_fail, ['ExtraConstraintsWildcardInTypeSpliceUsed', config.ghc_th_way_flags]) test('ExtraConstraintsWildcardInTypeSplice2', diff --git a/testsuite/tests/plugins/all.T b/testsuite/tests/plugins/all.T index c782ad13cdbdafe58fa0bcbdd8f0d0e7c464f717..e6ace916bd766d92e863582fde8892e86a8f7e6a 100644 --- a/testsuite/tests/plugins/all.T +++ b/testsuite/tests/plugins/all.T @@ -1,5 +1,9 @@ setTestOpts([ req_interp, + # Plugins aren't supported with the JS backend so we get unexpected passes + # for tests that expected to fail... Just skipping them for now until #14335 + # is fixed + js_skip, # The implementation of ghc-pkg doesn't seem to be multi-concurrent process # safe on windows. These tests which mutate the package db need to be run # sequentially until this is fixed. This likely means that #13194 isn't fully diff --git a/testsuite/tests/printer/all.T b/testsuite/tests/printer/all.T index 619a781810d762a2ef886f6cbeb89b22cc7f7ce5..e9de8ea590d1187c58a756345bf33e6f3430dc15 100644 --- a/testsuite/tests/printer/all.T +++ b/testsuite/tests/printer/all.T @@ -31,7 +31,7 @@ test('Ppr023', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr023']) test('Ppr024', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr024']) test('Ppr025', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr025']) test('Ppr026', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr026']) -test('Ppr027', [ignore_stderr, req_interp,req_ppr_deps], makefile_test, ['ppr027']) +test('Ppr027', [ignore_stderr, req_th,req_ppr_deps], makefile_test, ['ppr027']) test('Ppr028', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr028']) test('Ppr029', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr029']) test('Ppr030', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr030']) @@ -44,10 +44,10 @@ test('Ppr036', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr036']) test('Ppr037', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr037']) test('Ppr038', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr038']) test('Ppr039', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr039']) -test('Ppr040', [ignore_stderr, req_interp,req_ppr_deps], makefile_test, ['ppr040']) +test('Ppr040', [ignore_stderr, req_th,req_ppr_deps], makefile_test, ['ppr040']) test('Ppr041', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr041']) test('Ppr042', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr042']) -test('Ppr043', [ignore_stderr, req_interp,req_ppr_deps], makefile_test, ['ppr043']) +test('Ppr043', [ignore_stderr, req_th,req_ppr_deps], makefile_test, ['ppr043']) test('Ppr044', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr044']) test('Ppr045', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr045']) test('Ppr046', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr046']) @@ -60,12 +60,12 @@ test('Ppr053', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr053']) test('Ppr054', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr054']) test('Ppr055', [ignore_stderr, req_ppr_deps], makefile_test, ['ppr055']) test('T13050p', [ignore_stderr, req_ppr_deps], makefile_test, ['T13050p']) -test('T13199', [ignore_stderr, req_interp,req_ppr_deps], makefile_test, ['T13199']) -test('T13550', [ignore_stderr, req_interp,req_ppr_deps], makefile_test, ['T13550']) -test('T13942', [ignore_stderr, req_interp,req_ppr_deps], makefile_test, ['T13942']) -test('T14289', [ignore_stderr, req_interp,req_ppr_deps], makefile_test, ['T14289']) -test('T14289b', [ignore_stderr, req_interp,req_ppr_deps], makefile_test, ['T14289b']) -test('T14289c', [ignore_stderr, req_interp,req_ppr_deps], makefile_test, ['T14289c']) +test('T13199', [ignore_stderr, req_th,req_ppr_deps], makefile_test, ['T13199']) +test('T13550', [ignore_stderr, req_th,req_ppr_deps], makefile_test, ['T13550']) +test('T13942', [ignore_stderr, req_th,req_ppr_deps], makefile_test, ['T13942']) +test('T14289', [ignore_stderr, req_th,req_ppr_deps], makefile_test, ['T14289']) +test('T14289b', [ignore_stderr, req_th,req_ppr_deps], makefile_test, ['T14289b']) +test('T14289c', [ignore_stderr, req_th,req_ppr_deps], makefile_test, ['T14289c']) test('T14306', [ignore_stderr, req_ppr_deps], makefile_test, ['T14306']) test('T14343', normal, compile_fail, ['']) test('T14343b', normal, compile_fail, ['']) diff --git a/testsuite/tests/quasiquotation/T13863/all.T b/testsuite/tests/quasiquotation/T13863/all.T index c29dc20b561ed21323fbd88ea8a47b97541b0048..80bfa8a5184ad7c12dd48a126e230990c7af461b 100644 --- a/testsuite/tests/quasiquotation/T13863/all.T +++ b/testsuite/tests/quasiquotation/T13863/all.T @@ -1 +1 @@ -test('T13863', [req_interp, omit_ways(prof_ways), extra_files(['A.hs', 'B.hs'])], multimod_compile, ['B', '-fno-code -v0']) \ No newline at end of file +test('T13863', [req_th, omit_ways(prof_ways), extra_files(['A.hs', 'B.hs'])], multimod_compile, ['B', '-fno-code -v0']) diff --git a/testsuite/tests/quasiquotation/T4491/test.T b/testsuite/tests/quasiquotation/T4491/test.T index 01e73ea7b655acebe281a8b10c74ae8be4f8f6df..33d9cf0d42a5c255c56f1f05f8348b635f5a4516 100644 --- a/testsuite/tests/quasiquotation/T4491/test.T +++ b/testsuite/tests/quasiquotation/T4491/test.T @@ -1,7 +1,7 @@ test('T4491', [extra_files(['A.hs']), - req_interp, + req_th, # We'd need to jump through some hoops to run this test the # other ways, due to the TH use, so for now we only run it # the TH way diff --git a/testsuite/tests/quasiquotation/all.T b/testsuite/tests/quasiquotation/all.T index e8b3bd1e6f30682001865db97df57b3eff39792f..4029fdb00ae0b2adcf8221611f7e75e644bd6d55 100644 --- a/testsuite/tests/quasiquotation/all.T +++ b/testsuite/tests/quasiquotation/all.T @@ -1,6 +1,6 @@ -test('T3953', req_interp, compile_fail, ['']) +test('T3953', req_th, compile_fail, ['']) test('T4150', [expect_broken(4150)], makefile_test, ['T4150']) -test('T5204', req_interp, compile_fail, ['']) +test('T5204', req_th, compile_fail, ['']) test('T7918', [req_interp, extra_run_opts('"' + config.libdir + '"'), only_ways([config.ghc_th_way]), unless(have_dynamic(), skip)], diff --git a/testsuite/tests/quasiquotation/qq001/test.T b/testsuite/tests/quasiquotation/qq001/test.T index e70ba90a0571ac77494ed7cc7a6e7b78d1fc51af..275b1bd15b28d65b2dd96c50fe1714ab631d4c81 100644 --- a/testsuite/tests/quasiquotation/qq001/test.T +++ b/testsuite/tests/quasiquotation/qq001/test.T @@ -1 +1 @@ -test('qq001', req_interp, compile_fail, ['']) +test('qq001', req_th, compile_fail, ['']) diff --git a/testsuite/tests/quasiquotation/qq002/test.T b/testsuite/tests/quasiquotation/qq002/test.T index 8b648e03a7045129baf22f2028247cfde2915121..383298a1d147e672e8f6c65ba7578fc63123c4a4 100644 --- a/testsuite/tests/quasiquotation/qq002/test.T +++ b/testsuite/tests/quasiquotation/qq002/test.T @@ -1 +1 @@ -test('qq002', req_interp, compile_fail, ['']) +test('qq002', req_th, compile_fail, ['']) diff --git a/testsuite/tests/quasiquotation/qq003/test.T b/testsuite/tests/quasiquotation/qq003/test.T index 4d845349190ad1cde6c4e90b9a20e0986e7b643b..3551d0634a51343a6c2ac3cd5e1d2d406dca597d 100644 --- a/testsuite/tests/quasiquotation/qq003/test.T +++ b/testsuite/tests/quasiquotation/qq003/test.T @@ -1 +1 @@ -test('qq003', req_interp, compile_fail, ['']) +test('qq003', req_th, compile_fail, ['']) diff --git a/testsuite/tests/quasiquotation/qq004/test.T b/testsuite/tests/quasiquotation/qq004/test.T index 7280f5e4c4db39a56bbee8f16a1989d21f0d4488..e01ecec1100edc625a27dd399d0899e63a547907 100644 --- a/testsuite/tests/quasiquotation/qq004/test.T +++ b/testsuite/tests/quasiquotation/qq004/test.T @@ -1 +1 @@ -test('qq004', req_interp, compile_fail, ['']) +test('qq004', req_th, compile_fail, ['']) diff --git a/testsuite/tests/quasiquotation/qq007/test.T b/testsuite/tests/quasiquotation/qq007/test.T index 7e8251a6576676c94da53649ecc471336acb7e72..4c95c3037def8023612d2cce003c72b61dbcf4f8 100644 --- a/testsuite/tests/quasiquotation/qq007/test.T +++ b/testsuite/tests/quasiquotation/qq007/test.T @@ -2,5 +2,5 @@ test('qq007', [extra_files(['QQ.hs', 'Test.hs']), when(fast(), skip), pre_cmd('$MAKE -s --no-print-directory TH_QQ'), omit_ways(prof_ways), - req_interp], + req_th], multimod_compile, ['Test', '-v0 ' + config.ghc_th_way_flags]) diff --git a/testsuite/tests/quasiquotation/qq008/test.T b/testsuite/tests/quasiquotation/qq008/test.T index d17ce0deb5b69bc7108f36eb5a586a0641bbce27..ef5bb7a0aeb22725eccb89061c36d33ce6347bc5 100644 --- a/testsuite/tests/quasiquotation/qq008/test.T +++ b/testsuite/tests/quasiquotation/qq008/test.T @@ -2,5 +2,5 @@ test('qq008', [extra_files(['QQ.hs', 'Test.hs']), when(fast(), skip), pre_cmd('$MAKE -s --no-print-directory TH_QQ'), omit_ways(prof_ways), - req_interp], + req_th], multimod_compile, ['Test', '-v0 ' + config.ghc_th_way_flags]) diff --git a/testsuite/tests/quasiquotation/qq009/test.T b/testsuite/tests/quasiquotation/qq009/test.T index 2dfb976298fa3f0edff6c4522407c0b776824bd6..0a58ec4df1736b58563f26ca239e8ea780bd17ce 100644 --- a/testsuite/tests/quasiquotation/qq009/test.T +++ b/testsuite/tests/quasiquotation/qq009/test.T @@ -2,5 +2,5 @@ test('qq009', [extra_files(['QQ.hs', 'Test.hs']), when(fast(), skip), pre_cmd('$MAKE -s --no-print-directory TH_QQ'), omit_ways(prof_ways), - req_interp], + req_th], multimod_compile, ['Test', '-v0 ' + config.ghc_th_way_flags]) diff --git a/testsuite/tests/rename/should_fail/all.T b/testsuite/tests/rename/should_fail/all.T index c67d1460916917097d0012704c54b7d11e5cde40..4e97f45cfdf44f3a3f9ab45517c150580e3ff5c1 100644 --- a/testsuite/tests/rename/should_fail/all.T +++ b/testsuite/tests/rename/should_fail/all.T @@ -190,7 +190,7 @@ test('T21605c', normal, compile_fail, ['']) test('T21605d', normal, compile_fail, ['']) test('T22839', normal, compile_fail, ['']) test('T23301', normal, compile_fail, ['']) -test('RnPatternSynonymFail', [js_broken(22261), req_th], compile_fail, ['']) +test('RnPatternSynonymFail', req_th, compile_fail, ['']) test('RnMultipleFixityFail', normal, compile_fail, ['']) test('RnEmptyCaseFail', normal, compile_fail, ['']) test('RnDefaultSigFail', normal, compile_fail, ['']) diff --git a/testsuite/tests/safeHaskell/safeLanguage/all.T b/testsuite/tests/safeHaskell/safeLanguage/all.T index ac15d88a2156b0d834b1f46ffe6987ed021e8621..253ebee1af23505347acf9a17d771f5be73afcdf 100644 --- a/testsuite/tests/safeHaskell/safeLanguage/all.T +++ b/testsuite/tests/safeHaskell/safeLanguage/all.T @@ -23,7 +23,7 @@ test('SafeLang07', normal, compile_fail, ['']) test('SafeLang08', normal, compile_fail, ['']) test('SafeLang09', [exit_code(1)], compile_and_run, ['']) test('SafeLang10', [], multimod_compile_fail, ['SafeLang10', '']) -test('SafeLang11', [req_interp], multimod_compile_and_run, +test('SafeLang11', [req_th], multimod_compile_and_run, ['SafeLang11', config.ghc_th_way_flags]) test('SafeLang12', normal, multimod_compile_fail, ['SafeLang12', '']) test('SafeLang15', [exit_code(1)], multimod_compile_and_run, diff --git a/testsuite/tests/showIface/all.T b/testsuite/tests/showIface/all.T index da691a642c28be9b60ca8da03e3e70e8873022ae..04fc85e82e17cb3ebbaa7d99bae28b60d6c40e22 100644 --- a/testsuite/tests/showIface/all.T +++ b/testsuite/tests/showIface/all.T @@ -7,7 +7,7 @@ test('DocsInHiFile1', makefile_test, ['DocsInHiFile1']) test('T17871', [extra_files(['T17871a.hs'])], multimod_compile, ['T17871', '-v0']) test('DocsInHiFileTH', - [extra_files(['DocsInHiFileTHExternal.hs', 'DocsInHiFileTH.hs']), js_broken(22261)], + [extra_files(['DocsInHiFileTHExternal.hs', 'DocsInHiFileTH.hs']), req_th], makefile_test, ['DocsInHiFileTH']) test('NoExportList', normal, diff --git a/testsuite/tests/simplCore/should_compile/all.T b/testsuite/tests/simplCore/should_compile/all.T index 9886a96a63de6858d7ec0ac1153600b9d504d250..2eccbac3a3a056745e4e69ee5f992c961032cccb 100644 --- a/testsuite/tests/simplCore/should_compile/all.T +++ b/testsuite/tests/simplCore/should_compile/all.T @@ -134,7 +134,7 @@ test('T5366', normal, makefile_test, ['T5366']) test('T7796', [], makefile_test, ['T7796']) -test('T5550', req_interp, compile, ['']) +test('T5550', req_th, compile, ['']) test('T7865', normal, makefile_test, ['T7865']) # T7785: we want to check that we specialise 'shared'. But Tidy discards the @@ -347,7 +347,7 @@ test('T18747B', normal, compile, ['']) test('T18815', only_ways(['optasm']), makefile_test, ['T18815']) test('T18668', normal, compile, ['-dsuppress-uniques']) test('T18995', [ grep_errmsg(r'print') ], compile, ['-O -ddump-simpl -dsuppress-uniques']) -test('T19168', req_interp, compile, ['']) +test('T19168', req_th, compile, ['']) test('T19246', only_ways(['optasm']), multimod_compile, ['T19246', '-v0 -ddump-rules']) test('T19360', only_ways(['optasm']), compile, ['']) @@ -393,7 +393,7 @@ test('OpaqueNoCastWW', normal, compile, ['-O -ddump-simpl -dsuppress-uniques']) test('OpaqueNoRebox', [ grep_errmsg(r'$wf') ], compile, ['-O -ddump-simpl -dsuppress-uniques']) test('OpaqueNoRebox2', [ grep_errmsg(r'$wf') ], compile, ['-O -ddump-simpl -dsuppress-uniques']) test('OpaqueNoRebox3', [ grep_errmsg(r'$wf') ], compile, ['-O -ddump-simpl -dsuppress-uniques']) -test('OpaqueNoSpecConstr', [ req_interp, grep_errmsg(r'$sloop') ], compile, ['-O -ddump-simpl -dsuppress-uniques']) +test('OpaqueNoSpecConstr', [ req_th, grep_errmsg(r'$sloop') ], compile, ['-O -ddump-simpl -dsuppress-uniques']) test('OpaqueNoSpecialise', [ grep_errmsg(r'$sf') ], compile, ['-O -ddump-simpl -dsuppress-uniques']) test('OpaqueNoStrictArgWW', [ grep_errmsg(r'$wf') ], compile, ['-O -fworker-wrapper-cbv -ddump-simpl -dsuppress-uniques']) test('OpaqueNoWW', [ grep_errmsg(r'$wf') ], compile, ['-O -ddump-simpl -dsuppress-uniques']) diff --git a/testsuite/tests/th/T2014/all.T b/testsuite/tests/th/T2014/all.T index c74e4c1619b8560d9dbd155d5947e877ea0ff970..2c1672aec09cf0fb9ee3463d7f17176fa5cd6f80 100644 --- a/testsuite/tests/th/T2014/all.T +++ b/testsuite/tests/th/T2014/all.T @@ -1,3 +1,3 @@ test('T2014', [extra_files(['A.hs', 'A.hs-boot', 'B.hs', 'C.hs']), - req_interp], + req_th], makefile_test, ['T2014']) diff --git a/testsuite/tests/th/TH_import_loop/TH_import_loop.T b/testsuite/tests/th/TH_import_loop/TH_import_loop.T index 876447de59276464a945dd17df15f6b09426fdfc..acfa0cd95ca56be92d78a9310249b82df18834e0 100644 --- a/testsuite/tests/th/TH_import_loop/TH_import_loop.T +++ b/testsuite/tests/th/TH_import_loop/TH_import_loop.T @@ -1,4 +1,7 @@ -test('TH_import_loop', [extra_files(['Main.hs', 'ModuleA.hs', 'ModuleA.hs-boot', 'ModuleB.hs', 'ModuleC.hs']), - expect_broken(1012)], multimod_compile_and_run, +test('TH_import_loop', + [extra_files(['Main.hs', 'ModuleA.hs', 'ModuleA.hs-boot', 'ModuleB.hs', 'ModuleC.hs']) + # only broken for native linker, not the JS one + , unless(js_arch(), expect_broken(1012)) + ], multimod_compile_and_run, ['Main', '-v0']) diff --git a/testsuite/tests/th/all.T b/testsuite/tests/th/all.T index 9c74c133ef86834b40e0daf9e8f82c53642a6984..3e5a50928aebf3f5e05b9745eccc3e6dae683178 100644 --- a/testsuite/tests/th/all.T +++ b/testsuite/tests/th/all.T @@ -7,7 +7,8 @@ def f(name, opts): opts.extra_hc_opts = '-XTemplateHaskell -package template-haskell' setTestOpts(f) -setTestOpts(req_interp) +setTestOpts(req_th) + # TH should work with -fexternal-interpreter too if config.have_ext_interp : setTestOpts(extra_ways(['ext-interp'])) @@ -272,7 +273,7 @@ test('TH_Roles4', normal, compile, ['-v0']) test('T8186', normal, compile_and_run, ['-v0']) test('T8333', - only_ways(['normal']), + [req_interp, only_ways(['normal'])], makefile_test, ['T8333']) test('T4124', normal, compile, ['-v0']) @@ -336,8 +337,8 @@ test('T10704', [], multimod_compile_and_run, test('T6018th', normal, compile_fail, ['-v0']) test('TH_namePackage', normal, compile_and_run, ['-v0']) test('TH_nameSpace', normal, compile_and_run, ['-v0']) -test('T10796a', normal, compile, ['-v0']) -test('T10796b', normal, compile_fail, ['-v0']) +test('T10796a', normal, compile, ['-v0 -package containers']) +test('T10796b', normal, compile_fail, ['-v0 -package containers']) test('T10811', normal, compile, ['-v0']) test('T10810', normal, compile, ['-v0']) test('T10828', normal, compile, ['-v0 -dsuppress-uniques']) @@ -400,11 +401,11 @@ test('T13123', normal, compile, ['-v0']) test('T13098', normal, compile, ['-v0']) test('T11046', normal, multimod_compile, ['T11046','-v0']) test('T13366C', - [expect_broken_for(13366, ghci_ways)], + [req_c, expect_broken_for(13366, ghci_ways)], compile_and_run, ['-v0']) test('T13366Cxx', - [expect_broken_for(13366, ghci_ways)], + [req_c, expect_broken_for(13366, ghci_ways)], compile_and_run, ['-package system-cxx-std-lib -v0']) test('T13473', normal, multimod_compile_and_run, @@ -435,7 +436,7 @@ test('T14888', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques ' + config.ghc_th_way_flags]) # There is a phasing issue in the ghci way which prevents us from being # able to compile and link the foreign file while compiling the haskell module. -test('T14298', expect_broken_for(15161, ghci_ways), compile_and_run, ['-v0']) +test('T14298', [req_c, expect_broken_for(15161, ghci_ways)], compile_and_run, ['-v0']) test('T14875', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques']) test('T14885a', normal, compile, ['']) test('T14885b', normal, compile, ['']) @@ -484,6 +485,7 @@ test('T16133', normal, compile_fail, ['']) test('T15471', normal, multimod_compile, ['T15471.hs', '-v0']) test('T16180', [when(llvm_build(), expect_broken_for(16541, ['ext-interp'])), + req_asm, # Ideally OpenBSD should have expect_broken_for(14012, ['ext-interp']). # Except the failure is in compilation so skip seems the best we can do. when(opsys('openbsd'), skip), diff --git a/testsuite/tests/th/overloaded/all.T b/testsuite/tests/th/overloaded/all.T index 1cb1eb142451a152d5d0f5565714859d192053fd..4784f00cce0eff5e63c042deaa9267fcb432f4d9 100644 --- a/testsuite/tests/th/overloaded/all.T +++ b/testsuite/tests/th/overloaded/all.T @@ -7,7 +7,7 @@ def f(name, opts): opts.extra_hc_opts = '-XTemplateHaskell -package template-haskell' setTestOpts(f) -setTestOpts(req_interp) +setTestOpts(req_th) # TH should work with -fexternal-interpreter too if config.have_ext_interp : setTestOpts(extra_ways(['ext-interp'])) diff --git a/testsuite/tests/th/should_compile/T13949/all.T b/testsuite/tests/th/should_compile/T13949/all.T index edd3fe5c9fa04177b210362edd4e88440466ed34..4c0ed42b0abf458b331d87cfa6bb5539af18d457 100644 --- a/testsuite/tests/th/should_compile/T13949/all.T +++ b/testsuite/tests/th/should_compile/T13949/all.T @@ -7,6 +7,6 @@ # (1) Use -fexternal-interpreter, or # (2) Build the program twice: once with -dynamic, and then # with -prof using -osuf to set a different object file suffix. -test('T13949', [extra_files(['ASCII.hs', 'PatternGenerator.hs', 'These.hs', 'Tree.hs']), req_interp, +test('T13949', [extra_files(['ASCII.hs', 'PatternGenerator.hs', 'These.hs', 'Tree.hs']), req_th, omit_ways(['profasm'])], multimod_compile, ['ASCII PatternGenerator These Tree', '-fno-code -v0']) diff --git a/testsuite/tests/th/should_compile/T8025/all.T b/testsuite/tests/th/should_compile/T8025/all.T index 4cdf19cae8ebe08728bad69b5a5beb5ab6b3a524..516a78200543a7ccf8756e68a61e03ca093a1419 100644 --- a/testsuite/tests/th/should_compile/T8025/all.T +++ b/testsuite/tests/th/should_compile/T8025/all.T @@ -5,5 +5,5 @@ # (1) Use -fexternal-interpreter, or # (2) Build the program twice: once with -dynamic, and then # with -prof using -osuf to set a different object file suffix. -test('T8025', [extra_files(['A.hs', 'B.hs']), omit_ways(['profasm']), req_interp], +test('T8025', [extra_files(['A.hs', 'B.hs']), omit_ways(['profasm']), req_th], multimod_compile, ['A B', '-fno-code -v0']) diff --git a/testsuite/tests/type-data/should_compile/all.T b/testsuite/tests/type-data/should_compile/all.T index e4008cf5dbe5171380347b9cc3df30c85fbe95fc..2609e2e129b21bccc53e70080f891f99fd0dbb38 100644 --- a/testsuite/tests/type-data/should_compile/all.T +++ b/testsuite/tests/type-data/should_compile/all.T @@ -3,6 +3,6 @@ test('TDExistential', normal, compile, ['']) test('TDGADT', normal, compile, ['']) test('TDGoodConsConstraints', normal, compile, ['']) test('TDVector', normal, compile, ['']) -test('TD_TH_splice', [js_broken(22576), req_th], compile, ['']) +test('TD_TH_splice', req_th, compile, ['']) test('T22315a', [extra_files(['T22315a/'])], multimod_compile, ['T22315a.Lib T22315a.Main', '-v0']) test('T22948b', normal, compile, ['']) diff --git a/testsuite/tests/typecheck/should_fail/all.T b/testsuite/tests/typecheck/should_fail/all.T index b81e15f3b436d5cb253f480a3363f3164ddc8904..a1805014c858201e4be30900a5d98f43a6fdd062 100644 --- a/testsuite/tests/typecheck/should_fail/all.T +++ b/testsuite/tests/typecheck/should_fail/all.T @@ -442,7 +442,7 @@ test('T12837', normal, compile_fail, ['']) test('T12906', normal, compile_fail, ['']) test('T12918a', normal, compile_fail, ['']) test('T12918b', normal, compile_fail, ['']) -test('T12921', req_interp, compile_fail, ['']) +test('T12921', req_th, compile_fail, ['']) test('T12947', normal, compile_fail, ['']) test('StrictBinds', normal, compile_fail, ['']) test('T13068', [extra_files(['T13068.hs', 'T13068a.hs', 'T13068.hs-boot', 'T13068m.hs'])], multimod_compile_fail, ['T13068m', ''])