Cheng Shao pushed to branch wip/o0-fmax-simplifier-iterations-2 at Glasgow Haskell Compiler / GHC
Commits:
64b268d1 by Cheng Shao at 2026-06-04T11:02:37+00:00
compiler: set -fmax-simplifier-iterations=2 for -O0
This patch sets `-fmax-simplifier-iterations=2` as the new default
under `-O0` for faster compilation of unoptimized code. See added note
for details. Closes #27326.
Co-authored-by: Codex
- - - - -
4 changed files:
- + changelog.d/ghc-o0-fmax-simplifier-iterations-2
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Session.hs
- docs/users_guide/using-optimisation.rst
Changes:
=====================================
changelog.d/ghc-o0-fmax-simplifier-iterations-2
=====================================
@@ -0,0 +1,13 @@
+section: compiler
+synopsis: Set ``-fmax-simplifier-iterations=2`` for ``-O0``
+issues: #27326
+mrs: !16136
+
+description: {
+ The maximum number of simplifier iterations
+ (``-fmax-simplifier-iterations``) is now tied to the optimisation
+ level: at most 2 iterations at ``-O0`` (previously 4) and still 4 at
+ ``-O1``/``-O2``. This speeds up unoptimised compilation. An explicit
+ ``-fmax-simplifier-iterations=⟨n⟩`` still overrides the default, as
+ long as it appears after the ``-O`` flag on the command line.
+}
=====================================
compiler/GHC/Driver/DynFlags.hs
=====================================
@@ -576,7 +576,8 @@ defaultDynFlags mySettings =
verbosity = 0,
debugLevel = 0,
simplPhases = 2,
- maxSimplIterations = 4,
+ maxSimplIterations = 2, -- The -O0 default. 'updOptLevel' raises this to 4 at -O1/-O2. See
+ -- Note [Simplifier iterations and the optimisation level].
ruleCheck = Nothing,
binBlobThreshold = Just 500000, -- 500K is a good default (see #16190)
maxRelevantBinds = Just 6,
=====================================
compiler/GHC/Driver/Session.hs
=====================================
@@ -786,15 +786,45 @@ setInteractivePrint f d = d { interactivePrint = Just f}
-----------------------------------------------------------------------------
-- Setting the optimisation level
+{- Note [Simplifier iterations and the optimisation level]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+At -O0 the only Core-to-Core pass that runs is the final simplifier pass (see
+'GHC.Core.Opt.Pipeline.getCoreToDo'); every optimisation-specific pass is gated
+off. We therefore want as few simplifier iterations as possible at -O0, since
+the later iterations only chase optimisation opportunities the user opted out of
+by choosing -O0.
+
+We cannot drop to a *single* iteration, though. The final simplifier pass is
+still load-bearing for correctness: a single iteration can leave a recursive
+superclass dictionary with an unreduced superclass selector applied to it, which
+then builds an infinite dictionary tower and diverges (<<loop>> at -O, OOM at
+-O0). See #21973 and test T21973b. The fix needs (at least) two iterations: the
+first inlines the dictionary functions to expose a constructor, and the second
+reduces the superclass selector against it, making the recursive dictionary
+dead. Empirically two iterations suffice across the known reproducers; the
+simplifier offers no proof of a fixed bound, so we keep a small margin rather
+than the bare minimum.
+
+So we tie 'maxSimplIterations' to the optimisation level: 2 iterations at -O0
+and 4 at -O1/-O2. This is a noticeable win for unoptimised compilation, and most
+noticeable for the byte-code interpreter (GHCi), which defaults to -O0 and codegen
+does less work compared to other backends.
+
+An explicit -fmax-simplifier-iterations=⟨n⟩ still overrides this, as long as it
+appears after the -O flag on the command line (the usual flag-ordering rule for
+options that -O adjusts).
+-}
+
updOptLevelChanged :: Int -> DynFlags -> (DynFlags, Bool)
-- ^ Sets the 'DynFlags' to be appropriate to the optimisation level and signals if any changes took place
updOptLevelChanged n dfs
- = (dfs3, changed1 || changed2 || changed3)
+ = (dfs4, changed1 || changed2 || changed3 || changed4)
where
final_n = max 0 (min 2 n) -- Clamp to 0 <= n <= 2
(dfs1, changed1) = foldr unset (dfs , False) remove_gopts
(dfs2, changed2) = foldr set (dfs1, False) extra_gopts
(dfs3, changed3) = setLlvmOptLevel dfs2
+ (dfs4, changed4) = setSimplIterations dfs3
extra_gopts = [ f | (ns,f) <- optLevelFlags, final_n `elem` ns ]
remove_gopts = [ f | (ns,f) <- optLevelFlags, final_n `notElem` ns ]
@@ -814,6 +844,14 @@ updOptLevelChanged n dfs
| llvmOptLevel dfs /= llvm_n = (dfs{ llvmOptLevel = llvm_n }, True)
| otherwise = (dfs, False)
+ -- See Note [Simplifier iterations and the optimisation level]
+ simpl_iters = if final_n == 0 then 2 else 4
+
+ setSimplIterations dfs
+ | maxSimplIterations dfs /= simpl_iters =
+ (dfs{ maxSimplIterations = simpl_iters }, True)
+ | otherwise = (dfs, False)
+
updOptLevel :: Int -> DynFlags -> DynFlags
-- ^ Sets the 'DynFlags' to be appropriate to the optimisation level
updOptLevel n = fst . updOptLevelChanged n
=====================================
docs/users_guide/using-optimisation.rst
=====================================
@@ -914,11 +914,12 @@ as such you shouldn't need to set any of them explicitly. A flag
:ghc-flag:`-Wincomplete-patterns` and :ghc-flag:`-Wincomplete-uni-patterns`.
.. ghc-flag:: -fmax-simplifier-iterations=⟨n⟩
- :shortdesc: *default: 4.* Set the max iterations for the simplifier.
+ :shortdesc: *default: 2 at :ghc-flag:`-O0`, 4 otherwise.* Set the max iterations
+ for the simplifier.
:type: dynamic
:category:
- :default: 4
+ :default: 2 at :ghc-flag:`-O0`, 4 at :ghc-flag:`-O1` and :ghc-flag:`-O2`
Sets the maximal number of iterations for the simplifier.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/64b268d1c71bcba12d1cd47d90ec1120...
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/64b268d1c71bcba12d1cd47d90ec1120...
You're receiving this email because of your account on gitlab.haskell.org.