ghc-9.14.1: GHC/Unit/Home/Graph.hs
-- | A 'HomeUnitGraph' (HUG) collects information about all the home units.
-- Crucially, each node in a 'HomeUnitGraph' includes a 'HomePackageTable'.
--
-- Often, we don't want to query just a single 'HomePackageTable', but rather all
-- 'HomePackageTable's of all home units.
--
-- This module is responsible for maintaining this bridge between querying all
-- home units vs querying the home package table directly. Think 'lookupHug' vs
-- 'lookupHpt', 'hugAllInstances' vs 'hptAllInstances', where the @hug@ version
-- replies with information from all home units, and the @hpt@ version with
-- information pertaining to a single home unit.
--
-- Meant to be imported qualified as @HUG@.
-- Example usage:
--
-- @
-- import GHC.Unit.Home.Graph (HomeUnitGraph, HomeUnitEnv)
-- import qualified GHC.Unit.Home.Graph as HUG
-- usage = ... HUG.insertHug hug uid modname modinfo ...
-- @
module GHC.Unit.Home.Graph
( HomeUnitGraph
, HomeUnitEnv(..)
, mkHomeUnitEnv
-- * Operations
, addHomeModInfoToHug
, restrictHug
, renameUnitId
, allUnits
, updateUnitFlags
-- ** Lookups
, lookupHug
, lookupHugByModule
, lookupHugUnit
, lookupHugUnitId
, lookupAllHug
, memberHugUnit
, memberHugUnitId
-- ** Reachability
, transitiveHomeDeps
-- * Very important queries
, allInstances
, allFamInstances
, allAnns
, allCompleteSigs
-- * Utilities
, hugSCCs
, hugFromList
-- ** Printing
, pprHomeUnitGraph
, pprHomeUnitEnv
-- * Auxiliary internal structure
, UnitEnvGraph(..)
, unitEnv_lookup_maybe
, unitEnv_foldWithKey
, unitEnv_singleton
, unitEnv_adjust
, unitEnv_keys
, unitEnv_insert
, unitEnv_new
, unitEnv_lookup
, unitEnv_traverseWithKey
, unitEnv_assocs
) where
import GHC.Prelude
import GHC.Driver.DynFlags
import GHC.Unit.Home
import GHC.Unit.Home.ModInfo
import GHC.Unit.Home.PackageTable
import GHC.Unit.Module
import GHC.Unit.Module.ModIface
import GHC.Unit.State
import GHC.Utils.Monad (mapMaybeM)
import GHC.Utils.Outputable
import GHC.Utils.Panic
import GHC.Core.FamInstEnv
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import GHC.Data.Maybe
import GHC.Data.Graph.Directed
import GHC.Types.Annotations
import GHC.Types.CompleteMatch
import GHC.Core.InstEnv
-- | Get all 'CompleteMatches' (arising from COMPLETE pragmas) present across
-- all home units.
allCompleteSigs :: HomeUnitGraph -> IO CompleteMatches
allCompleteSigs hug = foldr go (pure []) hug where
go hue = liftA2 (++) (hptCompleteSigs (homeUnitEnv_hpt hue))
-- | Find all the instance declarations (of classes and families) from
-- the Home Package Table filtered by the provided predicate function.
-- Used in @tcRnImports@, to select the instances that are in the
-- transitive closure of imports from the currently compiled module.
allInstances :: HomeUnitGraph -> IO (InstEnv, [FamInst])
allInstances hug = foldr go (pure (emptyInstEnv, [])) hug where
go hue = liftA2 (\(a,b) (a',b') -> (a `unionInstEnv` a', b ++ b'))
(hptAllInstances (homeUnitEnv_hpt hue))
allFamInstances :: HomeUnitGraph -> IO (ModuleEnv FamInstEnv)
allFamInstances hug = foldr go (pure emptyModuleEnv) hug where
go hue = liftA2 plusModuleEnv (hptAllFamInstances (homeUnitEnv_hpt hue))
allAnns :: HomeUnitGraph -> IO AnnEnv
allAnns hug = foldr go (pure emptyAnnEnv) hug where
go hue = liftA2 plusAnnEnv (hptAllAnnotations (homeUnitEnv_hpt hue))
--------------------------------------------------------------------------------
-- HomeUnitGraph (HUG)
--------------------------------------------------------------------------------
type HomeUnitGraph = UnitEnvGraph HomeUnitEnv
data HomeUnitEnv = HomeUnitEnv
{ homeUnitEnv_units :: !UnitState
-- ^ External units
, homeUnitEnv_unit_dbs :: !(Maybe [UnitDatabase UnitId])
-- ^ Stack of unit databases for the target platform.
--
-- This field is populated with the result of `initUnits`.
--
-- 'Nothing' means the databases have never been read from disk.
--
-- Usually we don't reload the databases from disk if they are
-- cached, even if the database flags changed!
, homeUnitEnv_dflags :: DynFlags
-- ^ The dynamic flag settings
, homeUnitEnv_hpt :: HomePackageTable
-- ^ The home package table describes already-compiled
-- home-package modules, /excluding/ the module we
-- are compiling right now.
-- (In one-shot mode the current module is the only
-- home-package module, so homeUnitEnv_hpt is empty. All other
-- modules count as \"external-package\" modules.
-- However, even in GHCi mode, hi-boot interfaces are
-- demand-loaded into the external-package table.)
--
-- 'homeUnitEnv_hpt' is not mutable because we only demand-load
-- external packages; the home package is eagerly
-- loaded, module by module, by the compilation manager.
--
-- The HPT may contain modules compiled earlier by @--make@
-- but not actually below the current module in the dependency
-- graph.
--
-- (This changes a previous invariant: changed Jan 05.)
, homeUnitEnv_home_unit :: !(Maybe HomeUnit)
-- ^ Home-unit
}
mkHomeUnitEnv :: UnitState -> Maybe [UnitDatabase UnitId] -> DynFlags -> HomePackageTable -> Maybe HomeUnit -> HomeUnitEnv
mkHomeUnitEnv us dbs dflags hpt home_unit = HomeUnitEnv
{ homeUnitEnv_units = us
, homeUnitEnv_unit_dbs = dbs
, homeUnitEnv_dflags = dflags
, homeUnitEnv_hpt = hpt
, homeUnitEnv_home_unit = home_unit
}
--------------------------------------------------------------------------------
-- * Operations on HUG
--------------------------------------------------------------------------------
-- | Add an entry to the 'HomePackageTable' under the unit of that entry.
addHomeModInfoToHug :: HomeModInfo -> HomeUnitGraph -> IO ()
addHomeModInfoToHug hmi hug =
case unitEnv_lookup_maybe hmi_unit hug of
Nothing -> pprPanic "addHomeInfoToHug" (ppr hmi_mod)
Just hue -> do
addHomeModInfoToHpt hmi (homeUnitEnv_hpt hue)
where
hmi_mod :: Module
hmi_mod = mi_module (hm_iface hmi)
hmi_unit = toUnitId (moduleUnit hmi_mod)
-- | Thin each HPT variable to only contain keys from the given dependencies.
-- This is used at the end of upsweep to make sure that only completely successfully loaded
-- modules are visible for subsequent operations.
restrictHug :: [(UnitId, [HomeModInfo])] -> HomeUnitGraph -> IO ()
restrictHug deps hug = unitEnv_foldWithKey (\k uid hue -> restrict_one uid hue >> k) (return ()) hug
where
deps_map = Map.fromList deps
restrict_one uid hue =
restrictHpt (homeUnitEnv_hpt hue) (Map.findWithDefault [] uid deps_map)
-- | Rename a unit id in the 'HomeUnitGraph'
--
-- @'renameUnitId' oldUnit newUnit hug@, if @oldUnit@ is not found in @hug@, returns 'Nothing'.
-- If it exists, the result maps @newUnit@ to the 'HomeUnitEnv' of the
-- @oldUnit@ (and @oldUnit@ is removed from @hug@)
renameUnitId :: UnitId -> UnitId -> HomeUnitGraph -> Maybe HomeUnitGraph
renameUnitId oldUnit newUnit hug = case unitEnv_lookup_maybe oldUnit hug of
Nothing -> Nothing
Just oldHue -> pure $
unitEnv_insert newUnit oldHue $
unitEnv_delete oldUnit hug
-- | Retrieve all 'UnitId's of units in the 'HomeUnitGraph'.
allUnits :: HomeUnitGraph -> Set.Set UnitId
allUnits = unitEnv_keys
-- | Set the 'DynFlags' of the 'HomeUnitEnv' for unit in the 'HomeModuleGraph'
updateUnitFlags :: UnitId -> (DynFlags -> DynFlags) -> HomeUnitGraph -> HomeUnitGraph
updateUnitFlags uid f = unitEnv_adjust update uid
where
update hue = hue { homeUnitEnv_dflags = f (homeUnitEnv_dflags hue) }
--------------------------------------------------------------------------------
-- ** Reachability
--------------------------------------------------------------------------------
-- | Compute the transitive closure of a unit in the 'HomeUnitGraph'.
-- If the argument unit is not present in the graph returns Nothing.
transitiveHomeDeps :: UnitId -> HomeUnitGraph -> Maybe [UnitId]
transitiveHomeDeps uid hug = case lookupHugUnitId uid hug of
Nothing -> Nothing
Just hue -> Just $
Set.toList (loop (Set.singleton uid) (homeUnitDepends (homeUnitEnv_units hue)))
where
loop acc [] = acc
loop acc (uid:uids)
| uid `Set.member` acc = loop acc uids
| otherwise =
let hue = homeUnitDepends
. homeUnitEnv_units
. expectJust
$ lookupHugUnitId uid hug
in loop (Set.insert uid acc) (hue ++ uids)
--------------------------------------------------------------------------------
-- ** Lookups
--------------------------------------------------------------------------------
-- | Lookup the 'HomeModInfo' of a 'Module' in the 'HomeUnitGraph' given its
-- 'UnitId' and 'ModuleName' (via the 'HomePackageTable' of the corresponding unit)
lookupHug :: HomeUnitGraph -> UnitId -> ModuleName -> IO (Maybe HomeModInfo)
lookupHug hug uid mod = do
case unitEnv_lookup_maybe uid hug of
Nothing -> pure Nothing
Just hue -> lookupHpt (homeUnitEnv_hpt hue) mod
-- | Lookup the 'HomeModInfo' of a 'Module' in the 'HomeUnitGraph' (via the 'HomePackageTable' of the corresponding unit)
lookupHugByModule :: Module -> HomeUnitGraph -> IO (Maybe HomeModInfo)
lookupHugByModule mod hug =
case lookupHugUnit (moduleUnit mod) hug of
Nothing -> pure Nothing
Just env -> lookupHptByModule (homeUnitEnv_hpt env) mod
-- | Lookup all 'HomeModInfo' that have the same 'ModuleName' as the given 'ModuleName'.
-- 'ModuleName's are not unique in the case of multiple home units, so there can be
-- more than one possible 'HomeModInfo'.
--
-- You should always prefer 'lookupHug' and 'lookupHugByModule' when possible.
lookupAllHug :: HomeUnitGraph -> ModuleName -> IO [HomeModInfo]
lookupAllHug hug mod = mapMaybeM (\uid -> lookupHug hug uid mod) (Set.toList $ unitEnv_keys hug)
-- | Lookup a 'HomeUnitEnv' by 'UnitId' in a 'HomeUnitGraph'
lookupHugUnitId :: UnitId -> HomeUnitGraph -> Maybe HomeUnitEnv
lookupHugUnitId = unitEnv_lookup_maybe
-- | Check whether the 'UnitId' is present in the 'HomeUnitGraph'
memberHugUnitId :: UnitId -> HomeUnitGraph -> Bool
memberHugUnitId u = isJust . lookupHugUnitId u
-- | Lookup up the 'HomeUnitEnv' by the 'Unit' in the 'HomeUnitGraph'.
-- If the 'Unit' can be turned into a 'UnitId', we behave identical to 'lookupHugUnitId'.
--
-- A 'HoleUnit' is never part of the 'HomeUnitGraph', only instantiated 'Unit's
lookupHugUnit :: Unit -> HomeUnitGraph -> Maybe HomeUnitEnv
lookupHugUnit unit hug =
if isHoleUnit unit
then Nothing
else lookupHugUnitId (toUnitId unit) hug
-- | Check whether the 'Unit' is present in the 'HomeUnitGraph'
--
-- A 'HoleUnit' is never part of the 'HomeUnitGraph', only instantiated 'Unit's
memberHugUnit :: Unit -> HomeUnitGraph -> Bool
memberHugUnit u = isJust . lookupHugUnit u
--------------------------------------------------------------------------------
-- * Internal representation map
--------------------------------------------------------------------------------
-- Note: we purposefully do not export functions like "elems" to maintain a
-- good clean interface with the HUG.
type UnitEnvGraphKey = UnitId
newtype UnitEnvGraph v = UnitEnvGraph
{ unitEnv_graph :: Map UnitEnvGraphKey v
} deriving (Functor, Foldable, Traversable)
unitEnv_new :: Map UnitEnvGraphKey v -> UnitEnvGraph v
unitEnv_new m =
UnitEnvGraph
{ unitEnv_graph = m
}
unitEnv_insert :: UnitEnvGraphKey -> v -> UnitEnvGraph v -> UnitEnvGraph v
unitEnv_insert unitId env unitEnv = unitEnv
{ unitEnv_graph = Map.insert unitId env (unitEnv_graph unitEnv)
}
unitEnv_delete :: UnitEnvGraphKey -> UnitEnvGraph v -> UnitEnvGraph v
unitEnv_delete uid unitEnv =
unitEnv
{ unitEnv_graph = Map.delete uid (unitEnv_graph unitEnv)
}
unitEnv_adjust :: (v -> v) -> UnitEnvGraphKey -> UnitEnvGraph v -> UnitEnvGraph v
unitEnv_adjust f uid unitEnv = unitEnv
{ unitEnv_graph = Map.adjust f uid (unitEnv_graph unitEnv)
}
unitEnv_singleton :: UnitEnvGraphKey -> v -> UnitEnvGraph v
unitEnv_singleton active m = UnitEnvGraph
{ unitEnv_graph = Map.singleton active m
}
unitEnv_lookup_maybe :: UnitEnvGraphKey -> UnitEnvGraph v -> Maybe v
unitEnv_lookup_maybe u env = Map.lookup u (unitEnv_graph env)
unitEnv_keys :: UnitEnvGraph v -> Set.Set UnitEnvGraphKey
unitEnv_keys env = Map.keysSet (unitEnv_graph env)
unitEnv_foldWithKey :: (b -> UnitEnvGraphKey -> a -> b) -> b -> UnitEnvGraph a -> b
unitEnv_foldWithKey f z (UnitEnvGraph g)= Map.foldlWithKey' f z g
unitEnv_lookup :: UnitEnvGraphKey -> UnitEnvGraph v -> v
unitEnv_lookup u env = expectJust $ unitEnv_lookup_maybe u env
unitEnv_traverseWithKey :: Applicative f => (UnitEnvGraphKey -> a -> f b) -> UnitEnvGraph a -> f (UnitEnvGraph b)
unitEnv_traverseWithKey f unitEnv =
UnitEnvGraph <$> Map.traverseWithKey f (unitEnv_graph unitEnv)
unitEnv_assocs :: UnitEnvGraph a -> [(UnitEnvGraphKey, a)]
unitEnv_assocs (UnitEnvGraph x) = Map.assocs x
--------------------------------------------------------------------------------
-- * Utilities
--------------------------------------------------------------------------------
hugSCCs :: HomeUnitGraph -> [SCC UnitId]
hugSCCs hug = sccs where
mkNode :: (UnitId, HomeUnitEnv) -> Node UnitId UnitId
mkNode (uid, hue) = DigraphNode uid uid (homeUnitDepends (homeUnitEnv_units hue))
nodes = map mkNode (Map.toList $ unitEnv_graph hug)
sccs = stronglyConnCompFromEdgedVerticesOrd nodes
hugFromList :: [(UnitId, HomeUnitEnv)] -> HomeUnitGraph
hugFromList = UnitEnvGraph . Map.fromList
pprHomeUnitGraph :: HomeUnitGraph -> IO SDoc
pprHomeUnitGraph unitEnv = do
docs <- mapM (\(k, v) -> pprHomeUnitEnv k v) $ Map.assocs $ unitEnv_graph unitEnv
return $ vcat docs
pprHomeUnitEnv :: UnitId -> HomeUnitEnv -> IO SDoc
pprHomeUnitEnv uid env = do
hptDoc <- pprHPT $ homeUnitEnv_hpt env
return $
ppr uid <+> text "(flags:" <+> ppr (homeUnitId_ $ homeUnitEnv_dflags env) <> text "," <+> ppr (fmap homeUnitId $ homeUnitEnv_home_unit env) <> text ")" <+> text "->"
$$ nest 4 hptDoc