Boxity analysis should not consider binders used in arguments to bottoming functions as boxed-uses.
Consider this code:
{-# LANGUAGE NoImplicitPrelude, MagicHash, UnboxedTuples,
ScopedTypeVariables, BangPatterns #-}
{-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-unfoldings #-}
module M where
import GHC.Exts
import Data.Bits ( Bits, (.&.) )
import Data.Maybe
import Foreign.C.Types ( CSize(..) )
import Foreign.Storable ( Storable(sizeOf,alignment) )
import Foreign.ForeignPtr ( FinalizerPtr )
import GHC.IO.Exception
import GHC.Num
import GHC.Real
import GHC.Show
import GHC.Ptr
import GHC.Base
allocaBytesAligned :: Int -> Int -> b -> IO b
allocaBytesAligned !_size !align !_action
| not $ isPowerOfTwo align =
ioError $
IOError Nothing InvalidArgument
"allocaBytesAligned"
("alignment (="++noinline show align++") must be a power of two!")
Nothing Nothing
where
isPowerOfTwo :: (Bits i, Integral i) => i -> Bool
isPowerOfTwo x = x .&. (x-1) == 0
allocaBytesAligned !size !align !action =
return action
-- allocaBytesAlignedAndUnchecked size align action
{-# INLINABLE allocaBytesAligned #-}
We float out the rhs of the error branch
allocaBytesAligned :: Int -> Int -> (Ptr a -> IO b) -> IO b
allocaBytesAligned !_size !align !_action
| not $ isPowerOfTwo align = lvl_error align
| otherwise = stuff
lvl_error align =
ioError $ IOError Nothing InvalidArgument
"allocaBytesAligned"
("alignment (="++show align++") must be a power of two!")
Nothing Nothing
lvl_error
is lazy in align
so which prevents it from being W/Wed. This cascades into allocaBytesAligned
not being W/W'ed as well and bad things happen.
If this goes wrong or not depends on subtle inlining behavior. If we inling show
before we run float out we get $wshowInt (case align of I# x -> x)
which cancels out with the early case originating from the bang pattern. Resulting in lvl_error
abstracting over an unboxed Int# argument and all is well.
But i the inlining is delayed or doesn't happen we abstract over the boxed int and we never recover from that.
Besides the boxity change if we float out expr[fvs: y,z]
then if y/z are evaluated at the original call site
instead of generating let x y z = expr in ...
we should generate let x y z = (case y of y' -> case z of z' -> expr) in ...
This way it doesn't matter when exactly show inlines as long as it happens before W/W fires. As we will transform:
lvl_error align =
ioError $ IOError Nothing InvalidArgument
"allocaBytesAligned"
("alignment (="++show align++") must be a power of two!")
Nothing Nothing
into
lvl_error align =
case align of I# align' ->
ioError $ IOError Nothing InvalidArgument
"allocaBytesAligned"
("alignment (="++show align++") must be a power of two!")
Nothing Nothing
then at some point show inlines giving us:
lvl_error align =
case align of I# align' ->
ioError $ IOError Nothing InvalidArgument
"allocaBytesAligned"
("alignment (="++$wshowInt align'++") must be a power of two!")
Nothing Nothing
Which we can W/W, which allows allocaBytesAligned
to WW it's align argument as well.