diff options
Diffstat (limited to 'compiler/iface/MkIface.lhs')
-rw-r--r-- | compiler/iface/MkIface.lhs | 1066 |
1 files changed, 1066 insertions, 0 deletions
diff --git a/compiler/iface/MkIface.lhs b/compiler/iface/MkIface.lhs new file mode 100644 index 0000000000..cafb6b6692 --- /dev/null +++ b/compiler/iface/MkIface.lhs @@ -0,0 +1,1066 @@ +% +% (c) The GRASP/AQUA Project, Glasgow University, 1993-1998 +% + +\begin{code} +module MkIface ( + pprModIface, showIface, -- Print the iface in Foo.hi + + mkUsageInfo, -- Construct the usage info for a module + + mkIface, -- Build a ModIface from a ModGuts, + -- including computing version information + + writeIfaceFile, -- Write the interface file + + checkOldIface -- See if recompilation is required, by + -- comparing version information + ) where +\end{code} + + ----------------------------------------------- + MkIface.lhs deals with versioning + ----------------------------------------------- + +Here's the version-related info in an interface file + + module Foo 8 -- module-version + 3 -- export-list-version + 2 -- rule-version + Usages: -- Version info for what this compilation of Foo imported + Baz 3 -- Module version + [4] -- The export-list version if Foo depended on it + (g,2) -- Function and its version + (T,1) -- Type and its version + + <version> f :: Int -> Int {- Unfolding: \x -> Wib.t[2] x -} + -- The [2] says that f's unfolding + -- mentions verison 2 of Wib.t + + ----------------------------------------------- + Basic idea + ----------------------------------------------- + +Basic idea: + * In the mi_usages information in an interface, we record the + version number of each free variable of the module + + * In mkIface, we compute the version number of each exported thing A.f + by comparing its A.f's info with its new info, and bumping its + version number if it differs. If A.f mentions B.g, and B.g's version + number has changed, then we count A.f as having changed too. + + * In checkOldIface we compare the mi_usages for the module with + the actual version info for all each thing recorded in mi_usages + + +Fixities +~~~~~~~~ +We count A.f as changing if its fixity changes + +Rules +~~~~~ +If a rule changes, we want to recompile any module that might be +affected by that rule. For non-orphan rules, this is relatively easy. +If module M defines f, and a rule for f, just arrange that the version +number for M.f changes if any of the rules for M.f change. Any module +that does not depend on M.f can't be affected by the rule-change +either. + +Orphan rules (ones whose 'head function' is not defined in M) are +harder. Here's what we do. + + * We have a per-module orphan-rule version number which changes if + any orphan rule changes. (It's unaffected by non-orphan rules.) + + * We record usage info for any orphan module 'below' this one, + giving the orphan-rule version number. We recompile if this + changes. + +The net effect is that if an orphan rule changes, we recompile every +module above it. That's very conservative, but it's devilishly hard +to know what it might affect, so we just have to be conservative. + +Instance decls +~~~~~~~~~~~~~~ +In an iface file we have + module A where + instance Eq a => Eq [a] = dfun29 + dfun29 :: ... + +We have a version number for dfun29, covering its unfolding +etc. Suppose we are compiling a module M that imports A only +indirectly. If typechecking M uses this instance decl, we record the +dependency on A.dfun29 as if it were a free variable of the module +(via the tcg_inst_usages accumulator). That means that A will appear +in M's usage list. If the shape of the instance declaration changes, +then so will dfun29's version, triggering a recompilation. + +Adding an instance declaration, or changing an instance decl that is +not currently used, is more tricky. (This really only makes a +difference when we have overlapping instance decls, because then the +new instance decl might kick in to override the old one.) We handle +this in a very similar way that we handle rules above. + + * For non-orphan instance decls, identify one locally-defined tycon/class + mentioned in the decl. Treat the instance decl as part of the defn of that + tycon/class, so that if the shape of the instance decl changes, so does the + tycon/class; that in turn will force recompilation of anything that uses + that tycon/class. + + * For orphan instance decls, act the same way as for orphan rules. + Indeed, we use the same global orphan-rule version number. + +mkUsageInfo +~~~~~~~~~~~ +mkUsageInfo figures out what the ``usage information'' for this +moudule is; that is, what it must record in its interface file as the +things it uses. + +We produce a line for every module B below the module, A, currently being +compiled: + import B <n> ; +to record the fact that A does import B indirectly. This is used to decide +to look to look for B.hi rather than B.hi-boot when compiling a module that +imports A. This line says that A imports B, but uses nothing in it. +So we'll get an early bale-out when compiling A if B's version changes. + +The usage information records: + +\begin{itemize} +\item (a) anything reachable from its body code +\item (b) any module exported with a @module Foo@ +\item (c) anything reachable from an exported item +\end{itemize} + +Why (b)? Because if @Foo@ changes then this module's export list +will change, so we must recompile this module at least as far as +making a new interface file --- but in practice that means complete +recompilation. + +Why (c)? Consider this: +\begin{verbatim} + module A( f, g ) where | module B( f ) where + import B( f ) | f = h 3 + g = ... | h = ... +\end{verbatim} + +Here, @B.f@ isn't used in A. Should we nevertheless record @B.f@ in +@A@'s usages? Our idea is that we aren't going to touch A.hi if it is +*identical* to what it was before. If anything about @B.f@ changes +than anyone who imports @A@ should be recompiled in case they use +@B.f@ (they'll get an early exit if they don't). So, if anything +about @B.f@ changes we'd better make sure that something in A.hi +changes, and the convenient way to do that is to record the version +number @B.f@ in A.hi in the usage list. If B.f changes that'll force a +complete recompiation of A, which is overkill but it's the only way to +write a new, slightly different, A.hi. + +But the example is tricker. Even if @B.f@ doesn't change at all, +@B.h@ may do so, and this change may not be reflected in @f@'s version +number. But with -O, a module that imports A must be recompiled if +@B.h@ changes! So A must record a dependency on @B.h@. So we treat +the occurrence of @B.f@ in the export list *just as if* it were in the +code of A, and thereby haul in all the stuff reachable from it. + + *** Conclusion: if A mentions B.f in its export list, + behave just as if A mentioned B.f in its source code, + and slurp in B.f and all its transitive closure *** + +[NB: If B was compiled with -O, but A isn't, we should really *still* +haul in all the unfoldings for B, in case the module that imports A *is* +compiled with -O. I think this is the case.] + + +\begin{code} +#include "HsVersions.h" + +import HsSyn +import Packages ( isHomeModule, PackageIdH(..) ) +import IfaceSyn ( IfaceDecl(..), IfaceClassOp(..), IfaceConDecl(..), + IfaceRule(..), IfaceInst(..), IfaceExtName(..), + eqIfDecl, eqIfRule, eqIfInst, IfaceEq(..), (&&&), bool, + eqMaybeBy, eqListBy, visibleIfConDecls, + tyThingToIfaceDecl, instanceToIfaceInst, coreRuleToIfaceRule ) +import LoadIface ( readIface, loadInterface ) +import BasicTypes ( Version, initialVersion, bumpVersion ) +import TcRnMonad +import HscTypes ( ModIface(..), ModDetails(..), + ModGuts(..), IfaceExport, + HscEnv(..), hscEPS, Dependencies(..), FixItem(..), + ModSummary(..), msHiFilePath, + mkIfaceDepCache, mkIfaceFixCache, mkIfaceVerCache, + typeEnvElts, + GenAvailInfo(..), availName, + ExternalPackageState(..), + Usage(..), IsBootInterface, + Deprecs(..), IfaceDeprecs, Deprecations, + lookupIfaceByModule + ) + + +import Packages ( HomeModules ) +import DynFlags ( GhcMode(..), DynFlags(..), DynFlag(..), dopt ) +import StaticFlags ( opt_HiVersion ) +import Name ( Name, nameModule, nameOccName, nameParent, + isExternalName, isInternalName, nameParent_maybe, isWiredInName, + isImplicitName, NamedThing(..) ) +import NameEnv +import NameSet +import OccName ( OccName, OccEnv, mkOccEnv, lookupOccEnv, emptyOccEnv, + extendOccEnv_C, + OccSet, emptyOccSet, elemOccSet, occSetElts, + extendOccSet, extendOccSetList, + isEmptyOccSet, intersectOccSet, intersectsOccSet, + occNameFS, isTcOcc ) +import Module ( Module, moduleFS, + ModLocation(..), mkModuleFS, moduleString, + ModuleEnv, emptyModuleEnv, lookupModuleEnv, + extendModuleEnv_C + ) +import Outputable +import Util ( createDirectoryHierarchy, directoryOf ) +import Util ( sortLe, seqList ) +import Binary ( getBinFileWithDict ) +import BinIface ( writeBinIface, v_IgnoreHiWay ) +import Unique ( Unique, Uniquable(..) ) +import ErrUtils ( dumpIfSet_dyn, showPass ) +import Digraph ( stronglyConnComp, SCC(..) ) +import SrcLoc ( SrcSpan ) +import FiniteMap +import FastString + +import DATA_IOREF ( writeIORef ) +import Monad ( when ) +import List ( insert ) +import Maybes ( orElse, mapCatMaybes, isNothing, isJust, + expectJust, MaybeErr(..) ) +\end{code} + + + +%************************************************************************ +%* * +\subsection{Completing an interface} +%* * +%************************************************************************ + +\begin{code} +mkIface :: HscEnv + -> Maybe ModIface -- The old interface, if we have it + -> ModGuts -- Usages, deprecations, etc + -> ModDetails -- The trimmed, tidied interface + -> IO (ModIface, -- The new one, complete with decls and versions + Bool) -- True <=> there was an old Iface, and the new one + -- is identical, so no need to write it + +mkIface hsc_env maybe_old_iface + (ModGuts{ mg_module = this_mod, + mg_boot = is_boot, + mg_usages = usages, + mg_deps = deps, + mg_home_mods = home_mods, + mg_rdr_env = rdr_env, + mg_fix_env = fix_env, + mg_deprecs = src_deprecs }) + (ModDetails{ md_insts = insts, + md_rules = rules, + md_types = type_env, + md_exports = exports }) + +-- NB: notice that mkIface does not look at the bindings +-- only at the TypeEnv. The previous Tidy phase has +-- put exactly the info into the TypeEnv that we want +-- to expose in the interface + + = do { eps <- hscEPS hsc_env + ; let { ext_nm_rhs = mkExtNameFn hsc_env home_mods eps this_mod + ; ext_nm_lhs = mkLhsNameFn this_mod + + ; decls = [ tyThingToIfaceDecl ext_nm_rhs thing + | thing <- typeEnvElts type_env, + not (isImplicitName (getName thing)) ] + -- Don't put implicit Ids and class tycons in the interface file + + ; fixities = [(occ,fix) | FixItem occ fix _ <- nameEnvElts fix_env] + ; deprecs = mkIfaceDeprec src_deprecs + ; iface_rules = map (coreRuleToIfaceRule ext_nm_lhs ext_nm_rhs) rules + ; iface_insts = map (instanceToIfaceInst ext_nm_lhs) insts + + ; intermediate_iface = ModIface { + mi_module = this_mod, + mi_package = HomePackage, + mi_boot = is_boot, + mi_deps = deps, + mi_usages = usages, + mi_exports = mkIfaceExports exports, + mi_insts = sortLe le_inst iface_insts, + mi_rules = sortLe le_rule iface_rules, + mi_fixities = fixities, + mi_deprecs = deprecs, + mi_globals = Just rdr_env, + + -- Left out deliberately: filled in by addVersionInfo + mi_mod_vers = initialVersion, + mi_exp_vers = initialVersion, + mi_rule_vers = initialVersion, + mi_orphan = False, -- Always set by addVersionInfo, but + -- it's a strict field, so we can't omit it. + mi_decls = deliberatelyOmitted "decls", + mi_ver_fn = deliberatelyOmitted "ver_fn", + + -- And build the cached values + mi_dep_fn = mkIfaceDepCache deprecs, + mi_fix_fn = mkIfaceFixCache fixities } + + -- Add version information + ; (new_iface, no_change_at_all, pp_diffs, pp_orphs) + = _scc_ "versioninfo" + addVersionInfo maybe_old_iface intermediate_iface decls + } + + -- Debug printing + ; when (isJust pp_orphs && dopt Opt_WarnOrphans dflags) + (printDump (expectJust "mkIface" pp_orphs)) + ; when (dopt Opt_D_dump_hi_diffs dflags) (printDump pp_diffs) + ; dumpIfSet_dyn dflags Opt_D_dump_hi "FINAL INTERFACE" + (pprModIface new_iface) + + ; return (new_iface, no_change_at_all) } + where + r1 `le_rule` r2 = ifRuleName r1 <= ifRuleName r2 + i1 `le_inst` i2 = ifDFun i1 <= ifDFun i2 + + dflags = hsc_dflags hsc_env + deliberatelyOmitted x = panic ("Deliberately omitted: " ++ x) + + +----------------------------- +writeIfaceFile :: ModLocation -> ModIface -> IO () +writeIfaceFile location new_iface + = do createDirectoryHierarchy (directoryOf hi_file_path) + writeBinIface hi_file_path new_iface + where hi_file_path = ml_hi_file location + + +----------------------------- +mkExtNameFn :: HscEnv -> HomeModules -> ExternalPackageState -> Module -> Name -> IfaceExtName +mkExtNameFn hsc_env hmods eps this_mod + = ext_nm + where + hpt = hsc_HPT hsc_env + pit = eps_PIT eps + + ext_nm name + | mod == this_mod = case nameParent_maybe name of + Nothing -> LocalTop occ + Just par -> LocalTopSub occ (nameOccName par) + | isWiredInName name = ExtPkg mod occ + | isHomeModule hmods mod = HomePkg mod occ vers + | otherwise = ExtPkg mod occ + where + mod = nameModule name + occ = nameOccName name + par_occ = nameOccName (nameParent name) + -- The version of the *parent* is the one want + vers = lookupVersion mod par_occ + + lookupVersion :: Module -> OccName -> Version + -- Even though we're looking up a home-package thing, in + -- one-shot mode the imported interfaces may be in the PIT + lookupVersion mod occ + = mi_ver_fn iface occ `orElse` + pprPanic "lookupVers1" (ppr mod <+> ppr occ) + where + iface = lookupIfaceByModule hpt pit mod `orElse` + pprPanic "lookupVers2" (ppr mod <+> ppr occ) + + +--------------------- +-- mkLhsNameFn ignores versioning info altogether +-- It is used for the LHS of instance decls and rules, where we +-- there's no point in recording version info +mkLhsNameFn :: Module -> Name -> IfaceExtName +mkLhsNameFn this_mod name + | isInternalName name = pprTrace "mkLhsNameFn: unexpected internal" (ppr name) $ + LocalTop occ -- Should not happen + | mod == this_mod = LocalTop occ + | otherwise = ExtPkg mod occ + where + mod = nameModule name + occ = nameOccName name + + +----------------------------- +-- Compute version numbers for local decls + +addVersionInfo :: Maybe ModIface -- The old interface, read from M.hi + -> ModIface -- The new interface decls (lacking decls) + -> [IfaceDecl] -- The new decls + -> (ModIface, + Bool, -- True <=> no changes at all; no need to write new Iface + SDoc, -- Differences + Maybe SDoc) -- Warnings about orphans + +addVersionInfo Nothing new_iface new_decls +-- No old interface, so definitely write a new one! + = (new_iface { mi_orphan = anyNothing ifInstOrph (mi_insts new_iface) + || anyNothing ifRuleOrph (mi_rules new_iface), + mi_decls = [(initialVersion, decl) | decl <- new_decls], + mi_ver_fn = \n -> Just initialVersion }, + False, + ptext SLIT("No old interface file"), + pprOrphans orph_insts orph_rules) + where + orph_insts = filter (isNothing . ifInstOrph) (mi_insts new_iface) + orph_rules = filter (isNothing . ifRuleOrph) (mi_rules new_iface) + +addVersionInfo (Just old_iface@(ModIface { mi_mod_vers = old_mod_vers, + mi_exp_vers = old_exp_vers, + mi_rule_vers = old_rule_vers, + mi_decls = old_decls, + mi_ver_fn = old_decl_vers, + mi_fix_fn = old_fixities })) + new_iface@(ModIface { mi_fix_fn = new_fixities }) + new_decls + + | no_change_at_all = (old_iface, True, ptext SLIT("Interface file unchanged"), pp_orphs) + | otherwise = (final_iface, False, vcat [ptext SLIT("Interface file has changed"), + nest 2 pp_diffs], pp_orphs) + where + final_iface = new_iface { mi_mod_vers = bump_unless no_output_change old_mod_vers, + mi_exp_vers = bump_unless no_export_change old_exp_vers, + mi_rule_vers = bump_unless no_rule_change old_rule_vers, + mi_orphan = not (null new_orph_rules && null new_orph_insts), + mi_decls = decls_w_vers, + mi_ver_fn = mkIfaceVerCache decls_w_vers } + + decls_w_vers = [(add_vers decl, decl) | decl <- new_decls] + + ------------------- + (old_non_orph_insts, old_orph_insts) = mkOrphMap ifInstOrph (mi_insts old_iface) + (new_non_orph_insts, new_orph_insts) = mkOrphMap ifInstOrph (mi_insts new_iface) + same_insts occ = eqMaybeBy (eqListBy eqIfInst) + (lookupOccEnv old_non_orph_insts occ) + (lookupOccEnv new_non_orph_insts occ) + + (old_non_orph_rules, old_orph_rules) = mkOrphMap ifRuleOrph (mi_rules old_iface) + (new_non_orph_rules, new_orph_rules) = mkOrphMap ifRuleOrph (mi_rules new_iface) + same_rules occ = eqMaybeBy (eqListBy eqIfRule) + (lookupOccEnv old_non_orph_rules occ) + (lookupOccEnv new_non_orph_rules occ) + ------------------- + -- Computing what changed + no_output_change = no_decl_change && no_rule_change && + no_export_change && no_deprec_change + no_export_change = mi_exports new_iface == mi_exports old_iface -- Kept sorted + no_decl_change = isEmptyOccSet changed_occs + no_rule_change = not (changedWrt changed_occs (eqListBy eqIfRule old_orph_rules new_orph_rules) + || changedWrt changed_occs (eqListBy eqIfInst old_orph_insts new_orph_insts)) + no_deprec_change = mi_deprecs new_iface == mi_deprecs old_iface + + -- If the usages havn't changed either, we don't need to write the interface file + no_other_changes = mi_usages new_iface == mi_usages old_iface && + mi_deps new_iface == mi_deps old_iface + no_change_at_all = no_output_change && no_other_changes + + pp_diffs = vcat [pp_change no_export_change "Export list" + (ppr old_exp_vers <+> arrow <+> ppr (mi_exp_vers final_iface)), + pp_change no_rule_change "Rules" + (ppr old_rule_vers <+> arrow <+> ppr (mi_rule_vers final_iface)), + pp_change no_deprec_change "Deprecations" empty, + pp_change no_other_changes "Usages" empty, + pp_decl_diffs] + pp_change True what info = empty + pp_change False what info = text what <+> ptext SLIT("changed") <+> info + + ------------------- + old_decl_env = mkOccEnv [(ifName decl, decl) | (_,decl) <- old_decls] + same_fixity n = bool (old_fixities n == new_fixities n) + + ------------------- + -- Adding version info + new_version = bumpVersion old_mod_vers + add_vers decl | occ `elemOccSet` changed_occs = new_version + | otherwise = expectJust "add_vers" (old_decl_vers occ) + -- If it's unchanged, there jolly well + where -- should be an old version number + occ = ifName decl + + ------------------- + changed_occs :: OccSet + changed_occs = computeChangedOccs eq_info + + eq_info :: [(OccName, IfaceEq)] + eq_info = map check_eq new_decls + check_eq new_decl | Just old_decl <- lookupOccEnv old_decl_env occ + = (occ, new_decl `eqIfDecl` old_decl &&& + eq_indirects new_decl) + | otherwise {- No corresponding old decl -} + = (occ, NotEqual) + where + occ = ifName new_decl + + eq_indirects :: IfaceDecl -> IfaceEq + -- When seeing if two decls are the same, remember to + -- check whether any relevant fixity or rules have changed + eq_indirects (IfaceId {ifName = occ}) = eq_ind_occ occ + eq_indirects (IfaceClass {ifName = cls_occ, ifSigs = sigs}) + = same_insts cls_occ &&& + eq_ind_occs [op | IfaceClassOp op _ _ <- sigs] + eq_indirects (IfaceData {ifName = tc_occ, ifCons = cons}) + = same_insts tc_occ &&& same_fixity tc_occ &&& -- The TyCon can have a fixity too + eq_ind_occs (map ifConOcc (visibleIfConDecls cons)) + eq_indirects other = Equal -- Synonyms and foreign declarations + + eq_ind_occ :: OccName -> IfaceEq -- For class ops and Ids; check fixity and rules + eq_ind_occ occ = same_fixity occ &&& same_rules occ + eq_ind_occs = foldr ((&&&) . eq_ind_occ) Equal + + ------------------- + -- Diffs + pp_decl_diffs :: SDoc -- Nothing => no changes + pp_decl_diffs + | isEmptyOccSet changed_occs = empty + | otherwise + = vcat [ptext SLIT("Changed occs:") <+> ppr (occSetElts changed_occs), + ptext SLIT("Version change for these decls:"), + nest 2 (vcat (map show_change new_decls))] + + eq_env = mkOccEnv eq_info + show_change new_decl + | not (occ `elemOccSet` changed_occs) = empty + | otherwise + = vcat [ppr occ <+> ppr (old_decl_vers occ) <+> arrow <+> ppr new_version, + nest 2 why] + where + occ = ifName new_decl + why = case lookupOccEnv eq_env occ of + Just (EqBut occs) -> sep [ppr occ <> colon, ptext SLIT("Free vars (only) changed:"), + nest 2 (braces (fsep (map ppr (occSetElts + (occs `intersectOccSet` changed_occs)))))] + Just NotEqual + | Just old_decl <- lookupOccEnv old_decl_env occ + -> vcat [ptext SLIT("Old:") <+> ppr old_decl, + ptext SLIT("New:") <+> ppr new_decl] + | otherwise + -> ppr occ <+> ptext SLIT("only in new interface") + other -> pprPanic "MkIface.show_change" (ppr occ) + + pp_orphs = pprOrphans new_orph_insts new_orph_rules + +pprOrphans insts rules + | null insts && null rules = Nothing + | otherwise + = Just $ vcat [ + if null insts then empty else + hang (ptext SLIT("Warning: orphan instances:")) + 2 (vcat (map ppr insts)), + if null rules then empty else + hang (ptext SLIT("Warning: orphan rules:")) + 2 (vcat (map ppr rules)) + ] + +computeChangedOccs :: [(OccName, IfaceEq)] -> OccSet +computeChangedOccs eq_info + = foldl add_changes emptyOccSet (stronglyConnComp edges) + where + edges :: [((OccName,IfaceEq), Unique, [Unique])] + edges = [ (node, getUnique occ, map getUnique occs) + | node@(occ, iface_eq) <- eq_info + , let occs = case iface_eq of + EqBut occ_set -> occSetElts occ_set + other -> [] ] + + -- Changes in declarations + add_changes :: OccSet -> SCC (OccName, IfaceEq) -> OccSet + add_changes so_far (AcyclicSCC (occ, iface_eq)) + | changedWrt so_far iface_eq -- This one has changed + = extendOccSet so_far occ + add_changes so_far (CyclicSCC pairs) + | changedWrt so_far (foldr1 (&&&) (map snd pairs)) -- One of this group has changed + = extendOccSetList so_far (map fst pairs) + add_changes so_far other = so_far + +changedWrt :: OccSet -> IfaceEq -> Bool +changedWrt so_far Equal = False +changedWrt so_far NotEqual = True +changedWrt so_far (EqBut kids) = so_far `intersectsOccSet` kids + +---------------------- +-- mkOrphMap partitions instance decls or rules into +-- (a) an OccEnv for ones that are not orphans, +-- mapping the local OccName to a list of its decls +-- (b) a list of orphan decls +mkOrphMap :: (decl -> Maybe OccName) -- (Just occ) for a non-orphan decl, keyed by occ + -- Nothing for an orphan decl + -> [decl] -- Sorted into canonical order + -> (OccEnv [decl], -- Non-orphan decls associated with their key; + -- each sublist in canonical order + [decl]) -- Orphan decls; in canonical order +mkOrphMap get_key decls + = foldl go (emptyOccEnv, []) decls + where + go (non_orphs, orphs) d + | Just occ <- get_key d + = (extendOccEnv_C (\ ds _ -> d:ds) non_orphs occ [d], orphs) + | otherwise = (non_orphs, d:orphs) + +anyNothing :: (a -> Maybe b) -> [a] -> Bool +anyNothing p [] = False +anyNothing p (x:xs) = isNothing (p x) || anyNothing p xs + +---------------------- +mkIfaceDeprec :: Deprecations -> IfaceDeprecs +mkIfaceDeprec NoDeprecs = NoDeprecs +mkIfaceDeprec (DeprecAll t) = DeprecAll t +mkIfaceDeprec (DeprecSome env) = DeprecSome (sortLe (<=) (nameEnvElts env)) + +---------------------- +bump_unless :: Bool -> Version -> Version +bump_unless True v = v -- True <=> no change +bump_unless False v = bumpVersion v +\end{code} + + +%********************************************************* +%* * +\subsection{Keeping track of what we've slurped, and version numbers} +%* * +%********************************************************* + + +\begin{code} +mkUsageInfo :: HscEnv + -> HomeModules + -> ModuleEnv (Module, Bool, SrcSpan) + -> [(Module, IsBootInterface)] + -> NameSet -> IO [Usage] +mkUsageInfo hsc_env hmods dir_imp_mods dep_mods used_names + = do { eps <- hscEPS hsc_env + ; let usages = mk_usage_info (eps_PIT eps) hsc_env hmods + dir_imp_mods dep_mods used_names + ; usages `seqList` return usages } + -- seq the list of Usages returned: occasionally these + -- don't get evaluated for a while and we can end up hanging on to + -- the entire collection of Ifaces. + +mk_usage_info pit hsc_env hmods dir_imp_mods dep_mods proto_used_names + = mapCatMaybes mkUsage dep_mods + -- ToDo: do we need to sort into canonical order? + where + hpt = hsc_HPT hsc_env + + used_names = mkNameSet $ -- Eliminate duplicates + [ nameParent n -- Just record usage on the 'main' names + | n <- nameSetToList proto_used_names + , not (isWiredInName n) -- Don't record usages for wired-in names + , isExternalName n -- Ignore internal names + ] + + -- ent_map groups together all the things imported and used + -- from a particular module in this package + ent_map :: ModuleEnv [OccName] + ent_map = foldNameSet add_mv emptyModuleEnv used_names + add_mv name mv_map = extendModuleEnv_C add_item mv_map mod [occ] + where + occ = nameOccName name + mod = nameModule name + add_item occs _ = occ:occs + + depend_on_exports mod = case lookupModuleEnv dir_imp_mods mod of + Just (_,no_imp,_) -> not no_imp + Nothing -> True + + -- We want to create a Usage for a home module if + -- a) we used something from; has something in used_names + -- b) we imported all of it, even if we used nothing from it + -- (need to recompile if its export list changes: export_vers) + -- c) is a home-package orphan module (need to recompile if its + -- instance decls change: rules_vers) + mkUsage :: (Module, Bool) -> Maybe Usage + mkUsage (mod_name, _) + | isNothing maybe_iface -- We can't depend on it if we didn't + || not (isHomeModule hmods mod) -- even open the interface! + || (null used_occs + && isNothing export_vers + && not orphan_mod) + = Nothing -- Record no usage info + + | otherwise + = Just (Usage { usg_name = mod, + usg_mod = mod_vers, + usg_exports = export_vers, + usg_entities = ent_vers, + usg_rules = rules_vers }) + where + maybe_iface = lookupIfaceByModule hpt pit mod_name + -- In one-shot mode, the interfaces for home-package + -- modules accumulate in the PIT not HPT. Sigh. + + Just iface = maybe_iface + mod = mi_module iface + orphan_mod = mi_orphan iface + version_env = mi_ver_fn iface + mod_vers = mi_mod_vers iface + rules_vers = mi_rule_vers iface + export_vers | depend_on_exports mod = Just (mi_exp_vers iface) + | otherwise = Nothing + + -- The sort is to put them into canonical order + used_occs = lookupModuleEnv ent_map mod `orElse` [] + ent_vers :: [(OccName,Version)] + ent_vers = [ (occ, version_env occ `orElse` initialVersion) + | occ <- sortLe (<=) used_occs] +\end{code} + +\begin{code} +mkIfaceExports :: NameSet -> [(Module, [GenAvailInfo OccName])] + -- Group by module and sort by occurrence + -- This keeps the list in canonical order +mkIfaceExports exports + = [ (mkModuleFS fs, eltsFM avails) + | (fs, avails) <- fmToList groupFM + ] + where + groupFM :: FiniteMap FastString (FiniteMap FastString (GenAvailInfo OccName)) + -- Deliberately use the FastString so we + -- get a canonical ordering + groupFM = foldl add emptyFM (nameSetToList exports) + + add env name = addToFM_C add_avail env mod_fs + (unitFM avail_fs avail) + where + occ = nameOccName name + mod_fs = moduleFS (nameModule name) + avail | Just p <- nameParent_maybe name = AvailTC (nameOccName p) [occ] + | isTcOcc occ = AvailTC occ [occ] + | otherwise = Avail occ + avail_fs = occNameFS (availName avail) + add_avail avail_fm _ = addToFM_C add_item avail_fm avail_fs avail + + add_item (AvailTC p occs) _ = AvailTC p (List.insert occ occs) + add_item (Avail n) _ = pprPanic "MkIface.addAvail" (ppr n <+> ppr name) +\end{code} + + +%************************************************************************ +%* * + Load the old interface file for this module (unless + we have it aleady), and check whether it is up to date + +%* * +%************************************************************************ + +\begin{code} +checkOldIface :: HscEnv + -> ModSummary + -> Bool -- Source unchanged + -> Maybe ModIface -- Old interface from compilation manager, if any + -> IO (RecompileRequired, Maybe ModIface) + +checkOldIface hsc_env mod_summary source_unchanged maybe_iface + = do { showPass (hsc_dflags hsc_env) + ("Checking old interface for " ++ moduleString (ms_mod mod_summary)) ; + + ; initIfaceCheck hsc_env $ + check_old_iface mod_summary source_unchanged maybe_iface + } + +check_old_iface mod_summary source_unchanged maybe_iface + = -- CHECK WHETHER THE SOURCE HAS CHANGED + ifM (not source_unchanged) + (traceHiDiffs (nest 4 (text "Source file changed or recompilation check turned off"))) + `thenM_` + + -- If the source has changed and we're in interactive mode, avoid reading + -- an interface; just return the one we might have been supplied with. + getGhcMode `thenM` \ ghc_mode -> + if (ghc_mode == Interactive || ghc_mode == JustTypecheck) + && not source_unchanged then + returnM (outOfDate, maybe_iface) + else + + case maybe_iface of { + Just old_iface -> -- Use the one we already have + checkVersions source_unchanged old_iface `thenM` \ recomp -> + returnM (recomp, Just old_iface) + + ; Nothing -> + + -- Try and read the old interface for the current module + -- from the .hi file left from the last time we compiled it + let + iface_path = msHiFilePath mod_summary + in + readIface (ms_mod mod_summary) iface_path False `thenM` \ read_result -> + case read_result of { + Failed err -> -- Old interface file not found, or garbled; give up + traceIf (text "FYI: cannot read old interface file:" + $$ nest 4 err) `thenM_` + returnM (outOfDate, Nothing) + + ; Succeeded iface -> + + -- We have got the old iface; check its versions + checkVersions source_unchanged iface `thenM` \ recomp -> + returnM (recomp, Just iface) + }} +\end{code} + +@recompileRequired@ is called from the HscMain. It checks whether +a recompilation is required. It needs access to the persistent state, +finder, etc, because it may have to load lots of interface files to +check their versions. + +\begin{code} +type RecompileRequired = Bool +upToDate = False -- Recompile not required +outOfDate = True -- Recompile required + +checkVersions :: Bool -- True <=> source unchanged + -> ModIface -- Old interface + -> IfG RecompileRequired +checkVersions source_unchanged iface + | not source_unchanged + = returnM outOfDate + | otherwise + = do { traceHiDiffs (text "Considering whether compilation is required for" <+> + ppr (mi_module iface) <> colon) + + -- Source code unchanged and no errors yet... carry on + + -- First put the dependent-module info, read from the old interface, into the envt, + -- so that when we look for interfaces we look for the right one (.hi or .hi-boot) + -- + -- It's just temporary because either the usage check will succeed + -- (in which case we are done with this module) or it'll fail (in which + -- case we'll compile the module from scratch anyhow). + -- + -- We do this regardless of compilation mode + ; updateEps_ $ \eps -> eps { eps_is_boot = mod_deps } + + ; checkList [checkModUsage u | u <- mi_usages iface] + } + where + -- This is a bit of a hack really + mod_deps :: ModuleEnv (Module, IsBootInterface) + mod_deps = mkModDeps (dep_mods (mi_deps iface)) + +checkModUsage :: Usage -> IfG RecompileRequired +-- Given the usage information extracted from the old +-- M.hi file for the module being compiled, figure out +-- whether M needs to be recompiled. + +checkModUsage (Usage { usg_name = mod_name, usg_mod = old_mod_vers, + usg_rules = old_rule_vers, + usg_exports = maybe_old_export_vers, + usg_entities = old_decl_vers }) + = -- Load the imported interface is possible + let + doc_str = sep [ptext SLIT("need version info for"), ppr mod_name] + in + traceHiDiffs (text "Checking usages for module" <+> ppr mod_name) `thenM_` + + loadInterface doc_str mod_name ImportBySystem `thenM` \ mb_iface -> + -- Load the interface, but don't complain on failure; + -- Instead, get an Either back which we can test + + case mb_iface of { + Failed exn -> (out_of_date (sep [ptext SLIT("Can't find version number for module"), + ppr mod_name])); + -- Couldn't find or parse a module mentioned in the + -- old interface file. Don't complain -- it might just be that + -- the current module doesn't need that import and it's been deleted + + Succeeded iface -> + let + new_mod_vers = mi_mod_vers iface + new_decl_vers = mi_ver_fn iface + new_export_vers = mi_exp_vers iface + new_rule_vers = mi_rule_vers iface + in + -- CHECK MODULE + checkModuleVersion old_mod_vers new_mod_vers `thenM` \ recompile -> + if not recompile then + returnM upToDate + else + + -- CHECK EXPORT LIST + if checkExportList maybe_old_export_vers new_export_vers then + out_of_date_vers (ptext SLIT(" Export list changed")) + (expectJust "checkModUsage" maybe_old_export_vers) + new_export_vers + else + + -- CHECK RULES + if old_rule_vers /= new_rule_vers then + out_of_date_vers (ptext SLIT(" Rules changed")) + old_rule_vers new_rule_vers + else + + -- CHECK ITEMS ONE BY ONE + checkList [checkEntityUsage new_decl_vers u | u <- old_decl_vers] `thenM` \ recompile -> + if recompile then + returnM outOfDate -- This one failed, so just bail out now + else + up_to_date (ptext SLIT(" Great! The bits I use are up to date")) + } + +------------------------ +checkModuleVersion old_mod_vers new_mod_vers + | new_mod_vers == old_mod_vers + = up_to_date (ptext SLIT("Module version unchanged")) + + | otherwise + = out_of_date_vers (ptext SLIT(" Module version has changed")) + old_mod_vers new_mod_vers + +------------------------ +checkExportList Nothing new_vers = upToDate +checkExportList (Just v) new_vers = v /= new_vers + +------------------------ +checkEntityUsage new_vers (name,old_vers) + = case new_vers name of + + Nothing -> -- We used it before, but it ain't there now + out_of_date (sep [ptext SLIT("No longer exported:"), ppr name]) + + Just new_vers -- It's there, but is it up to date? + | new_vers == old_vers -> traceHiDiffs (text " Up to date" <+> ppr name <+> parens (ppr new_vers)) `thenM_` + returnM upToDate + | otherwise -> out_of_date_vers (ptext SLIT(" Out of date:") <+> ppr name) + old_vers new_vers + +up_to_date msg = traceHiDiffs msg `thenM_` returnM upToDate +out_of_date msg = traceHiDiffs msg `thenM_` returnM outOfDate +out_of_date_vers msg old_vers new_vers + = out_of_date (hsep [msg, ppr old_vers, ptext SLIT("->"), ppr new_vers]) + +---------------------- +checkList :: [IfG RecompileRequired] -> IfG RecompileRequired +-- This helper is used in two places +checkList [] = returnM upToDate +checkList (check:checks) = check `thenM` \ recompile -> + if recompile then + returnM outOfDate + else + checkList checks +\end{code} + +%************************************************************************ +%* * + Printing interfaces +%* * +%************************************************************************ + +\begin{code} +showIface :: FilePath -> IO () +-- Read binary interface, and print it out +showIface filename = do + -- skip the version check; we don't want to worry about profiled vs. + -- non-profiled interfaces, for example. + writeIORef v_IgnoreHiWay True + iface <- Binary.getBinFileWithDict filename + printDump (pprModIface iface) + where +\end{code} + + +\begin{code} +pprModIface :: ModIface -> SDoc +-- Show a ModIface +pprModIface iface + = vcat [ ptext SLIT("interface") + <+> ppr_package (mi_package iface) + <+> ppr (mi_module iface) <+> pp_boot + <+> ppr (mi_mod_vers iface) <+> pp_sub_vers + <+> (if mi_orphan iface then ptext SLIT("[orphan module]") else empty) + <+> int opt_HiVersion + <+> ptext SLIT("where") + , vcat (map pprExport (mi_exports iface)) + , pprDeps (mi_deps iface) + , vcat (map pprUsage (mi_usages iface)) + , pprFixities (mi_fixities iface) + , vcat (map pprIfaceDecl (mi_decls iface)) + , vcat (map ppr (mi_insts iface)) + , vcat (map ppr (mi_rules iface)) + , pprDeprecs (mi_deprecs iface) + ] + where + pp_boot | mi_boot iface = ptext SLIT("[boot]") + | otherwise = empty + ppr_package HomePackage = empty + ppr_package (ExtPackage id) = doubleQuotes (ppr id) + + exp_vers = mi_exp_vers iface + rule_vers = mi_rule_vers iface + + pp_sub_vers | exp_vers == initialVersion && rule_vers == initialVersion = empty + | otherwise = brackets (ppr exp_vers <+> ppr rule_vers) +\end{code} + +When printing export lists, we print like this: + Avail f f + AvailTC C [C, x, y] C(x,y) + AvailTC C [x, y] C!(x,y) -- Exporting x, y but not C + +\begin{code} +pprExport :: IfaceExport -> SDoc +pprExport (mod, items) + = hsep [ ptext SLIT("export"), ppr mod, hsep (map pp_avail items) ] + where + pp_avail :: GenAvailInfo OccName -> SDoc + pp_avail (Avail occ) = ppr occ + pp_avail (AvailTC _ []) = empty + pp_avail (AvailTC n (n':ns)) + | n==n' = ppr n <> pp_export ns + | otherwise = ppr n <> char '|' <> pp_export (n':ns) + + pp_export [] = empty + pp_export names = braces (hsep (map ppr names)) + +pprUsage :: Usage -> SDoc +pprUsage usage + = hsep [ptext SLIT("import"), ppr (usg_name usage), + int (usg_mod usage), + pp_export_version (usg_exports usage), + int (usg_rules usage), + pp_versions (usg_entities usage) ] + where + pp_versions nvs = hsep [ ppr n <+> int v | (n,v) <- nvs ] + pp_export_version Nothing = empty + pp_export_version (Just v) = int v + +pprDeps :: Dependencies -> SDoc +pprDeps (Deps { dep_mods = mods, dep_pkgs = pkgs, dep_orphs = orphs}) + = vcat [ptext SLIT("module dependencies:") <+> fsep (map ppr_mod mods), + ptext SLIT("package dependencies:") <+> fsep (map ppr pkgs), + ptext SLIT("orphans:") <+> fsep (map ppr orphs) + ] + where + ppr_mod (mod_name, boot) = ppr mod_name <+> ppr_boot boot + ppr_boot True = text "[boot]" + ppr_boot False = empty + +pprIfaceDecl :: (Version, IfaceDecl) -> SDoc +pprIfaceDecl (ver, decl) + = ppr_vers ver <+> ppr decl + where + -- Print the version for the decl + ppr_vers v | v == initialVersion = empty + | otherwise = int v + +pprFixities :: [(OccName, Fixity)] -> SDoc +pprFixities [] = empty +pprFixities fixes = ptext SLIT("fixities") <+> pprWithCommas pprFix fixes + where + pprFix (occ,fix) = ppr fix <+> ppr occ + +pprDeprecs NoDeprecs = empty +pprDeprecs (DeprecAll txt) = ptext SLIT("Deprecate all") <+> doubleQuotes (ftext txt) +pprDeprecs (DeprecSome prs) = ptext SLIT("Deprecate") <+> vcat (map pprDeprec prs) + where + pprDeprec (name, txt) = ppr name <+> doubleQuotes (ftext txt) +\end{code} |