packages feed

atomic-primops-0.8.8: testing/Test.hs

{-# LANGUAGE MagicHash, UnboxedTuples, BangPatterns, ScopedTypeVariables, NamedFieldPuns, CPP #-}

module Test ( main,
              test_all_hammer_one ) where

-- | This test has three different modes which can be toggled via the
-- C preprocessor.  Any subset of the three may be activated.

import Control.Monad
-- import Control.Monad.ST (stToIO)
import Control.Exception (evaluate)
import Data.IORef
import Data.Int
import Data.Primitive.Array
import Data.Word
import qualified Data.Set as S
import Data.List ((\\))
import Text.Printf
import GHC.Conc
import GHC.STRef
import GHC.IORef (IORef(..))
#if MIN_VERSION_base(4,10,0)
import GHC.Stats (getRTSStats, RTSStats(..))
#else
import GHC.Stats (getGCStats, GCStats(..))
#endif
import System.Random (randomIO, randomRIO)
import Test.HUnit (Assertion, assertEqual, assertBool)
import Test.Framework  (defaultMain,testGroup,mutuallyExclusive)
import Test.Framework.Providers.HUnit (testCase)
import System.Mem (performGC)

----------------------------------------
import Data.Atomics as A

import qualified Issue28

import CommonTesting
import qualified Counter
import qualified Fetch

------------------------------------------------------------------------

expect_false_positive_on_GC :: Bool
expect_false_positive_on_GC = False

getGCCount :: IO Int64
getGCCount | expect_false_positive_on_GC =
#if MIN_VERSION_base(4,10,0)
               do RTSStats{gcs} <- getRTSStats
                  return (fromIntegral gcs)
#else
               do GCStats{numGcs} <- getGCStats
                  return numGcs
#endif
           | otherwise = return 0

main :: IO ()
main = do
       -- TEMP: Fixing this at four processors because it takes a REALLY long time at larger numbers:
       -- It does 248 test cases and takes 55s at -N16...
       -- numcap <- getNumProcessors
       let numcap = 4
       when (numCapabilities /= numcap) $ setNumCapabilities numcap

       defaultMain $
        -- Make these run sequentially (hopefully), so we don't interfere with
        -- concurrent tests. TODO I guess: figure out how to run tests that
        -- don't fork in parallel, but forking tests sequentially
        return $ mutuallyExclusive $ testGroup "All tests" $
         [ testCase "casTicket1"              case_casTicket1
         , testCase "issue28_standalone"      case_issue28_standalone
         , testCase "issue28_copied "         case_issue28_copied
         , testCase "create_and_read"         case_create_and_read
         , testCase "create_and_mutate"       case_create_and_mutate
         , testCase "create_and_mutate_twice" case_create_and_mutate_twice
         , testCase "n_threads_mutate"        case_n_threads_mutate
         , testCase "run_barriers"            case_run_barriers

         , testCase "test_succeed_once Int"   (test_succeed_once (0::Int))
         , testCase "test_succeed_once Int64" (test_succeed_once (0::Int64))
         , testCase "test_succeed_once Word32" (test_succeed_once (0::Word32))
         , testCase "test_succeed_once Word16" (test_succeed_once (0::Word16))
         , testCase "test_succeed_once Word8"  (test_succeed_once (0::Word8))
         ]
         ++
  --       all_hammerConfigs (0::Int64)
         -- Test several configurations of this one:
         [ testCase ("test_all_hammer_one_"++show threads++"_"++show iters ++":")
                    (test_all_hammer_one threads iters (0::Int))
         | threads <- [1 .. 2*numcap]
         , iters   <- [1, 10, 100, 1000, 10000, 100000, 500000]] ++
         [ testCase ("test_hammer_many_threads_1000_10000:")
                    (test_all_hammer_one 1000 10000 (0::Int)) ]  ++

         [ testCase "casmutarray1"            case_casmutarray1] ++
         [ testCase ("test_random_array_comm_"++show threads++"_"++show size++"_"++show iters ++":")
                    (test_random_array_comm threads size iters)
         | threads <- filter (>0) $ setify $
                      [1, numcap `quot` 2, numcap, 2*numcap]
         , size    <- [1, 10, 100]
         , iters   <- [10000]]

         ++ Counter.tests
         ++ Fetch.tests

setify :: [Int] -> [Int]
setify = S.toList . S.fromList

------------------------------------------------------------------------
{-# NOINLINE mynum #-}
mynum :: Int
mynum = 33

-- Expected output:
{---------------------------------------
    Perform a CAS within a MutableArray#
      1st try should succeed: (True,33)
    2nd should fail: (False,44)
    Printing array:
      33  33  33  44  33
    Done.
-}
case_casmutarray1 :: IO ()
case_casmutarray1 = do
 putStrLn "Perform a CAS within a MutableArray#"
 arr <- newArray 5 mynum

 writeArray arr 4 33
 putStrLn "Wrote array elements..."

 tick <- A.readArrayElem arr 4
 putStrLn$ "(Peeking at array gave: "++show (peekTicket tick)++")"

 (res1,_tick2) <- A.casArrayElem arr 4 tick 44
 (res2,_)     <- A.casArrayElem arr 4 tick 44
-- res  <- stToIO$ casArrayST arr 4 mynum 44
-- res2 <- stToIO$ casArrayST arr 4 mynum 44

 putStrLn "Printing array:"
 forM_ [0..4] $ \ i -> do
   x <- readArray arr i
   putStr ("  "++show x)

 assertBool "1st try should succeed: " res1
 assertBool "2nd should fail: " (not res2)


-- case_casbytearray1 :: IO ()
-- case_casbytearray1 = do
--  putStrLn "Perform a CAS within a MutableByteArray#"

-- | This test uses a number of producer and consumer threads which push and pop
-- elements from random positions in an array.
test_random_array_comm :: Int -> Int -> Int -> IO ()
test_random_array_comm threads size iters = do
  arr <- newArray size Nothing
  tick0 <- A.readArrayElem arr 0
  for_ 1 size $ \ i -> do
    t2 <- A.readArrayElem arr i
    assertEqual "All initial Nothings in the array should be ticket-equal:" tick0 t2

  ls <- forkJoin threads $ \_tid -> do
    localAcc <- newIORef 0
    for_ 0 iters $ \iter -> do
      -- Randomly pick a position:
      ix <- randomRIO (0,size-1) :: IO Int
      -- Randomly either produce or consume:
      b <- randomIO :: IO Bool
      if b then do
        void $ A.casArrayElem arr ix tick0 (Just iter)
       else do -- Consume:
        tick <- A.readArrayElem arr ix
        case peekTicket tick of
          Just _  -> do (success,_) <- A.casArrayElem arr ix tick (peekTicket tick0) -- Set back to Nothing.
                        when success $ modifyIORef' localAcc (+1)
--                        print (peekTicket x)
          Nothing -> return ()
        return ()
    readIORef localAcc

  let successes = sum ls
      -- Pidgeonhole principle.
      -- min_success =
  _ <- printf "Communication through random array positions (threads/size/iters %s).\n" (show (threads,size,iters))
  _ <- printf "Successes: %d (expected 1/4 of total iterations on all threads)\n" successes
  _ <- printf "Per-thread successes: %s\n" (show ls)
  assertBool "Number of successes: " (successes <= (threads * iters) `quot` 2 && successes >= 0)
  for_ 0 size $ \ i -> do
    _x <- readArray arr i
--    putStr (show _x ++ " ")
    return ()
  putStrLn ""
  return ()


----------------------------------------------------------------------------------------------------
-- Simple, non-parameterized tests
 ----------------------------------------------------------------------------------------------------

case_casTicket1 :: IO ()
case_casTicket1 = do
  dbgPrint 1 "\nUsing new 'ticket' based compare and swap:"

  IORef (STRef mutvar) <- newIORef (3::Int)
  tick <- A.readMutVarForCAS mutvar
  dbgPrint 1$"YAY, read the IORef, ticket "++show tick
  dbgPrint 1$"     and the value was:  "++show (peekTicket tick)

  (True,tick2) <- A.casMutVar mutvar tick 99
  dbgPrint 1$"Hoorah!  Attempted compare and swap..."
--  dbgPrint 1$"         Result was: "++show (True,tick2)

  dbgPrint 1$"Ok, next take a look at a SECOND CAS attempt, to see if the ticket from the first works..."
  res2 <- A.casMutVar mutvar tick2 12345678
  dbgPrint 1$"Result was: "++show res2

--  res <- A.casMutVar mutvar tick 99
  res3 <- A.readMutVarForCAS mutvar
  dbgPrint 1$"To check contents, did a SECOND read: "++show res3

  return ()

case_issue28_standalone :: Assertion
case_issue28_standalone = Issue28.main

case_issue28_copied :: Assertion
case_issue28_copied = do
  r  <- newIORef "hi"
  t0 <- readForCAS r
  (True,_t1) <- casIORef r t0 "bye"
  return ()

---- toddaaro's tests -----

case_create_and_read :: Assertion
case_create_and_read = do
  dbgPrint 1$ "   Creating a single value and trying to read it."
  x <- newIORef (120::Int)
  valf <- readIORef x
  assertBool "   Does x equal 120?" (valf == 120)

case_create_and_mutate :: Assertion
case_create_and_mutate = do
  dbgPrint 1$ "   Creating a single 'ticket' based variable to use and mutating it once."
  x <- newIORef (5::Int)
  tick <- A.readForCAS(x)
  res <- A.casIORef x tick 120
  dbgPrint 1$ "  Did setting it to 120 work?"
  dbgPrint 1$ "  Result was: " ++ show res
  valf <- readIORef x
  assertBool "Does our x equal 120?" (valf == 120)

case_create_and_mutate_twice :: Assertion
case_create_and_mutate_twice = do
  dbgPrint 1$ "  Creating a single 'ticket' based variable to mutate twice."
  x <- newIORef (0::Int)
  tick1 <- A.readForCAS(x)
  void $ A.casIORef x tick1 5
  tick2 <- A.readForCAS(x)
  void $ A.casIORef x tick2 120
  valf <- readIORef x
  assertBool "Does the value after the first mutate equal 5?" (peekTicket tick2 == 5)
  assertBool "Does the value after the second mutate equal 120?" (valf == 120)


-- [2013.07.19] I just saw an isolated failure of this one:
-- [2014.01.31] I saw another failure of this on -N1 (0e0d64c3d7), observing 118 sum.
case_n_threads_mutate :: Assertion
case_n_threads_mutate = do
  dbgPrint 1$ "   Creating 120 threads and having each increment a counter value."
  counter <- newIORef (0::Int)
--  let work :: Int -> IORef Int -> IO (Int,StableName Int,Int,StableName Int,Int)
  let work :: Int -> IO (Int,Int,Int,Int,Int)
      work ix = do
        tick <- A.readForCAS(counter)
        let nxt = peekTicket tick + 1
        (b,was) <- A.casIORef counter tick nxt
        if b then do
          putStr $ show (peekTicket was) ++ "_"
          assertEqual "Check that the value written was the one we put in." nxt (peekTicket was)
          return (ix, unsafeName tick, unsafeName was, peekTicket tick, nxt)
         else do
          when (peekTicket was == peekTicket tick) $
             putStrLn ("(Spoofed by boxing, old val was indeed "++show was++")")
          putStr "!"
--          putStrLn $ "("++ show ix ++ ": Fail when putting "++show nxt
--                     ++", was already "++show (peekTicket was) ++")"
          work ix
  arr <- forkJoin 120 work
  ans <- readIORef counter

  let dups = [ n | (_,_,_,_,n) <- arr] \\ [1..120]
  putStrLn $ "\n Duplicates were "++show dups++", Array:"
  print arr

  -- assertBool "Did the 120 threads CASing yield a valid sum" (1 <= ans && ans <= 120)
  -- The retry loop should ensure that each thread increments ONCE:
  assertEqual "Did the 120 threads CASing all succeed?" 120 ans

-- | Just make sure these link and run properly:
case_run_barriers :: Assertion
case_run_barriers = do
  A.storeLoadBarrier
  A.loadLoadBarrier
  A.writeBarrier

----------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------------
-- Adapted Old tests from original CAS library:


-- | First test: Run a simple CAS a small number of times.
test_succeed_once :: (Show a, Num a, Eq a) => a -> Assertion
test_succeed_once initialVal =
  do
     performGC -- We *ASSUME* GC does not happen below.
     performGC -- We *ASSUME* GC does not happen below.
     checkGCStats
     gc1 <- getGCCount
     r <- newIORef initialVal
     bitls <- newIORef []
     tick1 <- A.readForCAS r
     let loop 0 = return ()
         loop n = do
          res <- A.casIORef r tick1 100
          atomicModifyIORef bitls (\x -> (res:x, ()))
--          putStrLn$ "  CAS result: " ++ show res
          loop (n-1)
     loop (10::Int)

     x <- readIORef r
     assertEqual "Finished with loop, read cell: " 100 x

     writeIORef r 111
     y <- readIORef r
     assertEqual "Wrote and read again read: " 111 y

     ls <- readIORef bitls
     let rev = (reverse ls)
      -- tickets = map snd rev
         (hd:tl) = map fst rev

     gc2 <- getGCCount
     if gc1 /= gc2
       then putStrLn " [skipped] test couldn't be assessed properly due to GC."
       else do
  --     print scrubbed
       assertBool "Only first succeeds" (all (/= hd) tl)
       assertBool "All but first fail" (all (== head tl) (tail tl))
       assertEqual "First should succeed, rest fail"
                   (hd : tl)
                   (True : replicate 9 False)


-- | This version hammers on CASref from all threads, then checks to see
-- if enough threads succeeded enough of the time.
--
-- If each thread tries K attempts, there should be at least K total successes.  To
-- establish this consider the inductive argument.  One thread should succeed all the
-- time.  Adding a second thread can only foil the K attempts of the first thread by
-- itself succeeding (leaving the total at or above K).  Likewise for the third
-- thread and so on.
--
-- Conversely, for N threads each aiming to complete K operations,
-- there should be at most N*N*K total operations required.
test_all_hammer_one :: (Show a, Num a, Eq a) => Int -> Int -> a -> Assertion
test_all_hammer_one threads iters seed = do
  ref <- newIORef seed
  logs::[[Bool]] <- forkJoin threads $ \_ ->
    do checkGCStats
       let loop 0 _ _ !acc = return (reverse acc)
           loop n !ticket !expected !acc = do
            -- This line will result in boxing/unboxing and using extra memory locations:
--            let bumped = expected + 1
            bumped <- evaluate$ expected + 1
            (res,tick) <- casIORef ref ticket bumped
            case res of
              True -> do
                when (iters < 30) $
                  dbgPrint 1$ "  Succeed CAS, old tick "++show ticket++" new "++show tick++", wrote "++show bumped
                loop (n-1) tick bumped (True:acc)
              False -> do
                let v = peekTicket tick
                when (iters < 30) $
                  dbgPrint 1 $
                            "  Fizzled CAS with ticket: "++show ticket ++" containing "++show v++
                            ", expected: "++ show expected ++
                            " (#"++show (unsafeName expected)++"): "
                            ++ " found " ++ show v ++ " (#"++show (unsafeName v)++", ticket "++show tick++")"
                loop (n-1) tick v      (False:acc)

       tick0 <- readForCAS ref
       loop iters tick0 (peekTicket tick0) []

  numGcs <- getGCCount
  let successes = map (length . filter id) logs
      total_success = sum successes
      bool2char True  = '1'
      bool2char False = '0'
      -- EACH thread may fail on a single GC (in theory)
      expected_success = iters - (threads * fromIntegral numGcs)
      msg = ("Runs "++show (map length logs)++" (GCs "++show numGcs++"), had enough successes?: "
              ++show successes++" >= "++ show expected_success ++"\n"
              ++(unlines $ map (dotdot 80 . ("  "++) . map bool2char) logs) )
  dbgPrint 1 msg
  assertBool msg
             (total_success >= expected_success)



------------------------------------------------------------------------
-- Reads and Writes with full barriers:
{-
 - WIP

import Data.Atomics (atomicReadIntArray, atomicWriteIntArray)
import Data.Primitive
import Control.Concurrent
import Data.List(sort)

-- TODO DEBUGGING: for required NoBuffering
import System.IO


test_atomic_read_write_sanity :: IO ()
test_atomic_read_write_sanity = do
    mba <- newByteArray (sizeOf (undefined :: Int))
    atomicWriteIntArray mba 0 0
    x <- atomicReadIntArray mba 0
    atomicWriteIntArray mba 0 1
    y <- atomicReadIntArray mba 0
    assertEqual "test_atomic_read_write_sanity x" x 0
    assertEqual "test_atomic_read_write_sanity y" y 1

-- These don't really adequately test that we have a *full* barrier, but only
-- store/store and load/load I think. TODO something better
test_atomic_read_write_barriers1, test_atomic_read_write_barriers2 :: Int -> IO ()

-- NOTE: We don't observe failure here on x86 with non-atomic reads/writes, but
-- maybe it will for other architectures. Otherwise this can be removed.
test_atomic_read_write_barriers1 iters = do
    let theWrite mba = atomicWriteIntArray mba 0
        theRead mba = atomicReadIntArray mba 0
    {- NOTE: We would like this to fail (but it seems to work on x86)
    let theWrite mba = writeByteArray mba 0
        theRead mba = readByteArray mba 0
     -}
    -- For kicks, a bunch of padding to ensure these are on different cache-lines:
    mba0 <- newByteArray (sizeOf (undefined :: Int) * 32)
    mba1 <- newByteArray (sizeOf (undefined :: Int) * 32)
    writeByteArray mba0 0 (0 :: Int)
    writeByteArray mba1 0 (1 :: Int)
    -- One thread increments mba0, then mba1 and repeats. The other repeatedly
    -- loops reading mba0 and mba1, checking that the value from the first is
    -- always <= the second:
    readerWait <- newEmptyMVar
    void $ forkIO $
        let go :: Int -> IO ()
            go n = unless (n > iters) $ do
                    theWrite mba0 n
                    theWrite mba1 (n+1)
                    go (n+1)
         in go 1
    void $ forkIO $
        let go = do x <- theRead mba0
                    y <- theRead mba1
                    assertBool "test_atomic_read_write_barriers" $
                        (x <= y)
                    when (x < iters) go
         in go
-- Peterson's lock: http://en.wikipedia.org/wiki/Peterson%27s_algorithm
--
-- TODO DEBUGGING see https://github.com/rrnewton/haskell-lockfree/issues/43#issuecomment-71294801
--                for a discussion of issues to be resolved here.
test_atomic_read_write_barriers2 iters = do

    hSetBuffering stdout NoBuffering  -- TODO DEBUGGING (THIS APPEARS NECESSARY FOR PUTSTR TRICK BELOW TO WORK, TOO)

    let theWrite mba = atomicWriteIntArray mba 0
        theRead mba = atomicReadIntArray mba 0
    {- NOTE: WE WANT TO MAKE SURE THESE FAIL, BUT THEY DON'T !!
    let theWrite mba (v::Int) = writeByteArray mba 0 v
        theRead mba = readByteArray mba 0 :: IO Int
     -}
    let true = 1 :: Int
        false = 0 :: Int
    -- For kicks, a bunch of padding to ensure these are on different cache-lines:
    flag0 <- newByteArray (sizeOf (undefined :: Int) * 32)
    flag1 <- newByteArray (sizeOf (undefined :: Int) * 32)
    turn <- newByteArray (sizeOf (undefined :: Int) * 32)
    writeByteArray flag0 0 false
    writeByteArray flag1 0 false

    -- We use our lock to get an atomic counter:
    counter <- newByteArray (sizeOf (undefined :: Int) * 32)
    writeByteArray counter 0 (0::Int)

    let petersonIncr flagA flagB turnVal = do
            theWrite flagA true
            theWrite turn turnVal
            let busyWait = do
                  flagBVal <- theRead flagB
                  turnVal' <- theRead turn
                  if turnVal == 1 then putStr "x"  else putStr "+" -- TODO DEBUGGING (THIS APPEARS NECESSARY, AND MUST HAPPEN HERE)
                  -- putStrLn ""                                      -- TODO DEBUGGING this works too (BUT NOT FOR 1MIL?)
                  -- void $ newEmptyMVar                              -- TODO DEBUGGING does some heap alloc help? NOPE
                  -- yield                                             -- TODO DEBUGGING neither this nor -fno-omit-yields seem to help
                  when (flagBVal == true && turnVal' == 1) busyWait
            busyWait
            -- start critical section --
            old <- theRead counter
            theWrite counter (old+1)
            -- exit critical section --
            theWrite flagA false
            return old

    out1 <- newEmptyMVar
    out2 <- newEmptyMVar
    void $ forkIO $
        (replicateM iters $ petersonIncr flag0 flag1 1)
          >>= putMVar out1
    void $ forkIO $
        (replicateM iters $ petersonIncr flag1 flag0 0)
          >>= putMVar out2

    -- make sure we got some interleaving, and that output was correct:
    res1 <- takeMVar out1
    res2 <- takeMVar out2

    let numGaps gaps _ [] = gaps
        numGaps gaps prev (x:xs)
            | prev+1 == x = numGaps gaps x xs
            | otherwise   = numGaps (gaps+1) x xs
    -- TODO DEBUGGING FYI:
    print $ numGaps (0::Int) (-1::Int) res1
    print $ numGaps (0::Int) (-1::Int) res2
    -- ------------------

    -- if this fails, fix the test or call with more iters
    assertBool "test_atomic_read_write_barriers2 had enough interleaving to be legit" $
           numGaps (0::Int) (-1::Int) res1 > 10000
        && numGaps (0::Int) (-1::Int) res2 > 10000

    -- braindead merge check:
    let ok = sort res1 == res1
              &&  sort res2 == res2
              &&  sort (res1++res2) == [0..iters*2-1]

    assertBool "test_atomic_read_write_barriers2" ok

 -}

----------------------------------------------------------------------------------------------------
{-

-- UNFINISHED
-- This tests repeated atomicModifyIORefCAS operations.

testCAS3 :: Int -> IORef ElemTy -> IO [()]
testCAS3 iters ref =
  forkJoin numCapabilities (loop iters)
 where
   loop 0  = return ()
   loop n  = do
    -- let bumped = expected+1 -- Must do this only once, should be NOINLINE
--        let bump !x !y = x+y
#ifdef T1
    A.atomicModifyIORefCAS_ ref (+1)
#endif
#ifdef T2
--  B.atomicModifyIORefCAS_ ref (+1)
--  B.atomicModifyIORefCAS_ ref (bump 1)
    x <- atomicModifyIORef ref (\x -> (x+1,x))
        evaluate x -- Avoid stack leak.
#endif
    loop (n-1)

----------------------------------------------------------------------------------------------------
-- This version uses a non-scalar type for CAS.  It instead
-- manipulates the tail pointers of a simple linked-list.

#if 0
data List k = Null | Cons Int (k (List k))

type ListA = List A.CASRef
type ListB = List B.CASRef
type ListC = List C.CASRef

-- testCAS4 :: CASable ref Int => List ref -> IO [Bool]
testCAS4 :: CASable ref Int => Int -> ref (List ref) -> IO ()
testCAS4 iters ref = do
  forkJoin numCapabilities $ do
     -- From each thread, attempt to extend the list 'iters' times:
     ref' <- readCASable ref
     nl   <- newIORef Null
     loop iters (Cons (-1) nl) ref'
     return ()

  return ()
 where
  loop 0 _ _ = return ()
  loop n new (Cons _ tl) = do
    tl' <- readCASable tl
    case tl' of
      Null -> do (b,v) <- cas tl tl' new
         if b then loop (n-1) v
              else loop v
      cons -> loop cons tl'
  loop n _ Null = error "too short"
#endif


----------------------------------------------------------------------------------------------------
-- Test Oracles

checkOutput1 msg ls =
  if ls == True : replicate (9) False
  then return ()
  else error$ "Test "++ msg ++ " failed to have the right CAS success pattern: " ++ show ls

checkOutput2 :: String -> Int -> [[Bool]] -> ElemTy -> IO ()
checkOutput2 msg iters ls fin = do
  let totalAttempts = sum $ map length ls
  putStrLn$ "Final value "++show fin++", Total successes "++ show (length $ filter id $ concat ls)
  when (fin < fromIntegral iters) $
    error$ "ERROR in "++ show msg ++ " expected at least "++show iters++" successful CAS's.."

checkOutput3 :: String -> Int -> [[Bool]] -> ElemTy -> IO ()
checkOutput3 msg iters ls fin = do

  return ()


----------------------------------------------------------------------------------------------------




-- test x = do
--   a <- newStablePtr x
--   b <- newStablePtr x
--   printf "First call, word %d IntPtr %d\n"
--   (unsafeCoerce a :: Word)
--   ((fromIntegral$ ptrToIntPtr $ castStablePtrToPtr a) :: Int)
--   printf "Second call, word %d IntPtr %d\n"
--   (unsafeCoerce b :: Word)
--   ((fromIntegral$ ptrToIntPtr $ castStablePtrToPtr b) :: Int)


-- main = test 3
-}