-- | Used by the end-user to easily give its arguments to dispatch.
module Language.Copilot.Interface (
Options(), baseOpts, test, interpret, compile, verify, interface
, help , setS, setE, setC, setO, setP, setI, setPP, setN, setV, setR
, setDir, setGCC, setTriggers,
module Language.Copilot.Dispatch
) where
import Language.Copilot.Core
import Language.Copilot.Language (opsF, opsF2, opsF3)
import Language.Copilot.Tests.Random
import Language.Copilot.Dispatch
import Language.Copilot.Help
import Data.Set as Set (fromList, toList)
import Data.List ((\\))
import System.Random
import System.Exit
import System.FilePath (takeBaseName)
import System.Cmd
import Data.Maybe
import Control.Monad
import Control.Monad.Writer (execWriter)
data Options = Options {
optStreams :: Maybe Streams, -- ^ If there's no Streams, then generate random streams.
optSends :: Sends, -- ^ For distributed monitors.
optExts :: Maybe Vars, -- ^ Assign values to external variables.
optCompile :: Maybe String, -- ^ Set gcc options.
optPeriod :: Maybe Period, -- ^ Set the period. If none is given, then the smallest feasible period is computed.
optInterpret :: Bool, -- ^ Interpreting?
optIterations :: Int, -- ^ How many iterations are we simulating for?
optVerbose :: Verbose, -- ^ Verbosity level: OnlyErrors | DefaultVerbose | Verbose.
optRandomSeed :: Maybe Int, -- ^ Random seed for generating Copilot specs.
optCName :: Name, -- ^ Name of the C file generated.
optCompiler :: String, -- ^ The C compiler to use, as a path to the executable.
optOutputDir :: String, -- ^ Where to place the output C files (.c, .h, and binary).
optPrePostCode :: Maybe (String, String), -- ^ Code to append above and below the C file.
optTriggers :: Maybe [(Var, String)] -- ^ A list of Copilot variable C
-- function name pairs. The C
-- funciton is called if the
-- Copilot stream becomes True.
-- The Stream must be of Booleans
-- and the C function must be of
-- type void @foo(void)@. There
-- should be no more than one
-- function per trigger variable.
-- Triggers fire in the same phase (1)
-- that output vars are assigned.
}
baseOpts :: Options
baseOpts = Options {
optStreams = Nothing,
optSends = emptySM,
optExts = Nothing,
optCompile = Nothing,
optPeriod = Nothing,
optInterpret = False,
optIterations = 0,
optVerbose = DefaultVerbose,
optRandomSeed = Nothing,
optCName = "copilotProgram",
optCompiler = "gcc",
optOutputDir = "./",
optPrePostCode = Nothing,
optTriggers = Nothing
}
-- Functions for making it easier for configuring copilot in the frequent use cases
test :: Int -> Options -> IO ()
test n opts =
interface $ setC "-Wall" $ setI $ setN n $ setV OnlyErrors $ opts
interpret :: Streams -> Int -> Options -> IO ()
interpret streams n opts = interface $ setI $ setN n $ opts {optStreams = Just streams}
compile :: Streams -> Name -> Options -> IO ()
compile streams fileName opts =
interface $ setC "-Wall" $ setO fileName $ opts {optStreams = Just streams}
verify :: FilePath -> IO ()
verify file = do
putStrLn "Calling cbmc, developed by Daniel Kroening \& Edmund Clarke "
putStrLn ", with the following checks:"
putStrLn " --bounds-check enable array bounds checks"
putStrLn " --div-by-zero-check enable division by zero checks"
putStrLn " --pointer-check enable pointer checks"
putStrLn " --overflow-check enable arithmetic over- and underflow checks"
putStrLn " --nan-check check floating-point for NaN"
putStrLn ""
putStrLn $ " assuming " ++ theMain ++ " as the entry point:"
putStrLn cmd
code <- system cmd
case code of
ExitSuccess -> return ()
_ -> do putStrLn $ "cbmc could not be called on file " ++ file ++ "."
putStrLn "Perhaps cbmc is not installed on your system, is"
putStrLn "not in your path, cbmc cannot be called from the"
putStrLn $ "command line on your system, or " ++ file
putStrLn $ "does not exist, or the dispatch function in" ++ file
putStrLn $ "is not called " ++ theMain ++ "."
putStrLn "See "
putStrLn "for more information on cbmc."
where theMain = takeBaseName file
cmd = unwords ("cbmc" : args)
args = ["--div-by-zero-check", "--overflow-check", "--bounds-check"
, "--nan-check", "--pointer-check"
, "--function " ++ theMain, file]
-- Small functions for easy modification of the Options record
setS :: DistributedStreams -> Options -> Options
setS (streams, sends) opts = opts {optStreams = Just streams, optSends = sends}
-- | Sets the environment for simulation by giving a mapping of external variables to lists of values. E.g.,
--
-- @ setE (emptySM {w32Map = fromList [(\"ext\", [0,1..])]}) ... @
--
-- sets the external variable names "ext" to take the natural numbers, up to the limit of Word32.
setE :: Vars -> Options -> Options
setE vs opts = opts {optExts = Just vs}
-- | Sets the options for the compiler, e.g.,
--
-- @ setC \"-O2\" ... @
--
-- Sets gcc options.
setC :: String -> Options -> Options
setC gccOptions opts = opts {optCompile = Just gccOptions}
-- | Manually set the period for the program. Otherwise, the minimum required period is computed automatically. E.g.,
--
-- @ setP 100 ... @
-- sets the period to be 100. The period must be at least 1.
setP :: Period -> Options -> Options
setP p opts = opts {optPeriod = Just p}
setI :: Options -> Options
setI opts = opts {optInterpret = True}
-- | Sets the number of iterations of the program to simulation:
--
-- @ setN 50 @
-- simulations the program for 50 steps.
setN :: Int -> Options -> Options
setN n opts = opts {optIterations = n}
-- | Set the verbosity level.
setV :: Verbose -> Options -> Options
setV v opts = opts {optVerbose = v}
setR :: Int -> Options -> Options
setR seed opts = opts {optRandomSeed = Just seed}
setO :: Name -> Options -> Options
setO fileName opts = opts {optCName = fileName}
-- | Sets the compiler to use, given as a path to the executable. The default
-- is \"gcc\".
setGCC :: String -> Options -> Options
setGCC compilerStr opts = opts {optCompiler = compilerStr}
-- | Sets the directory into which the output of compiled programs should be
-- placed. If the directory does not exist, it will be created.
setDir :: String -> Options -> Options
setDir dir opts = opts {optOutputDir = dir}
-- | Sets the code to precede and follow the copilot specification
-- If nothing, then code appropriate for communication with the interpreter is generated
setPP :: (String, String) -> Options -> Options
setPP pp opts = opts {optPrePostCode = Just pp}
-- | Give C function triggers for Copilot Boolean streams. The tiggers fire if
-- the stream becoms true.
setTriggers :: [(Var, String)] -> Options -> Options
setTriggers triggers opts =
if null repeats
then opts {optTriggers = Just triggers}
else error $ "Error: only one trigger per Copilot variable. Variables "
++ show (Set.fromList repeats) ++ " are given multiple triggers."
where vars = map fst triggers
repeats = vars \\ Set.toList (Set.fromList vars)
-- | The "main" function
interface :: Options -> IO ()
interface opts =
do
seed <- createSeed opts
let (streams, vars) = getStreamsVars opts seed
sends = optSends opts
backEnd = getBackend opts seed
iterations = optIterations opts
verbose = optVerbose opts
when (verbose == Verbose) $
putStrLn $ "Random seed :" ++ show seed
-- dispatch is doing all the heavy plumbing between
-- analyser, compiler, interpreter, gcc and the generated program
dispatch streams sends vars backEnd iterations verbose
createSeed :: Options -> IO Int
createSeed opts =
case optRandomSeed opts of
Nothing ->
do
g <- newStdGen
return . fst . random $ g
Just i -> return i
getStreamsVars :: Options -> Int -> (StreamableMaps Spec, Vars)
getStreamsVars opts seed =
case optStreams opts of
Nothing -> randomStreams opsF opsF2 opsF3 (mkStdGen seed)
Just s ->
case optExts opts of
Nothing -> (s', emptySM)
Just vs -> (s', vs)
where s' = execWriter s
getBackend :: Options -> Int -> BackEnd
getBackend opts seed =
case optCompile opts of
Nothing ->
if not $ optInterpret opts
then error "neither interpreted nor compiled: nothing to be done"
else Interpreter
Just gccOptions ->
Opts $ AtomToC {
cName = optCName opts ++ if isJust $ optRandomSeed opts then show seed else "",
gccOpts = gccOptions,
getPeriod = optPeriod opts,
interpreted = if optInterpret opts then Interpreted else NotInterpreted,
outputDir = optOutputDir opts,
compiler = optCompiler opts,
prePostCode = optPrePostCode opts,
triggers = optTriggers opts
}
help :: IO ()
help = putStrLn helpStr