arbb-vm-0.1.1.4: Intel/ArbbVM/Debug.hs
{-# LANGUAGE ForeignFunctionInterface, BangPatterns, ScopedTypeVariables #-}
{-# OPTIONS -XDeriveDataTypeable -fwarn-unused-imports #-}
{-
TODO LIST:
============================================================
* Constants need to be back-translated, e.g. "ArbbOpAdd". See the
example output below which is generated by the attached
test_debug.hs.
* Appropriate headers need to be included.
* Arguments to "dbg" need to be tweaked/pruned so that they
reflect ONLY the arguments to the actual C api calls.
o Actually.... get_function_type is a particularly
challenging example because it passes arrays of types. To
be honest I'm not sure what the best solution there is.
o The strings-only hack may not be sustainable.
o Perhaps the next step would be to include more structure
and metadata in the DbgEvent. That's fine with me though --
the main thing was that I wanted DbgEvent to have Show/Read
invariance, not depend on in-memory haskell data
structures...
* The array capture mechanism needs to be implemented. A
DbgArraySnapshot needs to be created when an array is sent to
ArBB (bind?), then makeCReproducer needs to generate a big
constant C array. OR another approach is to store each array in
its binary representation in a file.
* When the whole thing works I was going to factor out your
existing debugging printed messages and implement a separate
function that consumes DbgTraces and prints those same messages.
-}
module Intel.ArbbVM.Debug
(
DbgEvent(..),
dbg, dbg0, dbgfile,
runTrace, runReproducer, makeCReproducer,
-- TEMP:
newDBGFile, printInfo
)
where
import Debug.Trace()
import Control.Concurrent
import Data.IORef
import Data.List
import qualified Data.Map as M
import System.IO()
import System.Directory
import Text.PrettyPrint.HughesPJ
import C2HS hiding (sizeOf)
-- --------------------------------------------------------------------------------
-- Globals and Datatype Definitions
-- --------------------------------------------------------------------------------
-- | Flag to disable debugging. For now this is set statically in the
-- code. It should be dynamically configurable.
debug_arbbvm = False -- True
-- | Name of the debug output file, to be placed in the current directory.
dbgfile = "debugtrace_HaskellArBB.txt"
-- | A running ArBBVM session creates a trace of debug messages that
-- can be consumed immediately or accumulated in memory.
data DbgTrace = DbgCons Int TaggedDbgEvent (MVar DbgTrace)
-- Tagged with additional message and ThreadID:
type TaggedDbgEvent = (ThreadId,DbgEvent)
data DbgEvent =
DbgStart -- A dummy event.
| DbgCall { operator :: String,
operands :: [NamedValue],
result :: NamedValue }
-- NOTE: This is an inefficent representation, we could at least use
-- binary representations for common scalar types:
-- Also we could use bytestrings here and Text.Show.ByteString:
| DbgArraySnapshot [String]
deriving (Show, Read)
-- A printed value together with a descriptive name:
type NamedValue = (String,String)
-- --------------------------------------------------------------------------------
-- Run an computation that interacts with the ArBB-VM and capture its
-- trace.
runTrace :: IO a ->IO (a,[DbgEvent])
runTrace m =
do
-- ASSUMPTION: m is a single threaded computation!
tid <- myThreadId
-- Capture the starting point for this segment of trace:
head <- readIORef global_dbg_trace_tail
-- Run the computation:
x <- m
-- Capture the ending point:
DbgCons cntr2 _ _ <- readIORef global_dbg_trace_tail
-- Read everything inbetween:
ls <- extractTrace tid cntr2 head
return (x,ls)
reproducer_file = "reproducer.c"
-- Run an ArBBVM computation, log interactions, and generate a reproducer C program.
runReproducer :: IO a ->IO a
runReproducer m = do
putStrLn$ "Begin logging ArBB-VM interactions and generating reproducer: " ++ reproducer_file
(x,ls) <- runTrace m
writeFile reproducer_file (makeCReproducer ls)
putStrLn$ "Done writing reproducer to: "++reproducer_file
return x
extractTrace :: ThreadId -> Int -> DbgTrace -> IO [DbgEvent]
extractTrace tid end trace = loop trace []
where
loop (DbgCons ctr (id,evt) tl) !acc =
if ctr == end then
return (reverse (evt:acc))
else do nxt <- readMVar tl
loop nxt (if id==id then evt:acc else acc)
-- TODO: ASYNC VERSION:
-- Here the trace is returned as a lazy list so that it may be
-- consumed concurrently with the IO computation.
-- runWithTraceAsync :: IO a -> ([DbgEvent], IO a)
-- TOFIX: Presently debug traces are accumulated via a global
-- variable. In the future it's probably better that this go in a
-- state monad!
global_dbg_trace_tail :: IORef DbgTrace
global_dbg_trace_tail = unsafePerformIO initial
where initial = do tl <- newEmptyMVar
id <- myThreadId
newIORef (DbgCons 0 (id,DbgStart) tl)
-- | IO function to log an event in the debug trace.
dbg :: (Show c) =>
String ->
[(String,String)] ->
(String, b -> c) ->
-- (Error, b, ErrorDetails) -> IO (Error, b, ErrorDetails)
(d, b, e) -> IO (d, b, e)
dbg msg inputs (nom,accf) (ec, rv, ed) =
if debug_arbbvm
then
do
id <- myThreadId
new_tl <- newEmptyMVar
let evt = DbgCall msg inputs (nom, show (accf rv))
loop = do
-- Now to add a new entry to the debug trace. Things get tricky
-- because we want to do TWO things, extend the linked list and
-- modify the global variable to point to the new tail. We could
-- use TVars to do that atomically but the following protocol
-- works as well. Which runs better under contention?
DbgCons cntr hd tl <- readIORef global_dbg_trace_tail
let newcell = DbgCons (cntr+1) (id,evt) new_tl
success <- tryPutMVar tl newcell
if success then
-- If we succeed then we have the right to repoint the global:
writeIORef global_dbg_trace_tail newcell
-- If we fail to fill the tail then someone else beat us to it and we retry:
else loop
loop
putStrLn$ ", "++ show evt
-- TEMP: Keeping the old-style print messages for now as well:
appendFile dbgfile $
msg ++ concatMap printInfo inputs ++
"-> {" ++ nom ++ " = " ++ show (accf rv) ++ " }" ++ "\n"
return (ec, rv, ed)
else
return (ec, rv, ed)
-- | Log a call to a function without a return value.
dbg0 msg inputs (ec,ed) =
do
(a,b,c) <- dbg msg inputs ("unit", id) (ec,(),ed)
return (a,c)
-- --------------------------------------------------------------------------------
-- | Generate a C file that reproduces a logged interaction with the
-- ArBB VM. This function uses some dangerous heuristics and while
-- it is useful for debugging it should not be relied upon for
-- production purposes.
makeCReproducer :: [DbgEvent] -> String
makeCReproducer log = render doc
-- do let hndl = stdout
-- hPutStrLn hndl "int main() {"
where
doc =
text "#include <stdio.h>" $$
text "int main() {" $$
nest 4 (loop M.empty log) $$
text "}\n"
loop mp [] = empty
loop mp (DbgStart:tl) = loop mp tl
loop mp (DbgCall oper rands result : tl) =
let
-- rands' = map (text . show . snd) rands
rands' = map (text . dorand) rands
dorand (_,r) = if isPtr r
then case M.lookup r mp of
Nothing -> r
Just name -> name
else r
(mp2,prefix) =
if isPtr (snd result)
then let counter = M.size mp
name = "v"++ show counter
in
(M.insert (snd result) name mp,
"void* "++name++" = ")
else (mp,"")
in
text prefix <>
text oper <>
parens (hcat$ intersperse comma rands') <>
text ";" $$
loop mp2 tl
-- Hackish:
isPtr = isPrefixOf "0x"
newDBGFile x =
do
b <- doesFileExist dbgfile
if b then removeFile dbgfile else return ()
return x
printInfo ::(String, String) -> String
printInfo (nom,val) =
"{" ++ nom ++ " = " ++ val ++ " }"