Skip to content

INLINABLE pragma affects optimization choices when function body is inlined

I am using GHC 9.4.2 on x86_64 Ubuntu.

The attached example demonstrates that adding INLINABLE to a function can cause it to optimize worse than it would with no annotation at all. Interestingly, a full INLINE pragma restores the original performance.

Looking at the generated core, it does appear that all three cases are being inlined, yet for some reason the INLINABLE case doesn't optimize as well as the unannotated or INLINE cases do (there appears to be an extra jump in the loop).

The example code can be found here inline-anomalies.tar.gz, and the performance difference can be observed by using cabal build and time on the resulting binary. I've inlined the code here for quick eyeballing (EDIT: compiling with -fdicts-strict is necessary to reproduce):

Lib.hs

module Lib where

import Control.Monad.ST
import Data.Foldable
import qualified Data.Vector.Primitive.Mutable as MPR

-- {-# INLINABLE go #-}
go :: (MPR.Prim a , Integral a , Foldable f) => f a -> a
go xs = runST do
  mv <- MPR.new 1

  for_ xs \i -> do
    MPR.unsafeRead mv 0 >>= \v -> MPR.unsafeWrite mv 0 (v + i)
  
  MPR.unsafeRead mv 0

Main.hs

module Main where

import Lib


data MyList a = MyList [ a ] Int
  deriving Functor

instance Foldable MyList where
  {-# INLINE foldr #-}
  foldr f z (MyList xs _) = foldr f z xs
  {-# INLINE length #-}
  length (MyList _ l) = l


main :: IO ()
main = print do
  let c :: Int
      c = 1000000000
  go (MyList [ 1 .. c ] c)
Edited by Identical Snowflake
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information