summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/main/DynFlags.hs8
-rw-r--r--compiler/main/GhcMake.hs354
2 files changed, 359 insertions, 3 deletions
diff --git a/compiler/main/DynFlags.hs b/compiler/main/DynFlags.hs
index cb7d43c3f0..89ba319238 100644
--- a/compiler/main/DynFlags.hs
+++ b/compiler/main/DynFlags.hs
@@ -579,6 +579,10 @@ data DynFlags = DynFlags {
ruleCheck :: Maybe String,
strictnessBefore :: [Int], -- ^ Additional demand analysis
+ parUpsweepNum :: Maybe Int, -- ^ The number of modules to compile in parallel
+ -- during the upsweep, where Nothing ==> compile as
+ -- many in parallel as there are CPUs.
+
simplTickFactor :: Int, -- ^ Multiplier for simplifier ticks
specConstrThreshold :: Maybe Int, -- ^ Threshold for SpecConstr
specConstrCount :: Maybe Int, -- ^ Max number of specialisations for any one function
@@ -1254,6 +1258,8 @@ defaultDynFlags mySettings =
historySize = 20,
strictnessBefore = [],
+ parUpsweepNum = Just 1,
+
cmdlineHcIncludes = [],
importPaths = ["."],
mainModIs = mAIN,
@@ -2012,6 +2018,8 @@ dynamic_flags = [
addWarn "-#include and INCLUDE pragmas are deprecated: They no longer have any effect"))
, Flag "v" (OptIntSuffix setVerbosity)
+ , Flag "j" (OptIntSuffix (\n -> upd (\d -> d {parUpsweepNum = n})))
+
------- ways --------------------------------------------------------
, Flag "prof" (NoArg (addWay WayProf))
, Flag "eventlog" (NoArg (addWay WayEventLog))
diff --git a/compiler/main/GhcMake.hs b/compiler/main/GhcMake.hs
index c43b18a62a..c4b63b675a 100644
--- a/compiler/main/GhcMake.hs
+++ b/compiler/main/GhcMake.hs
@@ -40,9 +40,10 @@ import TcRnMonad ( initIfaceCheck )
import Bag ( listToBag )
import BasicTypes
import Digraph
-import Exception ( evaluate, tryIO )
+import Exception ( tryIO, gbracket, gfinally )
import FastString
import Maybes ( expectJust, mapCatMaybes )
+import MonadUtils ( allM )
import Outputable
import Panic
import SrcLoc
@@ -54,17 +55,24 @@ import Util
import qualified Data.Map as Map
import qualified FiniteMap as Map ( insertListWith )
+import Control.Concurrent ( forkIOWithUnmask, killThread )
+import Control.Concurrent.MVar
+import Control.Concurrent.QSem
+import Control.Exception
import Control.Monad
import Data.IORef
import Data.List
import qualified Data.List as List
import Data.Maybe
+import Data.Ord ( comparing )
import Data.Time
import System.Directory
import System.FilePath
import System.IO ( fixIO )
import System.IO.Error ( isDoesNotExistError )
+import GHC.Conc ( getNumProcessors, getNumCapabilities, setNumCapabilities )
+
-- -----------------------------------------------------------------------------
-- Loading the program
@@ -253,16 +261,22 @@ load how_much = do
mg = stable_mg ++ unstable_mg
-- clean up between compilations
- let cleanup hsc_env = intermediateCleanTempFiles dflags
+ let cleanup hsc_env = intermediateCleanTempFiles (hsc_dflags hsc_env)
(flattenSCCs mg2_with_srcimps)
hsc_env
liftIO $ debugTraceMsg dflags 2 (hang (text "Ready for upsweep")
2 (ppr mg))
+ n_jobs <- case parUpsweepNum dflags of
+ Nothing -> liftIO getNumProcessors
+ Just n -> return n
+ let upsweep_fn | n_jobs > 1 = parUpsweep n_jobs
+ | otherwise = upsweep
+
setSession hsc_env{ hsc_HPT = emptyHomePackageTable }
(upsweep_ok, modsUpswept)
- <- upsweep pruned_hpt stable_mods cleanup mg
+ <- upsweep_fn pruned_hpt stable_mods cleanup mg
-- Make modsDone be the summaries for each home module now
-- available; this should equal the domain of hpt3.
@@ -595,6 +609,340 @@ checkStability hpt sccs all_home_mods = foldl checkSCC ([],[]) sccs
linkableTime l >= ms_hs_date ms
_other -> False
+{- Parallel Upsweep
+ -
+ - The parallel upsweep attempts to concurrently compile the modules in the
+ - compilation graph using multiple Haskell threads.
+ -
+ - The Algorithm
+ -
+ - A Haskell thread is spawned for each module in the module graph, waiting for
+ - its direct dependencies to finish building before it itself begins to build.
+ -
+ - Each module is associated with an initially empty MVar that stores the
+ - result of that particular module's compile. If the compile succeeded, then
+ - the HscEnv (synchronized by an MVar) is updated with the fresh HMI of that
+ - module, and the module's HMI is deleted from the old HPT (synchronized by an
+ - IORef) to save space.
+ -
+ - Instead of immediately outputting messages to the standard handles, all
+ - compilation output is deferred to a per-module TQueue. A QSem is used to
+ - limit the number of workers that are compiling simultaneously.
+ -
+ - Meanwhile, the main thread sequentially loops over all the modules in the
+ - module graph, outputting the messages stored in each module's TQueue.
+-}
+
+-- | Each module is given a unique 'LogQueue' to redirect compilation messages
+-- to. A 'Nothing' value contains the result of compilation, and denotes the
+-- end of the message queue.
+data LogQueue = LogQueue !(IORef [Maybe (Severity, SrcSpan, PprStyle, MsgDoc)])
+ !(MVar ())
+
+-- | The graph of modules to compile and their corresponding result 'MVar' and
+-- 'LogQueue'.
+type CompilationGraph = [(ModSummary, MVar SuccessFlag, LogQueue)]
+
+-- | Build a 'CompilationGraph' out of a list of strongly-connected modules,
+-- also returning the first, if any, encountered module cycle.
+buildCompGraph :: [SCC ModSummary] -> IO (CompilationGraph, Maybe [ModSummary])
+buildCompGraph [] = return ([], Nothing)
+buildCompGraph (scc:sccs) = case scc of
+ AcyclicSCC ms -> do
+ mvar <- newEmptyMVar
+ log_queue <- do
+ ref <- newIORef []
+ sem <- newEmptyMVar
+ return (LogQueue ref sem)
+ (rest,cycle) <- buildCompGraph sccs
+ return ((ms,mvar,log_queue):rest, cycle)
+ CyclicSCC mss -> return ([], Just mss)
+
+-- | The entry point to the parallel upsweep.
+--
+-- See also the simpler, sequential 'upsweep'.
+parUpsweep
+ :: GhcMonad m
+ => Int
+ -- ^ The number of workers we wish to run in parallel
+ -> HomePackageTable
+ -> ([ModuleName],[ModuleName])
+ -> (HscEnv -> IO ())
+ -> [SCC ModSummary]
+ -> m (SuccessFlag,
+ [ModSummary])
+parUpsweep n_jobs old_hpt stable_mods cleanup sccs = do
+ hsc_env <- getSession
+ let dflags = hsc_dflags hsc_env
+
+ -- The bits of shared state we'll be using:
+
+ -- The global HscEnv is updated with the module's HMI when a module
+ -- successfully compiles.
+ hsc_env_var <- liftIO $ newMVar hsc_env
+
+ -- The old HPT is used for recompilation checking in upsweep_mod. When a
+ -- module sucessfully gets compiled, its HMI is pruned from the old HPT.
+ old_hpt_var <- liftIO $ newIORef old_hpt
+
+ -- The list of modules that have so far been successfully compiled. This is
+ -- used to re-typecheck module loops after the last module in the loop has
+ -- been compiled (see reTypecheckLoop).
+ mods_done_var <- liftIO $ newIORef []
+
+ -- What we use to limit parallelism with.
+ par_sem <- liftIO $ newQSem n_jobs
+
+
+ let updNumCapabilities = liftIO $ do
+ n_capabilities <- getNumCapabilities
+ unless (n_capabilities /= 1) $ setNumCapabilities n_jobs
+ return n_capabilities
+ -- Reset the number of capabilities once the upsweep ends.
+ let resetNumCapabilities orig_n = liftIO $ setNumCapabilities orig_n
+
+ gbracket updNumCapabilities resetNumCapabilities $ \_ -> do
+
+ -- Sync the global session with the latest HscEnv once the upsweep ends.
+ let finallySyncSession io = io `gfinally` do
+ hsc_env <- liftIO $ readMVar hsc_env_var
+ setSession hsc_env
+
+ finallySyncSession $ do
+
+ -- Build the compilation graph out of the list of SCCs. Module cycles are
+ -- handled at the very end, after some useful work gets done. Note that
+ -- this list is topologically sorted (by virtue of 'sccs' being sorted so).
+ (comp_graph,cycle) <- liftIO $ buildCompGraph sccs
+ let comp_graph_w_idx = zip comp_graph [1..]
+
+ -- Build a Map out of the compilation graph with which we can efficiently
+ -- look up the result MVar associated with a particular home module.
+ let mod_map :: Map.Map (Module,Bool) (MVar SuccessFlag, Int)
+ mod_map = Map.fromList [ ((ms_mod ms, isBootSummary ms), (mvar,idx))
+ | ((ms,mvar,_),idx) <- comp_graph_w_idx ]
+
+ -- For each module in the module graph, spawn a worker thread that will
+ -- compile this module.
+ let { spawnWorkers = forM comp_graph_w_idx $ \((mod,!mvar,!log_queue),!mod_idx) ->
+ forkIOWithUnmask $ \unmask -> do
+ -- Replace the default log_action with one that writes each
+ -- message to the module's log_queue. The main thread will
+ -- deal with synchronously printing these messages.
+ --
+ -- Use a local filesToClean var so that we can clean up
+ -- intermediate files in a timely fashion (as soon as
+ -- compilation for that module is finished) without having to
+ -- worry about accidentally deleting a simultaneous compile's
+ -- important files.
+ lcl_files_to_clean <- newIORef []
+ let lcl_dflags = dflags { log_action = parLogAction log_queue
+ , filesToClean = lcl_files_to_clean }
+
+ -- Unmask asynchronous exceptions and perform the thread-local
+ -- work to compile the module (see parUpsweep_one).
+ m_res <- try $ unmask $ prettyPrintGhcErrors lcl_dflags $
+ parUpsweep_one mod mod_map lcl_dflags cleanup par_sem
+ hsc_env_var old_hpt_var mods_done_var
+ stable_mods mod_idx (length sccs)
+
+ res <- case m_res of
+ Right flag -> return flag
+ Left exc -> do
+ -- Don't print ThreadKilled exceptions: they are used
+ -- to kill the worker thread in the event of a user
+ -- interrupt, and the user doesn't have to be informed
+ -- about that.
+ when (fromException exc /= Just ThreadKilled)
+ (errorMsg lcl_dflags (text (show exc)))
+ return Failed
+
+ -- Populate the result MVar.
+ putMVar mvar res
+
+ -- Write the end marker to the message queue, telling the main
+ -- thread that it can stop waiting for messages from this
+ -- particular compile.
+ writeLogQueue log_queue Nothing
+
+ -- Add the remaining files that weren't cleaned up to the
+ -- global filesToClean ref, for cleanup later.
+ files_kept <- readIORef (filesToClean lcl_dflags)
+ addFilesToClean dflags files_kept
+
+
+ -- Kill all the workers, masking interrupts (since killThread is
+ -- interruptible). XXX: This is not ideal.
+ ; killWorkers = uninterruptibleMask_ . mapM_ killThread }
+
+
+ -- Spawn the workers, making sure to kill them later. Collect the results
+ -- of each compile.
+ results <- liftIO $ bracket spawnWorkers killWorkers $ \_ ->
+ -- Loop over each module in the compilation graph in order, printing
+ -- each message from its log_queue.
+ forM comp_graph $ \(mod,mvar,log_queue) -> do
+ printLogs dflags log_queue
+ result <- readMVar mvar
+ if succeeded result then return (Just mod) else return Nothing
+
+
+ -- Collect and return the ModSummaries of all the successful compiles.
+ -- NB: Reverse this list to maintain output parity with the sequential upsweep.
+ let ok_results = reverse (catMaybes results)
+
+ -- Handle any cycle in the original compilation graph and return the result
+ -- of the upsweep.
+ case cycle of
+ Just mss -> do
+ liftIO $ fatalErrorMsg dflags (cyclicModuleErr mss)
+ return (Failed,ok_results)
+ Nothing -> do
+ let success_flag = successIf (all isJust results)
+ return (success_flag,ok_results)
+
+ where
+ writeLogQueue :: LogQueue -> Maybe (Severity,SrcSpan,PprStyle,MsgDoc) -> IO ()
+ writeLogQueue (LogQueue ref sem) msg = do
+ atomicModifyIORef ref $ \msgs -> (msg:msgs,())
+ _ <- tryPutMVar sem ()
+ return ()
+
+ -- The log_action callback that is used to synchronize messages from a
+ -- worker thread.
+ parLogAction :: LogQueue -> LogAction
+ parLogAction log_queue _dflags !severity !srcSpan !style !msg = do
+ writeLogQueue log_queue (Just (severity,srcSpan,style,msg))
+
+ -- Print each message from the log_queue using the log_action from the
+ -- session's DynFlags.
+ printLogs :: DynFlags -> LogQueue -> IO ()
+ printLogs !dflags (LogQueue ref sem) = read_msgs
+ where read_msgs = do
+ takeMVar sem
+ msgs <- atomicModifyIORef ref $ \xs -> ([], reverse xs)
+ print_loop msgs
+
+ print_loop [] = read_msgs
+ print_loop (x:xs) = case x of
+ Just (severity,srcSpan,style,msg) -> do
+ log_action dflags dflags severity srcSpan style msg
+ print_loop xs
+ -- Exit the loop once we encounter the end marker.
+ Nothing -> return ()
+
+-- The interruptible subset of the worker threads' work.
+parUpsweep_one
+ :: ModSummary
+ -- ^ The module we wish to compile
+ -> Map.Map (Module,Bool) (MVar SuccessFlag, Int)
+ -- ^ The map of home modules and their result MVar
+ -> DynFlags
+ -- ^ The thread-local DynFlags
+ -> (HscEnv -> IO ())
+ -- ^ The callback for cleaning up intermediate files
+ -> QSem
+ -- ^ The semaphore for limiting the number of simultaneous compiles
+ -> MVar HscEnv
+ -- ^ The MVar that synchronizes updates to the global HscEnv
+ -> IORef HomePackageTable
+ -- ^ The old HPT
+ -> IORef [ModSummary]
+ -- ^ The list of modules that have successfully compiled
+ -> ([ModuleName],[ModuleName])
+ -- ^ Lists of stable objects and BCOs
+ -> Int
+ -- ^ The index of this module
+ -> Int
+ -- ^ The total number of modules
+ -> IO SuccessFlag
+ -- ^ The result of this compile
+parUpsweep_one mod mod_map lcl_dflags cleanup par_sem hsc_env_var
+ old_hpt_var mods_done_var stable_mods mod_index num_mods = do
+ let home_imps = map unLoc $ ms_home_imps mod
+ home_src_imps = map unLoc $ ms_home_srcimps mod
+ all_imps = zip home_imps (repeat False) ++
+ zip home_src_imps (repeat True)
+
+ -- The module's home-module dependencies.
+ dependencies_w_idx =
+ [ (mvar,idx) | (imp_name,is_boot) <- all_imps
+ , let imp = mkModule (thisPackage lcl_dflags) imp_name
+ , Just (mvar,idx) <- [Map.lookup (imp,is_boot) mod_map] ]
+
+ -- Sort the list of dependencies in reverse-topological order. This
+ -- way, by the time we get woken up by the result of an earlier
+ -- dependency, subsequent dependencies are more likely to have
+ -- finished. This step effectively reduces the number of MVars that
+ -- each thread blocks on.
+ dependencies = map fst $ sortBy (flip (comparing snd)) dependencies_w_idx
+
+ -- Wait for the all the module's dependencies to finish building.
+ deps_ok <- allM (fmap succeeded . readMVar) dependencies
+
+ -- We can't build this module if any of its dependencies failed to build.
+ if not deps_ok
+ then return Failed
+ else do
+ -- Any hsc_env at this point is OK to use since we only really require
+ -- that the HPT contains the HMIs of our dependencies.
+ hsc_env <- readMVar hsc_env_var
+ old_hpt <- readIORef old_hpt_var
+
+ let logger err = printBagOfErrors lcl_dflags (srcErrorMessages err)
+
+ -- Limit the number of parallel compiles.
+ let withSem sem = bracket_ (waitQSem sem) (signalQSem sem)
+ mb_mod_info <- withSem par_sem $
+ handleSourceError (\err -> do logger err; return Nothing) $ do
+ -- Have the ModSummary and HscEnv point to our local log_action
+ -- and filesToClean var.
+ let lcl_mod = localize_mod mod
+ let lcl_hsc_env = localize_hsc_env hsc_env
+
+ -- Compile the module.
+ mod_info <- upsweep_mod lcl_hsc_env old_hpt stable_mods lcl_mod
+ mod_index num_mods
+ return (Just mod_info)
+
+ case mb_mod_info of
+ Nothing -> return Failed
+ Just mod_info -> do
+ let this_mod = ms_mod_name mod
+
+ -- Prune the old HPT unless this is an hs-boot module.
+ unless (isBootSummary mod) $
+ atomicModifyIORef old_hpt_var $ \old_hpt ->
+ (delFromUFM old_hpt this_mod, ())
+
+ -- Update and fetch the list of completed modules.
+ mods_done <- atomicModifyIORef mods_done_var $ \mods_done ->
+ let mods_done' = mod:mods_done
+ in (mods_done',mods_done')
+
+ -- Update and fetch the global HscEnv, and re-typecheck any
+ -- module loops.
+ lcl_hsc_env' <- modifyMVar hsc_env_var $ \hsc_env -> do
+ let hsc_env' = hsc_env { hsc_HPT = addToUFM (hsc_HPT hsc_env)
+ this_mod mod_info }
+ hsc_env'' <- reTypecheckLoop hsc_env' mod mods_done
+ return (hsc_env'', localize_hsc_env hsc_env'')
+
+ -- Clean up any intermediate files.
+ cleanup lcl_hsc_env'
+ return Succeeded
+
+ where
+ localize_mod mod
+ = mod { ms_hspp_opts = (ms_hspp_opts mod)
+ { log_action = log_action lcl_dflags
+ , filesToClean = filesToClean lcl_dflags } }
+
+ localize_hsc_env hsc_env
+ = hsc_env { hsc_dflags = (hsc_dflags hsc_env)
+ { log_action = log_action lcl_dflags
+ , filesToClean = filesToClean lcl_dflags } }
+
-- -----------------------------------------------------------------------------
--
-- | The upsweep