Ben Millwood
Ben Millwood

Reputation: 7021

Do INLINE pragmas cause inlining even if optimizations are disabled?

GHC docs on INLINE pragmas:

GHC (with -O, as always) tries to inline (or “unfold”) functions/values that are “small enough,” [...]

The major effect of an INLINE pragma is to declare a function’s “cost” to be very low. The normal unfolding machinery will then be very keen to inline it. [...]

I take this to mean that without optimizations enabled, even {-# INLINE ... #-} functions / values will not be inlined, since the "normal unfolding machinery" will not even be run to notice the function's cost has been reduced.

Is that correct?

Upvotes: 1

Views: 246

Answers (2)

K. A. Buhr
K. A. Buhr

Reputation: 51259

The reason this happens is that -O0 still invokes a single pass of the "simplifier", which is the optimization pass responsible for, among other things, inlining.

You can't prevent the simplifier from being called, but you can set the number of simplifier iterations to zero, which should cause it to bail out before doing anything:

ghc -O0 -fmax-simplifier-iterations=0 -ddump-simpl

For your handleThing example, this prevents the inlining you've observed.

The inlining that occurs with -O0 is limited to what can be accomplished by inlining fully saturated calls with no eta expansion/reduction. This means that your example:

handleThing = print
{-# INLINE handleThing #-}
main = mapM_ handleThing [1..10]

will inline, but the variation:

handleThing x = print x
{-# INLINE handleThing #-}
main = mapM_ handleThing [1..10]

won't, since the call to handleThing in main is no longer fully saturated.

On the other hand, this will inline both number and handleThing:

number = 10
{-# INLINE number #-}
handleThing x = print x >> print x
{-# INLINE handleThing #-}
main = handleThing number

into main, resulting in:

x = 10
main = >> $fMonadIO (print $fShowInteger x) (print $fShowInteger x)

Here, "inlining" number involves the de-optimization of giving it an extra name and using that.

Upvotes: 4

Ben Millwood
Ben Millwood

Reputation: 7021

Short answer: INLINE pragmas can cause inlining, even with optimizations off.

I used the following Main.hs as a test case:

module Main where

handleThing :: Integer -> IO ()
handleThing = print
-- {-# INLINE handleThing #-}

main :: IO ()
main = do
  mapM_ handleThing [1..10]

ghc -ddump-simpl with the pragma removed contains this:

main :: IO ()
[GblId]
main
  = mapM_
      @[]
      @IO
      @Integer
      @()
      Data.Foldable.$fFoldable[]
      GHC.Base.$fMonadIO
      handleThing
      (enumFromTo @Integer GHC.Enum.$fEnumInteger 1 10)

ghc -ddump-simpl Main.hs with the pragma has this instead:

main :: IO ()
[GblId]
main
  = mapM_
      @[]
      @IO
      @Integer
      @()
      Data.Foldable.$fFoldable[]
      GHC.Base.$fMonadIO
      (print @Integer GHC.Show.$fShowInteger)
      (enumFromTo @Integer GHC.Enum.$fEnumInteger 1 10)

handleThing has been replaced by (print @Integer GHC.Show.$fShowInteger). Obviously not a dramatic change, but interesting that the inliner runs at all.

In both cases, ghc --show-iface Main.hi did not contain an unfolding, so (AIUI) handleThing would not be available for cross-module inlining.

Upvotes: 2

Related Questions