ghc-9.14.1: GHC/Unit/Module/Graph.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DeriveTraversable #-}
-- | A module graph should be constructed once and never change from there onwards.
--
-- The only operations should be for building the 'ModuleGraph'
-- (once and for all -- no update-like/insert-like functions)
-- and querying the structure in various ways, e.g. to determine reachability.
--
-- We should avoid exposing fields like 'mg_mss' since it may be a footgun
-- trying to use the nodes directly... We do still expose it, but it feels like
-- all its use cases would be better served by a more proper ModuleGraph
-- abstraction
module GHC.Unit.Module.Graph
(
-- * Construct a module graph
--
-- | A module graph should be constructed once by downsweep and never modified.
ModuleGraph(..)
, emptyMG
, mkModuleGraph
, mkModuleGraphChecked
-- * Invariant checking
, checkModuleGraph
, ModuleGraphInvariantError(..)
-- * Nodes in a module graph
--
-- | The user-facing nodes in a module graph are 'ModuleGraphNode's.
-- There are a few things which we can query out of each 'ModuleGraphNode':
--
-- - 'mgNodeDependencies' gets the immediate dependencies of this node
-- - 'mgNodeUnitId' returns the 'UnitId' of that node
-- - 'mgNodeModSum' extracts the 'ModSummary' of a node if exists
, ModuleGraphNode(..)
, mgNodeDependencies
, mgNodeIsModule
, mgNodeUnitId
, ModuleNodeEdge(..)
, mkModuleEdge
, mkNormalEdge
, ModuleNodeInfo(..)
, moduleNodeInfoModule
, moduleNodeInfoUnitId
, moduleNodeInfoMnwib
, moduleNodeInfoModuleName
, moduleNodeInfoModNodeKeyWithUid
, moduleNodeInfoHscSource
, moduleNodeInfoLocation
, isBootModuleNodeInfo
-- * Module graph operations
, lengthMG
, isEmptyMG
-- ** 'ModSummary' operations
--
-- | A couple of operations on the module graph allow access to the
-- 'ModSummary's of the modules in it contained.
--
-- In particular, 'mapMG' and 'mapMGM' allow updating these 'ModSummary's
-- (without changing the 'ModuleGraph' structure itself!).
-- 'mgModSummaries' lists out all 'ModSummary's, and
-- 'mgLookupModule' looks up a 'ModSummary' for a given module.
, mapMG, mgMapM
, mgModSummaries
, mgLookupModule
, mgLookupModuleName
, mgHasHoles
, showModMsg
-- ** Reachability queries
--
-- | A module graph explains the structure and relationship between the
-- modules being compiled. Often times, this structure is relevant to
-- answer reachability queries -- is X reachable from Y; or, what is the
-- transitive closure of Z?
, mgReachable
, mgReachableLoop
, mgQuery
, ZeroScopeKey(..)
, mgQueryZero
, mgQueryMany
, mgQueryManyZero
, mgMember
-- ** Other operations
--
-- | These operations allow more-internal-than-ideal access to the
-- ModuleGraph structure. Ideally, we could restructure the code using
-- these functions to avoid deconstructing/reconstructing the ModuleGraph
-- and instead extend the "proper interface" of the ModuleGraph to achieve
-- what is currently done but through a better abstraction.
, mgModSummaries'
, moduleGraphNodes
, moduleGraphModulesBelow -- needed for 'hptSomeThingsBelowUs',
-- but I think we could be more clever and cache
-- the graph-ixs of boot modules to efficiently
-- filter them out of the returned list.
-- hptInstancesBelow is re-doing that work every
-- time it's called.
, filterToposortToModules
, moduleGraphNodesZero
, StageSummaryNode
, stageSummaryNodeSummary
, stageSummaryNodeKey
, mkStageDeps
-- * Keys into the 'ModuleGraph'
, NodeKey(..)
, mkNodeKey
, nodeKeyUnitId
, nodeKeyModName
, ModNodeKey
, ModNodeKeyWithUid(..)
, mnkToModule
, moduleToMnk
, mnkToInstalledModule
, installedModuleToMnk
, mnkIsBoot
, msKey
, mnKey
, miKey
, ImportLevel(..)
-- ** Internal node representation
--
-- | 'SummaryNode' is the internal representation for each node stored in
-- the graph. It's not immediately clear to me why users do depend on them.
, SummaryNode
, summaryNodeSummary
, summaryNodeKey
)
where
import GHC.Prelude
import GHC.Platform
import GHC.Data.Maybe
import Data.Either
import GHC.Data.Graph.Directed
import GHC.Data.Graph.Directed.Reachability
import GHC.Driver.Backend
import GHC.Driver.DynFlags
import GHC.Types.SourceFile ( hscSourceString, isHsigFile, HscSource(..))
import GHC.Types.Basic
import GHC.Unit.Module.ModSummary
import GHC.Unit.Types
import GHC.Utils.Outputable
import GHC.Unit.Module.ModIface
import GHC.Utils.Misc ( partitionWith )
import System.FilePath
import qualified Data.Map as Map
import GHC.Types.Unique.DSet
import qualified Data.Set as Set
import Data.Set (Set)
import GHC.Unit.Module
import GHC.Unit.Module.ModNodeKey
import GHC.Unit.Module.Stage
import GHC.Linker.Static.Utils
import Data.Bifunctor
import Data.Function
import Data.List (sort)
import Data.List.NonEmpty ( NonEmpty (..) )
import qualified Data.List.NonEmpty as NE
import Control.Monad
import qualified GHC.LanguageExtensions as LangExt
-- | A '@ModuleGraph@' contains all the nodes from the home package (only). See
-- '@ModuleGraphNode@' for information about the nodes.
--
-- Modules need to be compiled. hs-boots need to be typechecked before
-- the associated "real" module so modules with {-# SOURCE #-} imports can be
-- built. Instantiations also need to be typechecked to ensure that the module
-- fits the signature. Substantiation typechecking is roughly comparable to the
-- check that the module and its hs-boot agree.
--
-- The graph is not necessarily stored in topologically-sorted order. Use
-- 'GHC.topSortModuleGraph' and 'GHC.Data.Graph.Directed.flattenSCC' to achieve this.
data ModuleGraph = ModuleGraph
{ mg_mss :: [ModuleGraphNode]
, mg_graph :: (ReachabilityIndex SummaryNode, NodeKey -> Maybe SummaryNode)
, mg_loop_graph :: (ReachabilityIndex SummaryNode, NodeKey -> Maybe SummaryNode)
, mg_zero_graph :: (ReachabilityIndex ZeroSummaryNode, ZeroScopeKey -> Maybe ZeroSummaryNode)
-- `mg_graph` and `mg_loop_graph` cached transitive dependency calculations
-- so that a lot of work is not repeated whenever the transitive
-- dependencies need to be calculated (for example, hptInstances).
--
--- - `mg_graph` is a reachability index constructed from a module
-- graph /with/ boot nodes (which make the graph acyclic), and
--
--- * `mg_loop_graph` is a reachability index for the graph /without/
-- hs-boot nodes, that may be cyclic.
, mg_has_holes :: !Bool
-- Cached computation, whether any of the ModuleGraphNode are isHoleModule,
-- This is only used for a hack in GHC.Iface.Load to do with backpack, please
-- remove this at the earliest opportunity.
}
-- | Why do we ever need to construct empty graphs? Is it because of one shot mode?
emptyMG :: ModuleGraph
emptyMG = ModuleGraph [] (graphReachability emptyGraph, const Nothing)
(graphReachability emptyGraph, const Nothing)
(graphReachability emptyGraph, const Nothing)
False
-- | Construct a module graph. This function should be the only entry point for
-- building a 'ModuleGraph', since it is supposed to be built once and never modified.
--
-- If you ever find the need to build a 'ModuleGraph' iteratively, don't
-- add insert and update functions to the API since they become footguns.
-- Instead, design an API that allows iterative construction without posterior
-- modification, perhaps like what is done for building arrays from mutable
-- arrays.
mkModuleGraph :: [ModuleGraphNode] -> ModuleGraph
mkModuleGraph = foldr (flip extendMG) emptyMG
-- | A version of mkModuleGraph that checks the module graph for invariants.
mkModuleGraphChecked :: [ModuleGraphNode] -> Either [ModuleGraphInvariantError] ModuleGraph
mkModuleGraphChecked nodes =
let mg = mkModuleGraph nodes
in case checkModuleGraph mg of
[] -> Right mg
errors -> Left errors
--------------------------------------------------------------------------------
-- * Module Graph Nodes
--------------------------------------------------------------------------------
-- | A '@ModuleGraphNode@' is a node in the '@ModuleGraph@'.
-- Edges between nodes mark dependencies arising from module imports
-- and dependencies arising from backpack instantiations.
data ModuleGraphNode
-- | Instantiation nodes track the instantiation of other units
-- (backpack dependencies) with the holes (signatures) of the current package.
= InstantiationNode UnitId InstantiatedUnit
-- | There is a module node for each module being built.
-- A node is either fixed or can be compiled.
-- - Fixed modules are not compiled, the artifacts are just loaded from disk.
-- It is up to your to make sure the artifacts are up to date and available.
-- - Compile modules are compiled from source if needed.
| ModuleNode [ModuleNodeEdge] ModuleNodeInfo
-- | Link nodes are whether are are creating a linked product (ie executable/shared object etc) for a unit.
| LinkNode [NodeKey] UnitId
-- | Package dependency
| UnitNode [UnitId] UnitId
data ModuleNodeEdge = ModuleNodeEdge { edgeLevel :: ImportLevel
, edgeTargetKey :: NodeKey }
mkModuleEdge :: ImportLevel -> NodeKey -> ModuleNodeEdge
mkModuleEdge level key = ModuleNodeEdge level key
-- | A 'normal' edge in the graph which isn't offset by an import stage.
mkNormalEdge :: NodeKey -> ModuleNodeEdge
mkNormalEdge = mkModuleEdge NormalLevel
instance Outputable ModuleNodeEdge where
ppr (ModuleNodeEdge level key) =
let level_str = case level of
NormalLevel -> ""
SpliceLevel -> "(S)"
QuoteLevel -> "(Q)"
in text level_str <> ppr key
data ModuleGraphInvariantError =
FixedNodeDependsOnCompileNode ModNodeKeyWithUid [NodeKey]
| DuplicateModuleNodeKey NodeKey
| DependencyNotInGraph NodeKey [NodeKey]
deriving (Eq, Ord)
instance Outputable ModuleGraphInvariantError where
ppr = \case
FixedNodeDependsOnCompileNode key bad_deps ->
text "Fixed node" <+> ppr key <+> text "depends on compile nodes" <+> ppr bad_deps
DuplicateModuleNodeKey k ->
text "Duplicate module node key" <+> ppr k
DependencyNotInGraph from to ->
text "Dependency not in graph" <+> ppr from <+> text "->" <+> ppr to
-- Used for invariant checking. Is a NodeKey fixed or compilable?
data ModuleNodeType = MN_Fixed | MN_Compile
instance Outputable ModuleNodeType where
ppr = \case
MN_Fixed -> text "Fixed"
MN_Compile -> text "Compile"
moduleNodeType :: ModuleGraphNode -> ModuleNodeType
moduleNodeType (ModuleNode _ (ModuleNodeCompile _)) = MN_Compile
moduleNodeType (ModuleNode _ (ModuleNodeFixed _ _)) = MN_Fixed
moduleNodeType (UnitNode {}) = MN_Fixed
moduleNodeType _ = MN_Compile
checkModuleGraph :: ModuleGraph -> [ModuleGraphInvariantError]
checkModuleGraph ModuleGraph{..} =
mapMaybe (checkFixedModuleInvariant node_types) mg_mss
++ mapMaybe (checkAllDependenciesInGraph node_types) mg_mss
++ duplicate_errs
where
duplicate_errs = rights (Map.elems node_types)
node_types :: Map.Map NodeKey (Either ModuleNodeType ModuleGraphInvariantError)
node_types = Map.fromListWithKey go [ (mkNodeKey n, Left (moduleNodeType n)) | n <- mg_mss ]
where
-- Multiple nodes with the same key are not allowed.
go :: NodeKey -> Either ModuleNodeType ModuleGraphInvariantError
-> Either ModuleNodeType ModuleGraphInvariantError
-> Either ModuleNodeType ModuleGraphInvariantError
go k _ _ = Right (DuplicateModuleNodeKey k)
-- | Check that all dependencies in the graph are present in the node_types map.
-- This is a helper function used by checkModuleGraph.
checkAllDependenciesInGraph :: Map.Map NodeKey (Either ModuleNodeType ModuleGraphInvariantError)
-> ModuleGraphNode
-> Maybe ModuleGraphInvariantError
checkAllDependenciesInGraph node_types node =
let nodeKey = mkNodeKey node
deps = mgNodeDependencies False node
missingDeps = filter (\dep -> not (Map.member dep node_types)) deps
in if null missingDeps
then Nothing
else Just (DependencyNotInGraph nodeKey missingDeps)
-- | Check if for the fixed module node invariant:
--
-- Fixed nodes can only depend on other fixed nodes.
checkFixedModuleInvariant :: Map.Map NodeKey (Either ModuleNodeType ModuleGraphInvariantError)
-> ModuleGraphNode
-> Maybe ModuleGraphInvariantError
checkFixedModuleInvariant node_types node = case node of
ModuleNode deps (ModuleNodeFixed key _) ->
let check_node dep = case Map.lookup dep node_types of
-- Dependency is not fixed
Just (Left MN_Compile) -> Just dep
_ -> Nothing
bad_deps = mapMaybe check_node (map edgeTargetKey deps)
in if null bad_deps
then Nothing
else Just (FixedNodeDependsOnCompileNode key bad_deps)
_ -> Nothing
{- Note [Module Types in the ModuleGraph]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Modules can be one of two different types in the module graph.
1. ModuleNodeCompile, modules with source files we can compile.
2. ModuleNodeFixed, modules which we presume are already compiled and available.
The ModuleGraph can contain a combination of these two types of nodes but must
obey the invariant that Fixed nodes only depend on other Fixed nodes. This invariant
can be checked by the `checkModuleGraph` function, but it's
the responsibility of the code constructing the ModuleGraph to ensure it is upheld.
At the moment, when using --make mode, GHC itself will only use `ModuleNodeCompile` nodes.
In oneshot mode, we don't have access to the source files of dependencies but sometimes need to know
information about the module graph still (for example, getLinkDeps).
In theory, the whole compiler will work if an API program uses ModuleNodeFixed nodes, and
there is a simple test in FixedNodes, which can be extended in future to cover
any missing cases.
-}
data ModuleNodeInfo = ModuleNodeFixed ModNodeKeyWithUid ModLocation
| ModuleNodeCompile ModSummary
-- | Extract the Module from a ModuleNodeInfo
moduleNodeInfoModule :: ModuleNodeInfo -> Module
moduleNodeInfoModule (ModuleNodeFixed key _) = mnkToModule key
moduleNodeInfoModule (ModuleNodeCompile ms) = ms_mod ms
-- | Extract the ModNodeKeyWithUid from a ModuleNodeInfo
moduleNodeInfoModNodeKeyWithUid :: ModuleNodeInfo -> ModNodeKeyWithUid
moduleNodeInfoModNodeKeyWithUid (ModuleNodeFixed key _) = key
moduleNodeInfoModNodeKeyWithUid (ModuleNodeCompile ms) = msKey ms
-- | Extract the HscSource from a ModuleNodeInfo, if we can determine it.
moduleNodeInfoHscSource :: ModuleNodeInfo -> Maybe HscSource
moduleNodeInfoHscSource (ModuleNodeFixed _ _) = Nothing
moduleNodeInfoHscSource (ModuleNodeCompile ms) = Just (ms_hsc_src ms)
-- | Extract the ModLocation from a ModuleNodeInfo
moduleNodeInfoLocation :: ModuleNodeInfo -> ModLocation
moduleNodeInfoLocation (ModuleNodeFixed _ loc) = loc
moduleNodeInfoLocation (ModuleNodeCompile ms) = ms_location ms
-- | Extract the IsBootInterface from a ModuleNodeInfo
isBootModuleNodeInfo :: ModuleNodeInfo -> IsBootInterface
isBootModuleNodeInfo (ModuleNodeFixed mnwib _) = mnkIsBoot mnwib
isBootModuleNodeInfo (ModuleNodeCompile ms) = isBootSummary ms
-- | Extract the ModuleName from a ModuleNodeInfo
moduleNodeInfoModuleName :: ModuleNodeInfo -> ModuleName
moduleNodeInfoModuleName m = moduleName (moduleNodeInfoModule m)
moduleNodeInfoUnitId :: ModuleNodeInfo -> UnitId
moduleNodeInfoUnitId (ModuleNodeFixed key _) = mnkUnitId key
moduleNodeInfoUnitId (ModuleNodeCompile ms) = ms_unitid ms
moduleNodeInfoMnwib :: ModuleNodeInfo -> ModuleNameWithIsBoot
moduleNodeInfoMnwib (ModuleNodeFixed key _) = mnkModuleName key
moduleNodeInfoMnwib (ModuleNodeCompile ms) = ms_mnwib ms
-- | Collect the immediate dependencies of a ModuleGraphNode,
-- optionally avoiding hs-boot dependencies.
-- If the drop_hs_boot_nodes flag is False, and if this is a .hs and there is
-- an equivalent .hs-boot, add a link from the former to the latter. This
-- has the effect of detecting bogus cases where the .hs-boot depends on the
-- .hs, by introducing a cycle. Additionally, it ensures that we will always
-- process the .hs-boot before the .hs, and so the HomePackageTable will always
-- have the most up to date information.
mgNodeDependencies :: Bool -> ModuleGraphNode -> [NodeKey]
mgNodeDependencies drop_hs_boot_nodes = \case
LinkNode deps _uid -> deps
InstantiationNode uid iuid ->
[ NodeKey_Module (ModNodeKeyWithUid (GWIB mod NotBoot) uid) | mod <- uniqDSetToList (instUnitHoles iuid) ]
++ [ NodeKey_ExternalUnit (instUnitInstanceOf iuid) ]
ModuleNode deps _ms ->
map (drop_hs_boot . edgeTargetKey) deps
UnitNode deps _ -> map NodeKey_ExternalUnit deps
where
-- Drop hs-boot nodes by using HsSrcFile as the key
hs_boot_key | drop_hs_boot_nodes = NotBoot -- is regular mod or signature
| otherwise = IsBoot
drop_hs_boot (NodeKey_Module (ModNodeKeyWithUid (GWIB mn IsBoot) uid)) = (NodeKey_Module (ModNodeKeyWithUid (GWIB mn hs_boot_key) uid))
drop_hs_boot x = x
mgNodeIsModule :: ModuleGraphNode -> Maybe ModuleNodeInfo
mgNodeIsModule (InstantiationNode {}) = Nothing
mgNodeIsModule (LinkNode {}) = Nothing
mgNodeIsModule (ModuleNode _ ms) = Just ms
mgNodeIsModule (UnitNode {}) = Nothing
mgNodeUnitId :: ModuleGraphNode -> UnitId
mgNodeUnitId mgn =
case mgn of
InstantiationNode uid _iud -> uid
ModuleNode _ ms -> toUnitId (moduleUnit (moduleNodeInfoModule ms))
LinkNode _ uid -> uid
UnitNode _ uid -> uid
instance Outputable ModuleGraphNode where
ppr = \case
InstantiationNode _ iuid -> ppr iuid
ModuleNode nks ms -> ppr (mnKey ms) <+> ppr nks
LinkNode uid _ -> text "LN:" <+> ppr uid
UnitNode _ uid -> text "P:" <+> ppr uid
instance Eq ModuleGraphNode where
(==) = (==) `on` mkNodeKey
instance Ord ModuleGraphNode where
compare = compare `on` mkNodeKey
--------------------------------------------------------------------------------
-- * Module Graph operations
--------------------------------------------------------------------------------
-- | Returns the number of nodes in a 'ModuleGraph'
lengthMG :: ModuleGraph -> Int
lengthMG = length . mg_mss
isEmptyMG :: ModuleGraph -> Bool
isEmptyMG = null . mg_mss
--------------------------------------------------------------------------------
-- ** ModSummaries
--------------------------------------------------------------------------------
-- | Map a function 'f' over all the 'ModSummaries'.
-- To preserve invariants, 'f' can't change the isBoot status.
mapMG :: (ModSummary -> ModSummary) -> ModuleGraph -> ModuleGraph
mapMG f mg@ModuleGraph{..} = mg
{ mg_mss = flip fmap mg_mss $ \case
InstantiationNode uid iuid -> InstantiationNode uid iuid
LinkNode uid nks -> LinkNode uid nks
ModuleNode deps (ModuleNodeFixed key loc) -> ModuleNode deps (ModuleNodeFixed key loc)
ModuleNode deps (ModuleNodeCompile ms) -> ModuleNode deps (ModuleNodeCompile (f ms))
UnitNode deps uid -> UnitNode deps uid
}
-- | Map a function 'f' over all the 'ModSummaries', in 'IO'.
-- To preserve invariants, 'f' can't change the isBoot status.
mgMapM :: (ModuleNodeInfo -> IO ModuleNodeInfo) -> ModuleGraph -> IO ModuleGraph
mgMapM f mg@ModuleGraph{..} = do
mss' <- forM mg_mss $ \case
InstantiationNode uid iuid -> pure $ InstantiationNode uid iuid
LinkNode uid nks -> pure $ LinkNode uid nks
ModuleNode deps ms -> ModuleNode deps <$> (f ms)
UnitNode deps uid -> pure $ UnitNode deps uid
return $ mg { mg_mss = mss' }
mgModSummaries :: ModuleGraph -> [ModSummary]
mgModSummaries mg = [ m | ModuleNode _ (ModuleNodeCompile m) <- mgModSummaries' mg ]
-- | Look up a non-boot ModSummary in the ModuleGraph.
--
-- Careful: Linear in the size of the module graph
-- MP: This should probably be level aware
mgLookupModule :: ModuleGraph -> Module -> Maybe ModuleNodeInfo
mgLookupModule ModuleGraph{..} m = listToMaybe $ mapMaybe go mg_mss
where
go (ModuleNode _ ms)
| NotBoot <- isBootModuleNodeInfo ms
, moduleNodeInfoModule ms == m
= Just ms
go _ = Nothing
-- | Lookup up a 'ModuleNameWithIsBoot' in the 'ModuleGraph'.
--
-- Multiple nodes in the 'ModuleGraph' can have the same 'ModuleName'
-- and 'IsBootInterface'.
--
-- Careful: Linear in the size of the module graph.
mgLookupModuleName :: ModuleGraph -> ModuleNameWithIsBoot -> [ModuleNodeInfo]
mgLookupModuleName ModuleGraph{..} m = mapMaybe go mg_mss
where
go (ModuleNode _ ms)
| moduleNodeInfoMnwib ms == m
= Just ms
go _ = Nothing
mgMember :: ModuleGraph -> NodeKey -> Bool
mgMember graph k = isJust $ snd (mg_graph graph) k
-- | A function you should not need to use, or desire to use. Only used
-- in one place, `GHC.Iface.Load` to facilitate a misimplementation in Backpack.
mgHasHoles :: ModuleGraph -> Bool
mgHasHoles ModuleGraph{..} = mg_has_holes
--------------------------------------------------------------------------------
-- ** Reachability
--------------------------------------------------------------------------------
-- | Return all nodes reachable from the given 'NodeKey'.
--
-- @Nothing@ if the key couldn't be found in the graph.
mgReachable :: ModuleGraph -> NodeKey -> Maybe [ModuleGraphNode]
mgReachable mg nk = map summaryNodeSummary <$> modules_below where
(td_map, lookup_node) = mg_graph mg
modules_below =
allReachable td_map <$> lookup_node nk
-- | Things which are reachable if hs-boot files are ignored. Used by 'getLinkDeps'
mgReachableLoop :: ModuleGraph -> [NodeKey] -> [ModuleGraphNode]
mgReachableLoop mg nk = map summaryNodeSummary modules_below where
(td_map, lookup_node) = mg_loop_graph mg
modules_below =
allReachableMany td_map (mapMaybe lookup_node nk)
-- | @'mgQueryZero' g root b@ answers the question: can we reach @b@ from @root@
-- in the module graph @g@, only using normal (level 0) imports?
mgQueryZero :: ModuleGraph
-> ZeroScopeKey
-> ZeroScopeKey
-> Bool
mgQueryZero mg nka nkb = isReachable td_map na nb where
(td_map, lookup_node) = mg_zero_graph mg
na = expectJust $ lookup_node nka
nb = expectJust $ lookup_node nkb
-- | Reachability Query.
--
-- @mgQuery(g, a, b)@ asks:
-- Can we reach @b@ from @a@ in graph @g@?
--
-- Both @a@ and @b@ must be in @g@.
mgQuery :: ModuleGraph -- ^ @g@
-> NodeKey -- ^ @a@
-> NodeKey -- ^ @b@
-> Bool -- ^ @b@ is reachable from @a@
mgQuery mg nka nkb = isReachable td_map na nb where
(td_map, lookup_node) = mg_graph mg
na = expectJust $ lookup_node nka
nb = expectJust $ lookup_node nkb
-- | Many roots reachability Query.
--
-- @mgQuery(g, roots, b)@ asks:
-- Can we reach @b@ from any of the @roots@ in graph @g@?
--
-- Node @b@ must be in @g@.
mgQueryMany :: ModuleGraph -- ^ @g@
-> [NodeKey] -- ^ @roots@
-> NodeKey -- ^ @b@
-> Bool -- ^ @b@ is reachable from @roots@
mgQueryMany mg roots nkb = isReachableMany td_map nroots nb where
(td_map, lookup_node) = mg_graph mg
nroots = mapMaybe lookup_node roots
nb = expectJust $ lookup_node nkb
-- | Many roots reachability Query.
--
-- @mgQuery(g, roots, b)@ asks:
-- Can we reach @b@ from any of the @roots@ in graph @g@, only using normal (level 0) imports?
--
-- Node @b@ must be in @g@.
mgQueryManyZero :: ModuleGraph -- ^ @g@
-> [ZeroScopeKey] -- ^ @roots@
-> ZeroScopeKey -- ^ @b@
-> Bool -- ^ @b@ is reachable from @roots@
mgQueryManyZero mg roots nkb = isReachableMany td_map nroots nb where
(td_map, lookup_node) = mg_zero_graph mg
nroots = mapMaybe lookup_node roots
nb = expectJust $ lookup_node (pprTrace "mg" (ppr nkb) nkb)
--------------------------------------------------------------------------------
-- ** Other operations (read haddocks on export list)
--------------------------------------------------------------------------------
mgModSummaries' :: ModuleGraph -> [ModuleGraphNode]
mgModSummaries' = mg_mss
-- | Turn a list of graph nodes into an efficient queriable graph.
-- The first boolean parameter indicates whether nodes corresponding to hs-boot files
-- should be collapsed into their relevant hs nodes.
moduleGraphNodes :: Bool
-> [ModuleGraphNode]
-> (Graph SummaryNode, NodeKey -> Maybe SummaryNode)
moduleGraphNodes drop_hs_boot_nodes summaries =
(graphFromEdgedVerticesUniq nodes, lookup_node)
where
-- Map from module to extra boot summary dependencies which need to be merged in
(boot_summaries, nodes) = bimap Map.fromList id $ partitionWith go numbered_summaries
where
go (s, key) =
case s of
ModuleNode __deps ms | isBootModuleNodeInfo ms == IsBoot, drop_hs_boot_nodes
-- Using nodeDependencies here converts dependencies on other
-- boot files to dependencies on dependencies on non-boot files.
-> Left (moduleNodeInfoModule ms, mgNodeDependencies drop_hs_boot_nodes s)
_ -> normal_case
where
normal_case =
let lkup_key = moduleNodeInfoModule <$> mgNodeIsModule s
extra = (lkup_key >>= \key -> Map.lookup key boot_summaries)
in Right $ DigraphNode s key $ out_edge_keys $
(fromMaybe [] extra
++ mgNodeDependencies drop_hs_boot_nodes s)
numbered_summaries = zip summaries [1..]
lookup_node :: NodeKey -> Maybe SummaryNode
lookup_node key = Map.lookup key (unNodeMap node_map)
lookup_key :: NodeKey -> Maybe Int
lookup_key = fmap summaryNodeKey . lookup_node
node_map :: NodeMap SummaryNode
node_map = NodeMap $
Map.fromList [ (mkNodeKey s, node)
| node <- nodes
, let s = summaryNodeSummary node
]
out_edge_keys :: [NodeKey] -> [Int]
out_edge_keys = mapMaybe lookup_key
-- If we want keep_hi_boot_nodes, then we do lookup_key with
-- IsBoot; else False
-- | This function returns all the modules belonging to the home-unit that can
-- be reached by following the given dependencies. Additionally, if both the
-- boot module and the non-boot module can be reached, it only returns the
-- non-boot one.
moduleGraphModulesBelow :: ModuleGraph -> UnitId -> ModuleNameWithIsBoot -> Set ModNodeKeyWithUid
moduleGraphModulesBelow mg uid mn = filtered_mods [ mn | NodeKey_Module mn <- modules_below]
where
modules_below = maybe [] (map mkNodeKey) (mgReachable mg (NodeKey_Module (ModNodeKeyWithUid mn uid)))
filtered_mods = Set.fromDistinctAscList . filter_mods . sort
-- IsBoot and NotBoot modules are necessarily consecutive in the sorted list
-- (cf Ord instance of GenWithIsBoot). Hence we only have to perform a
-- linear sweep with a window of size 2 to remove boot modules for which we
-- have the corresponding non-boot.
filter_mods = \case
(r1@(ModNodeKeyWithUid (GWIB m1 b1) uid1) : r2@(ModNodeKeyWithUid (GWIB m2 _) uid2): rs)
| m1 == m2 && uid1 == uid2 ->
let !r' = case b1 of
NotBoot -> r1
IsBoot -> r2
in r' : filter_mods rs
| otherwise -> r1 : filter_mods (r2:rs)
rs -> rs
-- | This function filters out all the instantiation nodes from each SCC of a
-- topological sort. Use this with care, as the resulting "strongly connected components"
-- may not really be strongly connected in a direct way, as instantiations have been
-- removed. It would probably be best to eliminate uses of this function where possible.
filterToposortToModules
:: [SCC ModuleGraphNode] -> [SCC ModuleNodeInfo]
filterToposortToModules = mapMaybe $ mapMaybeSCC $ \case
ModuleNode _deps node -> Just node
_ -> Nothing
where
-- This higher order function is somewhat bogus,
-- as the definition of "strongly connected component"
-- is not necessarily respected.
mapMaybeSCC :: (a -> Maybe b) -> SCC a -> Maybe (SCC b)
mapMaybeSCC f = \case
AcyclicSCC a -> AcyclicSCC <$> f a
CyclicSCC as -> case mapMaybe f as of
[] -> Nothing
[a] -> Just $ AcyclicSCC a
as -> Just $ CyclicSCC as
--------------------------------------------------------------------------------
-- * Keys into ModuleGraph
--------------------------------------------------------------------------------
data NodeKey = NodeKey_Unit {-# UNPACK #-} !InstantiatedUnit
| NodeKey_Module {-# UNPACK #-} !ModNodeKeyWithUid
| NodeKey_Link !UnitId
| NodeKey_ExternalUnit !UnitId
deriving (Eq, Ord)
instance Outputable NodeKey where
ppr (NodeKey_Unit iu) = ppr iu
ppr (NodeKey_Module mk) = ppr mk
ppr (NodeKey_Link uid) = ppr uid
ppr (NodeKey_ExternalUnit uid) = ppr uid
mkNodeKey :: ModuleGraphNode -> NodeKey
mkNodeKey = \case
InstantiationNode _ iu -> NodeKey_Unit iu
ModuleNode _ x -> NodeKey_Module $ mnKey x
LinkNode _ uid -> NodeKey_Link uid
UnitNode _ uid -> NodeKey_ExternalUnit uid
nodeKeyUnitId :: NodeKey -> UnitId
nodeKeyUnitId (NodeKey_Unit iu) = instUnitInstanceOf iu
nodeKeyUnitId (NodeKey_Module mk) = mnkUnitId mk
nodeKeyUnitId (NodeKey_Link uid) = uid
nodeKeyUnitId (NodeKey_ExternalUnit uid) = uid
nodeKeyModName :: NodeKey -> Maybe ModuleName
nodeKeyModName (NodeKey_Module mk) = Just (gwib_mod $ mnkModuleName mk)
nodeKeyModName _ = Nothing
msKey :: ModSummary -> ModNodeKeyWithUid
msKey ms = ModNodeKeyWithUid (ms_mnwib ms) (ms_unitid ms)
mnKey :: ModuleNodeInfo -> ModNodeKeyWithUid
mnKey (ModuleNodeFixed key _) = key
mnKey (ModuleNodeCompile ms) = msKey ms
miKey :: ModIface -> ModNodeKeyWithUid
miKey hmi = ModNodeKeyWithUid (mi_mnwib hmi) ((toUnitId $ moduleUnit (mi_module hmi)))
type ModNodeKey = ModuleNameWithIsBoot
--------------------------------------------------------------------------------
-- ** Internal node representation (exposed)
--------------------------------------------------------------------------------
type SummaryNode = Node Int ModuleGraphNode
summaryNodeKey :: SummaryNode -> Int
summaryNodeKey = node_key
summaryNodeSummary :: SummaryNode -> ModuleGraphNode
summaryNodeSummary = node_payload
--------------------------------------------------------------------------------
-- * Misc utilities
--------------------------------------------------------------------------------
showModMsg :: DynFlags -> Bool -> ModuleGraphNode -> SDoc
showModMsg dflags _ (LinkNode {}) =
let staticLink = case ghcLink dflags of
LinkStaticLib -> True
_ -> False
platform = targetPlatform dflags
arch_os = platformArchOS platform
exe_file = exeFileName arch_os staticLink (outputFile_ dflags)
in text exe_file
showModMsg _ _ (UnitNode _deps uid) = ppr uid
showModMsg _ _ (InstantiationNode _uid indef_unit) =
ppr $ instUnitInstanceOf indef_unit
showModMsg dflags recomp (ModuleNode _ mni) =
if gopt Opt_HideSourcePaths dflags
then text mod_str
else hsep $
[ text (mod_str ++ replicate (max 0 (16 - length mod_str)) ' ')
, char '('
, text (moduleNodeInfoSource mni) <> char ','
, moduleNodeInfoExtraMessage dflags recomp mni, char ')' ]
where
mod_str = moduleNameString (moduleName (moduleNodeInfoModule mni)) ++
moduleNodeInfoBootString mni
-- | Extra information about a 'ModuleNodeInfo' to display in the progress message.
moduleNodeInfoExtraMessage :: DynFlags -> Bool -> ModuleNodeInfo -> SDoc
moduleNodeInfoExtraMessage dflags recomp (ModuleNodeCompile mod_summary) =
let dyn_file = normalise $ msDynObjFilePath mod_summary
obj_file = normalise $ msObjFilePath mod_summary
files = obj_file
:| [ dyn_file | gopt Opt_BuildDynamicToo dflags ]
++ [ "interpreted" | gopt Opt_ByteCodeAndObjectCode dflags ]
in case backendSpecialModuleSource (backend dflags) recomp of
Just special -> text special
Nothing -> foldr1 (\ofile rest -> ofile <> comma <+> rest) (NE.map text files)
moduleNodeInfoExtraMessage _ _ (ModuleNodeFixed {}) = text "fixed"
-- | The source location of the module node to show to the user.
moduleNodeInfoSource :: ModuleNodeInfo -> FilePath
moduleNodeInfoSource (ModuleNodeCompile ms) = normalise $ msHsFilePath ms
moduleNodeInfoSource (ModuleNodeFixed _ loc) = normalise $ ml_hi_file loc
-- | The extra info about a module [boot] or [sig] to display.
moduleNodeInfoBootString :: ModuleNodeInfo -> String
moduleNodeInfoBootString (ModuleNodeCompile ms) = hscSourceString (ms_hsc_src ms)
moduleNodeInfoBootString mn@(ModuleNodeFixed {}) =
hscSourceString (case isBootModuleNodeInfo mn of
IsBoot -> HsBootFile
NotBoot -> HsSrcFile)
--------------------------------------------------------------------------------
-- * Internal methods for module graph
--
-- These are *really* meant to be internal!
-- Don't expose them without careful consideration about the invariants
-- described in the export list haddocks.
--------------------------------------------------------------------------------
newtype NodeMap a = NodeMap { unNodeMap :: Map.Map NodeKey a }
deriving (Functor, Traversable, Foldable)
-- | Transitive dependencies, including SOURCE edges
mkTransDeps :: [ModuleGraphNode] -> (ReachabilityIndex SummaryNode, NodeKey -> Maybe SummaryNode)
mkTransDeps = first graphReachability {- module graph is acyclic -} . moduleGraphNodes False
-- | Transitive dependencies, ignoring SOURCE edges
mkTransLoopDeps :: [ModuleGraphNode] -> (ReachabilityIndex SummaryNode, NodeKey -> Maybe SummaryNode)
mkTransLoopDeps = first cyclicGraphReachability . moduleGraphNodes True
-- | Transitive dependencies, but only following "normal" level 0 imports.
-- This graph can be used to query what the transitive dependencies of a particular
-- level are within a module.
mkTransZeroDeps :: [ModuleGraphNode] -> (ReachabilityIndex ZeroSummaryNode, ZeroScopeKey -> Maybe ZeroSummaryNode)
mkTransZeroDeps = first graphReachability {- module graph is acyclic -} . moduleGraphNodesZero
-- | Transitive dependencies, but with the stage that each module is required at.
mkStageDeps :: [ModuleGraphNode] -> (ReachabilityIndex StageSummaryNode, (NodeKey, ModuleStage) -> Maybe StageSummaryNode)
mkStageDeps = first cyclicGraphReachability . moduleGraphNodesStages
type ZeroSummaryNode = Node Int ZeroScopeKey
zeroSummaryNodeKey :: ZeroSummaryNode -> Int
zeroSummaryNodeKey = node_key
zeroSummaryNodeSummary :: ZeroSummaryNode -> ZeroScopeKey
zeroSummaryNodeSummary = node_payload
-- | The 'ZeroScopeKey' indicates the different scopes which we can refer to in a zero-scope query.
data ZeroScopeKey = ModuleScope ModNodeKeyWithUid ImportLevel | UnitScope UnitId
deriving (Eq, Ord)
instance Outputable ZeroScopeKey where
ppr (ModuleScope mk il) = text "ModuleScope" <+> ppr mk <+> ppr il
ppr (UnitScope uid) = text "UnitScope" <+> ppr uid
-- | Turn a list of graph nodes into an efficient queriable graph.
-- This graph only has edges between level-0 imports
--
-- This query answers the question. If I am looking at level n in module M then which
-- modules are visible?
--
-- If you are looking at level -1 then the reachable modules are those imported at splice and
-- then any modules those modules import at zero. (Ie the zero scope for those modules)
moduleGraphNodesZero ::
[ModuleGraphNode]
-> (Graph ZeroSummaryNode, ZeroScopeKey -> Maybe ZeroSummaryNode)
moduleGraphNodesZero summaries =
(graphFromEdgedVerticesUniq nodes, lookup_node)
where
nodes = mapMaybe go numbered_summaries
where
go :: (((ModuleGraphNode, ImportLevel)), Int) -> Maybe ZeroSummaryNode
go (((ModuleNode nks ms), s), key) = Just $
DigraphNode (ModuleScope (mnKey ms) s) key $ out_edge_keys $
mapMaybe (classifyDeps s) nks
go (((UnitNode uids uid), _s), key) =
Just $ DigraphNode (UnitScope uid) key (mapMaybe lookup_key $ map UnitScope uids)
go _ = Nothing
-- This is the key part, a dependency edge also depends on the NormalLevel scope of an import.
classifyDeps s (ModuleNodeEdge il (NodeKey_Module k)) | s == il = Just (ModuleScope k NormalLevel)
classifyDeps s (ModuleNodeEdge il (NodeKey_ExternalUnit u)) | s == il = Just (UnitScope u)
classifyDeps _ _ = Nothing
numbered_summaries :: [((ModuleGraphNode, ImportLevel), Int)]
numbered_summaries = zip (([(s, l) | s <- summaries, l <- [SpliceLevel, QuoteLevel, NormalLevel]])) [0..]
lookup_node :: ZeroScopeKey -> Maybe ZeroSummaryNode
lookup_node key = Map.lookup key node_map
lookup_key :: ZeroScopeKey -> Maybe Int
lookup_key = fmap zeroSummaryNodeKey . lookup_node
node_map :: Map.Map ZeroScopeKey ZeroSummaryNode
node_map =
Map.fromList [ (s, node)
| node <- nodes
, let s = zeroSummaryNodeSummary node
]
out_edge_keys :: [ZeroScopeKey] -> [Int]
out_edge_keys = mapMaybe lookup_key
type StageSummaryNode = Node Int (NodeKey, ModuleStage)
stageSummaryNodeKey :: StageSummaryNode -> Int
stageSummaryNodeKey = node_key
stageSummaryNodeSummary :: StageSummaryNode -> (NodeKey, ModuleStage)
stageSummaryNodeSummary = node_payload
-- | Turn a list of graph nodes into an efficient queriable graph.
-- This graph has edges between modules and the stage they are required at.
--
-- This graph can be used to answer the query, if I am compiling a module at stage
-- S, then what modules do I need at which stages for that?
-- Used by 'downsweep' in order to determine which modules need code generation if you
-- are using 'TemplateHaskell'.
--
-- The rules for this query can be read in more detail in the Explicit Level Imports proposal.
-- Briefly:
-- * If NoImplicitStagePersistence then Quote/Splice/Normal imports offset the required stage
-- * If ImplicitStagePersistence and TemplateHaskell then imported module are needed at all stages.
-- * Otherwise, an imported module is just needed at the normal stage.
--
-- * A module using TemplateHaskellQuotes required at C stage is also required at R
-- stage.
moduleGraphNodesStages ::
[ModuleGraphNode]
-> (Graph StageSummaryNode, (NodeKey, ModuleStage) -> Maybe StageSummaryNode)
moduleGraphNodesStages summaries =
(graphFromEdgedVerticesUniq nodes, lookup_node)
where
nodes = map go numbered_summaries
where
go :: (((ModuleGraphNode, ModuleStage)), Int) -> StageSummaryNode
go (s, key) = normal_case s
where
normal_case :: (ModuleGraphNode, ModuleStage) -> StageSummaryNode
normal_case ((m@(ModuleNode nks ms), s)) =
DigraphNode ((mkNodeKey m, s)) key $ out_edge_keys $
selfEdges ms s (mkNodeKey m) ++ concatMap (classifyDeps ms s) nks
normal_case (m, s) =
DigraphNode (mkNodeKey m, s) key (out_edge_keys . map (, s) $ mgNodeDependencies False m)
isExplicitStageMS :: ModSummary -> Bool
isExplicitStageMS ms = not (xopt LangExt.ImplicitStagePersistence (ms_hspp_opts ms))
isTemplateHaskellQuotesMS :: ModSummary -> Bool
isTemplateHaskellQuotesMS ms = xopt LangExt.TemplateHaskellQuotes (ms_hspp_opts ms)
-- Accounting for persistence within a module.
-- If a module is required @ C and it persists an idenfifier, it's also required
-- at R.
selfEdges (ModuleNodeCompile ms) s self_key
| not (isExplicitStageMS ms)
&& (isTemplateHaskellQuotesMS ms
|| isTemplateHaskellOrQQNonBoot ms)
= [(self_key, s') | s' <- onlyFutureStages s]
selfEdges _ _ _ = []
-- Case 1. No implicit stage persistnce is enabled
classifyDeps (ModuleNodeCompile ms) s (ModuleNodeEdge il k)
| isExplicitStageMS ms = case il of
SpliceLevel -> [(k, decModuleStage s)]
NormalLevel -> [(k, s)]
QuoteLevel -> [(k, incModuleStage s)]
-- Case 2a. TemplateHaskellQuotes case (section 5.6 in the paper)
classifyDeps (ModuleNodeCompile ms) s (ModuleNodeEdge _ k)
| not (isExplicitStageMS ms)
, not (isTemplateHaskellOrQQNonBoot ms)
, isTemplateHaskellQuotesMS ms
= [(k, s') | s' <- nowAndFutureStages s]
-- Case 2b. Template haskell is enabled, with implicit stage persistence
classifyDeps (ModuleNodeCompile ms) _ (ModuleNodeEdge _ k)
| isTemplateHaskellOrQQNonBoot ms
, not (isExplicitStageMS ms) =
[(k, s) | s <- allStages]
-- Case 3. No template haskell, therefore no additional dependencies.
classifyDeps _ s (ModuleNodeEdge _ k) = [(k, s)]
numbered_summaries :: [((ModuleGraphNode, ModuleStage), Int)]
numbered_summaries = zip (([(s, l) | s <- summaries, l <- allStages])) [0..]
lookup_node :: (NodeKey, ModuleStage) -> Maybe StageSummaryNode
lookup_node key = Map.lookup key node_map
lookup_key :: (NodeKey, ModuleStage) -> Maybe Int
lookup_key = fmap stageSummaryNodeKey . lookup_node
node_map :: Map.Map (NodeKey, ModuleStage) StageSummaryNode
node_map =
Map.fromList [ (s, node)
| node <- nodes
, let s = stageSummaryNodeSummary node
]
out_edge_keys :: [(NodeKey, ModuleStage)] -> [Int]
out_edge_keys = mapMaybe lookup_key
-- If we want keep_hi_boot_nodes, then we do lookup_key with
-- IsBoot; else False
-- | Add an ExtendedModSummary to ModuleGraph. Assumes that the new ModSummary is
-- not an element of the ModuleGraph.
extendMG :: ModuleGraph -> ModuleGraphNode -> ModuleGraph
extendMG ModuleGraph{..} node =
ModuleGraph
{ mg_mss = node : mg_mss
, mg_graph = mkTransDeps (node : mg_mss)
, mg_loop_graph = mkTransLoopDeps (node : mg_mss)
, mg_zero_graph = mkTransZeroDeps (node : mg_mss)
, mg_has_holes = mg_has_holes || maybe False isHsigFile (moduleNodeInfoHscSource =<< mgNodeIsModule node)
}