{-# LANGUAGE CPP #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE UnliftedFFITypes #-} {-| Module : GHC.Exts.Heap Copyright : (c) 2012 Joachim Breitner License : BSD3 Maintainer : Joachim Breitner With this module, you can investigate the heap representation of Haskell values, i.e. to investigate sharing and lazy evaluation. -} module GHC.Exts.Heap ( -- * Closure types Closure , GenClosure(..) , ClosureType(..) , PrimType(..) , WhatNext(..) , WhyBlocked(..) , TsoFlags(..) , HasHeapRep(getClosureData) , getClosureDataFromHeapRep , getClosureDataFromHeapRepPrim -- * Info Table types , StgInfoTable(..) , EntryFunPtr , HalfWord , ItblCodes , itblSize , peekItbl , pokeItbl -- * Cost Centre (profiling) types , StgTSOProfInfo(..) , IndexTable(..) , CostCentre(..) , CostCentreStack(..) -- * Closure inspection , getBoxedClosureData , allClosures -- * Boxes , Box(..) , asBox , areBoxesEqual ) where import Prelude import GHC.Exts.Heap.Closures import GHC.Exts.Heap.ClosureTypes import GHC.Exts.Heap.Constants import GHC.Exts.Heap.ProfInfo.Types #if defined(PROFILING) import GHC.Exts.Heap.InfoTableProf #else import GHC.Exts.Heap.InfoTable #endif import GHC.Exts.Heap.Utils import qualified GHC.Exts.Heap.FFIClosures as FFIClosures import qualified GHC.Exts.Heap.ProfInfo.PeekProfInfo as PPI import Data.Bits import Foreign import GHC.Exts import GHC.Int import GHC.Word #include "ghcconfig.h" class HasHeapRep (a :: TYPE rep) where -- | Decode a closure to it's heap representation ('GenClosure'). getClosureData :: a -- ^ Closure to decode. -> IO Closure -- ^ Heap representation of the closure. #if __GLASGOW_HASKELL__ >= 901 instance HasHeapRep (a :: TYPE ('BoxedRep 'Lifted)) where #else instance HasHeapRep (a :: TYPE 'LiftedRep) where #endif getClosureData = getClosureDataFromHeapObject #if __GLASGOW_HASKELL__ >= 901 instance HasHeapRep (a :: TYPE ('BoxedRep 'Unlifted)) where #else instance HasHeapRep (a :: TYPE 'UnliftedRep) where #endif getClosureData x = getClosureDataFromHeapObject (unsafeCoerce# x) instance Int# ~ a => HasHeapRep (a :: TYPE 'IntRep) where getClosureData x = return $ IntClosure { ptipe = PInt, intVal = I# x } instance Word# ~ a => HasHeapRep (a :: TYPE 'WordRep) where getClosureData x = return $ WordClosure { ptipe = PWord, wordVal = W# x } instance Int64# ~ a => HasHeapRep (a :: TYPE 'Int64Rep) where getClosureData x = return $ Int64Closure { ptipe = PInt64, int64Val = I64# (unsafeCoerce# x) } instance Word64# ~ a => HasHeapRep (a :: TYPE 'Word64Rep) where getClosureData x = return $ Word64Closure { ptipe = PWord64, word64Val = W64# (unsafeCoerce# x) } instance Addr# ~ a => HasHeapRep (a :: TYPE 'AddrRep) where getClosureData x = return $ AddrClosure { ptipe = PAddr, addrVal = I# (unsafeCoerce# x) } instance Float# ~ a => HasHeapRep (a :: TYPE 'FloatRep) where getClosureData x = return $ FloatClosure { ptipe = PFloat, floatVal = F# x } instance Double# ~ a => HasHeapRep (a :: TYPE 'DoubleRep) where getClosureData x = return $ DoubleClosure { ptipe = PDouble, doubleVal = D# x } -- | Get the heap representation of a closure _at this moment_, even if it is -- unevaluated or an indirection or other exotic stuff. Beware when passing -- something to this function, the same caveats as for -- 'GHC.Exts.Heap.Closures.asBox' apply. -- -- For most use cases 'getClosureData' is an easier to use alternative. -- -- Currently TSO and STACK objects will return `UnsupportedClosure`. This is -- because it is not memory safe to extract TSO and STACK objects (done via -- `unpackClosure#`). Other threads may be mutating those objects and interleave -- with reads in `unpackClosure#`. This is particularly problematic with STACKs -- where pointer values may be overwritten by non-pointer values as the -- corresponding haskell thread runs. getClosureDataFromHeapObject :: a -- ^ Heap object to decode. -> IO Closure -- ^ Heap representation of the closure. getClosureDataFromHeapObject x = do case unpackClosure# x of (# infoTableAddr, heapRep, pointersArray #) -> do let infoTablePtr = Ptr infoTableAddr ptrList = [case indexArray# pointersArray i of (# ptr #) -> Box ptr | I# i <- [0..I# (sizeofArray# pointersArray) - 1] ] infoTable <- peekItbl infoTablePtr case tipe infoTable of TSO -> pure $ UnsupportedClosure infoTable STACK -> pure $ UnsupportedClosure infoTable _ -> getClosureDataFromHeapRep heapRep infoTablePtr ptrList -- | Convert an unpacked heap object, to a `GenClosure b`. The inputs to this -- function can be generated from a heap object using `unpackClosure#`. getClosureDataFromHeapRep :: ByteArray# -> Ptr StgInfoTable -> [b] -> IO (GenClosure b) getClosureDataFromHeapRep heapRep infoTablePtr pts = do itbl <- peekItbl infoTablePtr getClosureDataFromHeapRepPrim (dataConNames infoTablePtr) PPI.peekTopCCS itbl heapRep pts getClosureDataFromHeapRepPrim :: IO (String, String, String) -- ^ A continuation used to decode the constructor description field, -- in ghc-debug this code can lead to segfaults because dataConNames -- will dereference a random part of memory. -> (Ptr a -> IO (Maybe CostCentreStack)) -- ^ A continuation which is used to decode a cost centre stack -- In ghc-debug, this code will need to call back into the debuggee to -- fetch the representation of the CCS before decoding it. Using -- `peekTopCCS` for this argument can lead to segfaults in ghc-debug as -- the CCS argument will point outside the copied closure. -> StgInfoTable -- ^ The `StgInfoTable` of the closure, extracted from the heap -- representation. -> ByteArray# -- ^ Heap representation of the closure as returned by `unpackClosure#`. -- This includes all of the object including the header, info table -- pointer, pointer data, and non-pointer data. The ByteArray# may be -- pinned or unpinned. -> [b] -- ^ Pointers in the payload of the closure, extracted from the heap -- representation as returned by `collect_pointers()` in `Heap.c`. The type -- `b` is some representation of a pointer e.g. `Any` or `Ptr Any`. -> IO (GenClosure b) -- ^ Heap representation of the closure. getClosureDataFromHeapRepPrim getConDesc decodeCCS itbl heapRep pts = do let -- heapRep as a list of words. rawHeapWords :: [Word] rawHeapWords = [W# (indexWordArray# heapRep i) | I# i <- [0.. end] ] where nelems = I# (sizeofByteArray# heapRep) `div` wORD_SIZE end = fromIntegral nelems - 1 -- Just the payload of rawHeapWords (no header). payloadWords :: [Word] payloadWords = drop (closureTypeHeaderSize (tipe itbl)) rawHeapWords -- The non-pointer words in the payload. Only valid for closures with a -- "pointers first" layout. Not valid for bit field layout. npts :: [Word] npts = drop (closureTypeHeaderSize (tipe itbl) + length pts) rawHeapWords case tipe itbl of t | t >= CONSTR && t <= CONSTR_NOCAF -> do (p, m, n) <- getConDesc pure $ ConstrClosure itbl pts npts p m n t | t >= THUNK && t <= THUNK_STATIC -> do pure $ ThunkClosure itbl pts npts THUNK_SELECTOR -> case pts of [] -> fail "Expected at least 1 ptr argument to THUNK_SELECTOR" hd : _ -> pure $ SelectorClosure itbl hd t | t >= FUN && t <= FUN_STATIC -> do pure $ FunClosure itbl pts npts AP -> case pts of [] -> fail "Expected at least 1 ptr argument to AP" hd : tl -> case payloadWords of -- We expect at least the arity, n_args, and fun fields splitWord : _ : _ -> pure $ APClosure itbl #if defined(WORDS_BIGENDIAN) (fromIntegral $ shiftR splitWord (wORD_SIZE_IN_BITS `div` 2)) (fromIntegral splitWord) #else (fromIntegral splitWord) (fromIntegral $ shiftR splitWord (wORD_SIZE_IN_BITS `div` 2)) #endif hd tl _ -> fail "Expected at least 2 raw words to AP" PAP -> case pts of [] -> fail "Expected at least 1 ptr argument to PAP" hd : tl -> case payloadWords of -- We expect at least the arity, n_args, and fun fields splitWord : _ : _ -> pure $ PAPClosure itbl #if defined(WORDS_BIGENDIAN) (fromIntegral $ shiftR splitWord (wORD_SIZE_IN_BITS `div` 2)) (fromIntegral splitWord) #else (fromIntegral splitWord) (fromIntegral $ shiftR splitWord (wORD_SIZE_IN_BITS `div` 2)) #endif hd tl _ -> fail "Expected at least 2 raw words to PAP" AP_STACK -> case pts of [] -> fail "Expected at least 1 ptr argument to AP_STACK" hd : tl -> pure $ APStackClosure itbl hd tl IND -> case pts of [] -> fail "Expected at least 1 ptr argument to IND" hd : _ -> pure $ IndClosure itbl hd IND_STATIC -> case pts of [] -> fail "Expected at least 1 ptr argument to IND_STATIC" hd : _ -> pure $ IndClosure itbl hd BLACKHOLE -> case pts of [] -> fail "Expected at least 1 ptr argument to BLACKHOLE" hd : _ -> pure $ BlackholeClosure itbl hd BCO -> case pts of pts0 : pts1 : pts2 : _ -> case payloadWords of _ : _ : _ : splitWord : payloadRest -> pure $ BCOClosure itbl pts0 pts1 pts2 #if defined(WORDS_BIGENDIAN) (fromIntegral $ shiftR splitWord (wORD_SIZE_IN_BITS `div` 2)) (fromIntegral splitWord) #else (fromIntegral splitWord) (fromIntegral $ shiftR splitWord (wORD_SIZE_IN_BITS `div` 2)) #endif payloadRest _ -> fail $ "Expected at least 4 words to BCO, found " ++ show (length payloadWords) _ -> fail $ "Expected at least 3 ptr argument to BCO, found " ++ show (length pts) ARR_WORDS -> case payloadWords of [] -> fail $ "Expected at least 1 words to ARR_WORDS, found " ++ show (length payloadWords) hd : tl -> pure $ ArrWordsClosure itbl hd tl t | t >= MUT_ARR_PTRS_CLEAN && t <= MUT_ARR_PTRS_FROZEN_CLEAN -> case payloadWords of p0 : p1 : _ -> pure $ MutArrClosure itbl p0 p1 pts _ -> fail $ "Expected at least 2 words to MUT_ARR_PTRS_* " ++ "found " ++ show (length payloadWords) t | t >= SMALL_MUT_ARR_PTRS_CLEAN && t <= SMALL_MUT_ARR_PTRS_FROZEN_CLEAN -> case payloadWords of [] -> fail $ "Expected at least 1 word to SMALL_MUT_ARR_PTRS_* " ++ "found " ++ show (length payloadWords) hd : _ -> pure $ SmallMutArrClosure itbl hd pts t | t == MUT_VAR_CLEAN || t == MUT_VAR_DIRTY -> case pts of [] -> fail $ "Expected at least 1 words to MUT_VAR, found " ++ show (length pts) hd : _ -> pure $ MutVarClosure itbl hd t | t == MVAR_CLEAN || t == MVAR_DIRTY -> case pts of pts0 : pts1 : pts2 : _ -> pure $ MVarClosure itbl pts0 pts1 pts2 _ -> fail $ "Expected at least 3 ptrs to MVAR, found " ++ show (length pts) BLOCKING_QUEUE -> pure $ OtherClosure itbl pts rawHeapWords WEAK -> case pts of pts0 : pts1 : pts2 : pts3 : rest -> pure $ WeakClosure { info = itbl , cfinalizers = pts0 , key = pts1 , value = pts2 , finalizer = pts3 , weakLink = case rest of [] -> Nothing [p] -> Just p _ -> error $ "Expected 4 or 5 words in WEAK, but found more: " ++ show (length pts) } _ -> error $ "Expected 4 or 5 words in WEAK, but found less: " ++ show (length pts) TSO | ( u_lnk : u_gbl_lnk : tso_stack : u_trec : u_blk_ex : u_bq : other) <- pts -> withArray rawHeapWords (\ptr -> do fields <- FFIClosures.peekTSOFields decodeCCS ptr pure $ TSOClosure { info = itbl , link = u_lnk , global_link = u_gbl_lnk , tsoStack = tso_stack , trec = u_trec , blocked_exceptions = u_blk_ex , bq = u_bq , thread_label = case other of [tl] -> Just tl [] -> Nothing _ -> error $ "thead_label:Expected 0 or 1 extra arguments" , what_next = FFIClosures.tso_what_next fields , why_blocked = FFIClosures.tso_why_blocked fields , flags = FFIClosures.tso_flags fields , threadId = FFIClosures.tso_threadId fields , saved_errno = FFIClosures.tso_saved_errno fields , tso_dirty = FFIClosures.tso_dirty fields , alloc_limit = FFIClosures.tso_alloc_limit fields , tot_stack_size = FFIClosures.tso_tot_stack_size fields , prof = FFIClosures.tso_prof fields }) | otherwise -> fail $ "Expected at least 6 ptr arguments to TSO, found " ++ show (length pts) STACK | [] <- pts -> withArray rawHeapWords (\ptr -> do fields <- FFIClosures.peekStackFields ptr pure $ StackClosure { info = itbl , stack_size = FFIClosures.stack_size fields , stack_dirty = FFIClosures.stack_dirty fields #if __GLASGOW_HASKELL__ >= 811 , stack_marking = FFIClosures.stack_marking fields #endif }) | otherwise -> fail $ "Expected 0 ptr argument to STACK, found " ++ show (length pts) _ -> pure $ UnsupportedClosure itbl -- | Like 'getClosureData', but taking a 'Box', so it is easier to work with. getBoxedClosureData :: Box -> IO Closure getBoxedClosureData (Box a) = getClosureData a