Commit 6fd8629d authored by David Feuer's avatar David Feuer Committed by Ben Gamari
Browse files

Allow users to ignore optimization changes

* Add a new flag, `-fignore-optim-changes`, allowing them to avoid
  recompilation if the only changes are to the `-O` level or to
  flags controlling optimizations.

* When `-fignore-optim-changes` is *off*, recompile when optimization
  flags (e.g., `-fno-full-laziness`) change. Previously, we ignored
  these unconditionally when deciding whether to recompile a module.

Reviewers: austin, bgamari, simonmar

Reviewed By: simonmar

Subscribers: duog, carter, simonmar, rwbarton, thomie

GHC Trac Issues: #13604

Differential Revision: https://phabricator.haskell.org/D4123

(cherry picked from commit 708ed9ca)
parent ce8d8c01
......@@ -4,6 +4,8 @@
-- interface file as part of the recompilation checking infrastructure.
module FlagChecker (
fingerprintDynFlags
, fingerprintOptFlags
, fingerprintHpcFlags
) where
import GhcPrelude
......@@ -53,25 +55,45 @@ fingerprintDynFlags dflags@DynFlags{..} this_mod nameio =
-- -fprof-auto etc.
prof = if gopt Opt_SccProfilingOn dflags then fromEnum profAuto else 0
-- -O, see https://ghc.haskell.org/trac/ghc/ticket/10923
opt = if hscTarget == HscInterpreted ||
hscTarget == HscNothing
then 0
else optLevel
flags = (mainis, safeHs, lang, cpp, paths, prof)
in -- pprTrace "flags" (ppr flags) $
computeFingerprint nameio flags
-- Fingerprint the optimisation info. We keep this separate from the rest of
-- the flags because GHCi users (especially) may wish to ignore changes in
-- optimisation level or optimisation flags so as to use as many pre-existing
-- object files as they can.
-- See Note [Ignoring some flag changes]
fingerprintOptFlags :: DynFlags
-> (BinHandle -> Name -> IO ())
-> IO Fingerprint
fingerprintOptFlags DynFlags{..} nameio =
let
-- See https://ghc.haskell.org/trac/ghc/ticket/10923
-- We used to fingerprint the optimisation level, but as Joachim
-- Breitner pointed out in comment 9 on that ticket, it's better
-- to ignore that and just look at the individual optimisation flags.
opt_flags = map fromEnum $ filter (`EnumSet.member` optimisationFlags)
(EnumSet.toList generalFlags)
in computeFingerprint nameio opt_flags
-- Fingerprint the HPC info. We keep this separate from the rest of
-- the flags because GHCi users (especially) may wish to use an object
-- file compiled for HPC when not actually using HPC.
-- See Note [Ignoring some flag changes]
fingerprintHpcFlags :: DynFlags
-> (BinHandle -> Name -> IO ())
-> IO Fingerprint
fingerprintHpcFlags dflags@DynFlags{..} nameio =
let
-- -fhpc, see https://ghc.haskell.org/trac/ghc/ticket/11798
-- hpcDir is output-only, so we should recompile if it changes
hpc = if gopt Opt_Hpc dflags then Just hpcDir else Nothing
-- -fignore-asserts, which affects how `Control.Exception.assert` works
ignore_asserts = gopt Opt_IgnoreAsserts dflags
-- Nesting just to avoid ever more Binary tuple instances
flags = (mainis, safeHs, lang, cpp, paths,
(prof, opt, hpc, ignore_asserts))
in computeFingerprint nameio hpc
in -- pprTrace "flags" (ppr flags) $
computeFingerprint nameio flags
{- Note [path flags and recompilation]
......@@ -102,3 +124,22 @@ recompilation check; here we explain why.
The only path-related flag left is -hcsuf.
-}
{- Note [Ignoring some flag changes]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Normally, --make tries to reuse only compilation products that are
the same as those that would have been produced compiling from
scratch. Sometimes, however, users would like to be more aggressive
about recompilation avoidance. This is particularly likely when
developing using GHCi (see #13604). Currently, we allow users to
ignore optimisation changes using -fignore-optim-changes, and to
ignore HPC option changes using -fignore-hpc-changes. If there's a
demand for it, we could also allow changes to -fprof-auto-* flags
(although we can't allow -prof flags to differ). The key thing about
these options is that we can still successfully link a library or
executable when some of its components differ in these ways.
The way we accomplish this is to leave the optimization and HPC
options out of the flag hash, hashing them separately.
-}
......@@ -1020,6 +1020,8 @@ pprModIface iface
, nest 2 (text "export-list hash:" <+> ppr (mi_exp_hash iface))
, nest 2 (text "orphan hash:" <+> ppr (mi_orphan_hash iface))
, nest 2 (text "flag hash:" <+> ppr (mi_flag_hash iface))
, nest 2 (text "opt_hash:" <+> ppr (mi_opt_hash iface))
, nest 2 (text "hpc_hash:" <+> ppr (mi_hpc_hash iface))
, nest 2 (text "sig of:" <+> ppr (mi_sig_of iface))
, nest 2 (text "used TH splices:" <+> ppr (mi_used_th iface))
, nest 2 (text "where")
......
......@@ -4,6 +4,7 @@
-}
{-# LANGUAGE CPP, NondecreasingIndentation #-}
{-# LANGUAGE MultiWayIf #-}
-- | Module for constructing @ModIface@ values (interface files),
-- writing them to disk and comparing two versions to see if
......@@ -279,6 +280,8 @@ mkIface_ hsc_env maybe_old_fingerprint
mi_iface_hash = fingerprint0,
mi_mod_hash = fingerprint0,
mi_flag_hash = fingerprint0,
mi_opt_hash = fingerprint0,
mi_hpc_hash = fingerprint0,
mi_exp_hash = fingerprint0,
mi_used_th = used_th,
mi_orphan_hash = fingerprint0,
......@@ -660,6 +663,10 @@ addFingerprints hsc_env mb_old_fingerprint iface0 new_decls
-- the abi hash and one that should
flag_hash <- fingerprintDynFlags dflags this_mod putNameLiterally
opt_hash <- fingerprintOptFlags dflags putNameLiterally
hpc_hash <- fingerprintHpcFlags dflags putNameLiterally
-- the ABI hash depends on:
-- - decls
-- - export list
......@@ -695,6 +702,8 @@ addFingerprints hsc_env mb_old_fingerprint iface0 new_decls
mi_exp_hash = export_hash,
mi_orphan_hash = orphan_hash,
mi_flag_hash = flag_hash,
mi_opt_hash = opt_hash,
mi_hpc_hash = hpc_hash,
mi_orphan = not ( all ifRuleAuto orph_rules
-- See Note [Orphans and auto-generated rules]
&& null orph_insts
......@@ -1200,6 +1209,10 @@ checkVersions hsc_env mod_summary iface
then return (RecompBecause "-this-unit-id changed", Nothing) else do {
; recomp <- checkFlagHash hsc_env iface
; if recompileRequired recomp then return (recomp, Nothing) else do {
; recomp <- checkOptimHash hsc_env iface
; if recompileRequired recomp then return (recomp, Nothing) else do {
; recomp <- checkHpcHash hsc_env iface
; if recompileRequired recomp then return (recomp, Nothing) else do {
; recomp <- checkMergedSignatures mod_summary iface
; if recompileRequired recomp then return (recomp, Nothing) else do {
; recomp <- checkHsig mod_summary iface
......@@ -1223,7 +1236,7 @@ checkVersions hsc_env mod_summary iface
; updateEps_ $ \eps -> eps { eps_is_boot = mod_deps }
; recomp <- checkList [checkModUsage this_pkg u | u <- mi_usages iface]
; return (recomp, Just iface)
}}}}}}
}}}}}}}}
where
this_pkg = thisPackage (hsc_dflags hsc_env)
-- This is a bit of a hack really
......@@ -1255,6 +1268,36 @@ checkFlagHash hsc_env iface = do
(text " Module flags have changed")
old_hash new_hash
-- | Check the optimisation flags haven't changed
checkOptimHash :: HscEnv -> ModIface -> IfG RecompileRequired
checkOptimHash hsc_env iface = do
let old_hash = mi_opt_hash iface
new_hash <- liftIO $ fingerprintOptFlags (hsc_dflags hsc_env)
putNameLiterally
if | old_hash == new_hash
-> up_to_date (text "Optimisation flags unchanged")
| gopt Opt_IgnoreOptimChanges (hsc_dflags hsc_env)
-> up_to_date (text "Optimisation flags changed; ignoring")
| otherwise
-> out_of_date_hash "Optimisation flags changed"
(text " Optimisation flags have changed")
old_hash new_hash
-- | Check the HPC flags haven't changed
checkHpcHash :: HscEnv -> ModIface -> IfG RecompileRequired
checkHpcHash hsc_env iface = do
let old_hash = mi_hpc_hash iface
new_hash <- liftIO $ fingerprintHpcFlags (hsc_dflags hsc_env)
putNameLiterally
if | old_hash == new_hash
-> up_to_date (text "HPC flags unchanged")
| gopt Opt_IgnoreHpcChanges (hsc_dflags hsc_env)
-> up_to_date (text "HPC flags changed; ignoring")
| otherwise
-> out_of_date_hash "HPC flags changed"
(text " HPC flags have changed")
old_hash new_hash
-- Check that the set of signatures we are merging in match.
-- If the -unit-id flags change, this can change too.
checkMergedSignatures :: ModSummary -> ModIface -> IfG RecompileRequired
......
......@@ -60,6 +60,7 @@ module DynFlags (
makeDynFlagsConsistent,
shouldUseColor,
positionIndependent,
optimisationFlags,
Way(..), mkBuildTag, wayRTSOnly, addWay', updateWays,
wayGeneralFlags, wayUnsetGeneralFlags,
......@@ -400,6 +401,7 @@ data DumpFlag
| Opt_D_no_debug_output
deriving (Eq, Show, Enum)
-- | Enumerates the simple on-or-off dynamic flags
data GeneralFlag
-- See Note [Updating flag description in the User's Guide]
......@@ -478,6 +480,10 @@ data GeneralFlag
| Opt_AlignmentSanitisation
| Opt_CatchBottoms
-- PreInlining is on by default. The option is there just to see how
-- bad things get if you turn it off!
| Opt_SimplPreInlining
-- Interface files
| Opt_IgnoreInterfacePragmas
| Opt_OmitInterfacePragmas
......@@ -491,6 +497,8 @@ data GeneralFlag
-- misc opts
| Opt_Pp
| Opt_ForceRecomp
| Opt_IgnoreOptimChanges
| Opt_IgnoreHpcChanges
| Opt_ExcessPrecision
| Opt_EagerBlackHoling
| Opt_NoHsMain
......@@ -535,10 +543,6 @@ data GeneralFlag
| Opt_VersionMacros
| Opt_WholeArchiveHsLibs
-- PreInlining is on by default. The option is there just to see how
-- bad things get if you turn it off!
| Opt_SimplPreInlining
-- output style opts
| Opt_ErrorSpans -- Include full span info in error messages,
-- instead of just the start position.
......@@ -593,6 +597,65 @@ data GeneralFlag
| Opt_G_NoOptCoercion
deriving (Eq, Show, Enum)
-- Check whether a flag should be considered an "optimisation flag"
-- for purposes of recompilation avoidance (see
-- Note [Ignoring some flag changes] in FlagChecker). Being listed here is
-- not a guarantee that the flag has no other effect. We could, and
-- perhaps should, separate out the flags that have some minor impact on
-- program semantics and/or error behavior (e.g., assertions), but
-- then we'd need to go to extra trouble (and an additional flag)
-- to allow users to ignore the optimisation level even though that
-- means ignoring some change.
optimisationFlags :: EnumSet GeneralFlag
optimisationFlags = EnumSet.fromList
[ Opt_CallArity
, Opt_Strictness
, Opt_LateDmdAnal
, Opt_KillAbsence
, Opt_KillOneShot
, Opt_FullLaziness
, Opt_FloatIn
, Opt_Specialise
, Opt_SpecialiseAggressively
, Opt_CrossModuleSpecialise
, Opt_StaticArgumentTransformation
, Opt_CSE
, Opt_StgCSE
, Opt_LiberateCase
, Opt_SpecConstr
, Opt_SpecConstrKeen
, Opt_DoLambdaEtaExpansion
, Opt_IgnoreAsserts
, Opt_DoEtaReduction
, Opt_CaseMerge
, Opt_CaseFolding
, Opt_UnboxStrictFields
, Opt_UnboxSmallStrictFields
, Opt_DictsCheap
, Opt_EnableRewriteRules
, Opt_Vectorise
, Opt_VectorisationAvoidance
, Opt_RegsGraph
, Opt_RegsIterative
, Opt_PedanticBottoms
, Opt_LlvmTBAA
, Opt_LlvmPassVectorsInRegisters
, Opt_LlvmFillUndefWithGarbage
, Opt_IrrefutableTuples
, Opt_CmmSink
, Opt_CmmElimCommonBlocks
, Opt_OmitYields
, Opt_FunToThunk
, Opt_DictsStrict
, Opt_DmdTxDictSel
, Opt_Loopification
, Opt_CprAnal
, Opt_WorkerWrapper
, Opt_SolveConstantDicts
, Opt_CatchBottoms
, Opt_IgnoreAsserts
]
-- | Used when outputting warnings: if a reason is given, it is
-- displayed. If a warning isn't controlled by a flag, this is made
-- explicit at the point of use.
......@@ -3754,6 +3817,8 @@ fFlagsDeps = [
flagSpec "flat-cache" Opt_FlatCache,
flagSpec "float-in" Opt_FloatIn,
flagSpec "force-recomp" Opt_ForceRecomp,
flagSpec "ignore-optim-changes" Opt_IgnoreOptimChanges,
flagSpec "ignore-hpc-changes" Opt_IgnoreHpcChanges,
flagSpec "full-laziness" Opt_FullLaziness,
flagSpec "fun-to-thunk" Opt_FunToThunk,
flagSpec "gen-manifest" Opt_GenManifest,
......
......@@ -857,7 +857,10 @@ data ModIface
mi_iface_hash :: !Fingerprint, -- ^ Hash of the whole interface
mi_mod_hash :: !Fingerprint, -- ^ Hash of the ABI only
mi_flag_hash :: !Fingerprint, -- ^ Hash of the important flags
-- used when compiling this module
-- 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_orphan :: !WhetherHasOrphans, -- ^ Whether this module has orphans
mi_finsts :: !WhetherHasFamInst,
......@@ -1018,6 +1021,8 @@ instance Binary ModIface where
mi_iface_hash= iface_hash,
mi_mod_hash = mod_hash,
mi_flag_hash = flag_hash,
mi_opt_hash = opt_hash,
mi_hpc_hash = hpc_hash,
mi_orphan = orphan,
mi_finsts = hasFamInsts,
mi_deps = deps,
......@@ -1044,6 +1049,8 @@ instance Binary ModIface where
put_ bh iface_hash
put_ bh mod_hash
put_ bh flag_hash
put_ bh opt_hash
put_ bh hpc_hash
put_ bh orphan
put_ bh hasFamInsts
lazyPut bh deps
......@@ -1072,6 +1079,8 @@ instance Binary ModIface where
iface_hash <- get bh
mod_hash <- get bh
flag_hash <- get bh
opt_hash <- get bh
hpc_hash <- get bh
orphan <- get bh
hasFamInsts <- get bh
deps <- lazyGet bh
......@@ -1099,6 +1108,8 @@ instance Binary ModIface where
mi_iface_hash = iface_hash,
mi_mod_hash = mod_hash,
mi_flag_hash = flag_hash,
mi_opt_hash = opt_hash,
mi_hpc_hash = hpc_hash,
mi_orphan = orphan,
mi_finsts = hasFamInsts,
mi_deps = deps,
......@@ -1136,6 +1147,8 @@ emptyModIface mod
mi_iface_hash = fingerprint0,
mi_mod_hash = fingerprint0,
mi_flag_hash = fingerprint0,
mi_opt_hash = fingerprint0,
mi_hpc_hash = fingerprint0,
mi_orphan = False,
mi_finsts = False,
mi_hsc_src = HsSrcFile,
......
......@@ -256,6 +256,12 @@ Compiler
- Lots of other bugs. See `Trac <https://ghc.haskell.org/trac/ghc/query?status=closed&milestone=8.4.1&col=id&col=summary&col=status&col=type&col=priority&col=milestone&col=component&order=priority>`_
for a complete list.
- New flags :ghc-flag:`-fignore-optim-changes` and
:ghc-flag:`-fignore-hpc-changes` allow GHC to reuse previously compiled
modules even if they were compiled with different optimisation or HPC
flags. These options are enabled by default by :ghc-flag:`--interactive`.
See :ghc-ticket:`13604`
Runtime system
~~~~~~~~~~~~~~
......
......@@ -542,6 +542,23 @@ The recompilation checker
existing ``.o`` file in place, if it can be determined that the
module does not need to be recompiled.
.. ghc-flag:: -fignore-optim-changes
:shortdesc: Do not recompile modules just to match changes to
optimisation flags. This is especially useful for avoiding
recompilation when using GHCi, and is enabled by default for
GHCi.
:type: dynamic
:reverse: -fno-ignore-optim-changes
:category: recompilation
.. ghc-flag:: -fignore-hpc-changes
:shortdesc: Do not recompile modules just to match changes to
HPC flags. This is especially useful for avoiding recompilation
when using GHCi, and is enabled by default for GHCi.
:type: dynamic
:reverse: -fno-ignore-hpc-changes
:category: recompilation
In the olden days, GHC compared the newly-generated ``.hi`` file with
the previous version; if they were identical, it left the old one alone
and didn't change its modification date. In consequence, importers of a
......
......@@ -179,10 +179,16 @@ main' postLoadMode dflags0 args flagWarnings = do
-- can be overriden from the command-line
-- XXX: this should really be in the interactive DynFlags, but
-- we don't set that until later in interactiveUI
dflags2 | DoInteractive <- postLoadMode = imp_qual_enabled
| DoEval _ <- postLoadMode = imp_qual_enabled
-- We also set -fignore-optim-changes and -fignore-hpc-changes,
-- which are program-level options. Again, this doesn't really
-- feel like the right place to handle this, but we don't have
-- a great story for the moment.
dflags2 | DoInteractive <- postLoadMode = def_ghci_flags
| DoEval _ <- postLoadMode = def_ghci_flags
| otherwise = dflags1
where imp_qual_enabled = dflags1 `gopt_set` Opt_ImplicitImportQualified
where def_ghci_flags = dflags1 `gopt_set` Opt_ImplicitImportQualified
`gopt_set` Opt_IgnoreOptimChanges
`gopt_set` Opt_IgnoreHpcChanges
-- The rest of the arguments are "dynamic"
-- Leftover ones are presumably files
......
......@@ -637,6 +637,20 @@ T10923:
# should NOT output "compilation is NOT required"
"$(TEST_HC)" $(TEST_HC_OPTS) -v1 -O -c T10923.hs
.PHONY: T13604
T13604:
$(RM) -rf T13604.o T13604.hi
"$(TEST_HC)" $(TEST_HC_OPTS) -v1 -O0 -c T13604.hs
# SHOULD output "compilation is NOT required"
"$(TEST_HC)" $(TEST_HC_OPTS) -v1 -O -c -fignore-optim-changes T13604.hs
.PHONY: T13604a
T13604a:
$(RM) -rf T13604a.o T13604a.hi
"$(TEST_HC)" $(TEST_HC_OPTS) -v1 -O0 -c -fhpc T13604a.hs
# SHOULD output "compilation is NOT required"
"$(TEST_HC)" $(TEST_HC_OPTS) -v1 -O0 -c -fignore-hpc-changes T13604a.hs
.PHONY: T12955
T12955:
! "$(TEST_HC)" $(TEST_HC_OPTS) --make T12955
......
compilation IS NOT required
compilation IS NOT required
......@@ -5,11 +5,11 @@ main: Assertion failed
CallStack (from HasCallStack):
assert, called at main.hs:3:8 in main:Main
With -fignore-asserts
[1 of 1] Compiling Main ( main.hs, main.o ) [flags changed]
[1 of 1] Compiling Main ( main.hs, main.o ) [Optimisation flags changed]
Linking main ...
OK
Without -fignore-asserts
[1 of 1] Compiling Main ( main.hs, main.o ) [flags changed]
[1 of 1] Compiling Main ( main.hs, main.o ) [Optimisation flags changed]
Linking main ...
main: Assertion failed
CallStack (from HasCallStack):
......
......@@ -271,3 +271,5 @@ test('T12955', normal, run_command, ['$MAKE -s --no-print-directory T12955'])
test('T12971', ignore_stdout, run_command, ['$MAKE -s --no-print-directory T12971'])
test('json', normal, compile_fail, ['-ddump-json'])
test('json2', normal, compile, ['-ddump-types -ddump-json'])
test('T13604', [], run_command, ['$MAKE -s --no-print-directory T13604'])
test('T13604a', [], run_command, ['$MAKE -s --no-print-directory T13604a'])
......@@ -6,6 +6,8 @@ with the following modifiers:
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
-fno-diagnostics-show-caret
-fignore-optim-changes
-fignore-hpc-changes
-fno-ghci-history
-fimplicit-import-qualified
-fshow-warning-groups
......@@ -22,6 +24,8 @@ with the following modifiers:
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
-fno-diagnostics-show-caret
-fignore-optim-changes
-fignore-hpc-changes
-fno-ghci-history
-fimplicit-import-qualified
-fshow-warning-groups
......@@ -37,6 +41,8 @@ with the following modifiers:
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
-fno-diagnostics-show-caret
-fignore-optim-changes
-fignore-hpc-changes
-fno-ghci-history
-fimplicit-import-qualified
-fshow-warning-groups
......@@ -54,6 +60,8 @@ with the following modifiers:
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
-fno-diagnostics-show-caret
-fignore-optim-changes
-fignore-hpc-changes
-fno-ghci-history
-fimplicit-import-qualified
-fshow-warning-groups
......
......@@ -7,6 +7,8 @@ with the following modifiers:
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
-fno-diagnostics-show-caret
-fignore-optim-changes
-fignore-hpc-changes
-fno-ghci-history
-fimplicit-import-qualified
-fshow-warning-groups
......
......@@ -6,6 +6,8 @@ with the following modifiers:
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
-fno-diagnostics-show-caret
-fignore-optim-changes
-fignore-hpc-changes
-fno-ghci-history
-fimplicit-import-qualified
-fshow-warning-groups
......@@ -22,6 +24,8 @@ with the following modifiers:
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
-fno-diagnostics-show-caret
-fignore-optim-changes
-fignore-hpc-changes
-fno-ghci-history
-fimplicit-import-qualified
-fshow-warning-groups
......@@ -37,6 +41,8 @@ with the following modifiers:
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
-fno-diagnostics-show-caret
-fignore-optim-changes
-fignore-hpc-changes
-fno-ghci-history
-fimplicit-import-qualified
-fshow-warning-groups
......@@ -54,6 +60,8 @@ with the following modifiers:
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
-fno-diagnostics-show-caret
-fignore-optim-changes
-fignore-hpc-changes
-fno-ghci-history
-fimplicit-import-qualified
-fshow-warning-groups
......
[1 of 1] Compiling T11798 ( T11798.hs, T11798.o )
[1 of 1] Compiling T11798 ( T11798.hs, T11798.o ) [flags changed]
[1 of 1] Compiling T11798 ( T11798.hs, T11798.o ) [HPC flags changed]
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment