\begin{code}
\end{code}
The file is part of the Haskell Object Observation Debugger,
(HOOD) March 2010 release.
HOOD is a small post-mortem debugger for the lazy functional
language Haskell. It is based on the concept of observation of
intermediate data structures, rather than the more traditional
stepping and variable examination paradigm used by imperative
language debuggers.
Copyright (c) Andy Gill, 1992-2000
Copyright (c) The University of Kansas 2010
Copyright (c) Maarten Faddegon, 2013-2015
All rights reserved. HOOD is distributed as free software under
the license in the file "License", which available from the HOOD
web page, http://www.haskell.org/hood
This module produces CDS's, based on the observation made on Haskell
objects, including base types, constructors and functions.
WARNING: unrestricted use of unsafePerformIO below.
This was ported for the version found on www.haskell.org/hood.
%************************************************************************
%* *
\subsection{Exports}
%* *
%************************************************************************
\begin{code}
module Debug.Hoed.Observe
where
\end{code}
%************************************************************************
%* *
\subsection{Imports and infixing}
%* *
%************************************************************************
\begin{code}
import Prelude hiding (Right)
import qualified Prelude
import Control.Concurrent.MVar
import Control.Monad
import Data.Array as Array
import qualified Data.HashTable.IO as H
import Data.IORef
import Data.List (sortOn)
import Data.Maybe
import Data.Monoid ((<>))
import Data.Proxy
import Data.Rope.Mutable (Rope, new', write, reset)
import Data.Strict.Tuple (Pair(..))
import Data.Text (Text, pack)
import qualified Data.Vector as V
import qualified Data.Vector.Generic as VG
import Data.Vector.Unboxed (Vector)
import Data.Vector.Unboxed.Deriving
import Data.Vector.Unboxed.Mutable (MVector)
import Data.Word
import Debug.Hoed.Fields
import Debug.Trace
import GHC.Generics
import Data.IORef
import System.IO.Unsafe
\end{code}
\begin{code}
import qualified Control.Exception as Exception
import Control.Exception (throw, SomeException(..))
import Data.Dynamic ( Dynamic )
\end{code}
%************************************************************************
%* *
\subsection{Event stream}
%* *
%************************************************************************
Trival output functions
\begin{code}
type UID = Int
data Event = Event { eventParent :: !Parent
, change :: !Change }
deriving (Eq,Generic)
data EventWithId = EventWithId {eventUID :: !UID, event :: !Event}
data Change
= Observe !Text
| Cons !Word8 !Text
| ConsChar !Char
| Enter
| Fun
deriving (Eq, Show,Generic)
type ParentPosition = Word8
data Parent = Parent
{ parentUID :: !UID
, parentPosition :: !ParentPosition
} deriving (Eq,Generic)
instance Show Event where
show e = (show . change $ e) ++ " (" ++ (show . eventParent $ e) ++ ")"
instance Show EventWithId where
show (EventWithId uid e) = (show uid) ++ ": " ++ (show . change $ e) ++ " (" ++ (show . eventParent $ e) ++ ")"
instance Show Parent where
show p = "P " ++ (show . parentUID $ p) ++ " " ++ (show . parentPosition $ p)
root = Parent (1) 0
isRootEvent :: Event -> Bool
isRootEvent e = case change e of Observe{} -> True; _ -> False
\end{code}
\begin{code}
type Trace = Vector Event
endEventStream :: IO Trace
endEventStream = do
(stringsCount :!: stringsHashTable) <- takeMVar strings
unsortedStrings <- H.toList stringsHashTable
putMVar strings . (0 :!:) =<< H.new
let stringsTable = V.unsafeAccum (\_ -> id) (V.replicate stringsCount (error "uninitialized")) [(i,s) | (s,i) <- unsortedStrings]
writeIORef stringsLookupTable stringsTable
reset (Proxy :: Proxy Vector) events
sendEvent :: Int -> Parent -> Change -> IO ()
sendEvent nodeId !parent !change = do
write events nodeId (Event parent change)
lookupOrAddString s = do
(stringsCount :!: stringsTable) <- takeMVar strings
value <- H.lookup stringsTable s
(count',res) <- case value of
Just x -> return (stringsCount, x)
Nothing -> H.insert stringsTable s stringsCount >> return (stringsCount+1, stringsCount)
putMVar strings (count' :!: stringsTable)
return res
events :: Rope IO MVector Event
events = unsafePerformIO $ do
rope <- new' 10000
return rope
strings :: MVar(Pair Int (H.CuckooHashTable Text Int))
strings = unsafePerformIO $ do
h <- H.newSized 100000
newMVar (0 :!: h)
stringsLookupTable :: IORef (V.Vector Text)
stringsLookupTable = unsafePerformIO $ newIORef mempty
lookupString id = unsafePerformIO $ (V.! id) <$> readIORef stringsLookupTable
derivingUnbox "Change"
[t| Change -> (Word8, Word8, Int) |]
[| \case
Observe s -> (0,0,unsafePerformIO(lookupOrAddString s))
Cons c s -> (1,c,unsafePerformIO(lookupOrAddString s))
ConsChar c -> (2,0,fromEnum c)
Enter -> (3,0,0)
Fun -> (4,0,0)
|]
[| \case (0,_,s) -> Observe (lookupString s)
(1,c,s) -> Cons c (lookupString s)
(2,_,c) -> ConsChar (toEnum c)
(3,_,_) -> Enter
(4,_,_) -> Fun
|]
derivingUnbox "Parent"
[t| Parent -> (UID, ParentPosition) |]
[| \ (Parent a b) -> (a,b) |]
[| \ (a,b) -> Parent a b |]
derivingUnbox "Event"
[t| Event -> (Parent, Change) |]
[| \(Event a b) -> (a,b) |]
[| \ (a,b) -> Event a b |]
\end{code}
%************************************************************************
%* *
\subsection{unique name supply code}
%* *
%************************************************************************
Use the single threaded version
\begin{code}
initUniq :: IO ()
initUniq = writeIORef uniq 1
getUniq :: IO UID
getUniq = atomicModifyIORef' uniq (\n -> (n+1,n))
peepUniq :: IO UID
peepUniq = readIORef uniq
uniq :: IORef UID
uniq = unsafePerformIO $ newIORef 0
\end{code}
%************************************************************************
%* *
\subsection{GDM Generics}
%* *
%************************************************************************
The generic implementation of the observer function.
\begin{code}
class Observable a where
observer :: a -> Parent -> a
default observer :: (Generic a, GObservable (Rep a)) => a -> Parent -> a
observer x c = to (gdmobserver (from x) c)
constrain :: a -> a -> a
default constrain :: (Generic a, GConstrain (Rep a)) => a -> a -> a
constrain x c = to (gconstrain (from x) (from c))
class GObservable f where
gdmobserver :: f a -> Parent -> f a
gdmObserveArgs :: f a -> ObserverM (f a)
gdmShallowShow :: f a -> Text
constrainBase :: (Show a, Eq a) => a -> a -> a
constrainBase x c | x == c = x
| otherwise = error $ show x ++ " constrained by " ++ show c
\end{code}
A type generic definition of constrain
\begin{code}
class GConstrain f where gconstrain :: f a -> f a -> f a
instance (GConstrain a, GConstrain b) => GConstrain (a :+: b) where
gconstrain (L1 x) (L1 c) = L1 (gconstrain x c)
gconstrain (R1 x) (R1 c) = R1 (gconstrain x c)
instance (GConstrain a, GConstrain b) => GConstrain (a :*: b) where
gconstrain (x :*: y) (c :*: d) = (gconstrain x c) :*: (gconstrain y d)
instance GConstrain U1 where
gconstrain x c = x
instance (Observable a) => GConstrain (K1 i a) where
gconstrain (K1 x) (K1 c) = K1 (constrain x c)
instance (GConstrain a) => GConstrain (M1 D d a) where
gconstrain (M1 x) (M1 c) = M1 (gconstrain x c)
instance (GConstrain a, Selector s) => GConstrain (M1 S s a) where
gconstrain m@(M1 x) n@(M1 c) | selName m == selName n = M1 (gconstrain x c)
instance (GConstrain a, Constructor c) => GConstrain (M1 C c a) where
gconstrain m@(M1 x) n@(M1 c) | conName m == conName n = M1 (gconstrain x c)
\end{code}
Observing the children of Data types of kind *.
\begin{code}
instance (FieldLimit ('S ('S ('S ('S ('S ('S 'Z)))))) a, GObservable a) => GObservable (M1 D d a) where
gdmobserver m@(M1 x) cxt = M1 (gdmobserver x cxt)
gdmObserveArgs = gthunk
gdmShallowShow = error "gdmShallowShow not defined on the <<data meta type>>"
instance (GObservable a, Selector s) => GObservable (M1 S s a) where
gdmobserver (M1 x) cxt = M1 (gdmobserver x cxt)
gdmObserveArgs = gthunk
gdmShallowShow = error "gdmShallowShow not defined on the <<selector meta type>>"
instance (GObservable a, Constructor c) => GObservable (M1 C c a) where
gdmobserver m1 = send (gdmShallowShow m1) (gdmObserveArgs m1)
gdmObserveArgs (M1 x) = do {x' <- gdmObserveArgs x; return (M1 x')}
gdmShallowShow = pack . conName
instance GObservable U1 where
gdmobserver x _ = x
gdmObserveArgs = return
gdmShallowShow = error "gdmShallowShow not defined on <<the unit type>>"
instance (GObservable a, GObservable b) => GObservable (a :+: b) where
gdmobserver (L1 x) = send (gdmShallowShow x) (gdmObserveArgs $ L1 x)
gdmobserver (R1 x) = send (gdmShallowShow x) (gdmObserveArgs $ R1 x)
gdmShallowShow (L1 x) = gdmShallowShow x
gdmShallowShow (R1 x) = gdmShallowShow x
gdmObserveArgs (L1 x) = do {x' <- gdmObserveArgs x; return (L1 x')}
gdmObserveArgs (R1 x) = do {x' <- gdmObserveArgs x; return (R1 x')}
instance (GObservable a, GObservable b) => GObservable (a :*: b) where
gdmobserver (a :*: b) cxt = (gdmobserver a cxt) :*: (gdmobserver b cxt)
gdmObserveArgs (a :*: b) = do
a' <- gdmObserveArgs a
b' <- gdmObserveArgs b
return (a' :*: b')
gdmShallowShow = error "gdmShallowShow not defined on <<the product type>>"
instance (Observable a) => GObservable (K1 i a) where
gdmobserver (K1 x) cxt = K1 $ observer x cxt
gdmObserveArgs = gthunk
gdmShallowShow = error "gdmShallowShow not defined on <<the constant type>>"
\end{code}
Observing functions is done via the ad-hoc mechanism, because
we provide an instance definition the default is ignored for
this type.
\begin{code}
instance (Observable a,Observable b) => Observable (a -> b) where
observer fn cxt arg = gdmFunObserver cxt fn arg
constrain = error "how to constrain the function type?"
\end{code}
Observing the children of Data types of kind *->*.
\begin{code}
gdmFunObserver :: (Observable a,Observable b) => Parent -> (a->b) -> (a->b)
gdmFunObserver cxt fn arg
= sendObserveFnPacket
(do arg' <- thunk observer arg
thunk observer (fn arg')
) cxt
\end{code}
%************************************************************************
%* *
\subsection{Instances}
%* *
%************************************************************************
The Haskell Base types
\begin{code}
instance Observable Int where observer = observeBase
constrain = constrainBase
instance Observable Bool where observer = observeBase
constrain = constrainBase
instance Observable Integer where observer = observeBase
constrain = constrainBase
instance Observable Float where observer = observeBase
constrain = constrainBase
instance Observable Double where observer = observeBase
constrain = constrainBase
instance Observable Char where
observer lit cxt = seq lit $ unsafeWithUniq $ \node -> do
sendEvent node cxt (ConsChar lit)
return lit
constrain = constrainBase
instance Observable () where observer = observeOpaque "()"
constrain = constrainBase
observeBase :: (Show a) => a -> Parent -> a
observeBase lit cxt = seq lit $ send (pack $ show lit) (return lit) cxt
observeOpaque :: Text -> a -> Parent -> a
observeOpaque str val cxt = seq val $ send str (return val) cxt
\end{code}
The Constructors.
\begin{code}
instance (Observable a,Observable b) => Observable (a,b) where
observer (a,b) = send "," (return (,) << a << b)
instance (Observable a,Observable b,Observable c) => Observable (a,b,c) where
observer (a,b,c) = send "," (return (,,) << a << b << c)
instance (Observable a,Observable b,Observable c,Observable d)
=> Observable (a,b,c,d) where
observer (a,b,c,d) = send "," (return (,,,) << a << b << c << d)
instance (Observable a,Observable b,Observable c,Observable d,Observable e)
=> Observable (a,b,c,d,e) where
observer (a,b,c,d,e) = send "," (return (,,,,) << a << b << c << d << e)
instance (Observable a) => Observable [a] where
observer (a:as) = send ":" (return (:) << a << as)
observer [] = send "[]" (return [])
instance (Observable a) => Observable (Maybe a) where
observer (Just a) = send "Just" (return Just << a)
observer Nothing = send "Nothing" (return Nothing)
instance (Observable a,Observable b) => Observable (Either a b) where
observer (Left a) = send "Left" (return Left << a)
observer (Prelude.Right a) = send "Right" (return Prelude.Right << a)
\end{code}
Arrays.
\begin{code}
instance (Ix a,Observable a,Observable b) => Observable (Array.Array a b) where
observer arr = send "array" (return Array.array << Array.bounds arr
<< Array.assocs arr
)
constrain = undefined
\end{code}
IO monad.
\begin{code}
instance (Observable a) => Observable (IO a) where
observer fn cxt =
do res <- fn
send "<IO>" (return return << res) cxt
constrain = undefined
\end{code}
The Exception *datatype* (not exceptions themselves!).
\begin{code}
instance Observable SomeException where
observer e = send ("<Exception> " <> pack(show e)) (return e)
constrain = undefined
instance Observable Dynamic where
observer = observeOpaque "<Dynamic>"
constrain = undefined
\end{code}
%************************************************************************
%* *
\subsection{Classes and Data Definitions}
%* *
%************************************************************************
%************************************************************************
%* *
\subsection{The ObserveM Monad}
%* *
%************************************************************************
The Observer monad, a simple state monad,
for placing numbers on sub-observations.
\begin{code}
newtype ObserverM a = ObserverM { runMO :: Int -> Word8 -> (a,Word8) }
instance Functor ObserverM where
fmap = liftM
#if __GLASGOW_HASKELL__ >= 710
instance Applicative ObserverM where
pure = return
(<*>) = ap
#endif
instance Monad ObserverM where
return a = ObserverM (\ c i -> (a,i))
fn >>= k = ObserverM (\ c i ->
case runMO fn c i of
(r,i2) -> runMO (k r) c i2
)
thunk :: (a -> Parent -> a) -> a -> ObserverM a
thunk f a = ObserverM $ \ parent port ->
( observer_ f a (Parent
{ parentUID = parent
, parentPosition = port
})
, port+1 )
gthunk :: (GObservable f) => f a -> ObserverM (f a)
gthunk a = ObserverM $ \ parent port ->
( gdmobserver_ a (Parent
{ parentUID = parent
, parentPosition = port
})
, port+1 )
(<<) :: (Observable a) => ObserverM (a -> b) -> a -> ObserverM b
fn << a = gdMapM (thunk observer) fn a
infixl 9 <<
gdMapM :: (Monad m)
=> (a -> m a)
-> m (a -> b)
-> a
-> m b
gdMapM f c a = do { c' <- c ; a' <- f a ; return (c' a') }
\end{code}
%************************************************************************
%* *
\subsection{observe and friends}
%* *
%************************************************************************
Our principle function and class
\begin{code}
gobserve :: (a->Parent->a) -> Text -> a -> (a,Int)
gobserve f name a = generateContext f name a
observe :: (Observable a) => Text -> a -> a
observe lbl = fst . (gobserve observer lbl)
observer_ :: (a -> Parent -> a) -> a -> Parent -> a
observer_ f a context = sendEnterPacket f a context
gdmobserver_ :: (GObservable f) => f a -> Parent -> f a
gdmobserver_ a context = gsendEnterPacket a context
\end{code}
The functions that output the data. All are dirty.
\begin{code}
unsafeWithUniq :: (Int -> IO a) -> a
unsafeWithUniq fn
= unsafePerformIO $ do { node <- getUniq
; fn node
}
\end{code}
\begin{code}
generateContext :: (a->Parent->a) -> Text -> a -> (a,Int)
generateContext f label orig = unsafeWithUniq $ \node ->
do sendEvent node root (Observe label)
return (observer_ f orig (Parent
{ parentUID = node
, parentPosition = 0
})
, node)
send :: Text -> ObserverM a -> Parent -> a
send consLabel fn context = unsafeWithUniq $ \ node ->
do { let (r,portCount) = runMO fn node 0
; sendEvent node context (Cons portCount consLabel)
; return r
}
sendEnterPacket :: (a -> Parent -> a) -> a -> Parent -> a
sendEnterPacket f r context = unsafeWithUniq $ \ node ->
do { sendEvent node context Enter
; ourCatchAllIO (evaluate (f r context))
(handleExc context)
}
gsendEnterPacket :: (GObservable f) => f a -> Parent -> f a
gsendEnterPacket r context = unsafeWithUniq $ \ node ->
do { sendEvent node context Enter
; ourCatchAllIO (evaluate (gdmobserver r context))
(handleExc context)
}
evaluate :: a -> IO a
evaluate a = a `seq` return a
sendObserveFnPacket :: ObserverM a -> Parent -> a
sendObserveFnPacket fn context
= unsafeWithUniq $ \ node ->
do { let (r,_) = runMO fn node 0
; sendEvent node context Fun
; return r
}
\end{code}
%************************************************************************
%* *
\subsection{Global, initualizers, etc}
%* *
%************************************************************************
-- \begin{code}
-- openObserveGlobal :: IO ()
-- openObserveGlobal =
-- do { initUniq
-- ; startEventStream
-- }
--
-- closeObserveGlobal :: IO Trace
-- closeObserveGlobal =
-- do { evs <- endEventStream
-- ; putStrLn ""
-- ; return evs
-- }
-- \end{code}
%************************************************************************
%* *
\subsection{Simulations}
%* *
%************************************************************************
Here we provide stubs for the functionally that is not supported
by some compilers, and provide some combinators of various flavors.
\begin{code}
ourCatchAllIO :: IO a -> (SomeException -> IO a) -> IO a
ourCatchAllIO = Exception.catch
handleExc :: Parent -> SomeException -> IO a
handleExc context exc = return (send (pack $ show exc) (return (throw exc)) context)
\end{code}
%************************************************************************