{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RecursiveDo #-} {-# LANGUAGE NamedFieldPuns #-} module GHC.Debug.Types.Graph( -- * Types HeapGraph(..) , HeapGraphEntry(..) , HeapGraphIndex , PapHI , StackHI -- * Building a heap graph , DerefFunction , buildHeapGraph , multiBuildHeapGraph , generalBuildHeapGraph -- * Printing a heap graph , ppHeapGraph , ppClosure -- * Utility , lookupHeapGraph , traverseHeapGraph , updateHeapGraph , heapGraphSize , annotateHeapGraph -- * Reverse Graph , ReverseGraph , mkReverseGraph , reverseEdges ) where import Data.Char import Data.List (intercalate, foldl', sort, group, sortBy, groupBy) import Data.Maybe ( catMaybes ) import Data.Function import qualified Data.HashMap.Strict as M import qualified Data.IntMap as IM import qualified Data.IntSet as IS import Control.Monad import Control.Monad.Fix import Control.Monad.Trans.State import Control.Monad.Trans.Class import GHC.Debug.Types.Ptr import GHC.Debug.Types.Closures import qualified Data.List.NonEmpty as NE import Data.List.NonEmpty (NonEmpty(..)) -- | For heap graphs, i.e. data structures that also represent sharing and -- cyclic structures, these are the entries. If the referenced value is -- @Nothing@, then we do not have that value in the map, most likely due to -- exceeding the recursion bound passed to 'buildHeapGraph'. -- -- Besides a pointer to the stored value and the closure representation we -- have a slot for arbitrary data, for the user's convenience. data HeapGraphEntry a = HeapGraphEntry { hgeClosurePtr :: ClosurePtr, hgeClosure :: DebugClosure PapHI ConstrDesc StackHI (Maybe HeapGraphIndex), hgeData :: a} deriving (Show, Functor, Foldable, Traversable) type HeapGraphIndex = ClosurePtr type StackHI = GenStackFrames (Maybe HeapGraphIndex) type PapHI = GenPapPayload (Maybe HeapGraphIndex) -- | The whole graph. The suggested interface is to only use 'lookupHeapGraph', -- as the internal representation may change. Nevertheless, we export it here: -- Sometimes the user knows better what he needs than we do. data HeapGraph a = HeapGraph { roots :: !(NE.NonEmpty ClosurePtr) , graph :: !(IM.IntMap (HeapGraphEntry a)) } deriving (Show, Foldable, Traversable, Functor) traverseHeapGraph :: Applicative m => (HeapGraphEntry a -> m (HeapGraphEntry b)) -> HeapGraph a -> m (HeapGraph b) traverseHeapGraph f (HeapGraph r im) = HeapGraph r <$> traverse f im lookupHeapGraph :: HeapGraphIndex -> HeapGraph a -> Maybe (HeapGraphEntry a) lookupHeapGraph (ClosurePtr i) (HeapGraph _r m) = IM.lookup (fromIntegral i) m insertHeapGraph :: HeapGraphIndex -> HeapGraphEntry a -> HeapGraph a -> HeapGraph a insertHeapGraph (ClosurePtr i) a (HeapGraph r m) = HeapGraph r (IM.insert (fromIntegral i) a m) updateHeapGraph :: (HeapGraphEntry a -> Maybe (HeapGraphEntry a)) -> HeapGraphIndex -> HeapGraph a -> HeapGraph a updateHeapGraph f (ClosurePtr i) (HeapGraph r m) = HeapGraph r (IM.update f (fromIntegral i) m) heapGraphSize :: HeapGraph a -> Int heapGraphSize (HeapGraph _ g) = IM.size g -- | Creates a 'HeapGraph' for the value in the box, but not recursing further -- than the given limit. buildHeapGraph :: (MonadFix m) => DerefFunction m a -> Maybe Int -> ClosurePtr -- ^ The value to start with -> m (HeapGraph a) buildHeapGraph deref limit initialBox = multiBuildHeapGraph deref limit (NE.singleton initialBox) -- TODO: It is a bit undesirable that the ConstrDesc field is already -- dereferenced, but also, not such a big deal. It could lead to additional -- requests to the debuggee which are not necessary and causes a mismatch -- with the step-by-step decoding functions in `Client.hs` type DerefFunction m a = ClosurePtr -> m (DebugClosureWithExtra a PapPayload ConstrDesc StackFrames ClosurePtr) -- | Creates a 'HeapGraph' for the values in multiple boxes, but not recursing -- further than the given limit. multiBuildHeapGraph :: (MonadFix m) => DerefFunction m a -> Maybe Int -> NonEmpty ClosurePtr -- ^ Starting values with associated data entry -> m (HeapGraph a) multiBuildHeapGraph deref limit rs = generalBuildHeapGraph deref limit (HeapGraph rs IM.empty) rs {-# INLINE multiBuildHeapGraph #-} -- | Adds the given annotation to the entry at the given index, using the -- 'mappend' operation of its 'Monoid' instance. annotateHeapGraph :: (a -> a) -> HeapGraphIndex -> HeapGraph a -> HeapGraph a annotateHeapGraph f i hg = updateHeapGraph go i hg where go hge = Just $ hge { hgeData = f (hgeData hge) } {-# INLINE generalBuildHeapGraph #-} generalBuildHeapGraph :: forall m a . (MonadFix m) => DerefFunction m a -> Maybe Int -> HeapGraph a -> NonEmpty ClosurePtr -> m (HeapGraph a) generalBuildHeapGraph deref limit hg addBoxes = do -- First collect all boxes from the existing heap graph (_is, hg') <- runStateT (mapM (add limit) addBoxes) hg return hg' where add :: Maybe Int -> ClosurePtr -> StateT (HeapGraph a) m (Maybe ClosurePtr) add (Just 0) _ = return Nothing add n cp = do -- If the box is in the map, return the index hm <- get case lookupHeapGraph cp hm of Just {} -> return (Just cp) -- FIXME GHC BUG: change `mdo` to `do` below: -- "GHC internal error: ā€˜cā€™ is not in scope during type checking, but it passed the renamer" Nothing -> mdo -- Look up the closure c <- lift $ deref cp let new_add = add (subtract 1 <$> n) -- NOTE: We tie-the-knot here with RecursiveDo so that we don't -- get into an infinite loop with cycles in the heap. rec modify' (insertHeapGraph cp (HeapGraphEntry cp c' e)) -- Add the resulting closure below to the map (above): DCS e c' <- quadtraverse (traverse new_add) pure (traverse new_add) new_add c return (Just cp) -- | Pretty-prints a HeapGraph. The resulting string contains newlines. Example -- for @let s = \"Ki\" in (s, s, cycle \"Ho\")@: -- -- >let x1 = "Ki" -- > x6 = C# 'H' : C# 'o' : x6 -- >in (x1,x1,x6) ppHeapGraph :: (a -> String) -> HeapGraph a -> String ppHeapGraph printData (HeapGraph (heapGraphRoot :| rs) m) = letWrapper ++ "(" ++ printData (hgeData (iToE heapGraphRoot)) ++ ") " ++ roots where -- All variables occuring more than once bindings = boundMultipleTimes (HeapGraph (heapGraphRoot :| rs) m) [heapGraphRoot] roots = unlines [ "r" ++ show n ++ ":(" ++ printData (hgeData (iToE r)) ++ ") " ++ ppRef 0 (Just r) ++ "\n" | (n, r) <- zip [0 :: Int ..] (heapGraphRoot : rs) ] letWrapper = if null bindings then "" else "let " ++ intercalate "\n " (map ppBinding bindings) ++ "\nin " bindingLetter i = case hgeClosure (iToE i) of ThunkClosure {} -> 't' SelectorClosure {} -> 't' APClosure {} -> 't' PAPClosure {} -> 'f' BCOClosure {} -> 't' FunClosure {} -> 'f' _ -> 'x' ppBindingMap = M.fromList $ concatMap (zipWith (\j (i,c) -> (i, c : show j)) [(1::Int)..]) $ groupBy ((==) `on` snd) $ sortBy (compare `on` snd) [ (i, bindingLetter i) | i <- bindings ] ppVar i = ppBindingMap M.! i ppBinding i = ppVar i ++ "(" ++ printData (hgeData (iToE i)) ++ ") = " ++ ppEntry 0 (iToE i) ppEntry prec hge | Just s <- isString (hgeClosure hge) = show s | Just l <- isList (hgeClosure hge) = "[" ++ intercalate "," (map (ppRef 0) l) ++ "]" | otherwise = ppClosure (printData (hgeData hge)) ppRef prec (hgeClosure hge) where _app [a] = a ++ "()" _app xs = addBraces (10 <= prec) (unwords xs) ppRef _ Nothing = "..." ppRef prec (Just i) | i `elem` bindings = ppVar i | otherwise = ppEntry prec (iToE i) iToE (ClosurePtr i) = m IM.! (fromIntegral i) iToUnboundE cp@(ClosurePtr i) | cp `elem` bindings = Nothing | otherwise = IM.lookup (fromIntegral i) m isList :: DebugClosure p ConstrDesc s (Maybe HeapGraphIndex) -> Maybe [Maybe HeapGraphIndex] isList c | isNil c = return [] | otherwise = do (h,t) <- isCons c ti <- t e <- iToUnboundE ti t' <- isList (hgeClosure e) return $ (:) h t' isString :: DebugClosure p ConstrDesc s (Maybe HeapGraphIndex) -> Maybe String isString e = do list <- isList e -- We do not want to print empty lists as "" as we do not know that they -- are really strings. if null list then Nothing else mapM (isChar . hgeClosure <=< iToUnboundE <=< id) list -- | In the given HeapMap, list all indices that are used more than once. The -- second parameter adds external references, commonly @[heapGraphRoot]@. boundMultipleTimes :: HeapGraph a -> [HeapGraphIndex] -> [HeapGraphIndex] boundMultipleTimes (HeapGraph _rs m) roots = map head $ filter (not.null) $ group $ sort $ roots ++ concatMap (catMaybes . allClosures . hgeClosure) (IM.elems m) -- Utilities addBraces :: Bool -> String -> String addBraces True t = "(" ++ t ++ ")" addBraces False t = t braceize :: [String] -> String braceize [] = "" braceize xs = "{" ++ intercalate "," xs ++ "}" isChar :: DebugClosure p ConstrDesc s c -> Maybe Char isChar ConstrClosure{ constrDesc = ConstrDesc {pkg = "ghc-prim", modl = "GHC.Types", name = "C#"}, dataArgs = [ch], ptrArgs = []} = Just (chr (fromIntegral ch)) isChar _ = Nothing isNil :: DebugClosure p ConstrDesc s c -> Bool isNil ConstrClosure{ constrDesc = ConstrDesc {pkg = "ghc-prim", modl = "GHC.Types", name = "[]"}, dataArgs = _, ptrArgs = []} = True isNil _ = False isCons :: DebugClosure p ConstrDesc s c -> Maybe (c, c) isCons ConstrClosure{ constrDesc = ConstrDesc {pkg = "ghc-prim", modl = "GHC.Types", name = ":"}, dataArgs = [], ptrArgs = [h,t]} = Just (h,t) isCons _ = Nothing isTup :: DebugClosure p ConstrDesc s c -> Maybe [c] isTup ConstrClosure{ dataArgs = [], ..} = if length (name constrDesc) >= 3 && head (name constrDesc) == '(' && last (name constrDesc) == ')' && all (==',') (tail (init (name constrDesc))) then Just ptrArgs else Nothing isTup _ = Nothing -- | A pretty-printer that tries to generate valid Haskell for evalutated data. -- It assumes that for the included boxes, you already replaced them by Strings -- using 'Data.Foldable.map' or, if you need to do IO, 'Data.Foldable.mapM'. -- -- The parameter gives the precedendence, to avoid avoidable parenthesises. ppClosure :: String -> (Int -> c -> String) -> Int -> DebugClosure p ConstrDesc s c -> String ppClosure herald showBox prec c = case c of _ | Just ch <- isChar c -> app ["C#", show ch] _ | Just (h,t) <- isCons c -> addBraces (5 <= prec) $ showBox 5 h ++ " : " ++ showBox 4 t _ | Just vs <- isTup c -> "(" ++ intercalate "," (map (showBox 0) vs) ++ ")" ConstrClosure {..} -> app $ name constrDesc : map (showBox 10) ptrArgs ++ map show dataArgs ThunkClosure {..} -> app $ "_thunk(" : herald : ")" : map (showBox 10) ptrArgs ++ map show dataArgs SelectorClosure {..} -> app ["_sel", showBox 10 selectee] IndClosure {..} -> app ["_ind", showBox 10 indirectee] BlackholeClosure {..} -> app ["_bh", showBox 10 indirectee] APClosure {..} -> app $ map (showBox 10) $ [fun] -- TODO: Payload PAPClosure {..} -> app $ map (showBox 10) $ [fun] -- TODO payload APStackClosure {..} -> app $ map (showBox 10) $ [fun] -- TODO: stack TRecChunkClosure {} -> "_trecChunk" --TODO BCOClosure {..} -> app ["_bco", showBox 10 bcoptrs] ArrWordsClosure {..} -> app ["ARR_WORDS", "("++show bytes ++ " bytes)", ((show $ arrWordsBS arrWords)) ] MutArrClosure {..} -> app --["toMutArray", "("++show (length mccPayload) ++ " ptrs)", intercalate "," (shorten (map (showBox 10) mccPayload))] ["[", intercalate ", " (shorten (map (showBox 10) mccPayload)),"]"] SmallMutArrClosure {..} -> app ["[", intercalate ", " (shorten (map (showBox 10) mccPayload)),"]"] MutVarClosure {..} -> app ["_mutVar", showBox 10 var] MVarClosure {..} -> app ["MVar", showBox 10 value] FunClosure {..} -> "_fun" ++ braceize (map (showBox 0) ptrArgs ++ map show dataArgs) BlockingQueueClosure {} -> "_blockingQueue" OtherClosure {} -> "_other" TSOClosure {} -> "TSO" StackClosure {..} -> app ["Stack(", show stack_size, ")"] -- TODO WeakClosure {} -> "_wk" -- TODO TVarClosure {} -> "_tvar" -- TODO MutPrimClosure {} -> "_mutPrim" -- TODO UnsupportedClosure {info} -> (show info) where app [a] = a ++ "()" app xs = addBraces (10 <= prec) (unwords xs) shorten xs = if length xs > 20 then take 20 xs ++ ["(and more)"] else xs -- Reverse Edges -- closurePtrToInt :: ClosurePtr -> Int closurePtrToInt (ClosurePtr p) = fromIntegral p intToClosurePtr :: Int -> ClosurePtr intToClosurePtr i = mkClosurePtr (fromIntegral i) newtype ReverseGraph = ReverseGraph (IM.IntMap IS.IntSet) reverseEdges :: ClosurePtr -> ReverseGraph -> Maybe [ClosurePtr] reverseEdges cp (ReverseGraph rg) = map intToClosurePtr . IS.toList <$> IM.lookup (closurePtrToInt cp) rg mkReverseGraph :: HeapGraph a -> ReverseGraph mkReverseGraph (HeapGraph _ hg) = ReverseGraph graph where graph = IM.foldlWithKey' collectNodes IM.empty hg collectNodes newMap k h = let bs = allClosures (hgeClosure h) in foldl' (\m ma -> case ma of Nothing -> m Just a -> IM.insertWith IS.union (closurePtrToInt a) (IS.singleton k) m) newMap bs