{-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Utilities for the @closure_size@ tests
module ClosureSizeUtils (assertSize, assertSizeUnlifted) where

import Control.Monad
import GHC.Exts
import GHC.Exts.Heap.Closures
import GHC.Stack
import Type.Reflection

profHeaderSize :: Int
#if PROFILING
profHeaderSize = 2
#else
profHeaderSize = 0
#endif

assertSize
  :: forall a. (HasCallStack, Typeable a)
  => a     -- ^ closure
  -> Int   -- ^ expected size in words
  -> IO ()
assertSize x =
  assertSizeBox (asBox x) (typeRep @a)

assertSizeUnlifted
  :: forall (a :: TYPE ('BoxedRep 'Unlifted)). (HasCallStack, Typeable a)
  => a     -- ^ closure
  -> Int   -- ^ expected size in words
  -> IO ()
assertSizeUnlifted x =
  assertSizeBox (Box (unsafeCoerce# x)) (typeRep @a)

assertSizeBox
  :: forall a. (HasCallStack)
  => Box   -- ^ closure
  -> TypeRep a
  -> Int   -- ^ expected size in words
  -> IO ()
assertSizeBox x ty expected = do
  let !size = closureSize x
  when (size /= expected') $ do
    putStrLn $ "closureSize ("++show ty++") == "++show size++", expected "++show expected'
    putStrLn $ prettyCallStack callStack
  where expected' = expected + profHeaderSize
{-# NOINLINE assertSize #-}
