Skip to content

Inconsistent inline behavior across modules

A function f annotated as INLINE can be inlined or not inlined depending on whether the function making the call is in the same module or not, respectively. I find this counterintuitive, as there's nothing in the surface language that indicates the former should have any information unavailable to the latter.

I've attached a simple reproducer project to this post, but here are the key snippets for quick eyeballing:

Lib.hs

{-# LANGUAGE BangPatterns #-}

module Lib where

import Data.Int

{-# NOINLINE wrapperLib #-}
-- fast
wrapperLib :: Int64 -> Int64 -> Int64
wrapperLib a b = f a b

{-# INLINE f #-}
-- for Int64, this should reduce to 'x + y' - which amusingly, is slower than this 'f'!
f :: (Ord a , Num a) => a -> a -> a
f x y = do
  let a = 0
  let !b = 1
  let c = 2
  x + if a == b + 1
         then b + c
         else if c > a + 1
                 then y
                 else f (x - 1) (y - 1)

Main.hs

import Data.Int
import Data.List
import Lib

{-# NOINLINE wrapper #-}
-- slow, because 'f' is not inlined despite its annotation
wrapper :: Int64 -> Int64 -> Int64
wrapper a b = f a b

main :: IO ()
main = print $
  -- slow
  -- foldl' wrapper (0 :: Int64) [ 0 .. 1000000000 ]

  -- fast
  foldl' wrapperLib (0 :: Int64) [ 0 .. 1000000000 ]

To observe the difference, (un)comment fast/slow, compile with cabal build, and time the resulting binary.


There is a second anomaly as well: redefining f as f x y = x + y causes both cases to be slow, despite being a much simpler definition and what one would expect the original f would reduce to when the type Int64!

I am using GHC 9.4.2, on Ubuntu/x86_64, with -O2.

inline-anomalies.tar.gz

Edited by Sebastian Graf
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information