Skip to content

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.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information