{- Tock: a compiler for parallel languages Copyright (C) 2007 University of Kent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -} -- | Driver for the compiler. module Main (main) where import Control.Monad.Error import Control.Monad.State import List import System import System.Console.GetOpt import System.Directory import System.Exit import System.IO import System.Process import AnalyseAsm import CompilerCommands import CompState import Errors import GenerateC import GenerateCPPCSP import ParseOccam import ParseRain import Pass import PreprocessOccam import RainPasses import SimplifyComms import SimplifyExprs import SimplifyProcs import SimplifyTypes import Unnest commonPasses :: [(String, Pass)] commonPasses = [ ("Simplify types", simplifyTypes) , ("Simplify expressions", simplifyExprs) , ("Simplify processes", simplifyProcs) , ("Flatten nested declarations", unnest) , ("Simplify communications", simplifyComms) ] type OptFunc = CompState -> IO CompState options :: [OptDescr OptFunc] options = [ Option [] ["mode"] (ReqArg optMode "MODE") "select mode (options: parse, compile, post-c, full)" , Option [] ["backend"] (ReqArg optBackend "BACKEND") "code-generating backend (options: c, cppcsp)" , Option [] ["frontend"] (ReqArg optFrontend "FRONTEND") "language frontend (options: occam, rain)" , Option ['v'] ["verbose"] (NoArg $ optVerbose) "be more verbose (use multiple times for more detail)" , Option ['o'] ["output"] (ReqArg optOutput "FILE") "output file (default \"-\")" ] optMode :: String -> OptFunc optMode s ps = do mode <- case s of "parse" -> return ModeParse "compile" -> return ModeCompile "post-c" -> return ModePostC "full" -> return ModeFull _ -> dieIO (Nothing, "Unknown mode: " ++ s) return $ ps { csMode = mode } optBackend :: String -> OptFunc optBackend s ps = do backend <- case s of "c" -> return BackendC "cppcsp" -> return BackendCPPCSP _ -> dieIO (Nothing, "Unknown backend: " ++ s) return $ ps { csBackend = backend } optFrontend :: String -> OptFunc optFrontend s ps = do frontend <- case s of "occam" -> return FrontendOccam "rain" -> return FrontendRain _ -> dieIO (Nothing, "Unknown frontend: " ++ s) return $ ps { csFrontend = frontend } optVerbose :: OptFunc optVerbose ps = return $ ps { csVerboseLevel = csVerboseLevel ps + 1 } optOutput :: String -> OptFunc optOutput s ps = return $ ps { csOutputFile = s } getOpts :: [String] -> IO ([OptFunc], [String]) getOpts argv = case getOpt RequireOrder options argv of (o,n,[] ) -> return (o,n) (_,_,errs) -> error (concat errs ++ usageInfo header options) where header = "Usage: tock [OPTION...] SOURCEFILE" writeOccamWrapper :: Handle -> IO () writeOccamWrapper h = do write "#INCLUDE \"cifccsp.inc\"\n" write "#PRAGMA EXTERNAL \"PROC C.tock.main.init (INT raddr, CHAN BYTE in?, out!, err!) = 0\"\n" write "#PRAGMA EXTERNAL \"PROC C.tock.main.free (VAL INT raddr) = 0\"\n" write "PROC kroc.main (CHAN BYTE in?, out!, err!)\n" write " INT addr:\n" write " SEQ\n" write " C.tock.main.init (addr, in?, out!, err!)\n" write " cifccsp.startprocess (addr)\n" write " C.tock.main.free (addr)\n" write ":\n" where write = hPutStr h main :: IO () main = do argv <- getArgs (opts, args) <- getOpts argv let fn = case args of [fn] -> fn _ -> error "Must specify a single input file" initState <- foldl (>>=) (return emptyState) opts let operation = case csMode initState of ModeParse -> useOutputOptions (compile ModeParse fn) ModeCompile -> useOutputOptions (compile ModeCompile fn) ModePostC -> useOutputOptions (postCAnalyse fn) ModeFull -> evalStateT (compileFull fn) [] -- Run the compiler. v <- evalStateT (runErrorT operation) initState case v of Left e -> dieIO e Right r -> return () removeFiles :: [FilePath] -> IO () removeFiles = mapM_ (\file -> catch (removeFile file) doNothing) where doNothing :: IOError -> IO () doNothing _ = return () -- When we die inside the StateT [FilePath] monad, we should delete all the temporary files listed in the state, then die in the PassM monad: -- TODO Not totally sure this technique works if functions inside the PassM monad die, but there will only be temp files to clean up if postCAnalyse dies instance Die (StateT [FilePath] PassM) where dieReport err = do files <- get -- If removing the files fails, we don't want to die with that error; we want the user to see the original error, -- so ignore errors arising from removing the files: liftIO $ removeFiles files lift $ dieReport err compileFull :: String -> StateT [FilePath] PassM () compileFull fn = do optsPS <- lift get destBin <- case csOutputFile optsPS of "-" -> die "Must specify an output file when using full-compile mode" file -> return file -- First, compile the code into C/C++: tempCPath <- execWithTempFile "tock-temp-c" (compile ModeCompile fn) -- Then, compile the C/C++: case csBackend optsPS of -- Compile the C into an object file: BackendC -> do exec $ cCommand tempCPath (tempCPath ++ ".o") noteFile (tempCPath ++ ".o") -- Compile the same C into assembly: exec $ cAsmCommand tempCPath (tempCPath ++ ".s") noteFile (tempCPath ++ ".s") -- Analyse the assembly for stack sizes, and output a "post" C file: tempCPathPost <- execWithTempFile "tock-temp-post-c" (postCAnalyse (tempCPath ++ ".s")) -- Compile this new "post" C file into an object file: exec $ cCommand tempCPathPost (tempCPathPost ++ ".o") noteFile (tempCPathPost ++ ".o") -- Create a temporary occam file, and write the standard occam wrapper into it: tempPathOcc <- execWithTempFile "tock-temp-occ.occ" (liftIO . writeOccamWrapper) -- Use kroc to compile and link the occam file with the two object files from the C compilation: exec $ krocLinkCommand tempPathOcc [(tempCPath ++ ".o"),(tempCPathPost ++ ".o")] destBin -- For C++, just compile the source file directly into a binary: BackendCPPCSP -> exec $ cxxCommand tempCPath destBin -- Finally, remove the temporary files: tempFiles <- get liftIO $ removeFiles tempFiles where noteFile :: Monad m => FilePath -> StateT [FilePath] m () noteFile fp = modify (\fps -> (fp:fps)) -- Takes a temporary file pattern, a function to do something with that file, and returns the path of the now-closed temporary file execWithTempFile' :: String -> (Handle -> PassM ()) -> PassM FilePath execWithTempFile' pat func = do (path,handle) <- liftIO $ openTempFile "." pat func handle liftIO $ hClose handle return path execWithTempFile :: String -> (Handle -> PassM ()) -> StateT [FilePath] PassM FilePath execWithTempFile pat func = do file <- lift $ execWithTempFile' pat func noteFile file return file exec :: String -> StateT [FilePath] PassM () exec cmd = do lift $ progress $ "Executing command: " ++ cmd p <- liftIO $ runCommand cmd exitCode <- liftIO $ waitForProcess p case exitCode of ExitSuccess -> return () ExitFailure n -> die $ "Command \"" ++ cmd ++ "\" failed, exiting with code: " ++ show n -- | Picks out the handle from the options and passes it to the function: useOutputOptions :: (Handle -> PassM ()) -> PassM () useOutputOptions func = do optsPS <- get case csOutputFile optsPS of "-" -> func stdout file -> do progress $ "Writing output file " ++ file f <- liftIO $ openFile file WriteMode func f liftIO $ hClose f -- | Compile a file. -- This is written in the PassM monad -- as are most of the things it calls -- -- because then it's very easy to pass the state around. compile :: CompMode -> String -> Handle -> PassM () compile mode fn outHandle = do optsPS <- get debug "{{{ Parse" progress "Parse" ast1 <- case csFrontend optsPS of FrontendOccam -> preprocessOccamProgram fn >>= parseOccamProgram FrontendRain -> parseRainProgram fn debugAST ast1 debug "}}}" showWarnings output <- case mode of ModeParse -> return $ show ast1 ModeCompile -> do progress "Passes:" let passes = concat [ if csFrontend optsPS == FrontendRain then rainPasses else [] , commonPasses , case csBackend optsPS of BackendC -> genCPasses BackendCPPCSP -> genCPPCSPPasses ] ast2 <- runPasses passes ast1 debug "{{{ Generate code" let generator = case csBackend optsPS of BackendC -> generateC BackendCPPCSP -> generateCPPCSP code <- generator ast2 debug "}}}" return code showWarnings liftIO $ hPutStr outHandle output progress "Done" -- | Analyse an assembly file. postCAnalyse :: String -> Handle -> PassM () postCAnalyse fn outHandle = do asm <- liftIO $ readFile fn progress "Analysing assembly" output <- analyseAsm asm showWarnings liftIO $ hPutStr outHandle output