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
.