{-# 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) , getClosureInfoTbl , getClosureInfoTbl_maybe , getClosurePtrArgs , getClosurePtrArgs_maybe , 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.InfoTable () -- See Note [No way-dependent imports] import GHC.Exts.Heap.InfoTableProf #else import GHC.Exts.Heap.InfoTable import GHC.Exts.Heap.InfoTableProf () -- See Note [No way-dependent imports] {- Note [No way-dependent imports] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ `ghc -M` currently assumes that the imports for a module are the same in every way. This is arguably a bug, but breaking this assumption by importing different things in different ways can cause trouble. For example, this module in the profiling way imports and uses GHC.Exts.Heap.InfoTableProf. When it was not also imported in the vanilla way, there were intermittent build failures due to this module being compiled in the profiling way before GHC.Exts.Heap.InfoTableProf in the profiling way. (#15197) -} #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. instance HasHeapRep (a :: TYPE ('BoxedRep 'Lifted)) where getClosureData = getClosureDataFromHeapObject instance HasHeapRep (a :: TYPE ('BoxedRep 'Unlifted)) where 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 = Ptr 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 | [_link, bh, _owner, msg] <- pts -> pure $ BlockingQueueClosure itbl _link bh _owner msg 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 , stack_marking = FFIClosures.stack_marking fields }) | 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