Andreas Klebinger pushed to branch wip/andreask/ghc_par at Glasgow Haskell Compiler / GHC
Commits:
-
28582e8f
by Andreas Klebinger at 2026-03-10T18:19:40+00:00
-
e4a8b165
by Andreas Klebinger at 2026-03-12T01:56:04+00:00
-
f49aa675
by Andreas Klebinger at 2026-03-12T15:08:37+00:00
11 changed files:
- compiler/GHC/Core/Opt/CSE.hs
- compiler/GHC/Core/Opt/CallArity.hs
- compiler/GHC/Core/Opt/CompUnit.hs
- compiler/GHC/Core/Opt/CprAnal.hs
- compiler/GHC/Core/Opt/DmdAnal.hs
- compiler/GHC/Core/Opt/Exitify.hs
- compiler/GHC/Core/Opt/FloatIn.hs
- compiler/GHC/Core/Opt/LiberateCase.hs
- compiler/GHC/Core/Opt/Pipeline.hs
- compiler/GHC/Core/Opt/Simplify.hs
- compiler/GHC/Core/Opt/Split.hs
Changes:
| ... | ... | @@ -25,6 +25,7 @@ import GHC.Core.Map.Expr |
| 25 | 25 | import GHC.Core.Opt.CompUnit (parMapCompUnits)
|
| 26 | 26 | import GHC.Utils.Misc ( filterOut, equalLength )
|
| 27 | 27 | import GHC.Utils.Panic
|
| 28 | +import GHC.Utils.Logger (Logger)
|
|
| 28 | 29 | import Data.Functor.Identity ( Identity (..) )
|
| 29 | 30 | import Data.List ( mapAccumL )
|
| 30 | 31 | |
| ... | ... | @@ -380,8 +381,8 @@ body/rest of the module. |
| 380 | 381 | ************************************************************************
|
| 381 | 382 | -}
|
| 382 | 383 | |
| 383 | -cseProgram :: CoreProgram -> CoreProgram
|
|
| 384 | -cseProgram = parMapCompUnits cseCoreCompUnit
|
|
| 384 | +cseProgram :: Logger -> CoreProgram -> CoreProgram
|
|
| 385 | +cseProgram logger = parMapCompUnits logger "CommonSubExpr" cseCoreCompUnit
|
|
| 385 | 386 | |
| 386 | 387 | cseCoreCompUnit :: CoreCompUnit -> CoreCompUnit
|
| 387 | 388 | cseCoreCompUnit (CoreCompUnit unit_binds unit_rules)
|
| ... | ... | @@ -21,6 +21,7 @@ import GHC.Core.Opt.CompUnit (parMapCompUnits) |
| 21 | 21 | import GHC.Core.Utils ( exprIsCheap, exprIsTrivial )
|
| 22 | 22 | import GHC.Data.Graph.UnVar
|
| 23 | 23 | import GHC.Types.Demand
|
| 24 | +import GHC.Utils.Logger (Logger)
|
|
| 24 | 25 | import GHC.Utils.Misc
|
| 25 | 26 | |
| 26 | 27 | import Control.Arrow ( first, second )
|
| ... | ... | @@ -434,8 +435,8 @@ choice, and hence Call Arity sets the call arity for join points as well. |
| 434 | 435 | |
| 435 | 436 | -- Main entry point
|
| 436 | 437 | |
| 437 | -callArityAnalProgram :: CoreProgram -> CoreProgram
|
|
| 438 | -callArityAnalProgram = parMapCompUnits callArityCompUnit
|
|
| 438 | +callArityAnalProgram :: Logger -> CoreProgram -> CoreProgram
|
|
| 439 | +callArityAnalProgram logger = parMapCompUnits logger "CallArity" callArityCompUnit
|
|
| 439 | 440 | where
|
| 440 | 441 | callArityCompUnit (CoreCompUnit binds unit_rules)
|
| 441 | 442 | = let (_ae, binds') = callArityTopLvl [] emptyVarSet binds
|
| 1 | 1 | module GHC.Core.Opt.CompUnit
|
| 2 | 2 | ( parMapCompUnits
|
| 3 | + , coreCompUnitTimingDoc
|
|
| 4 | + , forceCompUnit
|
|
| 3 | 5 | ) where
|
| 4 | 6 | |
| 5 | 7 | import GHC.Prelude
|
| 6 | 8 | |
| 7 | -import GHC.Conc (par, pseq)
|
|
| 9 | +import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)
|
|
| 10 | +import Control.Exception (SomeException, evaluate, throwIO, try)
|
|
| 11 | +import System.IO.Unsafe (unsafePerformIO)
|
|
| 8 | 12 | |
| 13 | +import GHC.Driver.Flags (DumpFlag(Opt_D_dump_timings))
|
|
| 9 | 14 | import GHC.Core
|
| 10 | -import GHC.Core.Seq (seqBinds)
|
|
| 15 | +import GHC.Core.Stats (coreBindsSize)
|
|
| 16 | +import GHC.Core.Seq (seqBinds, seqRules)
|
|
| 17 | +import GHC.Utils.Error (withTiming)
|
|
| 18 | +import GHC.Utils.Logger (Logger, logHasDumpFlag)
|
|
| 19 | +import GHC.Utils.Outputable
|
|
| 20 | +import Debug.Trace (traceEventIO)
|
|
| 11 | 21 | |
| 12 | -parMapCompUnits :: (CoreCompUnit -> CoreCompUnit) -> CoreProgram -> CoreProgram
|
|
| 13 | -parMapCompUnits f = go
|
|
| 22 | +parMapCompUnits :: Logger -> String -> (CoreCompUnit -> CoreCompUnit) -> CoreProgram -> CoreProgram
|
|
| 23 | +parMapCompUnits logger pass_name f units = unsafePerformIO $ do
|
|
| 24 | + result_vars <- mapM (uncurry fork_unit) (zip [1 :: Int ..] units)
|
|
| 25 | + mapM take_unit result_vars
|
|
| 14 | 26 | where
|
| 15 | - go [] = []
|
|
| 16 | - go (unit:units) = unit' `par` (units' `pseq` (unit' : units'))
|
|
| 17 | - where
|
|
| 18 | - unit' = forceCompUnit (f unit)
|
|
| 19 | - units' = go units
|
|
| 20 | - |
|
| 21 | - forceCompUnit unit@(CoreCompUnit unit_binds _unit_rules) =
|
|
| 22 | - seqBinds unit_binds `seq` unit |
|
| 27 | + total_units = length units
|
|
| 28 | + do_timings = total_units > 1 && logHasDumpFlag logger Opt_D_dump_timings
|
|
| 29 | + |
|
| 30 | + fork_unit unit_no unit = do
|
|
| 31 | + result_var <- newEmptyMVar
|
|
| 32 | + _ <- forkIO $ do
|
|
| 33 | + traceEventIO ("parMapCompUnits: Start(" ++ (show unit_no) ++ "): " ++ pass_name)
|
|
| 34 | + result <- try $
|
|
| 35 | + if do_timings
|
|
| 36 | + then withTiming logger (coreCompUnitTimingDoc pass_name unit_no total_units unit) forceCompUnit $
|
|
| 37 | + evaluate $ let unit' = f unit in forceCompUnit unit' `seq` unit'
|
|
| 38 | + else evaluate $ let unit' = f unit in forceCompUnit unit' `seq` unit'
|
|
| 39 | + putMVar result_var result
|
|
| 40 | + traceEventIO ("parMapCompUnits: End(" ++ (show unit_no) ++ "): " ++ pass_name)
|
|
| 41 | + pure result_var
|
|
| 42 | + |
|
| 43 | + take_unit result_var = do
|
|
| 44 | + result <- takeMVar result_var
|
|
| 45 | + case result of
|
|
| 46 | + Left err -> throwIO (err :: SomeException)
|
|
| 47 | + Right unit -> pure unit
|
|
| 48 | + |
|
| 49 | +forceCompUnit :: CoreCompUnit -> ()
|
|
| 50 | +forceCompUnit (CoreCompUnit unit_binds unit_rules) =
|
|
| 51 | + seqBinds unit_binds `seq` seqRules unit_rules `seq` ()
|
|
| 52 | + |
|
| 53 | +coreCompUnitTimingDoc :: String -> Int -> Int -> CoreCompUnit -> SDoc
|
|
| 54 | +coreCompUnitTimingDoc pass_name unit_no total_units (CoreCompUnit unit_binds unit_rules) =
|
|
| 55 | + text pass_name
|
|
| 56 | + <+> parens
|
|
| 57 | + (text "unit"
|
|
| 58 | + <+> int unit_no <> char '/' <> int total_units <> comma
|
|
| 59 | + <+> text "binds=" <> int (length unit_binds) <> comma
|
|
| 60 | + <+> text "rules=" <> int (length unit_rules) <> comma
|
|
| 61 | + <+> text "size=" <> int (coreBindsSize unit_binds)) |
| ... | ... | @@ -182,7 +182,7 @@ So currently we have |
| 182 | 182 | |
| 183 | 183 | cprAnalProgram :: Logger -> FamInstEnvs -> CoreProgram -> IO CoreProgram
|
| 184 | 184 | cprAnalProgram logger fam_envs comp_units = do
|
| 185 | - let binds_plus_cpr = parMapCompUnits (cprAnalCompUnit (emptyAnalEnv fam_envs)) comp_units
|
|
| 185 | + let binds_plus_cpr = parMapCompUnits logger "CprAnal" (cprAnalCompUnit (emptyAnalEnv fam_envs)) comp_units
|
|
| 186 | 186 | putDumpFileMaybe logger Opt_D_dump_cpr_signatures "Cpr signatures" FormatText $
|
| 187 | 187 | dumpIdInfoOfProgram False (ppr . cprSigInfo) binds_plus_cpr
|
| 188 | 188 | -- See Note [Stamp out space leaks in demand analysis] in GHC.Core.Opt.DmdAnal
|
| ... | ... | @@ -38,6 +38,7 @@ import GHC.Builtin.PrimOps |
| 38 | 38 | import GHC.Builtin.Types.Prim ( realWorldStatePrimTy )
|
| 39 | 39 | |
| 40 | 40 | import GHC.Types.Unique.Set
|
| 41 | +import GHC.Utils.Logger (Logger)
|
|
| 41 | 42 | import GHC.Types.Unique.MemoFun
|
| 42 | 43 | import GHC.Types.RepType
|
| 43 | 44 | import GHC.Types.ForeignCall ( isSafeForeignCall )
|
| ... | ... | @@ -91,9 +92,9 @@ data DmdResult a b = R !a !b |
| 91 | 92 | --
|
| 92 | 93 | -- Note: use `seqBinds` on the result to avoid leaks due to laziness (cf Note
|
| 93 | 94 | -- [Stamp out space leaks in demand analysis])
|
| 94 | -dmdAnalProgram :: DmdAnalOpts -> FamInstEnvs -> [CoreRule] -> CoreProgram -> CoreProgram
|
|
| 95 | -dmdAnalProgram opts fam_envs rules binds
|
|
| 96 | - = parMapCompUnits dmd_anal_comp_unit binds
|
|
| 95 | +dmdAnalProgram :: Logger -> DmdAnalOpts -> FamInstEnvs -> [CoreRule] -> CoreProgram -> CoreProgram
|
|
| 96 | +dmdAnalProgram logger opts fam_envs rules binds
|
|
| 97 | + = parMapCompUnits logger "DmdAnal" dmd_anal_comp_unit binds
|
|
| 97 | 98 | where
|
| 98 | 99 | dmd_anal_comp_unit (CoreCompUnit unit_binds unit_rules)
|
| 99 | 100 | = let WithDmdType _unit_ty unit_binds' = go_unit (emptyAnalEnv opts fam_envs) [] unit_binds
|
| ... | ... | @@ -39,6 +39,7 @@ import GHC.Prelude |
| 39 | 39 | import GHC.Builtin.Uniques
|
| 40 | 40 | import GHC.Core
|
| 41 | 41 | import GHC.Core.Opt.CompUnit (parMapCompUnits)
|
| 42 | +import GHC.Utils.Logger (Logger)
|
|
| 42 | 43 | import GHC.Core.Utils
|
| 43 | 44 | import GHC.Core.FVs
|
| 44 | 45 | import GHC.Core.Type
|
| ... | ... | @@ -60,8 +61,8 @@ import Control.Monad |
| 60 | 61 | |
| 61 | 62 | -- | Traverses the AST, simply to find all joinrecs and call 'exitify' on them.
|
| 62 | 63 | -- The really interesting function is exitifyRec
|
| 63 | -exitifyProgram :: CoreProgram -> CoreProgram
|
|
| 64 | -exitifyProgram comp_units = parMapCompUnits exitifyCompUnit comp_units
|
|
| 64 | +exitifyProgram :: Logger -> CoreProgram -> CoreProgram
|
|
| 65 | +exitifyProgram logger comp_units = parMapCompUnits logger "Exitify" exitifyCompUnit comp_units
|
|
| 65 | 66 | where
|
| 66 | 67 | exitifyCompUnit (CoreCompUnit binds unit_rules) =
|
| 67 | 68 | CoreCompUnit (map goTopLvl binds) unit_rules
|
| ... | ... | @@ -35,6 +35,7 @@ import GHC.Types.Tickish |
| 35 | 35 | import GHC.Types.Var
|
| 36 | 36 | import GHC.Types.Var.Set
|
| 37 | 37 | |
| 38 | +import GHC.Utils.Logger (Logger)
|
|
| 38 | 39 | import GHC.Utils.Misc
|
| 39 | 40 | import GHC.Utils.Panic.Plain
|
| 40 | 41 | |
| ... | ... | @@ -47,8 +48,8 @@ Top-level interface function, @floatInwards@. Note that we do not |
| 47 | 48 | actually float any bindings downwards from the top-level.
|
| 48 | 49 | -}
|
| 49 | 50 | |
| 50 | -floatInwards :: Platform -> CoreProgram -> CoreProgram
|
|
| 51 | -floatInwards platform = parMapCompUnits floatCompUnit
|
|
| 51 | +floatInwards :: Logger -> Platform -> CoreProgram -> CoreProgram
|
|
| 52 | +floatInwards logger platform = parMapCompUnits logger "FloatInwards" floatCompUnit
|
|
| 52 | 53 | where
|
| 53 | 54 | floatCompUnit (CoreCompUnit binds unit_rules) =
|
| 54 | 55 | CoreCompUnit (map (fi_top_bind platform) binds) unit_rules
|
| ... | ... | @@ -19,6 +19,7 @@ import GHC.Core.Opt.Simplify.Inline |
| 19 | 19 | import GHC.Builtin.Types ( unitDataConId )
|
| 20 | 20 | import GHC.Types.Id
|
| 21 | 21 | import GHC.Types.Var.Env
|
| 22 | +import GHC.Utils.Logger (Logger)
|
|
| 22 | 23 | import GHC.Utils.Misc ( notNull )
|
| 23 | 24 | |
| 24 | 25 | {-
|
| ... | ... | @@ -105,8 +106,8 @@ and the level of @h@ is zero (NB not one). |
| 105 | 106 | ************************************************************************
|
| 106 | 107 | -}
|
| 107 | 108 | |
| 108 | -liberateCase :: LibCaseOpts -> CoreProgram -> CoreProgram
|
|
| 109 | -liberateCase opts = parMapCompUnits liberateCaseCompUnit
|
|
| 109 | +liberateCase :: Logger -> LibCaseOpts -> CoreProgram -> CoreProgram
|
|
| 110 | +liberateCase logger opts = parMapCompUnits logger "LiberateCase" liberateCaseCompUnit
|
|
| 110 | 111 | where
|
| 111 | 112 | liberateCaseCompUnit (CoreCompUnit binds unit_rules) =
|
| 112 | 113 | CoreCompUnit (snd (do_unit (initLiberateCaseEnv opts) binds)) unit_rules
|
| ... | ... | @@ -66,6 +66,7 @@ import GHC.Types.Unique.Supply ( UniqueTag(..) ) |
| 66 | 66 | |
| 67 | 67 | import Control.Monad
|
| 68 | 68 | import GHC.Unit.Module
|
| 69 | +import GHC.Conc (getNumCapabilities)
|
|
| 69 | 70 | |
| 70 | 71 | {-
|
| 71 | 72 | ************************************************************************
|
| ... | ... | @@ -484,7 +485,8 @@ doCorePass pass guts = do |
| 484 | 485 | updateBindsAndRulesM (desugarOpt dflags logger (mg_module guts))
|
| 485 | 486 | |
| 486 | 487 | CoreSplit -> {-# SCC "CoreSplit" #-}
|
| 487 | - do { let split_res = map (splitCompUnit (mg_module guts) (mg_boot_exports guts) (mg_rules guts)) (mg_binds guts)
|
|
| 488 | + do { n_threads <- liftIO getNumCapabilities
|
|
| 489 | + ; let split_res = map (splitCompUnit n_threads (mg_module guts) (mg_boot_exports guts) (mg_rules guts)) (mg_binds guts)
|
|
| 488 | 490 | binds' = concatMap fst split_res
|
| 489 | 491 | rules' = mg_rules guts ++ concatMap snd split_res
|
| 490 | 492 | ; return guts { mg_binds = binds', mg_rules = rules' } }
|
| ... | ... | @@ -537,13 +539,13 @@ doCorePass pass guts = do |
| 537 | 539 | liftIOWithCount $ simplifyPgm logger (hsc_unit_env hsc_env) name_ppr_ctx opts guts
|
| 538 | 540 | |
| 539 | 541 | CoreCSE -> {-# SCC "CommonSubExpr" #-}
|
| 540 | - updateBinds cseProgram
|
|
| 542 | + updateBinds (cseProgram logger)
|
|
| 541 | 543 | |
| 542 | 544 | CoreLiberateCase -> {-# SCC "LiberateCase" #-}
|
| 543 | - updateBinds (liberateCase (initLiberateCaseOpts dflags))
|
|
| 545 | + updateBinds (liberateCase logger (initLiberateCaseOpts dflags))
|
|
| 544 | 546 | |
| 545 | 547 | CoreDoFloatInwards -> {-# SCC "FloatInwards" #-}
|
| 546 | - updateBinds (floatInwards platform)
|
|
| 548 | + updateBinds (floatInwards logger platform)
|
|
| 547 | 549 | |
| 548 | 550 | CoreDoFloatOutwards f -> {-# SCC "FloatOutwards" #-}
|
| 549 | 551 | updateBindsM $ \units -> liftIO $
|
| ... | ... | @@ -556,10 +558,10 @@ doCorePass pass guts = do |
| 556 | 558 | updateBinds (doStaticArgs us)
|
| 557 | 559 | |
| 558 | 560 | CoreDoCallArity -> {-# SCC "CallArity" #-}
|
| 559 | - updateBinds callArityAnalProgram
|
|
| 561 | + updateBinds (callArityAnalProgram logger)
|
|
| 560 | 562 | |
| 561 | 563 | CoreDoExitify -> {-# SCC "Exitify" #-}
|
| 562 | - updateBinds exitifyProgram
|
|
| 564 | + updateBinds (exitifyProgram logger)
|
|
| 563 | 565 | |
| 564 | 566 | CoreDoDemand before_ww -> {-# SCC "DmdAnal" #-}
|
| 565 | 567 | updateBindsM (liftIO . dmdAnal logger before_ww dflags fam_envs (mg_rules guts))
|
| ... | ... | @@ -632,7 +634,7 @@ dmdAnal logger before_ww dflags fam_envs rules binds = do |
| 632 | 634 | , dmd_unbox_width = dmdUnboxWidth dflags
|
| 633 | 635 | , dmd_max_worker_args = maxWorkerArgs dflags
|
| 634 | 636 | }
|
| 635 | - binds_plus_dmds = dmdAnalProgram opts fam_envs rules binds
|
|
| 637 | + binds_plus_dmds = dmdAnalProgram logger opts fam_envs rules binds
|
|
| 636 | 638 | Logger.putDumpFileMaybe logger Opt_D_dump_dmd_signatures "Demand signatures" FormatText $
|
| 637 | 639 | dumpIdInfoOfProgram (hasPprDebug dflags) (ppr . zapDmdEnvSig . dmdSigInfo) binds_plus_dmds
|
| 638 | 640 | -- See Note [Stamp out space leaks in demand analysis] in GHC.Core.Opt.DmdAnal
|
| ... | ... | @@ -13,6 +13,7 @@ import GHC.Core |
| 13 | 13 | import GHC.Core.FVs (ruleFreeVars)
|
| 14 | 14 | import GHC.Core.Rules
|
| 15 | 15 | import GHC.Core.Ppr ( pprCoreBindings, pprCoreExpr )
|
| 16 | +import GHC.Core.Opt.CompUnit (coreCompUnitTimingDoc, forceCompUnit)
|
|
| 16 | 17 | import GHC.Core.Opt.OccurAnal ( occurAnalyseCompUnit, occurAnalyseExpr )
|
| 17 | 18 | import GHC.Core.Stats ( coreBindsSize, coreBindsStats, exprSize )
|
| 18 | 19 | import GHC.Core.Utils ( mkTicks, stripTicksTop )
|
| ... | ... | @@ -230,17 +231,33 @@ simplifyPgm' logger unit_env name_ppr_ctx opts |
| 230 | 231 | |
| 231 | 232 | zero_counts = zeroSimplCount $ logHasDumpFlag logger Opt_D_dump_simpl_stats
|
| 232 | 233 | |
| 233 | - run_units :: (CoreCompUnit -> IO a) -> [CoreCompUnit] -> IO [a]
|
|
| 234 | + run_units
|
|
| 235 | + :: (CoreCompUnit -> IO (CoreCompUnit, (String, Int), SimplCount))
|
|
| 236 | + -> [CoreCompUnit]
|
|
| 237 | + -> IO [(CoreCompUnit, (String, Int), SimplCount)]
|
|
| 234 | 238 | run_units f units
|
| 235 | - | parallel_units = mapParallelIO f units
|
|
| 239 | + | parallel_units = mapParallelIO timed_f (zip [1 :: Int ..] units)
|
|
| 236 | 240 | | otherwise = mapM f units
|
| 237 | 241 | where
|
| 242 | + total_units = length units
|
|
| 238 | 243 | parallel_units = length units > 1 && not disable_parallel
|
| 239 | 244 | |
| 240 | 245 | disable_parallel =
|
| 241 | 246 | logHasDumpFlag logger Opt_D_dump_occur_anal
|
| 242 | 247 | || logHasDumpFlag logger Opt_D_dump_simpl_iterations
|
| 243 | 248 | |
| 249 | + timed_f (unit_no, unit)
|
|
| 250 | + | logHasDumpFlag logger Opt_D_dump_timings
|
|
| 251 | + = withTiming logger
|
|
| 252 | + (coreCompUnitTimingDoc "Simplify" unit_no total_units unit)
|
|
| 253 | + force_unit_result
|
|
| 254 | + (f unit)
|
|
| 255 | + | otherwise
|
|
| 256 | + = f unit
|
|
| 257 | + |
|
| 258 | + force_unit_result (unit', _, count) =
|
|
| 259 | + forceCompUnit unit' `seq` count `seq` ()
|
|
| 260 | + |
|
| 244 | 261 | mapParallelIO :: (a -> IO b) -> [a] -> IO [b]
|
| 245 | 262 | mapParallelIO f xs = mask $ \restore -> do
|
| 246 | 263 | workers <- forM xs $ \x -> do
|
| ... | ... | @@ -9,15 +9,15 @@ import GHC.Prelude hiding ( head, init, last ) |
| 9 | 9 | |
| 10 | 10 | import GHC.Core
|
| 11 | 11 | import GHC.Core.FVs
|
| 12 | -import GHC.Core.Opt.OccurAnal (occurAnalyseCompUnit)
|
|
| 12 | +import GHC.Core.Ppr (pprRule)
|
|
| 13 | 13 | import GHC.Core.Stats (coreBindsSize)
|
| 14 | 14 | |
| 15 | 15 | import GHC.Data.Graph.Directed (SCC(..), Node(..), stronglyConnCompFromEdgedVerticesUniq)
|
| 16 | -import GHC.Data.Maybe (orElse)
|
|
| 16 | +import GHC.Data.Maybe (mapMaybe, orElse)
|
|
| 17 | 17 | |
| 18 | 18 | import GHC.Types.Unique.Set
|
| 19 | 19 | import GHC.Types.Name (Name, isExternalName, nameModule)
|
| 20 | -import GHC.Types.Name.Set (NameSet, isEmptyNameSet)
|
|
| 20 | +import GHC.Types.Name.Set (NameSet, elemNameSet, isEmptyNameSet, mkNameSet)
|
|
| 21 | 21 | import GHC.Types.Var.Set
|
| 22 | 22 | import GHC.Types.Var.Env
|
| 23 | 23 | import GHC.Types.Var
|
| ... | ... | @@ -27,6 +27,8 @@ import GHC.Utils.Panic |
| 27 | 27 | |
| 28 | 28 | import GHC.Unit.Module (Module)
|
| 29 | 29 | |
| 30 | +import Data.List (foldl', sortOn)
|
|
| 31 | +import Data.Ord (Down(..))
|
|
| 30 | 32 | import qualified Data.IntMap.Strict as IntMap
|
| 31 | 33 | import qualified Data.IntSet as IntSet
|
| 32 | 34 | |
| ... | ... | @@ -96,6 +98,57 @@ scope. So we must ensure `fa1` and fa2 end up in the same compilation unit. |
| 96 | 98 | |
| 97 | 99 | But for now I think I will just disable splitting if there is a boot module.
|
| 98 | 100 | |
| 101 | +--------------------------------
|
|
| 102 | + |
|
| 103 | +Another wrinkle involving rules:
|
|
| 104 | +Consider this:
|
|
| 105 | + |
|
| 106 | +module A where
|
|
| 107 | + |
|
| 108 | +{-# INLINE[1] foo #-}
|
|
| 109 | +A.f1 x = 42
|
|
| 110 | +A.f2 = A.f1
|
|
| 111 | +-------------------
|
|
| 112 | +module B where
|
|
| 113 | + |
|
| 114 | +{-# RULES "rule-foo-bar" forall x. A.f1 x = bar x #-}
|
|
| 115 | + |
|
| 116 | +foo x = A.f2 x
|
|
| 117 | + |
|
| 118 | +bar x = 16
|
|
| 119 | + |
|
| 120 | + |
|
| 121 | +Here rule-foo-bar only mentions one local binder so naively we would assume foo and bar are independent as the rule doesn't connect any two local binders directly.
|
|
| 122 | +However consider this sequence of events:
|
|
| 123 | + |
|
| 124 | +foo x = A.f2 x
|
|
| 125 | + |
|
| 126 | +=> inline f2
|
|
| 127 | +foo x = A.f1 x
|
|
| 128 | + |
|
| 129 | +=> fire rule
|
|
| 130 | +foo x = bar x
|
|
| 131 | + |
|
| 132 | + |
|
| 133 | +Suddenly those two binders are not so independent anymore! The main point here is we might need to follow arbitrarily
|
|
| 134 | +deep chains of imported unfoldings to avoid this. But doing so I think is prohibitively expensive especially if we don't
|
|
| 135 | +know if we can actually uncover much parallelism for doing so.
|
|
| 136 | +Where does this leave us?
|
|
| 137 | + |
|
| 138 | +If a rule mentions zero local binders we can ignore it.
|
|
| 139 | +If a rule mentions a local binder on both sides it's just an edge between those two binders. (easy)
|
|
| 140 | +If a rule mentions a local binder on the lhs we can ignore it.
|
|
| 141 | +If a rule mentions a single local binder on the rhs then unless we do a deep traversal of imported unfoldings we have to
|
|
| 142 | +treat any imported bindings as potentially linking back to that local binder. (hard)
|
|
| 143 | + |
|
| 144 | +We could the simple thing and say such a rule just makes splitting core unviable and compile any module with such a rule as a
|
|
| 145 | +single compilation unit. And while rules of the last kind are rare specialzation rules are a notable exception:
|
|
| 146 | +In particular if we have: imported :: C a => a -> T2 and {-# SPECIALISE imported :: T1 -> T2 #-} GHC translates this to a
|
|
| 147 | +RULE `imported @T1 $dCT1 = $simported`; The exact shape the last point is talking about!
|
|
| 148 | +It's unclear how to handle this. Do the unfolding traversel only if there are such rules? Might still be quite expensive.
|
|
| 149 | +Especially with aggressive unfolding flags it could end up inspecting every unfolding in a project! Just give up on parallelism
|
|
| 150 | +if there are such rules? Specialization pragmas aren't that rare, so seems like a big loss. Tricky!
|
|
| 151 | + |
|
| 99 | 152 | -}
|
| 100 | 153 | |
| 101 | 154 | data DepGraphNode
|
| ... | ... | @@ -121,6 +174,29 @@ maybeRuleEdges this_module rule = |
| 121 | 174 | where
|
| 122 | 175 | local_fvs = filter (varFromModule this_module) (nonDetEltsUniqSet (ruleFreeVars rule))
|
| 123 | 176 | |
| 177 | +data UnifyingRule = UnifyingRule
|
|
| 178 | + { unifyingRule :: !CoreRule
|
|
| 179 | + , unifyingRuleRhsFvs :: ![Var]
|
|
| 180 | + }
|
|
| 181 | + |
|
| 182 | +findUnifyingRule :: VarSet -> NameSet -> CoreRule -> Maybe UnifyingRule
|
|
| 183 | +findUnifyingRule local_top_bndrs local_top_names rule
|
|
| 184 | + | lhs_has_no_local_binder && not (null rhs_local_fvs)
|
|
| 185 | + = Just (UnifyingRule rule rhs_local_fvs)
|
|
| 186 | + | otherwise
|
|
| 187 | + = Nothing
|
|
| 188 | + where
|
|
| 189 | + lhs_local_fvs = ruleLhsFreeIds rule `intersectVarSet` local_top_bndrs
|
|
| 190 | + lhs_has_no_local_binder =
|
|
| 191 | + isEmptyVarSet lhs_local_fvs && not (ruleHeadIsLocal local_top_names rule)
|
|
| 192 | + |
|
| 193 | + rhs_local_fvs =
|
|
| 194 | + nonDetEltsUniqSet (ruleRhsFreeVars rule `intersectVarSet` local_top_bndrs)
|
|
| 195 | + |
|
| 196 | +ruleHeadIsLocal :: NameSet -> CoreRule -> Bool
|
|
| 197 | +ruleHeadIsLocal local_top_names Rule { ru_fn = fn } = fn `elemNameSet` local_top_names
|
|
| 198 | +ruleHeadIsLocal _ BuiltinRule {} = False
|
|
| 199 | + |
|
| 124 | 200 | bindNode :: VarSet -> CoreBind -> ([DepGraphNode], [Edge])
|
| 125 | 201 | bindNode local_top_bndrs bind =
|
| 126 | 202 | case bindersOf bind of
|
| ... | ... | @@ -197,7 +273,7 @@ assignLocalRules unit_rules binder_components = |
| 197 | 273 | [] -> (rule_map, rule : no_comp_rules)
|
| 198 | 274 | is -> pprPanic "splitCompUnit"
|
| 199 | 275 | ( text "Rule free vars span multiple components"
|
| 200 | - $$ text "rule:" <+> ppr rule
|
|
| 276 | + $$ text "rule:" <+> pprRule rule
|
|
| 201 | 277 | $$ text "components:" <+> ppr is
|
| 202 | 278 | $$ text "rule_fvs:" <+> pprVarsWithModule (nonDetEltsUniqSet (ruleFreeVars rule))
|
| 203 | 279 | $$ vcat [ text "component" <+> int i <> colon <+> pprVarsWithModule hits
|
| ... | ... | @@ -236,20 +312,26 @@ pprVarWithModule v |
| 236 | 312 | |
| 237 | 313 | -- After optimizations a rule might no longer reference binders from this module.
|
| 238 | 314 | -- In these cases we return them here and then add them to mg_rules.
|
| 239 | -splitCompUnit :: Module -> NameSet -> [CoreRule] -> CoreCompUnit -> ([CoreCompUnit], [CoreRule])
|
|
| 240 | -splitCompUnit this_module boot_exported imp_rules unit
|
|
| 315 | +splitCompUnit :: Int -> Module -> NameSet -> [CoreRule] -> CoreCompUnit -> ([CoreCompUnit], [CoreRule])
|
|
| 316 | +splitCompUnit n_threads this_module boot_exported _imp_rules unit
|
|
| 317 | + | n_threads <= 1 = single_comp_unit
|
|
| 241 | 318 | | not boot_exported_is_empty = single_comp_unit
|
| 319 | + | unifying_rule : _ <- unifying_rules
|
|
| 320 | + = pprTrace "splitCompUnit"
|
|
| 321 | + ( text "Not splitting build unit due to unifying rule"
|
|
| 322 | + $$ text "rule:" <+> pprRule (unifyingRule unifying_rule)
|
|
| 323 | + $$ text "local rhs binders:" <+> pprVarsWithModule (unifyingRuleRhsFvs unifying_rule) )
|
|
| 324 | + single_comp_unit
|
|
| 242 | 325 | | otherwise
|
| 243 | - = let comp_units = map mk_comp_unit components_with_rules
|
|
| 326 | + = let comp_units = combineCompUnits max_units (map mk_comp_unit components_with_rules)
|
|
| 244 | 327 | result = (comp_units, rules_for_imps ++ rules_without_component)
|
| 245 | 328 | in -- pprTrace "CoreSplitTrace" (pprSplitTrace comp_units) $
|
| 246 | 329 | checkNameClashes comp_units `seq`
|
| 247 | 330 | result
|
| 248 | 331 | where
|
| 249 | - CoreCompUnit occ_binds unit_rules =
|
|
| 250 | - occurAnalyseCompUnit this_module (const True) (const True) imp_rules unit
|
|
| 332 | + CoreCompUnit unit_binds unit_rules = unit
|
|
| 251 | 333 | |
| 252 | - top_level_bndrs = bindersOfBinds occ_binds
|
|
| 334 | + top_level_bndrs = bindersOfBinds unit_binds
|
|
| 253 | 335 | checked_bndrs =
|
| 254 | 336 | assertPpr (all isLocalVar top_level_bndrs)
|
| 255 | 337 | ( text "splitCompUnit: non-local top-level binder(s)"
|
| ... | ... | @@ -257,8 +339,9 @@ splitCompUnit this_module boot_exported imp_rules unit |
| 257 | 339 | top_level_bndrs
|
| 258 | 340 | |
| 259 | 341 | local_top_bndrs = mkVarSet checked_bndrs
|
| 342 | + local_top_names = mkNameSet (map varName checked_bndrs)
|
|
| 260 | 343 | |
| 261 | - bind_node_info = checked_bndrs `seq` map (bindNode local_top_bndrs) occ_binds
|
|
| 344 | + bind_node_info = checked_bndrs `seq` map (bindNode local_top_bndrs) unit_binds
|
|
| 262 | 345 | bind_nodes = concatMap fst bind_node_info
|
| 263 | 346 | bind_edges = concatMap snd bind_node_info
|
| 264 | 347 | |
| ... | ... | @@ -266,6 +349,8 @@ splitCompUnit this_module boot_exported imp_rules unit |
| 266 | 349 | rule_edges = concat [ es | (_, Just es) <- rule_edge_pairs ]
|
| 267 | 350 | rules_for_imps = [ r | (r, Nothing) <- rule_edge_pairs ]
|
| 268 | 351 | unit_rules_local = [ r | (r, Just _) <- rule_edge_pairs ]
|
| 352 | + unifying_rules =
|
|
| 353 | + mapMaybe (findUnifyingRule local_top_bndrs local_top_names) unit_rules
|
|
| 269 | 354 | |
| 270 | 355 | all_edges = bind_edges ++ rule_edges
|
| 271 | 356 | binder_components = splitCoreBinders bind_nodes all_edges
|
| ... | ... | @@ -275,9 +360,60 @@ splitCompUnit this_module boot_exported imp_rules unit |
| 275 | 360 | mk_comp_unit (_, binds, rules) = CoreCompUnit binds rules
|
| 276 | 361 | |
| 277 | 362 | boot_exported_is_empty = isEmptyNameSet boot_exported
|
| 363 | + max_units = n_threads + 1
|
|
| 278 | 364 | |
| 279 | 365 | single_comp_unit = ([unit], [])
|
| 280 | 366 | |
| 367 | +combineCompUnits :: Int -> [CoreCompUnit] -> [CoreCompUnit]
|
|
| 368 | +combineCompUnits max_units units
|
|
| 369 | + | length units <= max_units = units
|
|
| 370 | + | otherwise = map finishBucket (IntMap.elems final_buckets)
|
|
| 371 | + where
|
|
| 372 | + initial_buckets =
|
|
| 373 | + IntMap.fromDistinctAscList
|
|
| 374 | + [ (i, Bucket 0 [] [])
|
|
| 375 | + | i <- [0 .. max_units - 1]
|
|
| 376 | + ]
|
|
| 377 | + |
|
| 378 | + final_buckets =
|
|
| 379 | + foldl' assignUnit initial_buckets sorted_units
|
|
| 380 | + |
|
| 381 | + sorted_units =
|
|
| 382 | + sortOn (Down . unitSize . snd) (zip [0 :: Int ..] units)
|
|
| 383 | + |
|
| 384 | + assignUnit buckets (_, unit) =
|
|
| 385 | + IntMap.adjust (addUnit unit) target_bucket buckets
|
|
| 386 | + where
|
|
| 387 | + target_bucket = smallestBucket buckets
|
|
| 388 | + |
|
| 389 | + smallestBucket buckets =
|
|
| 390 | + case IntMap.toAscList buckets of
|
|
| 391 | + [] -> panic "combineCompUnits.smallestBucket: empty bucket map"
|
|
| 392 | + b : bs -> fst (foldl' choose_bucket b bs)
|
|
| 393 | + |
|
| 394 | + choose_bucket best@(i1, b1) candidate@(i2, b2)
|
|
| 395 | + | (bucketSize b2, i2) < (bucketSize b1, i1) = candidate
|
|
| 396 | + | otherwise = best
|
|
| 397 | + |
|
| 398 | + finishBucket (Bucket _ binds_acc rules_acc) =
|
|
| 399 | + CoreCompUnit (reverse binds_acc) (reverse rules_acc)
|
|
| 400 | + |
|
| 401 | + unitSize = coreBindsSize . coreCompUnitBinds
|
|
| 402 | + |
|
| 403 | +data Bucket = Bucket
|
|
| 404 | + { bucketSize :: !Int
|
|
| 405 | + , bucketBinds :: [CoreBind]
|
|
| 406 | + , bucketRules :: [CoreRule]
|
|
| 407 | + }
|
|
| 408 | + |
|
| 409 | +addUnit :: CoreCompUnit -> Bucket -> Bucket
|
|
| 410 | +addUnit (CoreCompUnit binds rules) (Bucket sz binds_acc rules_acc) =
|
|
| 411 | + Bucket
|
|
| 412 | + { bucketSize = sz + coreBindsSize binds
|
|
| 413 | + , bucketBinds = foldl' (flip (:)) binds_acc binds
|
|
| 414 | + , bucketRules = foldl' (flip (:)) rules_acc rules
|
|
| 415 | + }
|
|
| 416 | + |
|
| 281 | 417 | checkNameClashes :: [CoreCompUnit] -> ()
|
| 282 | 418 | checkNameClashes comp_units
|
| 283 | 419 | | null dup_bndrs = ()
|