diff options
author | Matthew Pickering <matthewtpickering@gmail.com> | 2021-07-15 17:16:49 +0100 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2021-08-18 17:57:42 -0400 |
commit | 5f0d2dab9be5b0f89d61e9957bb728538b162230 (patch) | |
tree | 46a725692e2cea227160a61266063cc4a5c2444a /compiler/GHC/Tc/Utils | |
parent | 0ba21dbe28882d506c3536c40224ebff337a9f49 (diff) | |
download | haskell-5f0d2dab9be5b0f89d61e9957bb728538b162230.tar.gz |
Driver rework pt3: the upsweep
This patch specifies and simplifies the module cycle compilation
in upsweep. How things work are described in the Note [Upsweep]
Note [Upsweep]
~~~~~~~~~~~~~~
Upsweep takes a 'ModuleGraph' as input, computes a build plan and then executes
the plan in order to compile the project.
The first step is computing the build plan from a 'ModuleGraph'.
The output of this step is a `[BuildPlan]`, which is a topologically sorted plan for
how to build all the modules.
```
data BuildPlan = SingleModule ModuleGraphNode -- A simple, single module all alone but *might* have an hs-boot file which isn't part of a cycle
| ResolvedCycle [ModuleGraphNode] -- A resolved cycle, linearised by hs-boot files
| UnresolvedCycle [ModuleGraphNode] -- An actual cycle, which wasn't resolved by hs-boot files
```
The plan is computed in two steps:
Step 1: Topologically sort the module graph without hs-boot files. This returns a [SCC ModuleGraphNode] which contains
cycles.
Step 2: For each cycle, topologically sort the modules in the cycle *with* the relevant hs-boot files. This should
result in an acyclic build plan if the hs-boot files are sufficient to resolve the cycle.
The `[BuildPlan]` is then interpreted by the `interpretBuildPlan` function.
* `SingleModule nodes` are compiled normally by either the upsweep_inst or upsweep_mod functions.
* `ResolvedCycles` need to compiled "together" so that the information which ends up in
the interface files at the end is accurate (and doesn't contain temporary information from
the hs-boot files.)
- During the initial compilation, a `KnotVars` is created which stores an IORef TypeEnv for
each module of the loop. These IORefs are gradually updated as the loop completes and provide
the required laziness to typecheck the module loop.
- At the end of typechecking, all the interface files are typechecked again in
the retypecheck loop. This time, the knot-tying is done by the normal laziness
based tying, so the environment is run without the KnotVars.
* UnresolvedCycles are indicative of a proper cycle, unresolved by hs-boot files
and are reported as an error to the user.
The main trickiness of `interpretBuildPlan` is deciding which version of a dependency
is visible from each module. For modules which are not in a cycle, there is just
one version of a module, so that is always used. For modules in a cycle, there are two versions of
'HomeModInfo'.
1. Internal to loop: The version created whilst compiling the loop by upsweep_mod.
2. External to loop: The knot-tied version created by typecheckLoop.
Whilst compiling a module inside the loop, we need to use the (1). For a module which
is outside of the loop which depends on something from in the loop, the (2) version
is used.
As the plan is interpreted, which version of a HomeModInfo is visible is updated
by updating a map held in a state monad. So after a loop has finished being compiled,
the visible module is the one created by typecheckLoop and the internal version is not
used again.
This plan also ensures the most important invariant to do with module loops:
> If you depend on anything within a module loop, before you can use the dependency,
the whole loop has to finish compiling.
The end result of `interpretBuildPlan` is a `[MakeAction]`, which are pairs
of `IO a` actions and a `MVar (Maybe a)`, somewhere to put the result of running
the action. This list is topologically sorted, so can be run in order to compute
the whole graph.
As well as this `interpretBuildPlan` also outputs an `IO [Maybe (Maybe HomeModInfo)]` which
can be queried at the end to get the result of all modules at the end, with their proper
visibility. For example, if any module in a loop fails then all modules in that loop will
report as failed because the visible node at the end will be the result of retypechecking
those modules together.
Along the way we also fix a number of other bugs in the driver:
* Unify upsweep and parUpsweep.
* Fix #19937 (static points, ghci and -j)
* Adds lots of module loop tests due to Divam.
Also related to #20030
Co-authored-by: Divam Narula <dfordivam@gmail.com>
-------------------------
Metric Decrease:
T10370
-------------------------
Diffstat (limited to 'compiler/GHC/Tc/Utils')
-rw-r--r-- | compiler/GHC/Tc/Utils/Env.hs | 5 | ||||
-rw-r--r-- | compiler/GHC/Tc/Utils/Monad.hs | 42 |
2 files changed, 28 insertions, 19 deletions
diff --git a/compiler/GHC/Tc/Utils/Env.hs b/compiler/GHC/Tc/Utils/Env.hs index f291c57ff9..65785fc822 100644 --- a/compiler/GHC/Tc/Utils/Env.hs +++ b/compiler/GHC/Tc/Utils/Env.hs @@ -135,6 +135,7 @@ import qualified GHC.LanguageExtensions as LangExt import Data.IORef import Data.List (intercalate) import Control.Monad +import GHC.Driver.Env.KnotVars {- ********************************************************************* * * @@ -365,7 +366,9 @@ setGlobalTypeEnv :: TcGblEnv -> TypeEnv -> TcM TcGblEnv -- * the tcg_type_env_var field seen by interface files setGlobalTypeEnv tcg_env new_type_env = do { -- Sync the type-envt variable seen by interface files - writeMutVar (tcg_type_env_var tcg_env) new_type_env + ; case lookupKnotVars (tcg_type_env_var tcg_env) (tcg_mod tcg_env) of + Just tcg_env_var -> writeMutVar tcg_env_var new_type_env + Nothing -> return () ; return (tcg_env { tcg_type_env = new_type_env }) } diff --git a/compiler/GHC/Tc/Utils/Monad.hs b/compiler/GHC/Tc/Utils/Monad.hs index 1645333f32..1c5e79013d 100644 --- a/compiler/GHC/Tc/Utils/Monad.hs +++ b/compiler/GHC/Tc/Utils/Monad.hs @@ -133,6 +133,7 @@ module GHC.Tc.Utils.Monad( initIfaceLcl, initIfaceLclWithSubst, initIfaceLoad, + initIfaceLoadModule, getIfModule, failIfM, forkM_maybe, @@ -221,6 +222,7 @@ import GHC.Tc.Errors.Types import {-# SOURCE #-} GHC.Tc.Utils.Env ( tcInitTidyEnv ) import qualified Data.Map as Map +import GHC.Driver.Env.KnotVars {- ************************************************************************ @@ -249,9 +251,7 @@ initTc hsc_env hsc_src keep_rn_syntax mod loc do_this infer_var <- newIORef True ; infer_reasons_var <- newIORef emptyMessages ; dfun_n_var <- newIORef emptyOccSet ; - type_env_var <- case hsc_type_env_var hsc_env of { - Just (_mod, te_var) -> return te_var ; - Nothing -> newIORef emptyNameEnv } ; + let { type_env_var = hsc_type_env_vars hsc_env }; dependent_files_var <- newIORef [] ; static_wc_var <- newIORef emptyWC ; @@ -2063,8 +2063,8 @@ initIfaceTcRn thing_inside = do { tcg_env <- getGblEnv ; hsc_env <- getTopEnv -- bangs to avoid leaking the envs (#19356) - ; let !mod = tcg_semantic_mod tcg_env - !home_unit = hsc_home_unit hsc_env + ; let !home_unit = hsc_home_unit hsc_env + !knot_vars = tcg_type_env_var tcg_env -- When we are instantiating a signature, we DEFINITELY -- do not want to knot tie. is_instantiate = isHomeUnitInstantiating home_unit @@ -2072,21 +2072,30 @@ initIfaceTcRn thing_inside if_doc = text "initIfaceTcRn", if_rec_types = if is_instantiate - then Nothing - else Just (mod, get_type_env) + then emptyKnotVars + else readTcRef <$> knot_vars + } } - ; get_type_env = readTcRef (tcg_type_env_var tcg_env) } ; setEnvs (if_env, ()) thing_inside } --- Used when sucking in a ModIface into a ModDetails to put in --- the HPT. Notably, unlike initIfaceCheck, this does NOT use --- hsc_type_env_var (since we're not actually going to typecheck, --- so this variable will never get updated!) +-- | 'initIfaceLoad' can be used when there's no chance that the action will +-- call 'typecheckIface' when inside a module loop and hence 'tcIfaceGlobal'. initIfaceLoad :: HscEnv -> IfG a -> IO a initIfaceLoad hsc_env do_this = do let gbl_env = IfGblEnv { if_doc = text "initIfaceLoad", - if_rec_types = Nothing + if_rec_types = emptyKnotVars + } + initTcRnIf 'i' hsc_env gbl_env () do_this + +-- | This is used when we are doing to call 'typecheckModule' on an 'ModIface', +-- if it's part of a loop with some other modules then we need to use their +-- IORef TypeEnv vars when typechecking but crucially not our own. +initIfaceLoadModule :: HscEnv -> Module -> IfG a -> IO a +initIfaceLoadModule hsc_env this_mod do_this + = do let gbl_env = IfGblEnv { + if_doc = text "initIfaceLoadModule", + if_rec_types = readTcRef <$> knotVarsWithout this_mod (hsc_type_env_vars hsc_env) } initTcRnIf 'i' hsc_env gbl_env () do_this @@ -2094,12 +2103,9 @@ initIfaceCheck :: SDoc -> HscEnv -> IfG a -> IO a -- Used when checking the up-to-date-ness of the old Iface -- Initialise the environment with no useful info at all initIfaceCheck doc hsc_env do_this - = do let rec_types = case hsc_type_env_var hsc_env of - Just (mod,var) -> Just (mod, readTcRef var) - Nothing -> Nothing - gbl_env = IfGblEnv { + = do let gbl_env = IfGblEnv { if_doc = text "initIfaceCheck" <+> doc, - if_rec_types = rec_types + if_rec_types = readTcRef <$> hsc_type_env_vars hsc_env } initTcRnIf 'i' hsc_env gbl_env () do_this |