Inlining of constant fails when both cross-module and recursive
When an inline recursive function is applied to a constant, that application may reduce if it is in the same module, but will not reduce when in a different module. (Naturally, this harms the ability to split a program into modules while retaining efficiency.)
For example, consider the following files:
T1.hs:
module T1 where
data IntList = Nil | Cons Int IntList
mapIntList :: (Int -> Int) -> IntList -> IntList
mapIntList f Nil = Nil
mapIntList f (Cons x xs) = Cons (f x) (mapIntList f xs)
{-# INLINE mapIntList #-}
mappedNil :: IntList
mappedNil = mapIntList id Nil
T2.hs:
module T2 where
data IntList = Nil | Cons Int IntList
mapIntList :: (Int -> Int) -> IntList -> IntList
mapIntList f Nil = Nil
mapIntList f (Cons x xs) = Cons (f x) (mapIntList f xs)
{-# INLINE mapIntList #-}
T3.hs:
module T3 where
import T2
mappedNil :: IntList
mappedNil = mapIntList id Nil
The program built from T1.hs should be equivalent to the one built from T2.hs and T3.hs; however, the core output from GHC 8.6.1 with -O2 differs significantly.
In the single-module case we obtain:
mappedNil = T1.Nil
Whereas in the two-module case we see:
mappedNil = mapIntList (id @ Int) T2.Nil
Recursion is relevant; the problem disappears if we make this change:
data IntList = Nil | Cons Int Int
mapIntList :: (Int -> Int) -> IntList -> IntList
mapIntList f Nil = Nil
mapIntList f (Cons x xs) = Cons (f x) (f xs)
{-# INLINE mapIntList #-}
Trac metadata
| Trac field | Value |
|---|---|
| Version | 8.6.1 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Compiler |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | |
| Operating system | |
| Architecture |