I was trying to find the source for a certain kind of inlining that happens in GHC, where a function that is passed as an argument to another function is inlined. For example, I may write a definition like the following (using my own List type to avoid rewrite rules):
data MyList a = Nil | Cons a (MyList a)
deriving (Show)
mapMyList :: (a -> b) -> MyList a -> MyList b
mapMyList f Nil = Nil
mapMyList f (Cons a rest) = Cons (f a) $ mapMyList f rest
followed by a call
fromList :: [a] -> MyList a
fromList = ...
main = do
print $ mapMyList (*2) $ fromList [1..5]
mapMyList is recursive, so it is not directly inlinable. However, in the generated Core, I see the following definition:
Rec {
-- RHS size: {terms: 16, types: 11, coercions: 0, joins: 0/0}
Main.main_$smapMyList [Occ=LoopBreaker] :: MyList Int -> MyList Int
[GblId, Arity=1, Str=<S,1*U>, Unf=OtherCon []]
Main.main_$smapMyList
= \ (sc_s2Rb :: MyList Int) ->
case sc_s2Rb of {
Nil -> Main.Nil @Int;
Cons a_aBe rest_aBf ->
Main.Cons
@Int
(case a_aBe of { GHC.Types.I# x_a24u ->
GHC.Types.I# (GHC.Prim.*# x_a24u 2#)
})
(Main.main_$smapMyList rest_aBf)
}
end Rec }
In particular, smapMyList no longer takes a function as a parameter, and the (* 2) is inlined here. I wanted to know which optimization pass is generating this code, and how it works?
I found this related issue, which seems to be asking for a way to guarantee this behavior using a SPECIALIZE pragma, which led me to believe that the specialization pass is doing this. However, when reading the documentation on specialization in GHC, it seems to be for specializing typeclass dictionaries, not function arguments (there are no typeclasses in my example).
I also looked at static argument transformation which seems related; for example the GHC source says this about the pass:
May be seen as removing invariants from loops: Arguments of recursive functions that do not change in recursive calls are removed from the recursion, which is done locally and only passes the arguments which effectively change.
However, I tried disabling both of these passes with -fno-static-argument-transformation -fno-specialise and found that this transformation still happens anyway.
My motivation for asking this is that I'm implementing this transformation in another language (Koka) so I'd like to understand how other languages do it.
Generated core (after disabling specialisation and static argument transformation)