ghc-core-0.2: ghc-core.hs
{-# LANGUAGE PatternGuards #-}
--------------------------------------------------------------------
-- |
-- Module : ghc-core
-- Copyright : (c) Galois, Inc. 2008
-- License : BSD3
--
-- Maintainer: Don Stewart <dons@galois.com>
-- Stability : provisional
-- Portability: Needs a few libraries from hackage.
--
--------------------------------------------------------------------
--
-- Inspect the optimised core and assembly produce by GHC.
--
-- Examples:
--
-- > ghc-core zipwith
--
-- > ghc-core -fvia-C zipwith
--
------------------------------------------------------------------------
import Control.Concurrent
import Control.Exception
import Control.Monad
import Data.List
import Data.Maybe
import System.Cmd
import System.Console.GetOpt
import System.Directory
import System.Environment
import System.Exit
import System.FilePath
import System.IO
import System.Process
import Text.Printf
import qualified Control.Exception as C
import Text.Regex.PCRE.Light.Char8
import Language.Haskell.HsColour
import Language.Haskell.HsColour.Colourise
------------------------------------------------------------------------
--
-- Command line parsing
--
data Options = Options
{ optHelp :: Bool
, optFormat :: Output
, optGhcExe :: String
} deriving (Eq, Show)
defaultOptions :: Options
defaultOptions = Options
{ optHelp = False
, optFormat = TTY
, optGhcExe = "ghc"
}
formats :: [(String, Output)]
formats = [("css", CSS), ("html", HTML), ("tty", TTY)]
options :: [OptDescr (Options -> Options)]
options =
[ Option ['f'] ["format"]
(ReqArg (\x opts -> opts { optFormat = fromString x }) "FORMAT")
("Output format " ++ formats' ++ ".")
,Option ['h'] ["help"]
(NoArg (\opts -> opts { optHelp = True }))
"Print this help message."
,Option [] ["with-ghc"]
(ReqArg (\x opts -> opts { optGhcExe = x }) "PROGRAM")
"Ghc executable to use."
]
where
fromString f = fromMaybe (formatError f) (lookup f formats)
formatError f = error $
"invalid format `" ++ f ++ "'"
++ ", must be one of " ++ formats' ++ "."
formats' = "(" ++ concat (intersperse ", " (map fst formats)) ++ ")"
parseOptions :: [String] -> IO (Options, [String])
parseOptions argv =
case getOpt RequireOrder options argv of
(o, n, []) -> let o' = foldl (flip ($)) defaultOptions o in
if optHelp o'
then do hPutStr stderr (usageInfo header options)
exitWith ExitSuccess
else return (o', n)
(_, _, es) -> ioError (userError (concat es ++ usageInfo header options))
where header = "Usage: ghc-core [OPTION...] [--] [GHC_OPTION...] [files...]"
------------------------------------------------------------------------
main :: IO ()
main = do
-- Parse command line
(opts, args) <- getArgs >>= parseOptions
-- Read colors from .hscolour
colourPrefs <- readColourPrefs
mv <- getEnvMaybe "PAGER"
let less = case mv of Just s -> s ; _ -> "less"
strs' <- polish `fmap` compileWithCore (optGhcExe opts) args
(strs,tmps) <- do
x <- readProcess "sh" ["-c","ls /tmp/ghc*/*.s | head -1"] []
case x of
Left _ -> return (strs', Nothing)
Right s -> if "-fvia-C" `elem` args
then do asm <- readFile (init s)
return ((strs' ++ asm), Just $ takeDirectory s)
else return (strs', Just $ takeDirectory s)
let nice = hscolour
(optFormat opts)
colourPrefs False True False strs
bracket
(openTempFile "/tmp" "ghc-core-XXXX.hcr")
(\(f,h) -> do hClose h
removeFile f
case tmps of
Just g -> system ("rm -rf " ++ g) >> return ()
_ -> return ()
)
(\(f,h) -> do
hPutStrLn h nice >> hFlush h
e <- system $ less ++ " -r " ++ f
exitWith e)
--
-- Clean up the output with some regular expressions.
--
polish :: String -> String
polish = unlines . dups . map polish' . lines
where
polish' [] = []
polish' s
| Just [_,a,b] <- match name s [] = polish' (a ++ b)
| Just [_,a,b] <- match local s [] = polish' (a ++ b)
| Just _ <- match core s [] = "------------------------------- Core -----------------------------------"
| Just _ <- match asm s [] = "------------------------------- Assembly -------------------------------"
| Just _ <- match junk s [] = []
| Just _ <- match junk2 s [] = []
| Just _ <- match junk3 s [] = []
| Just _ <- match junk4 s [] = []
| Just _ <- match junk5 s [] = []
| Just _ <- match junk6 s [] = []
| Just _ <- match junk7 s [] = []
| otherwise = s
-- simplify some qualified names
name = compile
"^(.*)GHC\\.[^\\.]*\\.(.*)$"
[ungreedy]
local = compile
"^(.*)Main\\.(.*)$"
[ungreedy]
-- remove boring things
core = compile "Tidy Core" [ungreedy]
asm = compile "Asm code" [ungreedy]
junk = compile "^.GlobalId" []
junk2 = compile "^.Arity .*" []
junk3 = compile "^Rec {|^end Rec" []
junk4 = compile "DmdType" []
junk5 = compile "NoCafRefs" []
junk6 = compile "^\\[\\]$" []
junk7 = compile "==========" []
-- remove duplicate blank lines
dups [] = []
dups ([]:[]:xs) = dups ([]:xs)
dups (x:xs) = x : dups xs
------------------------------------------------------------------------
compileWithCore :: String -> [String] -> IO String
compileWithCore ghc opts = do
let args = words "-O2 -keep-tmp-files -ddump-simpl -ddump-asm -ddump-simpl-stats -no-recomp --make"
x <- readProcess ghc (opts ++ args) []
case x of
Left (err,str,std) -> do
mapM_ putStrLn (lines str)
mapM_ putStrLn (lines std)
printf "GHC failed to compile %s\n" (show err)
exitWith (ExitFailure 1) -- fatal
Right str -> return str
------------------------------------------------------------------------
--
-- Strict process reading
--
readProcess :: FilePath -- ^ command to run
-> [String] -- ^ any arguments
-> String -- ^ standard input
-> IO (Either (ExitCode,String,String) String) -- ^ either the stdout, or an exitcode and any output
readProcess cmd args input = C.handle (return . handler) $ do
(inh,outh,errh,pid) <- runInteractiveProcess cmd args Nothing Nothing
output <- hGetContents outh
outMVar <- newEmptyMVar
forkIO $ (C.evaluate (length output) >> putMVar outMVar ())
errput <- hGetContents errh
errMVar <- newEmptyMVar
forkIO $ (C.evaluate (length errput) >> putMVar errMVar ())
when (not (null input)) $ hPutStr inh input
takeMVar outMVar
takeMVar errMVar
ex <- C.catch (waitForProcess pid) (\_e -> return ExitSuccess)
hClose outh
hClose inh -- done with stdin
hClose errh -- ignore stderr
return $ case ex of
ExitSuccess -> Right output
ExitFailure _ -> Left (ex, errput, output)
where
handler (C.ExitException e) = Left (e,"","")
handler e = Left (ExitFailure 1, show e, "")
-- Safe wrapper for getEnv
getEnvMaybe :: String -> IO (Maybe String)
getEnvMaybe name = handle (const $ return Nothing) (Just `fmap` getEnv name)