Worker/Wrapper inhibits cross-module specialisation
Consider the following setup:
module A where
f :: Ord a => [a] -> ([a], a)
f xs = (ys, maximum ys)
where
ys = reverse . reverse . reverse . reverse . reverse . reverse $ xs
{-# INLINABLE f #-}
module B where
import A
{-# SPECIALISE f :: [Int] -> ([Int], Int) #-}
And compile with optimisations: ghc -O2 B.hs
. You'll get the following warning:
[1 of 2] Compiling A ( A.hs, A.o )
[2 of 2] Compiling B ( B.hs, B.o )
B.hs:7:1: warning:
You cannot SPECIALISE ‘f’
because its definition has no INLINE/INLINABLE pragma
(or its defining module ‘A’ was compiled without -O)
|
7 | {-# SPECIALISE f :: [Int] -> ([Int], Int) #-}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
And indeed f
won't be specialised.
The reason is
Note [Worker-wrapper for INLINABLE functions]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If we have
{-# INLINABLE f #-}
f :: Ord a => [a] -> Int -> a
f x y = ....f....
where f is strict in y, we might get a more efficient loop by w/w'ing
f. But that would make a new unfolding which would overwrite the old
one! So the function would no longer be INLINABLE, and in particular
will not be specialised at call sites in other modules.
This comes up in practice (#6056).
Solution: do the w/w for strictness analysis, but transfer the Stable
unfolding to the *worker*. So we will get something like this:
{-# INLINE[0] f #-}
f :: Ord a => [a] -> Int -> a
f d x y = case y of I# y' -> fw d x y'
{-# INLINABLE[0] fw #-}
fw :: Ord a => [a] -> Int# -> a
fw d x y' = let y = I# y' in ...f...
How do we "transfer the unfolding"? Easy: by using the old one, wrapped
in work_fn! See GHC.Core.Unfold.mkWorkerUnfolding.
But obviously, the wrapper being INLINE means we won't specialise it in B
.
What should happen (I think) is that we should specialise through the INLINE wrapper.