ghc9.10/9604.patch
2024-03-14 17:25:57 +08:00

1327 lines
57 KiB
Diff

From 40c9da19decb68e1bf4be6fef7b0592c19a8cacd Mon Sep 17 00:00:00 2001
From: Matthew Pickering <matthewtpickering@gmail.com>
Date: Tue, 13 Dec 2022 12:30:36 +0000
Subject: [PATCH] Add flag to control whether self-recompilation information is
written to interface
This patch adds the flag -fwrite-self-recomp-info which controls whether
interface files contain the information necessary to answer the
question:
Do I need to recompile myself or is this current interface file
suitable?
Why? Most packages are only built once either by a distribution or cabal
and then placed into an immutable store, after which we will never ask
this question. Therefore we can derive two benefits from omitting this
information.
* Primary motivation: It vastly reduces the surface area for creating
non-deterministic interface files. See issue #10424 which motivated a
proper fix to that issue. Distributions have long contained versions
of GHC which just have broken self-recompilation checking (in order to
get deterministic interface files).
* Secondary motivation: This reduces the size of interface files
slightly.. the `mi_usages` field can be quite big but probably this
isn't such a great benefit.
* Third motivation: Conceptually clarity about which parts of an
interface file are used in order to **communicate** with subsequent
packages about the **interface** for a module. And which parts are
used to self-communicate during recompilation checking.
In addition to this, the change alerted me to the incorrect
implemenation of the reifyModule function. See #8489 for more discussion
about how to fix this if anyone was so inclined. For now I just added a
warning `-Wreify-module-missing-info` which triggers if the module you
are trying to reify doesn't have a suitable interface. Interfaces which
are unsuitable include:
* The GHC.Prim interface, which is a fake interface
* Interfaces compiled with -fno-write-self-recomp-info
The main tracking issue is #22188 but fixes issues such as #10424 in a
proper way.
---
compiler/GHC/Driver/Flags.hs | 6 +-
compiler/GHC/Driver/Session.hs | 8 +-
compiler/GHC/HsToCore.hs | 23 +-
compiler/GHC/Iface/Binary.hs | 10 +-
compiler/GHC/Iface/Load.hs | 22 +-
compiler/GHC/Iface/Make.hs | 59 ++--
compiler/GHC/Iface/Recomp.hs | 77 +++---
compiler/GHC/Tc/Errors/Ppr.hs | 6 +
compiler/GHC/Tc/Errors/Types.hs | 6 +
compiler/GHC/Tc/Gen/Splice.hs | 21 +-
compiler/GHC/Types/Error/Codes.hs | 2 +
compiler/GHC/Unit/Module/ModGuts.hs | 2 +-
compiler/GHC/Unit/Module/ModIface.hs | 253 +++++++++++++-----
docs/users_guide/phases.rst | 18 ++
docs/users_guide/using-warnings.rst | 15 ++
.../should_compile/th/annth_compunits.stderr | 11 +
.../should_compile/th/annth_make.stderr | 11 +
testsuite/tests/driver/self-recomp/Makefile | 38 +++
.../tests/driver/self-recomp/SelfRecomp01.hs | 2 +
.../tests/driver/self-recomp/SelfRecomp02.hs | 6 +
.../tests/driver/self-recomp/SelfRecomp03.hs | 2 +
.../tests/driver/self-recomp/SelfRecomp04.hs | 1 +
.../driver/self-recomp/SelfRecomp04.stdout | 2 +
testsuite/tests/driver/self-recomp/all.T | 4 +
24 files changed, 449 insertions(+), 156 deletions(-)
create mode 100644 testsuite/tests/annotations/should_compile/th/annth_compunits.stderr
create mode 100644 testsuite/tests/annotations/should_compile/th/annth_make.stderr
create mode 100644 testsuite/tests/driver/self-recomp/Makefile
create mode 100644 testsuite/tests/driver/self-recomp/SelfRecomp01.hs
create mode 100644 testsuite/tests/driver/self-recomp/SelfRecomp02.hs
create mode 100644 testsuite/tests/driver/self-recomp/SelfRecomp03.hs
create mode 100644 testsuite/tests/driver/self-recomp/SelfRecomp04.hs
create mode 100644 testsuite/tests/driver/self-recomp/SelfRecomp04.stdout
create mode 100644 testsuite/tests/driver/self-recomp/all.T
diff --git a/compiler/GHC/Driver/Flags.hs b/compiler/GHC/Driver/Flags.hs
index f45397d8872..fbae3c7a242 100644
--- a/compiler/GHC/Driver/Flags.hs
+++ b/compiler/GHC/Driver/Flags.hs
@@ -302,6 +302,7 @@ data GeneralFlag
| Opt_OmitInterfacePragmas
| Opt_ExposeAllUnfoldings
| Opt_WriteInterface -- forces .hi files to be written even with -fno-code
+ | Opt_WriteSelfRecompInfo
| Opt_WriteHie -- generate .hie files
-- profiling opts
@@ -624,6 +625,7 @@ data WarningFlag =
| Opt_WarnGADTMonoLocalBinds -- Since 9.4
| Opt_WarnTypeEqualityOutOfScope -- Since 9.4
| Opt_WarnTypeEqualityRequiresOperators -- Since 9.4
+ | Opt_WarnReifyModuleMissingInfo -- Since 9.6
deriving (Eq, Ord, Show, Enum)
-- | Return the names of a WarningFlag
@@ -729,6 +731,7 @@ warnFlagNames wflag = case wflag of
Opt_WarnGADTMonoLocalBinds -> "gadt-mono-local-binds" :| []
Opt_WarnTypeEqualityOutOfScope -> "type-equality-out-of-scope" :| []
Opt_WarnTypeEqualityRequiresOperators -> "type-equality-requires-operators" :| []
+ Opt_WarnReifyModuleMissingInfo -> "reify-module-missing-info" :| []
-- -----------------------------------------------------------------------------
-- Standard sets of warning options
@@ -824,7 +827,8 @@ standardWarnings -- see Note [Documenting warning flags]
Opt_WarnForallIdentifier,
Opt_WarnUnicodeBidirectionalFormatCharacters,
Opt_WarnGADTMonoLocalBinds,
- Opt_WarnTypeEqualityRequiresOperators
+ Opt_WarnTypeEqualityRequiresOperators,
+ Opt_WarnReifyModuleMissingInfo
]
-- | Things you get with -W
diff --git a/compiler/GHC/Driver/Session.hs b/compiler/GHC/Driver/Session.hs
index 831267e2bf6..0dd79006999 100644
--- a/compiler/GHC/Driver/Session.hs
+++ b/compiler/GHC/Driver/Session.hs
@@ -3327,7 +3327,8 @@ wWarningFlagsDeps = mconcat [
warnSpec Opt_WarnUnicodeBidirectionalFormatCharacters,
warnSpec Opt_WarnGADTMonoLocalBinds,
warnSpec Opt_WarnTypeEqualityOutOfScope,
- warnSpec Opt_WarnTypeEqualityRequiresOperators
+ warnSpec Opt_WarnTypeEqualityRequiresOperators,
+ warnSpec Opt_WarnReifyModuleMissingInfo
]
-- | These @-\<blah\>@ flags can all be reversed with @-no-\<blah\>@
@@ -3486,6 +3487,7 @@ fFlagsDeps = [
flagSpec "use-rpaths" Opt_RPath,
flagSpec "write-interface" Opt_WriteInterface,
flagSpec "write-if-simplified-core" Opt_WriteIfSimplifiedCore,
+ flagSpec "write-self-recomp-info" Opt_WriteSelfRecompInfo,
flagSpec "write-ide-info" Opt_WriteHie,
flagSpec "unbox-small-strict-fields" Opt_UnboxSmallStrictFields,
flagSpec "unbox-strict-fields" Opt_UnboxStrictFields,
@@ -3809,7 +3811,9 @@ defaultFlags settings
Opt_DumpWithWays,
Opt_CompactUnwind,
Opt_ShowErrorContext,
- Opt_SuppressStgReps
+ Opt_SuppressStgReps,
+ Opt_WriteSelfRecompInfo
+
]
++ [f | (ns,f) <- optLevelFlags, 0 `elem` ns]
diff --git a/compiler/GHC/HsToCore.hs b/compiler/GHC/HsToCore.hs
index 3c6ec710790..dd578475864 100644
--- a/compiler/GHC/HsToCore.hs
+++ b/compiler/GHC/HsToCore.hs
@@ -22,14 +22,12 @@ import GHC.Driver.Session
import GHC.Driver.Config
import GHC.Driver.Config.Core.Lint ( endPassHscEnvIO )
import GHC.Driver.Config.HsToCore.Ticks
-import GHC.Driver.Config.HsToCore.Usage
import GHC.Driver.Env
import GHC.Driver.Backend
import GHC.Driver.Plugins
import GHC.Hs
-import GHC.HsToCore.Usage
import GHC.HsToCore.Monad
import GHC.HsToCore.Errors.Types
import GHC.HsToCore.Expr
@@ -41,7 +39,7 @@ import GHC.HsToCore.Coverage
import GHC.HsToCore.Docs
import GHC.Tc.Types
-import GHC.Tc.Utils.Monad ( finalSafeMode, fixSafeInstances, initIfaceLoad )
+import GHC.Tc.Utils.Monad ( finalSafeMode, fixSafeInstances )
import GHC.Tc.Module ( runTcInteractive )
import GHC.Core.Type
@@ -98,6 +96,7 @@ import GHC.Unit.Module.Deps
import Data.List (partition)
import Data.IORef
import Data.Traversable (for)
+import GHC.Iface.Make (mkRecompUsageInfo)
{-
************************************************************************
@@ -125,12 +124,10 @@ deSugar hsc_env
tcg_fix_env = fix_env,
tcg_inst_env = inst_env,
tcg_fam_inst_env = fam_inst_env,
- tcg_merged = merged,
tcg_warns = warns,
tcg_anns = anns,
tcg_binds = binds,
tcg_imp_specs = imp_specs,
- tcg_dependent_files = dependent_files,
tcg_ev_binds = ev_binds,
tcg_th_foreign_files = th_foreign_files_var,
tcg_fords = fords,
@@ -224,8 +221,7 @@ deSugar hsc_env
; endPassHscEnvIO hsc_env name_ppr_ctx CoreDesugarOpt ds_binds ds_rules_for_imps
- ; let used_names = mkUsedNames tcg_env
- pluginModules = map lpModule (loadedPlugins (hsc_plugins hsc_env))
+ ; let pluginModules = map lpModule (loadedPlugins (hsc_plugins hsc_env))
home_unit = hsc_home_unit hsc_env
; let deps = mkDependencies home_unit
(tcg_mod tcg_env)
@@ -233,17 +229,10 @@ deSugar hsc_env
(map mi_module pluginModules)
; used_th <- readIORef tc_splice_used
- ; dep_files <- readIORef dependent_files
; safe_mode <- finalSafeMode dflags tcg_env
- ; (needed_mods, needed_pkgs) <- readIORef (tcg_th_needed_deps tcg_env)
-
- ; let uc = initUsageConfig hsc_env
- ; let plugins = hsc_plugins hsc_env
- ; let fc = hsc_FC hsc_env
- ; let unit_env = hsc_unit_env hsc_env
- ; usages <- initIfaceLoad hsc_env $
- mkUsageInfo uc plugins fc unit_env mod (imp_mods imports) used_names
- dep_files merged needed_mods needed_pkgs
+
+ ; usages <- mkRecompUsageInfo hsc_env tcg_env
+
-- id_mod /= mod when we are processing an hsig, but hsigs
-- never desugared and compiled (there's no code!)
-- Consequently, this should hold for any ModGuts that make
diff --git a/compiler/GHC/Iface/Binary.hs b/compiler/GHC/Iface/Binary.hs
index 78045aa782f..48dc54fa8ba 100644
--- a/compiler/GHC/Iface/Binary.hs
+++ b/compiler/GHC/Iface/Binary.hs
@@ -45,7 +45,6 @@ import GHC.Types.Name.Cache
import GHC.Types.SrcLoc
import GHC.Platform
import GHC.Settings.Constants
-import GHC.Utils.Fingerprint
import Data.Array
import Data.Array.IO
@@ -75,7 +74,7 @@ readBinIfaceHeader
-> CheckHiWay
-> TraceBinIFace
-> FilePath
- -> IO (Fingerprint, BinHandle)
+ -> IO BinHandle
readBinIfaceHeader profile _name_cache checkHiWay traceBinIFace hi_path = do
let platform = profilePlatform profile
@@ -117,8 +116,7 @@ readBinIfaceHeader profile _name_cache checkHiWay traceBinIFace hi_path = do
when (checkHiWay == CheckHiWay) $
errorOnMismatch "mismatched interface file profile tag" tag check_tag
- src_hash <- get bh
- pure (src_hash, bh)
+ pure bh
-- | Read an interface file.
readBinIface
@@ -129,7 +127,7 @@ readBinIface
-> FilePath
-> IO ModIface
readBinIface profile name_cache checkHiWay traceBinIface hi_path = do
- (src_hash, bh) <- readBinIfaceHeader profile name_cache checkHiWay traceBinIface hi_path
+ bh <- readBinIfaceHeader profile name_cache checkHiWay traceBinIface hi_path
extFields_p <- get bh
@@ -140,7 +138,6 @@ readBinIface profile name_cache checkHiWay traceBinIface hi_path = do
return mod_iface
{ mi_ext_fields = extFields
- , mi_src_hash = src_hash
}
-- | This performs a get action after reading the dictionary and symbol
@@ -182,7 +179,6 @@ writeBinIface profile traceBinIface hi_path mod_iface = do
put_ bh (show hiVersion)
let tag = profileBuildTag profile
put_ bh tag
- put_ bh (mi_src_hash mod_iface)
extFields_p_p <- tellBin bh
put_ bh extFields_p_p
diff --git a/compiler/GHC/Iface/Load.hs b/compiler/GHC/Iface/Load.hs
index bf7ae8e0059..400782628be 100644
--- a/compiler/GHC/Iface/Load.hs
+++ b/compiler/GHC/Iface/Load.hs
@@ -1105,28 +1105,31 @@ pprModIfaceSimple unit_state iface =
-- The UnitState is used to pretty-print units
pprModIface :: UnitState -> ModIface -> SDoc
pprModIface unit_state iface@ModIface{ mi_final_exts = exts }
- = vcat [ text "interface"
+ = vcat $ [ text "interface"
<+> ppr (mi_module iface) <+> pp_hsc_src (mi_hsc_src iface)
+ <+> whenIsSelfRecomp (text "[self-recomp]")
<+> (if mi_orphan exts then text "[orphan module]" else Outputable.empty)
<+> (if mi_finsts exts then text "[family instance module]" else Outputable.empty)
<+> (if mi_hpc iface then text "[hpc]" else Outputable.empty)
<+> integer hiVersion
- , nest 2 (text "interface hash:" <+> ppr (mi_iface_hash exts))
, nest 2 (text "ABI hash:" <+> ppr (mi_mod_hash exts))
, nest 2 (text "export-list hash:" <+> ppr (mi_exp_hash exts))
+ , whenIsSelfRecomp $ vcat
+ [ nest 2 (text "interface hash:" <+> ppr (mi_iface_hash exts))
+ , nest 2 (text "flag hash:" <+> ppr (mi_flag_hash exts))
+ , nest 2 (text "opt_hash:" <+> ppr (mi_opt_hash exts))
+ , nest 2 (text "hpc_hash:" <+> ppr (mi_hpc_hash exts))
+ , nest 2 (text "plugin_hash:" <+> ppr (mi_plugin_hash exts))
+ ]
, nest 2 (text "orphan hash:" <+> ppr (mi_orphan_hash exts))
- , nest 2 (text "flag hash:" <+> ppr (mi_flag_hash exts))
- , nest 2 (text "opt_hash:" <+> ppr (mi_opt_hash exts))
- , nest 2 (text "hpc_hash:" <+> ppr (mi_hpc_hash exts))
- , nest 2 (text "plugin_hash:" <+> ppr (mi_plugin_hash exts))
- , nest 2 (text "src_hash:" <+> ppr (mi_src_hash iface))
, nest 2 (text "sig of:" <+> ppr (mi_sig_of iface))
, nest 2 (text "used TH splices:" <+> ppr (mi_used_th iface))
+ , whenIsSelfRecomp $ nest 2 (text "src_hash:" <+> ppr (mi_src_hash iface))
, nest 2 (text "where")
, text "exports:"
, nest 2 (vcat (map pprExport (mi_exports iface)))
, pprDeps unit_state (mi_deps iface)
- , vcat (map pprUsage (mi_usages iface))
+ , whenIsSelfRecomp $ vcat (map pprUsage (mi_usages iface))
, vcat (map pprIfaceAnnotation (mi_anns iface))
, pprFixities (mi_fixities iface)
, vcat [ppr ver $$ nest 2 (ppr decl) | (ver,decl) <- mi_decls iface]
@@ -1145,6 +1148,9 @@ pprModIface unit_state iface@ModIface{ mi_final_exts = exts }
, text "extensible fields:" $$ nest 2 (pprExtensibleFields (mi_ext_fields iface))
]
where
+ whenIsSelfRecomp action =
+ if (isSelfRecompilationInterface iface) then action else empty
+
pp_hsc_src HsBootFile = text "[boot]"
pp_hsc_src HsigFile = text "[hsig]"
pp_hsc_src HsSrcFile = Outputable.empty
diff --git a/compiler/GHC/Iface/Make.hs b/compiler/GHC/Iface/Make.hs
index ac55220cbfb..f55e943dbe1 100644
--- a/compiler/GHC/Iface/Make.hs
+++ b/compiler/GHC/Iface/Make.hs
@@ -13,6 +13,7 @@ module GHC.Iface.Make
( mkPartialIface
, mkFullIface
, mkIfaceTc
+ , mkRecompUsageInfo
, mkIfaceExports
, coAxiomToIfaceDecl
, tyThingToIfaceDecl -- Converting things to their Iface equivalents
@@ -203,14 +204,11 @@ mkIfaceTc hsc_env safe_mode mod_details mod_summary mb_program
tcg_imports = imports,
tcg_rdr_env = rdr_env,
tcg_fix_env = fix_env,
- tcg_merged = merged,
tcg_warns = warns,
tcg_hpc = other_hpc_info,
- tcg_th_splice_used = tc_splice_used,
- tcg_dependent_files = dependent_files
+ tcg_th_splice_used = tc_splice_used
}
= do
- let used_names = mkUsedNames tc_result
let pluginModules = map lpModule (loadedPlugins (hsc_plugins hsc_env))
let home_unit = hsc_home_unit hsc_env
let deps = mkDependencies home_unit
@@ -219,47 +217,58 @@ mkIfaceTc hsc_env safe_mode mod_details mod_summary mb_program
(map mi_module pluginModules)
let hpc_info = emptyHpcInfo other_hpc_info
used_th <- readIORef tc_splice_used
- dep_files <- (readIORef dependent_files)
- (needed_links, needed_pkgs) <- readIORef (tcg_th_needed_deps tc_result)
- let uc = initUsageConfig hsc_env
- plugins = hsc_plugins hsc_env
- fc = hsc_FC hsc_env
- unit_env = hsc_unit_env hsc_env
- -- Do NOT use semantic module here; this_mod in mkUsageInfo
- -- is used solely to decide if we should record a dependency
- -- or not. When we instantiate a signature, the semantic
- -- module is something we want to record dependencies for,
- -- but if you pass that in here, we'll decide it's the local
- -- module and does not need to be recorded as a dependency.
- -- See Note [Identity versus semantic module]
- usages <- initIfaceLoad hsc_env $ mkUsageInfo uc plugins fc unit_env this_mod (imp_mods imports) used_names
- dep_files merged needed_links needed_pkgs
+ usage <- mkRecompUsageInfo hsc_env tc_result
docs <- extractDocs (ms_hspp_opts mod_summary) tc_result
let partial_iface = mkIface_ hsc_env
this_mod (fromMaybe [] mb_program) hsc_src
used_th deps rdr_env
fix_env warns hpc_info
- (imp_trust_own_pkg imports) safe_mode usages
+ (imp_trust_own_pkg imports) safe_mode usage
docs mod_summary
mod_details
mkFullIface hsc_env partial_iface Nothing Nothing
+mkRecompUsageInfo :: HscEnv -> TcGblEnv -> IO (Maybe [Usage])
+mkRecompUsageInfo hsc_env tc_result = do
+ let dflags = hsc_dflags hsc_env
+ if not (gopt Opt_WriteSelfRecompInfo dflags)
+ then return Nothing
+ else do
+ let used_names = mkUsedNames tc_result
+ dep_files <- (readIORef (tcg_dependent_files tc_result))
+ (needed_links, needed_pkgs) <- readIORef (tcg_th_needed_deps tc_result)
+ let uc = initUsageConfig hsc_env
+ plugins = hsc_plugins hsc_env
+ fc = hsc_FC hsc_env
+ unit_env = hsc_unit_env hsc_env
+
+ -- Do NOT use semantic module here; this_mod in mkUsageInfo
+ -- is used solely to decide if we should record a dependency
+ -- or not. When we instantiate a signature, the semantic
+ -- module is something we want to record dependencies for,
+ -- but if you pass that in here, we'll decide it's the local
+ -- module and does not need to be recorded as a dependency.
+ -- See Note [Identity versus semantic module]
+ usages <- initIfaceLoad hsc_env $ mkUsageInfo uc plugins fc unit_env (tcg_mod tc_result) (imp_mods (tcg_imports tc_result)) used_names
+ dep_files (tcg_merged tc_result) needed_links needed_pkgs
+ return (Just usages)
+
mkIface_ :: HscEnv -> Module -> CoreProgram -> HscSource
-> Bool -> Dependencies -> GlobalRdrEnv
-> NameEnv FixItem -> Warnings GhcRn -> HpcInfo
-> Bool
-> SafeHaskellMode
- -> [Usage]
+ -> Maybe [Usage]
-> Maybe Docs
-> ModSummary
-> ModDetails
-> PartialModIface
mkIface_ hsc_env
this_mod core_prog hsc_src used_th deps rdr_env fix_env src_warns
- hpc_info pkg_trust_req safe_mode usages
+ hpc_info pkg_trust_req safe_mode musages
docs mod_summary
ModDetails{ md_insts = insts,
md_fam_insts = fam_insts,
@@ -307,6 +316,9 @@ mkIface_ hsc_env
trust_info = setSafeMode safe_mode
annotations = map mkIfaceAnnotation anns
icomplete_matches = map mkIfaceCompleteMatch complete_matches
+ self_recomp = case musages of
+ Nothing -> NoSelfRecomp
+ Just usages -> ModIfaceSelfRecomp (ms_hs_hash mod_summary) usages
ModIface {
mi_module = this_mod,
@@ -317,7 +329,6 @@ mkIface_ hsc_env
else Just semantic_mod,
mi_hsc_src = hsc_src,
mi_deps = deps,
- mi_usages = usages,
mi_exports = mkIfaceExports exports,
-- Sort these lexicographically, so that
@@ -340,7 +351,7 @@ mkIface_ hsc_env
mi_docs = docs,
mi_final_exts = (),
mi_ext_fields = emptyExtensibleFields,
- mi_src_hash = ms_hs_hash mod_summary
+ mi_self_recomp_info = self_recomp
}
where
cmp_rule = lexicalCompareFS `on` ifRuleName
diff --git a/compiler/GHC/Iface/Recomp.hs b/compiler/GHC/Iface/Recomp.hs
index 886bc12192e..8abf38d5373 100644
--- a/compiler/GHC/Iface/Recomp.hs
+++ b/compiler/GHC/Iface/Recomp.hs
@@ -171,6 +171,7 @@ data RecompReason
= UnitDepRemoved UnitId
| ModulePackageChanged FastString
| SourceFileChanged
+ | NoSelfRecompInfo
| ThisUnitIdChanged
| ImpurePlugin
| PluginsChanged
@@ -203,6 +204,7 @@ instance Outputable RecompReason where
UnitDepRemoved uid -> ppr uid <+> text "removed"
ModulePackageChanged s -> ftext s <+> text "package changed"
SourceFileChanged -> text "Source file changed"
+ NoSelfRecompInfo -> text "Old interface lacks recompilation info"
ThisUnitIdChanged -> text "-this-unit-id changed"
ImpurePlugin -> text "Impure plugin forced recompilation"
PluginsChanged -> text "Plugins changed"
@@ -373,6 +375,8 @@ checkVersions hsc_env mod_summary iface
-- but we ALSO must make sure the instantiation matches up. See
-- test case bkpcabal04!
; hsc_env <- getTopEnv
+ ; if not (isSelfRecompilationInterface iface)
+ then return $ outOfDateItemBecause NoSelfRecompInfo Nothing else do {
; if mi_src_hash iface /= ms_hs_hash mod_summary
then return $ outOfDateItemBecause SourceFileChanged Nothing else do {
; if not (isHomeModule home_unit (mi_module iface))
@@ -407,7 +411,7 @@ checkVersions hsc_env mod_summary iface
| u <- mi_usages iface]
; case recomp of (NeedsRecompile reason) -> return $ OutOfDateItem reason (Just iface) ; _ -> do {
; return $ UpToDateItem iface
- }}}}}}}
+ }}}}}}}}
where
logger = hsc_logger hsc_env
dflags = hsc_dflags hsc_env
@@ -1210,18 +1214,6 @@ addFingerprints hsc_env iface0
sorted_extra_decls :: Maybe [IfaceBindingX IfaceMaybeRhs IfaceTopBndrInfo]
sorted_extra_decls = sortOn binding_key <$> mi_extra_decls iface0
- -- the flag hash depends on:
- -- - (some of) dflags
- -- it returns two hashes, one that shouldn't change
- -- the abi hash and one that should
- flag_hash <- fingerprintDynFlags hsc_env this_mod putNameLiterally
-
- opt_hash <- fingerprintOptFlags dflags putNameLiterally
-
- hpc_hash <- fingerprintHpcFlags dflags putNameLiterally
-
- plugin_hash <- fingerprintPlugins (hsc_plugins hsc_env)
-
-- the ABI hash depends on:
-- - decls
-- - export list
@@ -1233,29 +1225,18 @@ addFingerprints hsc_env iface0
export_hash, -- includes orphan_hash
mi_warns iface0)
- -- The interface hash depends on:
- -- - the ABI hash, plus
- -- - the source file hash,
- -- - the module level annotations,
- -- - usages
- -- - deps (home and external packages, dependent files)
- -- - hpc
- iface_hash <- computeFingerprint putNameLiterally
- (mod_hash,
- mi_src_hash iface0,
- ann_fn (mkVarOccFS (fsLit "module")), -- See mkIfaceAnnCache
- mi_usages iface0,
- sorted_deps,
- mi_hpc iface0)
+ -- the flag hash depends on:
+ -- - (some of) dflags
+ -- it returns two hashes, one that shouldn't change
+ -- the abi hash and one that should
+ self_recomp <- if gopt Opt_WriteSelfRecompInfo dflags
+ then mkSelfRecomp mod_hash sorted_deps
+ else return NoSelfRecompBackend
let
final_iface_exts = ModIfaceBackend
- { mi_iface_hash = iface_hash
+ { mi_self_recomp_backend_info = self_recomp
, mi_mod_hash = mod_hash
- , mi_flag_hash = flag_hash
- , mi_opt_hash = opt_hash
- , mi_hpc_hash = hpc_hash
- , mi_plugin_hash = plugin_hash
, mi_orphan = not ( all ifRuleAuto orph_rules
-- See Note [Orphans and auto-generated rules]
&& null orph_insts
@@ -1280,6 +1261,38 @@ addFingerprints hsc_env iface0
(non_orph_fis, orph_fis) = mkOrphMap ifFamInstOrph (mi_fam_insts iface0)
ann_fn = mkIfaceAnnCache (mi_anns iface0)
+ mkSelfRecomp mod_hash sorted_deps = do
+
+ flag_hash <- fingerprintDynFlags hsc_env this_mod putNameLiterally
+
+ opt_hash <- fingerprintOptFlags dflags putNameLiterally
+
+ hpc_hash <- fingerprintHpcFlags dflags putNameLiterally
+
+ plugin_hash <- fingerprintPlugins (hsc_plugins hsc_env)
+
+ -- The interface hash depends on:
+ -- - the ABI hash, plus
+ -- - the source file hash,
+ -- - the module level annotations,
+ -- - usages
+ -- - deps (home and external packages, dependent files)
+ -- - hpc
+ iface_hash <- computeFingerprint putNameLiterally
+ (mod_hash,
+ mi_src_hash iface0,
+ ann_fn (mkVarOccFS (fsLit "module")), -- See mkIfaceAnnCache
+ mi_usages iface0,
+ sorted_deps,
+ mi_hpc iface0)
+
+ return (ModIfaceSelfRecompBackend
+ { mi_sr_flag_hash = flag_hash
+ , mi_sr_hpc_hash = hpc_hash
+ , mi_sr_opt_hash = opt_hash
+ , mi_sr_iface_hash = iface_hash
+ , mi_sr_plugin_hash = plugin_hash })
+
-- | Retrieve the orphan hashes 'mi_orphan_hash' for a list of modules
-- (in particular, the orphan modules which are transitively imported by the
-- current module).
diff --git a/compiler/GHC/Tc/Errors/Ppr.hs b/compiler/GHC/Tc/Errors/Ppr.hs
index 33b75e3eb15..014ffe33c2e 100644
--- a/compiler/GHC/Tc/Errors/Ppr.hs
+++ b/compiler/GHC/Tc/Errors/Ppr.hs
@@ -1223,6 +1223,9 @@ instance Diagnostic TcRnMessage where
TcRnSectionWithoutParentheses expr -> mkSimpleDecorated $
hang (text "A section must be enclosed in parentheses")
2 (text "thus:" <+> (parens (ppr expr)))
+ TcRnReifyModuleMissingInfo m -> mkSimpleDecorated $
+ vcat [ (ppr m) <+> text "can't be reified due to missing information in its interface file."
+ , text "Possible cause:" <+> ppr m <+> text "was compiled with -fno-write-self-recomp-info" ]
diagnosticReason = \case
@@ -1628,6 +1631,8 @@ instance Diagnostic TcRnMessage where
-> ErrorWithoutFlag
TcRnIllegalTupleSection{}
-> ErrorWithoutFlag
+ TcRnReifyModuleMissingInfo {} ->
+ WarningWithFlag Opt_WarnReifyModuleMissingInfo
diagnosticHints = \case
TcRnUnknownMessage m
@@ -2037,6 +2042,7 @@ instance Diagnostic TcRnMessage where
-> noHints
TcRnIllegalTupleSection{}
-> [suggestExtension LangExt.TupleSections]
+ TcRnReifyModuleMissingInfo {} -> noHints
diagnosticCode = constructorCode
diff --git a/compiler/GHC/Tc/Errors/Types.hs b/compiler/GHC/Tc/Errors/Types.hs
index 335e7c49653..b7b1a32b871 100644
--- a/compiler/GHC/Tc/Errors/Types.hs
+++ b/compiler/GHC/Tc/Errors/Types.hs
@@ -2762,6 +2762,12 @@ data TcRnMessage where
-}
TcRnSectionWithoutParentheses :: HsExpr GhcPs -> TcRnMessage
+ {-| TcRnReifyModuleMissingInfo is a warning triggered by attempting to
+ call reifyModule on a module whose interface file lacks the necessary information
+ to satisfy the query. This normally occurs when the module is compiled with `-fno-write-self-recomp-info`.
+ -}
+ TcRnReifyModuleMissingInfo:: Module -> TcRnMessage
+
deriving Generic
-- | Things forbidden in @type data@ declarations.
diff --git a/compiler/GHC/Tc/Gen/Splice.hs b/compiler/GHC/Tc/Gen/Splice.hs
index 6ba304be169..3bbe9a9af7c 100644
--- a/compiler/GHC/Tc/Gen/Splice.hs
+++ b/compiler/GHC/Tc/Gen/Splice.hs
@@ -2808,6 +2808,10 @@ modToTHMod :: Module -> TH.Module
modToTHMod m = TH.Module (TH.PkgName $ unitString $ moduleUnit m)
(TH.ModName $ moduleNameString $ moduleName m)
+-- | Note that reifyModule will not work if the module is compiled with `-fno-write-self-recomp-info`
+-- because the implementation works by consulting the `mi_usages` field which is intended to be only
+-- used for recompilation checking. See #8489 for a ticket which tracks improvement
+-- of this function.
reifyModule :: TH.Module -> TcM TH.ModuleInfo
reifyModule (TH.Module (TH.PkgName pkgString) (TH.ModName mString)) = do
this_mod <- getModule
@@ -2820,9 +2824,20 @@ reifyModule (TH.Module (TH.PkgName pkgString) (TH.ModName mString)) = do
reifyFromIface reifMod = do
iface <- loadInterfaceForModule (text "reifying module from TH for" <+> ppr reifMod) reifMod
- let usages = [modToTHMod m | usage <- mi_usages iface,
- Just m <- [usageToModule (moduleUnit reifMod) usage] ]
- return $ TH.ModuleInfo usages
+ case mi_self_recomp_info iface of
+ NoSelfRecomp -> do
+ -- Arguably this should fail here but GHC.Prim always has NoSelfRecomp, so
+ -- any existing traversals would just stop working. Now they will start warning
+ -- and a user is expected to add a special case to avoid GHC.Prim in their traversal.
+
+ -- An alternative would be to add that special case for GHC.Prim here and make it a hard
+ -- error if reifyModule was attempted to be used with these partial interface files.
+ addDiagnosticTc (TcRnReifyModuleMissingInfo reifMod)
+ return (TH.ModuleInfo [])
+ ModIfaceSelfRecomp{ mi_sr_usages } -> do
+ let usages = [modToTHMod m | usage <- mi_sr_usages
+ , Just m <- [usageToModule (moduleUnit reifMod) usage] ]
+ return $ TH.ModuleInfo usages
usageToModule :: Unit -> Usage -> Maybe Module
usageToModule _ (UsageFile {}) = Nothing
diff --git a/compiler/GHC/Types/Error/Codes.hs b/compiler/GHC/Types/Error/Codes.hs
index ad8028ef387..e157c14a2ba 100644
--- a/compiler/GHC/Types/Error/Codes.hs
+++ b/compiler/GHC/Types/Error/Codes.hs
@@ -500,6 +500,8 @@ type family GhcDiagnosticCode c = n | n -> c where
GhcDiagnosticCode "TcRnMissingClassAssoc" = 06205
GhcDiagnosticCode "TcRnBadFamInstDecl" = 06206
GhcDiagnosticCode "TcRnNotOpenFamily" = 06207
+ GhcDiagnosticCode "TcRnReifyModuleMissingInfo" = 89264
+
-- IllegalNewtypeReason
GhcDiagnosticCode "DoesNotHaveSingleField" = 23517
diff --git a/compiler/GHC/Unit/Module/ModGuts.hs b/compiler/GHC/Unit/Module/ModGuts.hs
index d54e836d714..9f14ba91651 100644
--- a/compiler/GHC/Unit/Module/ModGuts.hs
+++ b/compiler/GHC/Unit/Module/ModGuts.hs
@@ -52,7 +52,7 @@ data ModGuts
mg_exports :: ![AvailInfo], -- ^ What it exports
mg_deps :: !Dependencies, -- ^ What it depends on, directly or
-- otherwise
- mg_usages :: ![Usage], -- ^ What was used? Used for interfaces.
+ mg_usages :: !(Maybe [Usage]), -- ^ What was used? Used for interfaces.
mg_used_th :: !Bool, -- ^ Did we run a TH splice?
mg_rdr_env :: !GlobalRdrEnv, -- ^ Top-level lexical environment
diff --git a/compiler/GHC/Unit/Module/ModIface.hs b/compiler/GHC/Unit/Module/ModIface.hs
index 1d5280f4fa7..9a748c9ea0b 100644
--- a/compiler/GHC/Unit/Module/ModIface.hs
+++ b/compiler/GHC/Unit/Module/ModIface.hs
@@ -3,12 +3,16 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE NamedFieldPuns #-}
module GHC.Unit.Module.ModIface
( ModIface
, ModIface_ (..)
, PartialModIface
, ModIfaceBackend (..)
+ , ModIfaceSelfRecompBackend (..)
+ , ModIfaceSelfRecomp (..)
+ , isSelfRecompilationInterface
, IfaceDeclExts
, IfaceBackendExts
, IfaceExport
@@ -19,6 +23,13 @@ module GHC.Unit.Module.ModIface
, mi_semantic_module
, mi_free_holes
, mi_mnwib
+ , mi_flag_hash
+ , mi_iface_hash
+ , mi_opt_hash
+ , mi_hpc_hash
+ , mi_plugin_hash
+ , mi_usages
+ , mi_src_hash
, renameFreeHoles
, emptyPartialModIface
, emptyFullModIface
@@ -57,6 +68,9 @@ import GHC.Utils.Binary
import Control.DeepSeq
import Control.Exception
+import GHC.Utils.Panic
+import GHC.Utils.Outputable
+import GHC.Utils.Misc
{- Note [Interface file stages]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -82,19 +96,11 @@ type ModIface = ModIface_ 'ModIfaceFinal
-- * Or computed just before writing the iface to disk. (Hashes)
-- In order to fully instantiate it.
data ModIfaceBackend = ModIfaceBackend
- { mi_iface_hash :: !Fingerprint
- -- ^ Hash of the whole interface
- , mi_mod_hash :: !Fingerprint
+ { mi_mod_hash :: !Fingerprint
-- ^ Hash of the ABI only
- , mi_flag_hash :: !Fingerprint
- -- ^ Hash of the important flags used when compiling the module, excluding
- -- optimisation flags
- , mi_opt_hash :: !Fingerprint
- -- ^ Hash of optimisation flags
- , mi_hpc_hash :: !Fingerprint
- -- ^ Hash of hpc flags
- , mi_plugin_hash :: !Fingerprint
- -- ^ Hash of plugins
+ , mi_self_recomp_backend_info :: !ModIfaceSelfRecompBackend
+ -- ^ Information needed for checking self-recompilation.
+ -- See Note [Self recompilation information in interface files]
, mi_orphan :: !WhetherHasOrphans
-- ^ Whether this module has orphans
, mi_finsts :: !WhetherHasFamInst
@@ -136,6 +142,81 @@ type family IfaceBackendExts (phase :: ModIfacePhase) = bk | bk -> phase where
IfaceBackendExts 'ModIfaceCore = ()
IfaceBackendExts 'ModIfaceFinal = ModIfaceBackend
+-- | The information for a module which is only used when deciding whether to recompile
+-- itself. In particular the external interface of a module is recorded by the ABI
+-- hash
+data ModIfaceSelfRecompBackend = NoSelfRecompBackend | ModIfaceSelfRecompBackend {
+ mi_sr_flag_hash :: !Fingerprint
+ -- ^ Hash of the important flags used when compiling the module, excluding
+ -- optimisation flags
+ , mi_sr_iface_hash :: !Fingerprint
+ -- ^ Hash of the whole interface
+ , mi_sr_opt_hash :: !Fingerprint
+ -- ^ Hash of optimisation flags
+ , mi_sr_hpc_hash :: !Fingerprint
+ -- ^ Hash of hpc flags
+ , mi_sr_plugin_hash :: !Fingerprint
+ -- ^ Hash of plugins
+}
+withSelfRecompBackend :: HasCallStack => (ModIfaceSelfRecompBackend-> t) -> ModIfaceBackend-> t
+
+withSelfRecompBackend f mi =
+ case mi_self_recomp_backend_info mi of
+ NoSelfRecompBackend -> panic "Trying to use self-recomp info"
+ x -> f x
+
+mi_flag_hash :: HasCallStack => ModIfaceBackend -> Fingerprint
+mi_flag_hash = withSelfRecompBackend mi_sr_flag_hash
+mi_iface_hash :: HasCallStack => ModIfaceBackend -> Fingerprint
+mi_iface_hash = withSelfRecompBackend mi_sr_iface_hash
+mi_opt_hash :: HasCallStack => ModIfaceBackend -> Fingerprint
+mi_opt_hash = withSelfRecompBackend mi_sr_opt_hash
+mi_hpc_hash :: HasCallStack => ModIfaceBackend -> Fingerprint
+mi_hpc_hash = withSelfRecompBackend mi_sr_hpc_hash
+mi_plugin_hash :: HasCallStack => ModIfaceBackend -> Fingerprint
+mi_plugin_hash = withSelfRecompBackend mi_sr_plugin_hash
+
+isSelfRecompilationInterface :: ModIface -> Bool
+isSelfRecompilationInterface iface =
+ case mi_self_recomp_info iface of
+ NoSelfRecomp -> False
+ ModIfaceSelfRecomp {} -> True
+
+{-
+Note [Self recompilation information in interface files]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The flag -fwrite-self-recomp-info controls whether
+interface files contain the information necessary to answer the
+question:
+
+ Do I need to recompile myself or is this current interface file
+ suitable?
+
+Why? Most packages are only built once either by a distribution or cabal
+and then placed into an immutable store, after which we will never ask
+this question. Therefore we can derive two benefits from omitting this
+information.
+
+* Primary motivation: It vastly reduces the surface area for creating
+ non-deterministic interface files. See issue #10424 which motivated a
+ proper fix to that issue. Distributions have long contained versions
+ of GHC which just have broken self-recompilation checking (in order to
+ get deterministic interface files).
+
+* Secondary motivation: This reduces the size of interface files
+ slightly.. the `mi_usages` field can be quite big but probably this
+ isn't such a great benefit.
+
+* Third motivation: Conceptually clarity about which parts of an
+ interface file are used in order to **communicate** with subsequent
+ packages about the **interface** for a module. And which parts are
+ used to self-communicate during recompilation checking.
+
+The main tracking issue is #22188 but fixes issues such as #10424 in a
+proper way.
+
+-}
-- | A 'ModIface' plus a 'ModDetails' summarises everything we know
@@ -162,12 +243,6 @@ data ModIface_ (phase :: ModIfacePhase)
-- consulted for directly-imported modules, but not
-- for anything else (hence lazy)
- mi_usages :: [Usage],
- -- ^ Usages; kept sorted so that it's easy to decide
- -- whether to write a new iface file (changing usages
- -- doesn't affect the hash of this module)
- -- NOT STRICT! we read this field lazily from the interface file
- -- It is *only* consulted by the recompilation checker
mi_exports :: ![IfaceExport],
-- ^ Exports
@@ -259,10 +334,40 @@ data ModIface_ (phase :: ModIfacePhase)
-- chosen over `ByteString`s.
--
- mi_src_hash :: !Fingerprint
- -- ^ Hash of the .hs source, used for recompilation checking.
+ mi_self_recomp_info :: !ModIfaceSelfRecomp
+ -- ^ Information needed for checking self-recompilation.
+ -- See Note [Self recompilation information in interface files]
}
+data ModIfaceSelfRecomp = NoSelfRecomp
+ | ModIfaceSelfRecomp { mi_sr_src_hash :: !Fingerprint
+ -- ^ Hash of the .hs source, used for recompilation checking.
+ , mi_sr_usages :: [Usage]
+ -- ^ Usages; kept sorted so that it's easy to decide
+ -- whether to write a new iface file (changing usages
+ -- doesn't affect the hash of this module)
+ -- NOT STRICT! we read this field lazily from the interface file
+ -- It is *only* consulted by the recompilation checker
+ }
+
+instance Outputable ModIfaceSelfRecomp where
+ ppr NoSelfRecomp = text "NoSelfRecomp"
+ ppr (ModIfaceSelfRecomp{mi_sr_src_hash, mi_sr_usages}) = vcat [text "Self-Recomp"
+ , nest 2 (vcat [text "src hash:" <+> ppr mi_sr_src_hash
+ , text "usages:" <+> ppr (length mi_sr_usages)])]
+
+withSelfRecomp :: HasCallStack => (ModIfaceSelfRecomp-> t) -> ModIface_ phase -> t
+withSelfRecomp f mi =
+ case mi_self_recomp_info mi of
+ NoSelfRecomp -> panic "Trying to use self-recomp info"
+ x -> f x
+
+mi_usages :: HasCallStack => ModIface_ phase -> [Usage]
+mi_usages = withSelfRecomp mi_sr_usages
+mi_src_hash :: HasCallStack => ModIface_ phase -> Fingerprint
+mi_src_hash = withSelfRecomp mi_sr_src_hash
+
+
{-
Note [Strictness in ModIface]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -337,17 +442,53 @@ renameFreeHoles fhs insts =
-- It wasn't actually a hole
| otherwise = emptyUniqDSet
+instance Binary ModIfaceSelfRecompBackend where
+ put_ bh NoSelfRecompBackend = put_ bh (0 :: Int)
+ put_ bh (ModIfaceSelfRecompBackend {mi_sr_flag_hash, mi_sr_iface_hash, mi_sr_plugin_hash, mi_sr_opt_hash, mi_sr_hpc_hash}) = do
+ put_ bh (1 :: Int)
+ put_ bh mi_sr_flag_hash
+ put_ bh mi_sr_iface_hash
+ put_ bh mi_sr_plugin_hash
+ put_ bh mi_sr_opt_hash
+ put_ bh mi_sr_hpc_hash
+
+ get bh = do
+ (tag :: Int) <- get bh
+ case tag of
+ 0 -> return NoSelfRecompBackend
+ 1 -> do
+ mi_sr_flag_hash <- get bh
+ mi_sr_iface_hash <- get bh
+ mi_sr_plugin_hash <- get bh
+ mi_sr_opt_hash <- get bh
+ mi_sr_hpc_hash <- get bh
+ return (ModIfaceSelfRecompBackend {mi_sr_flag_hash, mi_sr_iface_hash, mi_sr_plugin_hash, mi_sr_opt_hash, mi_sr_hpc_hash})
+ x -> pprPanic "get_ModIfaceSelfRecomp" (ppr x)
+
+instance Binary ModIfaceSelfRecomp where
+ put_ bh NoSelfRecomp = put_ bh (0 :: Int)
+ put_ bh (ModIfaceSelfRecomp{mi_sr_src_hash, mi_sr_usages}) = do
+ put_ bh (1 :: Int)
+ put_ bh mi_sr_src_hash
+ lazyPut bh mi_sr_usages
+
+ get bh = do
+ (tag :: Int) <- get bh
+ case tag of
+ 0 -> return NoSelfRecomp
+ 1 -> do
+ src_hash <- get bh
+ usages <- {-# SCC "bin_usages" #-} lazyGet bh
+ return $ ModIfaceSelfRecomp { mi_sr_src_hash = src_hash, mi_sr_usages = usages }
+ x -> pprPanic "get_ModIfaceSelfRecomp" (ppr x)
+
-- See Note [Strictness in ModIface] about where we use lazyPut vs put
instance Binary ModIface where
put_ bh (ModIface {
mi_module = mod,
mi_sig_of = sig_of,
mi_hsc_src = hsc_src,
- mi_src_hash = _src_hash, -- Don't `put_` this in the instance
- -- because we are going to write it
- -- out separately in the actual file
mi_deps = deps,
- mi_usages = usages,
mi_exports = exports,
mi_used_th = used_th,
mi_fixities = fixities,
@@ -366,13 +507,10 @@ instance Binary ModIface where
mi_ext_fields = _ext_fields, -- Don't `put_` this in the instance so we
-- can deal with it's pointer in the header
-- when we write the actual file
+ mi_self_recomp_info = self_recomp,
mi_final_exts = ModIfaceBackend {
- mi_iface_hash = iface_hash,
+ mi_self_recomp_backend_info = self_recomp_backend,
mi_mod_hash = mod_hash,
- mi_flag_hash = flag_hash,
- mi_opt_hash = opt_hash,
- mi_hpc_hash = hpc_hash,
- mi_plugin_hash = plugin_hash,
mi_orphan = orphan,
mi_finsts = hasFamInsts,
mi_exp_hash = exp_hash,
@@ -381,16 +519,12 @@ instance Binary ModIface where
put_ bh mod
put_ bh sig_of
put_ bh hsc_src
- put_ bh iface_hash
+ put_ bh self_recomp
+ put_ bh self_recomp_backend
put_ bh mod_hash
- put_ bh flag_hash
- put_ bh opt_hash
- put_ bh hpc_hash
- put_ bh plugin_hash
put_ bh orphan
put_ bh hasFamInsts
lazyPut bh deps
- lazyPut bh usages
put_ bh exports
put_ bh exp_hash
put_ bh used_th
@@ -413,16 +547,12 @@ instance Binary ModIface where
mod <- get bh
sig_of <- get bh
hsc_src <- get bh
- iface_hash <- get bh
+ self_recomp_info <- get bh
+ self_recomp_backend_info <- get bh
mod_hash <- get bh
- flag_hash <- get bh
- opt_hash <- get bh
- hpc_hash <- get bh
- plugin_hash <- get bh
orphan <- get bh
hasFamInsts <- get bh
deps <- lazyGet bh
- usages <- {-# SCC "bin_usages" #-} lazyGet bh
exports <- {-# SCC "bin_exports" #-} get bh
exp_hash <- get bh
used_th <- get bh
@@ -444,10 +574,7 @@ instance Binary ModIface where
mi_module = mod,
mi_sig_of = sig_of,
mi_hsc_src = hsc_src,
- mi_src_hash = fingerprint0, -- placeholder because this is dealt
- -- with specially when the file is read
mi_deps = deps,
- mi_usages = usages,
mi_exports = exports,
mi_used_th = used_th,
mi_anns = anns,
@@ -467,13 +594,10 @@ instance Binary ModIface where
mi_docs = docs,
mi_ext_fields = emptyExtensibleFields, -- placeholder because this is dealt
-- with specially when the file is read
+ mi_self_recomp_info = self_recomp_info,
mi_final_exts = ModIfaceBackend {
- mi_iface_hash = iface_hash,
+ mi_self_recomp_backend_info = self_recomp_backend_info,
mi_mod_hash = mod_hash,
- mi_flag_hash = flag_hash,
- mi_opt_hash = opt_hash,
- mi_hpc_hash = hpc_hash,
- mi_plugin_hash = plugin_hash,
mi_orphan = orphan,
mi_finsts = hasFamInsts,
mi_exp_hash = exp_hash,
@@ -491,9 +615,7 @@ emptyPartialModIface mod
= ModIface { mi_module = mod,
mi_sig_of = Nothing,
mi_hsc_src = HsSrcFile,
- mi_src_hash = fingerprint0,
mi_deps = noDependencies,
- mi_usages = [],
mi_exports = [],
mi_used_th = False,
mi_fixities = [],
@@ -511,6 +633,7 @@ emptyPartialModIface mod
mi_complete_matches = [],
mi_docs = Nothing,
mi_final_exts = (),
+ mi_self_recomp_info = NoSelfRecomp,
mi_ext_fields = emptyExtensibleFields
}
@@ -518,13 +641,9 @@ emptyFullModIface :: Module -> ModIface
emptyFullModIface mod =
(emptyPartialModIface mod)
{ mi_decls = []
- , mi_final_exts = ModIfaceBackend
- { mi_iface_hash = fingerprint0,
+ , mi_final_exts = ModIfaceBackend {
mi_mod_hash = fingerprint0,
- mi_flag_hash = fingerprint0,
- mi_opt_hash = fingerprint0,
- mi_hpc_hash = fingerprint0,
- mi_plugin_hash = fingerprint0,
+ mi_self_recomp_backend_info = NoSelfRecompBackend, -- TODO
mi_orphan = False,
mi_finsts = False,
mi_exp_hash = fingerprint0,
@@ -550,19 +669,31 @@ emptyIfaceHashCache _occ = Nothing
-- Take care, this instance only forces to the degree necessary to
-- avoid major space leaks.
instance (NFData (IfaceBackendExts (phase :: ModIfacePhase)), NFData (IfaceDeclExts (phase :: ModIfacePhase))) => NFData (ModIface_ phase) where
- rnf (ModIface f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
+ rnf (ModIface f1 f2 f3 f4 f6 f7 f8 f9 f10 f11 f12
f13 f14 f15 f16 f17 f18 f19 f20 f21 f22 f23 f24) =
- rnf f1 `seq` rnf f2 `seq` f3 `seq` f4 `seq` f5 `seq` f6 `seq` rnf f7 `seq` f8 `seq`
+ rnf f1 `seq` rnf f2 `seq` f3 `seq` f4 `seq` f6 `seq` rnf f7 `seq` f8 `seq`
f9 `seq` rnf f10 `seq` rnf f11 `seq` rnf f12 `seq` f13 `seq` rnf f14 `seq` rnf f15 `seq` rnf f16 `seq`
rnf f17 `seq` f18 `seq` rnf f19 `seq` rnf f20 `seq` f21 `seq` f22 `seq` f23 `seq` rnf f24
`seq` ()
instance NFData (ModIfaceBackend) where
- rnf (ModIfaceBackend f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13)
+ rnf (ModIfaceBackend f1 f2 f3 f4 f5 f6 f7 f8 f9)
= rnf f1 `seq` rnf f2 `seq` rnf f3 `seq` rnf f4 `seq`
rnf f5 `seq` rnf f6 `seq` rnf f7 `seq` rnf f8 `seq`
- rnf f9 `seq` rnf f10 `seq` rnf f11 `seq` rnf f12 `seq` rnf f13
+ rnf f9
+
+instance NFData ModIfaceSelfRecompBackend where
+ -- Sufficient as all fields are strict (and simple)
+ rnf NoSelfRecompBackend = ()
+ -- Written like this so if you add another field you have to think about it
+ rnf !(ModIfaceSelfRecompBackend _ _ _ _ _) = ()
+instance NFData ModIfaceSelfRecomp where
+ -- Sufficient as all fields are strict (and simple)
+ rnf NoSelfRecomp = ()
+ -- MP: Note does not deeply force Usages but the old ModIface logic didn't either, so
+ -- I left it as a shallow force.
+ rnf (ModIfaceSelfRecomp src_hash usages) = src_hash `seq` usages `seq` ()
forceModIface :: ModIface -> IO ()
diff --git a/docs/users_guide/phases.rst b/docs/users_guide/phases.rst
index aa171c2055b..b9557193985 100644
--- a/docs/users_guide/phases.rst
+++ b/docs/users_guide/phases.rst
@@ -653,6 +653,24 @@ Options affecting code generation
depend on the optimisation level. Any definitions which are already included in
an interface file (via an unfolding for an exported identifier) are reused.
+.. ghc-flag:: -fwrite-self-recomp-info
+ :shortdesc: Write information for self-recompilation checking in an interface file
+ :type: dynamic
+ :category: codegen
+
+ :default: on
+
+ Include information in an interface file which can be used in future to determine
+ whether we need to recompile a module or can reuse the existing interface.
+
+ This is intended to be turned off in situations where you know you will never try
+ to recompile a module, such as when compiling a package for distribution.
+ The advantage is that by omitting unecessary information to do with dependencies
+ there is less chance of build paths leaking into the interface file and affecting
+ determinism.
+
+
+
.. ghc-flag:: -fobject-code
:shortdesc: Generate object code
diff --git a/docs/users_guide/using-warnings.rst b/docs/users_guide/using-warnings.rst
index 977bb699410..d61fc46319c 100644
--- a/docs/users_guide/using-warnings.rst
+++ b/docs/users_guide/using-warnings.rst
@@ -2355,6 +2355,21 @@ of ``-W(no-)*``.
triggered whenever this happens, and can be addressed by enabling the
extension.
+.. ghc-flag:: -Wreify-module-missing-info
+ :shortdesc: warn when `reifyModule`
+ :type: dynamic
+ :reverse: -Wno-reify-module-missing-info
+
+ :since: 9.6.1
+
+ The `reifyModule` function in the Template Haskell API can fail to find the necessary
+ information when an interface file is generated with `-fno-write-self-recomp-info`. This
+ is due to a shortcoming in `reifyModule` tracked by :ghc-ticket:`8489`.
+
+ This flag warns the user when they try to call `reifyModule` on a module where this
+ situation occurs so that they know the traversal has ended prematurely.
+
+
If you're feeling really paranoid, the :ghc-flag:`-dcore-lint` option is a good choice.
It turns on heavyweight intra-pass sanity-checking within GHC. (It checks GHC's
sanity, not yours.)
diff --git a/testsuite/tests/annotations/should_compile/th/annth_compunits.stderr b/testsuite/tests/annotations/should_compile/th/annth_compunits.stderr
new file mode 100644
index 00000000000..417b2a5a567
--- /dev/null
+++ b/testsuite/tests/annotations/should_compile/th/annth_compunits.stderr
@@ -0,0 +1,11 @@
+
+annth.hs:12:4: warning: [GHC-89264] [-Wreify-module-missing-info (in -Wdefault)]
+ • GHC.Prim can't be reified due to missing information in its interface file.
+ Possible cause: GHC.Prim was compiled with -fno-write-self-recomp-info
+ • In the untyped splice:
+ $(do anns <- traverseModuleAnnotations
+ runIO $ print (anns :: [String])
+ anns <- reifyAnnotations (AnnLookupName 'testValue)
+ runIO $ print (anns :: [String])
+ anns <- reifyAnnotations (AnnLookupName 'testValueTH)
+ ....)
diff --git a/testsuite/tests/annotations/should_compile/th/annth_make.stderr b/testsuite/tests/annotations/should_compile/th/annth_make.stderr
new file mode 100644
index 00000000000..417b2a5a567
--- /dev/null
+++ b/testsuite/tests/annotations/should_compile/th/annth_make.stderr
@@ -0,0 +1,11 @@
+
+annth.hs:12:4: warning: [GHC-89264] [-Wreify-module-missing-info (in -Wdefault)]
+ • GHC.Prim can't be reified due to missing information in its interface file.
+ Possible cause: GHC.Prim was compiled with -fno-write-self-recomp-info
+ • In the untyped splice:
+ $(do anns <- traverseModuleAnnotations
+ runIO $ print (anns :: [String])
+ anns <- reifyAnnotations (AnnLookupName 'testValue)
+ runIO $ print (anns :: [String])
+ anns <- reifyAnnotations (AnnLookupName 'testValueTH)
+ ....)
diff --git a/testsuite/tests/driver/self-recomp/Makefile b/testsuite/tests/driver/self-recomp/Makefile
new file mode 100644
index 00000000000..c6ac89fba19
--- /dev/null
+++ b/testsuite/tests/driver/self-recomp/Makefile
@@ -0,0 +1,38 @@
+TOP=../../..
+include $(TOP)/mk/boilerplate.mk
+include $(TOP)/mk/test.mk
+
+TEST_HC_OPTS_NO_RTSOPTS = $(filter-out -rtsopts,$(TEST_HC_OPTS))
+
+# -----------------------------------------------------------------------------
+# One-shot compilations, non-hierarchical modules
+
+# Check that modifying flags doesn't affect interface
+SelfRecomp01:
+ "$(TEST_HC)" $(TEST_HC_OPTS) SelfRecomp01.hs -fno-write-self-recomp-info -v0
+ "$(TEST_HC)" --show-iface SelfRecomp01.hi > iface1
+ rm SelfRecomp01.hi
+ "$(TEST_HC)" $(TEST_HC_OPTS) SelfRecomp01.hs -fno-write-self-recomp-info -Iidir -v0
+ "$(TEST_HC)" --show-iface SelfRecomp01.hi > iface2
+ diff iface1 iface2
+
+# Check that the result of addDependentFile doesn't end up in interface
+SelfRecomp02:
+ "$(TEST_HC)" $(TEST_HC_OPTS) SelfRecomp02.hs -fno-write-self-recomp-info -v0
+ "$(TEST_HC)" --show-iface SelfRecomp02.hi > iface
+ [ -z $(grep iface SelfRecomp02.hs) ]
+
+# Check that modifying source doesn't affect interface
+SelfRecomp03:
+ "$(TEST_HC)" $(TEST_HC_OPTS) SelfRecomp03.hs -fno-write-self-recomp-info -v0
+ "$(TEST_HC)" --show-iface SelfRecomp03.hi > iface1
+ rm SelfRecomp03.hi
+ echo "" >> SelfRecomp03.hs
+ "$(TEST_HC)" $(TEST_HC_OPTS) SelfRecomp03.hs -fno-write-self-recomp-info -v0
+ "$(TEST_HC)" --show-iface SelfRecomp03.hi > iface2
+ diff iface1 iface2
+
+# Check that if you don't have recompilation info then you always recompile.
+SelfRecomp04:
+ "$(TEST_HC)" $(TEST_HC_OPTS) SelfRecomp04.hs -fno-write-self-recomp-info -fhide-source-paths
+ "$(TEST_HC)" $(TEST_HC_OPTS) SelfRecomp04.hs -fno-write-self-recomp-info -fhide-source-paths
diff --git a/testsuite/tests/driver/self-recomp/SelfRecomp01.hs b/testsuite/tests/driver/self-recomp/SelfRecomp01.hs
new file mode 100644
index 00000000000..1d6400e92bc
--- /dev/null
+++ b/testsuite/tests/driver/self-recomp/SelfRecomp01.hs
@@ -0,0 +1,2 @@
+module SelfRecomp01 where
+
diff --git a/testsuite/tests/driver/self-recomp/SelfRecomp02.hs b/testsuite/tests/driver/self-recomp/SelfRecomp02.hs
new file mode 100644
index 00000000000..dd38566b398
--- /dev/null
+++ b/testsuite/tests/driver/self-recomp/SelfRecomp02.hs
@@ -0,0 +1,6 @@
+{-# LANGUAGE TemplateHaskell #-}
+module SelfRecomp02 where
+
+import Language.Haskell.TH.Syntax
+
+main = $(addDependentFile "SelfRecomp02.hs" >> [| print () |])
diff --git a/testsuite/tests/driver/self-recomp/SelfRecomp03.hs b/testsuite/tests/driver/self-recomp/SelfRecomp03.hs
new file mode 100644
index 00000000000..ca181a1f34c
--- /dev/null
+++ b/testsuite/tests/driver/self-recomp/SelfRecomp03.hs
@@ -0,0 +1,2 @@
+module SelfRecomp03 where
+
diff --git a/testsuite/tests/driver/self-recomp/SelfRecomp04.hs b/testsuite/tests/driver/self-recomp/SelfRecomp04.hs
new file mode 100644
index 00000000000..8a956509e90
--- /dev/null
+++ b/testsuite/tests/driver/self-recomp/SelfRecomp04.hs
@@ -0,0 +1 @@
+module SelfRecomp04 where
diff --git a/testsuite/tests/driver/self-recomp/SelfRecomp04.stdout b/testsuite/tests/driver/self-recomp/SelfRecomp04.stdout
new file mode 100644
index 00000000000..5dc637533d1
--- /dev/null
+++ b/testsuite/tests/driver/self-recomp/SelfRecomp04.stdout
@@ -0,0 +1,2 @@
+[1 of 1] Compiling SelfRecomp04
+[1 of 1] Compiling SelfRecomp04 [Old interface lacks recompilation info]
diff --git a/testsuite/tests/driver/self-recomp/all.T b/testsuite/tests/driver/self-recomp/all.T
new file mode 100644
index 00000000000..91713d459da
--- /dev/null
+++ b/testsuite/tests/driver/self-recomp/all.T
@@ -0,0 +1,4 @@
+test('SelfRecomp01', normal, makefile_test, ['SelfRecomp01'])
+test('SelfRecomp02', normal, makefile_test, ['SelfRecomp02'])
+test('SelfRecomp03', [copy_files], makefile_test, ['SelfRecomp03'])
+test('SelfRecomp04', normal, makefile_test, ['SelfRecomp04'])
--
GitLab