module Foreign.Marshal.Array.Guarded.Debug (
create,
alloca,
MutablePtr,
new,
withMutablePtr,
thaw,
thawInplace,
freeze,
freezeInplace,
) where
import qualified Foreign.Marshal.Array.Guarded.Plain as Plain
import Foreign.Marshal.Array
(mallocArray, allocaArray, pokeArray, copyArray, advancePtr)
import Foreign.Marshal.Alloc (free)
import Foreign.Storable (Storable, peekByteOff, sizeOf)
import Foreign.Concurrent (newForeignPtr, addForeignPtrFinalizer)
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import Foreign.Ptr (Ptr, castPtr)
import Control.Monad (when)
import Control.Applicative ((<$>))
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Foldable (for_)
import Data.Word (Word8)
import Text.Printf (printf)
create :: (Storable a) => Int -> (Ptr a -> IO b) -> IO (ForeignPtr a, b)
create = flip asTypeOf Plain.create $ \size f -> do
let border = 64
let fullSize = size + 2*border
ptrApre <- mallocArray fullSize
ptrsA@(_ptrApre, ptrA, _ptrApost) <- fillAll border size ptrApre
result <- f ptrA
checkAll border ptrsA
ptrB <- copyToNew size ptrA
fmap (flip (,) result) $ newForeignPtr ptrA $ do
verify size ptrA ptrB
trash fullSize ptrApre
free ptrApre
free ptrB
alloca :: (Storable a) => Int -> (Ptr a -> IO b) -> IO b
alloca = flip asTypeOf Plain.alloca $ \size f -> do
let border = 64
let fullSize = size + 2*border
allocaArray fullSize $ \ptrPre -> do
ptrs@(_ptrPre, ptr, _ptrPost) <- fillAll border size ptrPre
result <- f ptr
checkAll border ptrs
trash fullSize ptrPre
return result
data MutablePtr a =
MutablePtr {
_mutableSize :: !Int,
_mutableFPtr :: !(ForeignPtr a),
_mutableFrozenRef :: !(IORef Bool)
}
new :: (Storable a) => Int -> IO (MutablePtr a)
new size = do
let border = 64
let fullSize = size + 2*border
ptrApre <- mallocArray fullSize
ptrsA <- fillAll border size ptrApre
newMutablePtr size =<< newCheckedForeignPtr border fullSize ptrsA
withMutablePtr :: MutablePtr a -> (Ptr a -> IO b) -> IO b
withMutablePtr (MutablePtr _size fptr frozenRef) f = do
checkFrozen "withMutablePtr" frozenRef
withForeignPtr fptr f
thaw :: (Storable a) => Int -> ForeignPtr a -> IO (MutablePtr a)
thaw size fptrB = do
let border = 64
let fullSize = size + 2*border
ptrApre <- mallocArray fullSize
ptrsA@(_ptrApre, ptrA, _ptrApost) <- fillBorder border size ptrApre
withForeignPtr fptrB $ \ptrB -> copyArray ptrA ptrB size
newMutablePtr size =<< newCheckedForeignPtr border fullSize ptrsA
newCheckedForeignPtr ::
(Storable a) => Int -> Int -> (Ptr a, Ptr a, Ptr a) -> IO (ForeignPtr a)
newCheckedForeignPtr border fullSize ptrsA@(ptrApre, ptrA, _ptrApost) =
newForeignPtr ptrA $ do
checkAll border ptrsA
trash fullSize ptrApre
free ptrApre
thawInplace :: (Storable a) => Int -> ForeignPtr a -> IO (MutablePtr a)
thawInplace = newMutablePtr
freeze :: (Storable a) => Int -> MutablePtr a -> IO (ForeignPtr a)
freeze freezeSize (MutablePtr size fptr frozenRef) = do
checkSize freezeSize size
checkFrozen "freeze" frozenRef
ptrA <- withForeignPtr fptr $ copyToNew size
ptrB <- withForeignPtr fptr $ copyToNew size
newForeignPtr ptrA $ do
verify size ptrA ptrB
trash size ptrA
free ptrA
free ptrB
freezeInplace :: (Storable a) => Int -> MutablePtr a -> IO (ForeignPtr a)
freezeInplace freezeSize (MutablePtr size fptr frozenRef) = do
checkSize freezeSize size
checkFrozen "freezeInplace" frozenRef
writeIORef frozenRef True
withForeignPtr fptr $ \ptrA -> do
ptrB <- copyToNew size ptrA
addForeignPtrFinalizer fptr $ do
verify size ptrA ptrB
free ptrB
return fptr
newMutablePtr :: Int -> ForeignPtr a -> IO (MutablePtr a)
newMutablePtr size fptr = MutablePtr size fptr <$> newIORef False
checkSize :: Int -> Int -> IO ()
checkSize freezeSize size =
when (freezeSize/=size) $ error $
printf "allocation (%d) and freeze (%d) size differ" size freezeSize
checkFrozen :: String -> IORef Bool -> IO ()
checkFrozen name frozenRef =
flip when (error $ name ++ ": array already frozen")
=<< readIORef frozenRef
fillAll :: (Storable a) => Int -> Int -> Ptr a -> IO (Ptr a, Ptr a, Ptr a)
fillAll border size ptrPre = do
ptrs@(_ptrPre, ptr, _ptrPost) <- fillBorder border size ptrPre
fill ptr size [0xDE,0xAD,0xF0,0x0D]
return ptrs
fillBorder :: (Storable a) => Int -> Int -> Ptr a -> IO (Ptr a, Ptr a, Ptr a)
fillBorder border size ptrPre = do
let ptr = advancePtr ptrPre border
let ptrPost = advancePtr ptr size
fill ptrPre border [0xAB,0xAD,0xCA,0xFE]
fill ptrPost border [0xAB,0xAD,0xCA,0xFE]
return (ptrPre, ptr, ptrPost)
checkAll :: (Storable a) => Int -> (Ptr a, Ptr a, Ptr a) -> IO ()
checkAll border (ptrPre, _ptr, ptrPost) = do
check "leading" ptrPre border [0xAB,0xAD,0xCA,0xFE]
check "trailing" ptrPost border [0xAB,0xAD,0xCA,0xFE]
trash :: (Storable a) => Int -> Ptr a -> IO ()
trash fullSize ptrPre = fill ptrPre fullSize [0xDE,0xAD,0xBE,0xEF]
{-# INLINE fill #-}
fill :: (Storable a) => Ptr a -> Int -> [Word8] -> IO ()
fill ptr n bytes =
pokeArray (castPtr ptr) $ take (arraySize ptr n) $ cycle bytes
{-# INLINE check #-}
check :: (Storable a) => String -> Ptr a -> Int -> [Word8] -> IO ()
check name ptr n bytes =
for_ (take (arraySize ptr n) $ zip [0..] $ cycle bytes) $ \(i,b) -> do
a <- peekByteOff ptr i
when (a/=(b::Word8)) $
error $ "damaged " ++ name ++ " fence at position " ++ show i
copyToNew :: (Storable a) => Int -> Ptr a -> IO (Ptr a)
copyToNew size ptrA = do
ptrB <- mallocArray size
copyArray ptrB ptrA size
return ptrB
verify :: (Storable a) => Int -> Ptr a -> Ptr a -> IO ()
verify size ptrA ptrB =
for_ (take (arraySize ptrA size) [0..]) $ \i -> do
a <- peekByteOff ptrA i
b <- peekByteOff ptrB i
when (a/=(b::Word8)) $
error $ "immutable array was altered at byte position " ++ show i
arraySize :: (Storable a) => Ptr a -> Int -> Int
arraySize ptr n = arraySizeAux ptr n $ error "arraySize: undefined element"
arraySizeAux :: (Storable a) => Ptr a -> Int -> a -> Int
arraySizeAux _ n a = n * sizeOf a