summaryrefslogtreecommitdiff
path: root/compiler/GHC/Driver/Make.hs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/GHC/Driver/Make.hs')
-rw-r--r--compiler/GHC/Driver/Make.hs125
1 files changed, 107 insertions, 18 deletions
diff --git a/compiler/GHC/Driver/Make.hs b/compiler/GHC/Driver/Make.hs
index de3401455f..dfc0af7e38 100644
--- a/compiler/GHC/Driver/Make.hs
+++ b/compiler/GHC/Driver/Make.hs
@@ -27,7 +27,7 @@
-- -----------------------------------------------------------------------------
module GHC.Driver.Make (
depanal, depanalE, depanalPartial, checkHomeUnitsClosed,
- load, loadWithCache, load', LoadHowMuch(..),
+ load, loadWithCache, load', LoadHowMuch(..), HomeModInfoCache(..), noHomeCache, newHomeModInfoCache,
instantiationNodes,
downsweep,
@@ -121,7 +121,7 @@ import Data.Either ( rights, partitionEithers, lefts )
import qualified Data.Map as Map
import qualified Data.Set as Set
-import Control.Concurrent ( newQSem, waitQSem, signalQSem, ThreadId, killThread, forkIOWithUnmask )
+import Control.Concurrent ( newQSem, waitQSem, signalQSem, ThreadId, killThread, forkIOWithUnmask , forkIO )
import qualified GHC.Conc as CC
import Control.Concurrent.MVar
import Control.Monad
@@ -409,6 +409,64 @@ data LoadHowMuch
-- ^ Load only the dependencies of the given module, but not the module
-- itself.
+{-
+Note [Caching HomeModInfo]
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+API clients who call `load` like to cache the HomeModInfo in memory between
+calls to this function. In the old days, this cache was a simple MVar which stored
+a HomePackageTable. This was insufficient, as the interface files for boot modules
+were not recorded in the cache. In the less old days, the cache was returned at the
+end of load, and supplied at the start of load, however, this was not sufficient
+because it didn't account for the possibility of exceptions such as SIGINT (#20780).
+
+So now, in the current day, we have this HomeModInfoCache abstraction which
+can incrementally be updated during the process of upsweep. This alllows us
+to store interface files for boot modules in an exception-safe way.
+
+When the final version of an interface file is completed then it is placed into
+the cache. The contents of the cache is retrieved, and the cache cleared, by hmi_clearCache.
+
+If a module is in a loop then the final version is the retypechecked version of
+the inteface file which is created after the loop has concluded. For modules not
+in a loop, the final interface file is just the normal one we create at the end
+of compiling the module.
+
+One slightly tricky part is keeping the cache up-to-date when performing parallel
+upsweep. In order to do this we spawn a thread for each module which blocks on
+the result variable and once unblocked adds the interface to the cache. This also
+has the effect of writing the maximal number of modules into the cache as modules are not
+inserted in the linearised order. In addition
+to this we are careful to block the program continuing whilst there are still threads
+waiting to write to the cache, lest we end up in situations where load is called
+again before all threads have finished writing and old information will be placed into
+the cache.
+
+
+
+-}
+
+
+-- Abstract interface to a cache of HomeModInfo
+-- See Note [Caching HomeModInfo]
+data HomeModInfoCache = HomeModInfoCache { hmi_clearCache :: IO [HomeModInfo]
+ , hmi_addToCache :: HomeModInfo -> IO () }
+
+noHomeCache :: Maybe HomeModInfoCache
+noHomeCache = Nothing
+
+newHomeModInfoCache :: IO HomeModInfoCache
+newHomeModInfoCache = do
+ ioref <- newIORef []
+ return $
+ HomeModInfoCache
+ { hmi_clearCache = atomicModifyIORef' ioref (\c -> ([], c))
+ , hmi_addToCache = \hmi -> atomicModifyIORef' ioref (\c -> (hmi:c, ()))
+ }
+
+
+
+
-- | Try to load the program. See 'LoadHowMuch' for the different modes.
--
-- This function implements the core of GHC's @--make@ mode. It preprocesses,
@@ -425,7 +483,7 @@ data LoadHowMuch
-- All other errors are reported using the 'defaultWarnErrLogger'.
load :: GhcMonad f => LoadHowMuch -> f SuccessFlag
-load how_much = fst <$> loadWithCache [] how_much
+load how_much = loadWithCache noHomeCache how_much
mkBatchMsg :: HscEnv -> Messager
mkBatchMsg hsc_env =
@@ -434,7 +492,8 @@ mkBatchMsg hsc_env =
then batchMultiMsg
else batchMsg
-loadWithCache :: GhcMonad m => [HomeModInfo] -> LoadHowMuch -> m (SuccessFlag, [HomeModInfo])
+
+loadWithCache :: GhcMonad m => Maybe HomeModInfoCache -> LoadHowMuch -> m SuccessFlag
loadWithCache cache how_much = do
(errs, mod_graph) <- depanalE [] False -- #17459
msg <- mkBatchMsg <$> getSession
@@ -586,8 +645,8 @@ createBuildPlan mod_graph maybe_top_mod =
-- | Generalized version of 'load' which also supports a custom
-- 'Messager' (for reporting progress) and 'ModuleGraph' (generally
-- produced by calling 'depanal'.
-load' :: GhcMonad m => [HomeModInfo] -> LoadHowMuch -> Maybe Messager -> ModuleGraph -> m (SuccessFlag, [HomeModInfo])
-load' cache how_much mHscMessage mod_graph = do
+load' :: GhcMonad m => Maybe HomeModInfoCache -> LoadHowMuch -> Maybe Messager -> ModuleGraph -> m SuccessFlag
+load' mhmi_cache how_much mHscMessage mod_graph = do
modifySession $ \hsc_env -> hsc_env { hsc_mod_graph = mod_graph }
guessOutputFile
hsc_env <- getSession
@@ -621,7 +680,7 @@ load' cache how_much mHscMessage mod_graph = do
| otherwise = do
liftIO $ errorMsg logger
(text "no such module:" <+> quotes (ppr (moduleUnit m) <> colon <> ppr (moduleName m)))
- return (Failed, [])
+ return Failed
checkHowMuch how_much $ do
@@ -642,6 +701,7 @@ load' cache how_much mHscMessage mod_graph = do
build_plan = createBuildPlan mod_graph maybe_top_mod
+ cache <- liftIO $ maybe (return []) hmi_clearCache mhmi_cache
let
-- prune the HPT so everything is not retained when doing an
-- upsweep.
@@ -668,10 +728,10 @@ load' cache how_much mHscMessage mod_graph = do
setSession $ hscUpdateHUG (unitEnv_map pruneHomeUnitEnv) hsc_env
hsc_env <- getSession
- (upsweep_ok, hsc_env1, new_cache) <- withDeferredDiagnostics $
- liftIO $ upsweep n_jobs hsc_env mHscMessage (toCache pruned_cache) build_plan
+ (upsweep_ok, hsc_env1) <- withDeferredDiagnostics $
+ liftIO $ upsweep n_jobs hsc_env mhmi_cache mHscMessage (toCache pruned_cache) build_plan
setSession hsc_env1
- fmap (, new_cache) $ case upsweep_ok of
+ case upsweep_ok of
Failed -> loadFinish upsweep_ok
Succeeded -> do
liftIO $ debugTraceMsg logger 2 (text "Upsweep completely successful.")
@@ -950,18 +1010,46 @@ type RunMakeM a = ReaderT MakeEnv (MaybeT IO) a
-- if the module participates in a loop. This step also labels each node with a number for the output.
-- See Note [Upsweep] for a high-level description.
interpretBuildPlan :: HomeUnitGraph
+ -> Maybe HomeModInfoCache
-> M.Map ModNodeKeyWithUid HomeModInfo
-> [BuildPlan]
-> IO ( Maybe [ModuleGraphNode] -- Is there an unresolved cycle
, [MakeAction] -- Actions we need to run in order to build everything
, IO [Maybe (Maybe HomeModInfo)]) -- An action to query to get all the built modules at the end.
-interpretBuildPlan hug old_hpt plan = do
+interpretBuildPlan hug mhmi_cache old_hpt plan = do
hug_var <- newMVar hug
((mcycle, plans), build_map) <- runStateT (buildLoop plan) (BuildLoopState M.empty 1 hug_var)
- return (mcycle, plans, collect_results (buildDep build_map))
+ wait <- collect_results (buildDep build_map)
+ return (mcycle, plans, wait)
where
- collect_results build_map = mapM (\(_doc, res_var) -> runMaybeT (waitResult res_var)) (M.elems build_map)
+ collect_results build_map = do
+ -- 1. On success, write the result to the cache, fork threads waiting for each result
+ -- so that the cache can be updated out of order, before we wait at the end for everything to
+ -- finish. We do this here rather than when we update the hug_var because we only ever
+ -- want to add things to the cache which are externally visible
+ -- (something which is hard to work out in the main loop).
+ waits <- mapM (\(_doc, res_var) -> collect_result res_var) (M.elems build_map)
+ -- 2. Block waiting for all to finish
+ return (sequence waits)
+
+ -- See Note [Caching HomeModInfo]
+ collect_result res_var = case mhmi_cache of
+ -- Avoid forking a new thread when there's no cache
+ Nothing -> return (runMaybeT (waitResult res_var))
+ Just hmi_cache -> do
+ -- Barrier used to ensure that we write to the cache before execution
+ -- continues.
+ barrier <- newEmptyMVar
+ forkIO (do
+ r <- runMaybeT (waitResult res_var)
+ case r of
+ Just (Just hmi) -> do
+ hmi_addToCache hmi_cache hmi
+ _ -> do
+ return ()
+ putMVar barrier r)
+ return (takeMVar barrier)
n_mods = sum (map countMods plan)
@@ -1057,12 +1145,13 @@ withCurrentUnit uid = do
upsweep
:: Int -- ^ The number of workers we wish to run in parallel
-> HscEnv -- ^ The base HscEnv, which is augmented for each module
+ -> Maybe HomeModInfoCache -- ^ A cache to incrementally write final interface files to
-> Maybe Messager
-> M.Map ModNodeKeyWithUid HomeModInfo
-> [BuildPlan]
- -> IO (SuccessFlag, HscEnv, [HomeModInfo])
-upsweep n_jobs hsc_env mHscMessage old_hpt build_plan = do
- (cycle, pipelines, collect_result) <- interpretBuildPlan (hsc_HUG hsc_env) old_hpt build_plan
+ -> IO (SuccessFlag, HscEnv)
+upsweep n_jobs hsc_env hmi_cache mHscMessage old_hpt build_plan = do
+ (cycle, pipelines, collect_result) <- interpretBuildPlan (hsc_HUG hsc_env) hmi_cache old_hpt build_plan
runPipelines n_jobs hsc_env mHscMessage pipelines
res <- collect_result
@@ -1075,10 +1164,10 @@ upsweep n_jobs hsc_env mHscMessage old_hpt build_plan = do
Just mss -> do
let logger = hsc_logger hsc_env
liftIO $ fatalErrorMsg logger (cyclicModuleErr mss)
- return (Failed, hsc_env, completed)
+ return (Failed, hsc_env)
Nothing -> do
let success_flag = successIf (all isJust res)
- return (success_flag, hsc_env', completed)
+ return (success_flag, hsc_env')
toCache :: [HomeModInfo] -> M.Map (ModNodeKeyWithUid) HomeModInfo
toCache hmis = M.fromList ([(miKey $ hm_iface hmi, hmi) | hmi <- hmis])