module GHC.Vis.Internal (
walkHeap,
parseBoxes,
parseBoxesHeap,
pointersToFollow2,
showClosure
)
where
#if __GLASGOW_HASKELL__ < 706
import Prelude hiding (catch)
#endif
import GHC.Vis.Types
import GHC.HeapView hiding (name, pkg, modl, fun, arrWords)
import Control.Monad
import Control.Monad.State hiding (State, fix)
import Data.Word
import Data.Char
import Data.List hiding (insert)
import Text.Printf
import Unsafe.Coerce
import Control.Monad.Trans.Maybe
import System.IO.Unsafe
maxHeapWalkDepth :: Int
maxHeapWalkDepth = 100
parseBoxes :: [(Box, String)] -> IO [[VisObject]]
parseBoxes bs = do
r <- generalParseBoxes evalState bs
case r of
Just x -> return x
_ -> do
putStrLn "Failure, trying again"
parseBoxes bs
parseBoxesHeap :: [(Box, String)] -> IO ([[VisObject]], PState)
parseBoxesHeap bs = do
r <- generalParseBoxes runState bs
case r of
(Just x, y) -> return (x,y)
_ -> parseBoxesHeap bs
generalParseBoxes ::
(PrintState (Maybe [[VisObject]]) -> PState -> b)
-> [(Box, String)] -> IO b
generalParseBoxes f bs = do
h <- walkHeapSimply bs
h2 <- walkHeapWithBCO bs
return $ f (g bs) $ PState 0 0 0 h h2
where g bs' = runMaybeT $ go bs'
go ((b',_):b's) = do h <- lift $ gets heapMap'
(_,c) <- lookupT b' h
r <- parseClosure b' c
rs <- go b's
return $ simplify r : rs
go [] = return []
lookupT :: Eq a => a -> [(a,b)] -> MaybeT PrintState b
lookupT b' h = MaybeT $ return $ lookup b' h
simplify :: [VisObject] -> [VisObject]
simplify [] = []
simplify [Named a bs] = [Named a $ simplify bs]
simplify [a] = [a]
simplify (Unnamed a : Unnamed b : xs) = simplify $ Unnamed (a ++ b) : xs
simplify (Named a bs : xs) = Named a (simplify bs) : simplify xs
simplify (a:xs) = a : simplify xs
parseClosure :: Box -> Closure -> MaybeT PrintState [VisObject]
parseClosure b c = do
o <- correctObject b
case o of
Link n -> return [Link n]
_ -> do i <- parseInternal b c
return $ insertObjects o i
correctObject :: Box -> MaybeT PrintState VisObject
correctObject box = do
r <- lift $ countReferences box
n <- getName box
case n of
Just name -> return $ Link name
Nothing -> if r > 1 then
(do setName box
mbName <- getName box
name <- MaybeT $ return mbName
return $ Named name [])
else return $ Unnamed ""
insertObjects :: VisObject -> [VisObject] -> [VisObject]
insertObjects _ xs@(Function _ : _) = xs
insertObjects _ xs@(Thunk _ : _) = xs
insertObjects (Link name) _ = [Link name]
insertObjects (Named name _) xs = [Named name xs]
insertObjects (Unnamed _) xs = xs
insertObjects _ _ = error "unexpected arguments"
walkHeap :: [(Box, String)] -> IO HeapMap
walkHeap = walkHeapGeneral Just pointersToFollow
walkHeapSimply :: [(Box, String)] -> IO HeapMap
walkHeapSimply = walkHeapGeneral (const Nothing) pointersToFollow
walkHeapWithBCO :: [(Box, String)] -> IO HeapMap
walkHeapWithBCO = walkHeapGeneral (const Nothing) pointersToFollow2
walkHeapGeneral :: (String -> Maybe String) -> (Closure -> IO [Box]) -> [(Box, String)] -> IO HeapMap
walkHeapGeneral topF p2fF bs = foldM (topNodes topF) [dummy] bs >>= \s -> foldM (startWalk p2fF) s bs
where topNodes hn l (b,n) = do
c' <- getBoxedClosureData b
return $ insert (b, (hn n, c')) l
startWalk p2f l (b,_) = do
c' <- getBoxedClosureData b
p <- p2f c'
foldM (step maxHeapWalkDepth p2f) l p
step depth p2f l b = case lookup b l of
Just _ -> return l
Nothing -> do
c' <- getBoxedClosureData b
p <- p2f c'
if depth == 0 && not (null p)
then do
putStrLn "Warning: Maximum data structure depth reached, output is truncated"
return $ insert (b, (Nothing, maxDepthClosure)) l
else foldM (step (depth 1) p2f) (insert (b, (Nothing, c')) l) p
dummy = (asBox (1 :: Integer),
(Nothing, ConsClosure (StgInfoTable 0 0 CONSTR 0) (map fst bs) [] "" "" ""))
maxDepthClosure = ConsClosure (StgInfoTable 0 0 CONSTR 0) [] [] "" "" "..."
pointersToFollow :: Closure -> IO [Box]
pointersToFollow BCOClosure{} = return []
pointersToFollow (MutArrClosure _ _ _ bPtrs) =
do cPtrs <- mapM getBoxedClosureData bPtrs
return $ fix $ zip bPtrs cPtrs
where fix ((_,ConsClosure _ _ _ _ "ByteCodeInstr" "BreakInfo"):_:_:xs) = fix xs
fix ((_,ConsClosure _ _ _ _ "ByteCodeInstr" "BreakInfo"):_:xs) = fix xs
fix ((_,ConsClosure _ _ _ _ "ByteCodeInstr" "BreakInfo"):xs) = fix xs
fix ((x,_):xs) = x : fix xs
fix [] = []
pointersToFollow x = return $ allPtrs x
pointersToFollow2 :: Closure -> IO [Box]
pointersToFollow2 (MutArrClosure _ _ _ bPtrs) =
do cPtrs <- mapM getBoxedClosureData bPtrs
return $ fix $ zip bPtrs cPtrs
where fix ((_,ConsClosure _ _ _ _ "ByteCodeInstr" "BreakInfo"):_:_:xs) = fix xs
fix ((_,ConsClosure _ _ _ _ "ByteCodeInstr" "BreakInfo"):_:xs) = fix xs
fix ((_,ConsClosure _ _ _ _ "ByteCodeInstr" "BreakInfo"):xs) = fix xs
fix ((x,_):xs) = x : fix xs
fix [] = []
pointersToFollow2 x = return $ allPtrs x
insert :: (Box, HeapEntry) -> HeapMap -> HeapMap
insert (b,x) xs = case find (\(c,_) -> c == b) xs of
Just _ -> xs
Nothing -> (b,x):xs
adjust :: (HeapEntry -> HeapEntry) -> Box -> HeapMap -> Maybe HeapMap
adjust f b h = do i <- findIndex (\(y,_) -> y == b) h
let (h1,(_,x):h2) = splitAt i h
return $ h1 ++ ((b,f x) : h2)
setName :: Box -> MaybeT PrintState ()
setName b = do PState ti fi bi h h2 <- lift get
(_,c) <- lookupT b h2
let (n, ti',fi',bi') = case c of
ThunkClosure{} -> ('t' : show ti, ti+1, fi, bi)
APClosure{} -> ('t' : show ti, ti+1, fi, bi)
FunClosure{} -> ('f' : show fi, ti, fi+1, bi)
PAPClosure{} -> ('f' : show fi, ti, fi+1, bi)
_ -> ('b' : show bi, ti, fi, bi+1)
set (Nothing, closure) = (Just n, closure)
set _ = error "unexpected pattern"
h' <- MaybeT $ return $ adjust set b h
h2' <- MaybeT $ return $ adjust set b h2
lift $ put $ PState ti' fi' bi' h' h2'
setVisited :: Box -> MaybeT PrintState ()
setVisited b = do PState ti fi bi h h2 <- lift get
let set (Nothing, closure) = (Just "visited", closure)
set _ = error "unexpected pattern"
h2' <- MaybeT $ return $ adjust set b h2
lift $ put $ PState ti fi bi h h2'
getName :: Box -> MaybeT PrintState (Maybe String)
getName b = do h <- lift $ gets heapMap'
(name,_) <- lookupT b h
return name
getSetName :: Box -> MaybeT PrintState String
getSetName b = do mn <- getName b
case mn of
Nothing -> do setName b
name <- getName b
MaybeT $ return name
Just name -> return name
countReferences :: Box -> PrintState Int
countReferences b = do
h <- gets heapMap'
return $ sum $ map countR h
where countR (_,(_,c)) = length $ filter (== b) $ allPtrs c
parseInternal :: Box -> Closure -> MaybeT PrintState [VisObject]
parseInternal _ (ConsClosure _ [] [dataArg] _pkg modl name) =
return [Unnamed $ case (modl, name) of
k | k `elem` [ ("GHC.Word", "W#")
, ("GHC.Word", "W8#")
, ("GHC.Word", "W16#")
, ("GHC.Word", "W32#")
, ("GHC.Word", "W64#")
] -> name ++ " " ++ show dataArg
k | k `elem` [ ("GHC.Integer.Type", "S#")
, ("GHC.Types", "I#")
, ("GHC.Int", "I8#")
, ("GHC.Int", "I16#")
, ("GHC.Int", "I32#")
, ("GHC.Int", "I64#")
] -> name ++ " " ++ show ((fromIntegral :: Word -> Int) dataArg)
("GHC.Types", "C#") -> show . chr $ fromIntegral dataArg
("GHC.Types", "D#") -> printf "D# %0.5f" (unsafeCoerce dataArg :: Double)
("GHC.Types", "F#") -> printf "F# %0.5f" (unsafeCoerce dataArg :: Double)
_ -> printf "%s %d" (infixFix name) dataArg
]
parseInternal _ (ConsClosure (StgInfoTable 1 3 _ _) _ [_,0,0] _ "Data.ByteString.Internal" "PS")
= return [Unnamed "ByteString 0 0"]
parseInternal _ (ConsClosure (StgInfoTable 1 3 _ _) [bPtr] [_,start,end] _ "Data.ByteString.Internal" "PS")
= do cPtr <- liftM mbParens $ contParse bPtr
return $ Unnamed (printf "ByteString %d %d " start end) : cPtr
parseInternal _ (ConsClosure (StgInfoTable 2 3 _ _) [bPtr1,bPtr2] [_,start,end] _ "Data.ByteString.Lazy.Internal" "Chunk")
= do cPtr1 <- liftM mbParens $ contParse bPtr1
cPtr2 <- liftM mbParens $ contParse bPtr2
return $ Unnamed (printf "Chunk %d %d " start end) : cPtr1 ++ [Unnamed " "] ++ cPtr2
parseInternal _ (ConsClosure (StgInfoTable 2 0 _ _) [bHead,bTail] [] _ "GHC.Types" ":")
= do cHead <- liftM mbParens $ contParse bHead
cTail <- liftM mbParens $ contParse bTail
return $ cHead ++ [Unnamed ":"] ++ cTail
parseInternal _ (ConsClosure _ bPtrs dArgs _ _ name)
= do cPtrs <- mapM (liftM mbParens . contParse) bPtrs
let tPtrs = intercalate [Unnamed " "] cPtrs
let sPtrs = if null tPtrs then [Unnamed ""] else Unnamed " " : tPtrs
return $ Unnamed (unwords $ infixFix name : map show dArgs) : sPtrs
parseInternal _ (ArrWordsClosure _ _ arrWords)
= return $ intercalate [Unnamed ","] (map (\x -> [Unnamed (printf "0x%x" x)]) arrWords)
parseInternal _ (IndClosure _ b)
= contParse b
parseInternal _ (SelectorClosure _ b)
= contParse b
parseInternal _ (BlackholeClosure _ b)
= contParse b
parseInternal _ BlockingQueueClosure{}
= return [Unnamed "BlockingQueue"]
parseInternal _ (OtherClosure (StgInfoTable _ _ cTipe _) _ _)
= return [Unnamed $ show cTipe]
parseInternal _ (UnsupportedClosure (StgInfoTable _ _ cTipe _))
= return [Unnamed $ show cTipe]
parseInternal b (ThunkClosure _ bPtrs args) = do
name <- getSetName b
cPtrs <- mapM contParse $ reverse bPtrs
let tPtrs = intercalate [Unnamed ","] cPtrs
sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "(" : tPtrs ++ [Unnamed ")"]
sArgs = Unnamed $ if null args then "" else show args
return $ Thunk (infixFix name) : sPtrs ++ [sArgs]
parseInternal b (FunClosure _ bPtrs args) = do
name <- getSetName b
cPtrs <- mapM contParse $ reverse bPtrs
let tPtrs = intercalate [Unnamed ","] cPtrs
sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "(" : tPtrs ++ [Unnamed ")"]
sArgs = Unnamed $ if null args then "" else show args
return $ Function (infixFix name) : sPtrs ++ [sArgs]
parseInternal _ (MutArrClosure _ _ _ bPtrs)
= do cPtrs <- mutArrContParse bPtrs
let tPtrs = intercalate [Unnamed ","] cPtrs
return $ if null tPtrs then [Unnamed ""] else Unnamed "(" : tPtrs ++ [Unnamed ")"]
parseInternal _ (MutVarClosure _ b)
= do c <- contParse b
return $ Unnamed "MutVar " : c
parseInternal b (BCOClosure _ _ _ bPtr _ _ _)
= do cPtrs <- bcoContParse [bPtr]
let tPtrs = intercalate [Unnamed ","] cPtrs
r <- lift $ countReferences b
return $ if null tPtrs then if r > 1 then [Unnamed "BCO"] else [Unnamed ""] else (if r > 1 then Unnamed "BCO(" else Unnamed "(") : tPtrs ++ [Unnamed ")"]
parseInternal b (APClosure _ _ _ fun pl) = do
name <- getSetName b
fPtr <- contParse fun
pPtrs <- mapM contParse $ reverse pl
let tPtrs = intercalate [Unnamed ","] pPtrs
sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "[" : tPtrs ++ [Unnamed "]"]
return $ Thunk (infixFix name) : fPtr ++ sPtrs
parseInternal b (PAPClosure _ _ _ fun pl) = do
name <- getSetName b
fPtr <- contParse fun
pPtrs <- mapM contParse $ reverse pl
let tPtrs = intercalate [Unnamed ","] pPtrs
sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "[" : tPtrs ++ [Unnamed "]"]
return $ Function (infixFix name) : fPtr ++ sPtrs
parseInternal b (APStackClosure _ fun pl) = do
name <- getSetName b
fPtr <- contParse fun
pPtrs <- mapM contParse $ reverse pl
let tPtrs = intercalate [Unnamed ","] pPtrs
sPtrs = if null tPtrs then [Unnamed ""] else Unnamed "[" : tPtrs ++ [Unnamed "]"]
return $ Thunk (infixFix name) : fPtr ++ sPtrs
parseInternal _ (MVarClosure _ qHead qTail qValue)
= do cHead <- liftM mbParens $ contParse qHead
cTail <- liftM mbParens $ contParse qTail
cValue <- liftM mbParens $ contParse qValue
return $ Unnamed "MVar#(" : cHead ++ [Unnamed ","] ++ cTail ++ [Unnamed ","] ++ cValue ++ [Unnamed ")"]
contParse :: Box -> MaybeT PrintState [VisObject]
contParse b = do h <- lift $ gets heapMap
(_,c) <- lookupT b h
parseClosure b c
bcoContParse :: [Box] -> MaybeT PrintState [[VisObject]]
bcoContParse [] = return []
bcoContParse (b:bs) = gets heapMap >>= \h -> case lookup b h of
Nothing -> do let ptf = unsafePerformIO $ getBoxedClosureData b >>= pointersToFollow2
r <- lift $ countReferences b
n <- getName b
case n of
Just _ -> bcoContParse bs
Nothing -> do
when (r > 1) $ setVisited b
bcoContParse $ ptf ++ bs
Just (_,c) -> do p <- parseClosure b c
ps <- bcoContParse bs
return $ p : ps
mutArrContParse :: [Box] -> MaybeT PrintState [[VisObject]]
mutArrContParse [] = return []
mutArrContParse (b:bs) = gets heapMap >>= \h -> case lookup b h of
Nothing -> mutArrContParse bs
Just (_,c) -> do p <- parseClosure b c
ps <- mutArrContParse bs
return $ p : ps
mbParens :: [VisObject] -> [VisObject]
mbParens t = if needsParens
then Unnamed "(" : t ++ [Unnamed ")"]
else t
where needsParens = go (0 :: Int) $ show t
go 0 (' ':_) = True
go _ [] = False
go c (')':ts) = go (c1) ts
go c ('(':ts) = go (c+1) ts
go c ('\'':'(':ts) = go c ts
go c ('\'':')':ts) = go c ts
go c (_:ts) = go c ts
showClosure :: Closure -> String
showClosure (ConsClosure _ _ [dataArg] _ modl name) =
case (modl, name) of
k | k `elem` [ ("GHC.Word", "W#")
, ("GHC.Word", "W8#")
, ("GHC.Word", "W16#")
, ("GHC.Word", "W32#")
, ("GHC.Word", "W64#")
] -> name ++ " " ++ show dataArg
k | k `elem` [ ("GHC.Integer.Type", "S#")
, ("GHC.Types", "I#")
, ("GHC.Int", "I8#")
, ("GHC.Int", "I16#")
, ("GHC.Int", "I32#")
, ("GHC.Int", "I64#")
] -> name ++ " " ++ show ((fromIntegral :: Word -> Int) dataArg)
("GHC.Types", "C#") -> show . chr $ fromIntegral dataArg
("GHC.Types", "D#") -> printf "D# %0.5f" (unsafeCoerce dataArg :: Double)
("GHC.Types", "F#") -> printf "F# %0.5f" (unsafeCoerce dataArg :: Double)
_ -> printf "%s %d" name dataArg
showClosure (ConsClosure (StgInfoTable 1 3 _ 0) _ [_,0,0] _ "Data.ByteString.Internal" "PS")
= "ByteString 0 0"
showClosure (ConsClosure (StgInfoTable 1 3 _ 0) [_] [_,start,end] _ "Data.ByteString.Internal" "PS")
= printf "ByteString %d %d" start end
showClosure (ConsClosure (StgInfoTable 2 3 _ 1) [_,_] [_,start,end] _ "Data.ByteString.Lazy.Internal" "Chunk")
= printf "Chunk %d %d" start end
showClosure (ConsClosure _ _ dArgs _ _ name)
= unwords $ name : map show dArgs
showClosure ThunkClosure{}
= "Thunk"
showClosure SelectorClosure{}
= "Selector"
showClosure IndClosure{}
= "Ind"
showClosure BlackholeClosure{}
= "Blackhole"
showClosure APClosure{}
= "AP"
showClosure PAPClosure{}
= "PAP"
showClosure APStackClosure{}
= "APStack"
showClosure BCOClosure{}
= "BCO"
showClosure (ArrWordsClosure _ _ arrWords)
= intercalate ",\n" $ map (printf "0x%x") arrWords
showClosure MutArrClosure{}
= "MutArr"
showClosure MutVarClosure{}
= "MutVar"
showClosure MVarClosure{}
= "MVar#"
showClosure FunClosure{}
= "Fun"
showClosure BlockingQueueClosure{}
= "BlockingQueue"
showClosure (OtherClosure (StgInfoTable _ _ cTipe _) _ _)
= show cTipe
showClosure (UnsupportedClosure (StgInfoTable _ _ cTipe _))
= show cTipe
infixFix :: String -> String
infixFix xs
| isInfix xs = '(' : xs ++ ")"
| otherwise = xs
isInfix :: String -> Bool
isInfix [] = False
isInfix ('[':_) = False
isInfix ('(':_) = False
isInfix (x:_)
| x `elem` ascSymbols = True
| isSymbol x = True
| isPunctuation x = True
| otherwise = False
where ascSymbols = "!#$%&*+./<=>?@ \\^|-~:"