Skip to content

`-fno-omit-interface-pragmas` causes an unsilenceable warning about optimizations in GHCi

Summary

Calling ghci with -fno-omit-interface-pragmas causes GHCi to issue a warning.

If -Werror is also enabled, then ghci exits entirely without booting.

It is possible to work around this issue by providing -O0 explicitly, but the call to -O0 must be after - calling it before has no effect.

Steps to reproduce

$ ghci -fno-omit-interface-pragmas

when making flags consistent: warning:
    Optimization flags conflict with --interactive; optimization flags ignored.
GHCi, version 9.4.2: https://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /home/matt/.dotfiles/haskell/ghci.symlink
λ> 

To work around, you must provide -O0 explicitly.

$ ghci -fno-omit-interface-pragmas -O0
GHCi, version 9.4.2: https://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /home/matt/.dotfiles/haskell/ghci.symlink
λ> 

Calling with -Werror also causes a problem:

$ ghci -fno-omit-interface-pragmas -Werror

when making flags consistent: error: [-Werror]
    Optimization flags conflict with --interactive; optimization flags ignored.
$

The work around does not work if -O0 is provided before -fno-omit-interface-pragmas:

$ ghci -O0 -fno-omit-interface-pragmas -Werror

when making flags consistent: error: [-Werror]
    Optimization flags conflict with --interactive; optimization flags ignored.
$

Expected behavior

I'd expect the order of providing -O0 to not matter. I'd prefer a clearer error message, as well - calling cabal repl --verbose and seeing a command like ghc --interactive -O0 (lots of stuff here ...) and then it complains about "Optimization flags conflict with --interactive" is really hard to diagnose.

There are a few upstream fixes that would be nice here:

  1. Give the warning a name so I can write -Wno-optimizer-flags-conflict or -Wwarn=optimizer-flags-conflict and prevent it from breaking in the presence of -Werror

  2. The warning appears to come from this part of the code:

-- compiler/GHC/Driver/Session.hs

 | backendForcesOptimization0 (backend dflags)
 , let (dflags', changed) = updOptLevelChanged 0 dflags
 , changed
    = loop dflags' ("Optimization flags are incompatible with the " ++
                   backendDescription (backend dflags) ++
                                          "; optimization flags ignored.")

updOptLevelChanged is here:

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)
  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

   extra_gopts  = [ f | (ns,f) <- optLevelFlags, final_n `elem` ns ]
   remove_gopts = [ f | (ns,f) <- optLevelFlags, final_n `notElem` ns ]

   set f (dfs, changed)
     | gopt f dfs = (dfs, changed)
     | otherwise = (gopt_set dfs f, True)

   unset f (dfs, changed)
     | not (gopt f dfs) = (dfs, changed)
     | otherwise = (gopt_unset dfs f, True)

   setLlvmOptLevel dfs
     | llvmOptLevel dfs /= final_n = (dfs{ llvmOptLevel = final_n }, True)
     | otherwise = (dfs, False)

optLevelFlags has this entry:

    , ([0],     Opt_OmitInterfacePragmas)

Opt_OmitInterfacePragmas is included in the default [GeneralFlag] since it has a 0 - O0 implies the flag.

defaultFlags :: Settings -> [GeneralFlag]
defaultFlags settings
  {- snip -}
    ++ [f | (ns,f) <- optLevelFlags, 0 `elem` ns]
             -- The default -O0 options
  {- snip -}

So, -fno-omit-interface-pragmas causes GHC to create the EnumSet containing the Opt_OmitInterfacePragmas. Then, extra_gopts looks at optLevelFlags and pulls all the flags with elem 0 ns - this gets Opt_OmitInterfacePragmas. When folding over the options, set is called, which checks if gopt Opt_OmitInterfacePragmas dfs is set. -fno-omit-interface-pragmas was passed, so this returns False, and set flips the Changed switch to True and does a gopt_set dfs Opt_OmitInterfacePragmas.

Condensing:

  1. GHC has -fomit-interface-pragmas on by default
  2. Calling -fno-omit-interface-pragmas removes that setting from the DynFlags
  3. When checking for compatibility with Interactive (or forcing optimization 0 in HEAD), it forces -O0 and sees if anything changes.
  4. Because forcing to -O0 re-enables the flag, this counts as a change, and GHC issues a warning.

🤔

Reasoning

We use -fno-omit-interface-pragmas so that we can get build warnings on irrelevant pragmas. The specific case that came up was:

data X = X {-# UNBOX #-} !Int

which passed CI fine (building with -O0) but failed when deploying (due to -Werror -O2). So we have -fno-omit-interface-pragmas even when building locally with -O0.

However, for the developers that have done cabal configure --ghc-options="-Werror", they can't get into a cabal repl or ghcid.

Environment

  • GHC version used: 9.4.2, 9.4.3

Optional:

  • Operating System:
  • System Architecture:
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information