{-# LANGUAGE FlexibleContexts #-} -- | Interpreter for Boogie 2 module Language.Boogie.Interpreter ( -- * Executing programs executeProgram, -- * State Value (..), Environment (..), emptyEnv, lookupFunction, lookupProcedure, modifyTypeContext, setV, setAll, -- * Executions Execution, SafeExecution, execSafely, execUnsafely, -- * Run-time failures FailureSource (..), InternalCode, StackFrame (..), StackTrace, RuntimeFailure (..), FailureKind (..), failureKind, -- * Executing parts of programs eval, exec, execProcedure, collectDefinitions, -- * Pretty-printing valueDoc, varsDoc, functionsDoc, runtimeFailureDoc ) where import Language.Boogie.AST import Language.Boogie.Util import Language.Boogie.Intervals import Language.Boogie.Position import Language.Boogie.Tokens (nonIdChar) import Language.Boogie.PrettyPrinter import Language.Boogie.TypeChecker import Language.Boogie.NormalForm import Language.Boogie.BasicBlocks import Data.List import Data.Map (Map, (!)) import qualified Data.Map as M import Control.Monad.Error hiding (join) import Control.Applicative hiding (empty) import Control.Monad.State hiding (join) import Text.PrettyPrint {- Interface -} -- | 'executeProgram' @p tc entryPoint@ : -- Execute program @p@ in type context @tc@ starting from procedure @entryPoint@, -- and return the final environment; -- requires that @entryPoint@ have no in- or out-parameters executeProgram :: Program -> Context -> Id -> Either RuntimeFailure Environment executeProgram p tc entryPoint = finalEnvironment where initEnvironment = emptyEnv { envTypeContext = tc } finalEnvironment = case runState (runErrorT programExecution) initEnvironment of (Left err, _) -> Left err (_, env) -> Right env programExecution = do execUnsafely $ collectDefinitions p execCall [] entryPoint [] noPos {- State -} -- | Run-time value data Value = IntValue Integer | -- ^ Integer value BoolValue Bool | -- ^ Boolean value MapValue (Map [Value] Value) | -- ^ Value of a map type CustomValue Integer -- ^ Value of a user-defined type (values with the same code are considered equal) deriving (Eq, Ord) -- | Default value of a type (used to initialize variables) defaultValue :: Type -> Value defaultValue BoolType = BoolValue False defaultValue IntType = IntValue 0 defaultValue (MapType _ _ _) = MapValue M.empty defaultValue (Instance _ _) = CustomValue 0 -- | Pretty-printed value valueDoc :: Value -> Doc valueDoc (IntValue n) = integer n valueDoc (BoolValue False) = text "false" valueDoc (BoolValue True) = text "true" valueDoc (MapValue m) = brackets (commaSep (map itemDoc (M.toList m))) where itemDoc (keys, v) = commaSep (map valueDoc keys) <+> text "->" <+> valueDoc v valueDoc (CustomValue n) = text "custom_" <> integer n instance Show Value where show v = show (valueDoc v) -- | Execution state data Environment = Environment { envLocals :: Map Id Value, -- ^ Local variable names to values envGlobals :: Map Id Value, -- ^ Global variable names to values envOld :: Map Id Value, -- ^ Global variable names to old values (in two-state contexts) envConstants :: Map Id Expression, -- ^ Constant names to expressions envFunctions :: Map Id [FDef], -- ^ Function names to definitions envProcedures :: Map Id [PDef], -- ^ Procedure names to definitions envTypeContext :: Context -- ^ Type context } -- | Empty environment emptyEnv = Environment { envLocals = M.empty, envGlobals = M.empty, envOld = M.empty, envConstants = M.empty, envFunctions = M.empty, envProcedures = M.empty, envTypeContext = emptyContext } -- | 'lookupFunction' @id env@ : All definitions of function @id@ in @env@ lookupFunction id env = case M.lookup id (envFunctions env) of Nothing -> [] Just defs -> defs -- | 'lookupProcedure' @id env@ : All definitions of procedure @id@ in @env@ lookupProcedure id env = case M.lookup id (envProcedures env) of Nothing -> [] Just defs -> defs setGlobal id val env = env { envGlobals = M.insert id val (envGlobals env) } setLocal id val env = env { envLocals = M.insert id val (envLocals env) } addConstantDef id def env = env { envConstants = M.insert id def (envConstants env) } addFunctionDefs id defs env = env { envFunctions = M.insert id (lookupFunction id env ++ defs) (envFunctions env) } addProcedureDef id def env = env { envProcedures = M.insert id (def : (lookupProcedure id env)) (envProcedures env) } modifyTypeContext f env = env { envTypeContext = f (envTypeContext env) } -- | Pretty-printed mapping of variables to values varsDoc :: Map Id Value -> Doc varsDoc vars = vsep $ map varDoc (M.toList vars) where varDoc (id, val) = text id <+> text "=" <+> valueDoc val -- | Pretty-printed set of function definitions functionsDoc :: Map Id [FDef] -> Doc functionsDoc funcs = vsep $ map funcDoc (M.toList funcs) where funcDoc (id, defs) = vsep $ map (funcsDefDoc id) defs funcsDefDoc id (FDef formals guard body) = exprDoc guard <+> text "->" <+> text id <> parens (commaSep (map text formals)) <+> text "=" <+> exprDoc body {- Executions -} -- | Computations with 'Environment' as state, which can result in either @a@ or 'RuntimeFailure' type Execution a = ErrorT RuntimeFailure (State Environment) a -- | Computations with 'Environment' as state, which always result in @a@ type SafeExecution a = State Environment a -- | 'execUnsafely' @computation@ : Execute a safe @computation@ in an unsafe environment execUnsafely :: SafeExecution a -> Execution a execUnsafely computation = ErrorT (Right <$> computation) -- | 'execSafely' @computation handler@ : Execute an unsafe @computation@ in a safe environment, handling errors that occur in @computation@ with @handler@ execSafely :: Execution a -> (RuntimeFailure -> SafeExecution a) -> SafeExecution a execSafely computation handler = do eres <- runErrorT computation either handler return eres -- | Computations that perform a cleanup at the end class Monad m => Finalizer m where finally :: m a -> m () -> m a instance (Monad m) => Finalizer (StateT s m) where finally main cleanup = do res <- main cleanup return res instance (Error e, Monad m) => Finalizer (ErrorT e m) where finally main cleanup = do res <- main `catchError` (\err -> cleanup >> throwError err) cleanup return res -- | 'setV' @id val@ : set value of variable @id@ to @val@; -- @id@ has to be declared in the current type context setV id val = do tc <- gets envTypeContext if M.member id (localScope tc) then modify $ setLocal id val else modify $ setGlobal id val -- | 'setAll' @ids vals@ : set values of variables @ids@ to @vals@; -- all @ids@ have to be declared in the current type context setAll ids vals = zipWithM_ setV ids vals -- | Run execution in the old environment old :: Execution a -> Execution a old execution = do env <- get put env { envGlobals = envOld env } res <- execution put env return res -- | Save current values of global variables in the "old" environment, return the previous "old" environment saveOld :: Execution (Map Id Value) saveOld = do env <- get put env { envOld = envGlobals env } return $ envOld env -- | Set the "old" environment to olds restoreOld :: Map Id Value -> Execution () restoreOld olds = do env <- get put env { envOld = olds } -- | Enter local scope (apply localTC to the type context and assign actuals to formals), -- execute computation, -- then restore type context and local variables to their initial values executeLocally :: (MonadState Environment m, Finalizer m) => (Context -> Context) -> [Id] -> [Value] -> m a -> m a executeLocally localTC formals actuals computation = do oldEnv <- get modify $ modifyTypeContext localTC setAll formals actuals computation `finally` unwind oldEnv where -- | Restore type context and the values of local variables unwind oldEnv = do env <- get put env { envTypeContext = envTypeContext oldEnv, envLocals = envLocals oldEnv } {- Nondeterminism -} -- | Generate a value of type t, -- such that when it is set, guard does not fail. -- Fail if cannot find such a value. -- (So far just returns the default value, but will be more elaborate in the future) generateValue :: Type -> (Value -> Execution ()) -> (Execution ()) -> Execution Value generateValue t set guard = let newValue = defaultValue t in do set newValue guard return newValue {- Runtime failures -} data FailureSource = SpecViolation SpecClause | -- ^ Violation of user-defined specification DivisionByZero | -- ^ Division by zero UnsupportedConstruct String | -- ^ Language construct is not yet supported (should disappear in later versions) InfiniteDomain Id Interval | -- ^ Quantification over an infinite set NoImplementation Id | -- ^ Call to a procedure with no implementation InternalFailure InternalCode -- ^ Must be cought inside the interpreter and never reach the user deriving Eq -- | Information about a procedure or function call data StackFrame = StackFrame { callPos :: SourcePos, -- ^ Source code position of the call callName :: Id -- ^ Name of procedure or function } deriving Eq type StackTrace = [StackFrame] -- | Failures that occur during execution data RuntimeFailure = RuntimeFailure { rtfSource :: FailureSource, -- ^ Source of the failure rtfPos :: SourcePos, -- ^ Location where the failure occurred rtfEnv :: Environment, -- ^ Environment at the time of failure rtfTrace :: StackTrace -- ^ Stack trace from the program entry point to the procedure where the failure occurred } -- | Throw a run-time failure throwRuntimeFailure source pos = do env <- get throwError (RuntimeFailure source pos env []) -- | Push frame on the stack trace of a runtime failure addStackFrame frame (RuntimeFailure source pos env trace) = throwError (RuntimeFailure source pos env (frame : trace)) -- | Kinds of run-time failures data FailureKind = Error | -- ^ Error state reached (assertion violation) Unreachable | -- ^ Unreachable state reached (assumption violation) Nonexecutable -- ^ The state is OK in Boogie semantics, but the execution cannot continue due to the limitations of the interpreter deriving Eq -- | Kind of a run-time failure failureKind :: RuntimeFailure -> FailureKind failureKind err = case rtfSource err of SpecViolation (SpecClause _ True _) -> Unreachable SpecViolation (SpecClause _ False _) -> Error DivisionByZero -> Error _ -> Nonexecutable instance Error RuntimeFailure where noMsg = RuntimeFailure (UnsupportedConstruct "unknown") noPos emptyEnv [] strMsg s = RuntimeFailure (UnsupportedConstruct s) noPos emptyEnv [] -- | Pretty-printed run-time failure runtimeFailureDoc err = failureSourceDoc (rtfSource err) <+> posDoc (rtfPos err) $+$ text "with" <+> varsDoc revelantVars $+$ vsep (map stackFrameDoc (reverse (rtfTrace err))) where failureSourceDoc (SpecViolation (SpecClause specType isFree e)) = text (clauseName specType isFree) <+> doubleQuotes (exprDoc e) <+> defPosition specType e <+> text "violated" failureSourceDoc (DivisionByZero) = text "Division by zero" failureSourceDoc (InfiniteDomain var int) = text "Variable" <+> text var <+> text "quantified over an infinite domain" <+> text (show int) failureSourceDoc (NoImplementation name) = text "Procedure" <+> text name <+> text "with no implementation called" failureSourceDoc (UnsupportedConstruct s) = text "Unsupported construct" <+> text s clauseName Inline isFree = if isFree then "Assumption" else "Assertion" clauseName Precondition isFree = if isFree then "Free precondition" else "Precondition" clauseName Postcondition isFree = if isFree then "Free postcondition" else "Postcondition" clauseName LoopInvariant isFree = if isFree then "Free loop invariant" else "Loop invariant" clauseName Where True = "Where clause" -- where clauses cannot be non-free defPosition Inline _ = empty defPosition LoopInvariant _ = empty defPosition _ e = text "defined" <+> posDoc (position e) revelantVars = let env = rtfEnv err in M.filterWithKey (\k _ -> isRelevant k) (envLocals env `M.union` envGlobals env) isRelevant k = case rtfSource err of SpecViolation (SpecClause _ _ expr) -> k `elem` freeVars expr _ -> False stackFrameDoc f = text "in call to" <+> text (callName f) <+> posDoc (callPos f) posDoc pos | pos == noPos = text "from the environment" | otherwise = text "at" <+> text (sourceName pos) <+> text "line" <+> int (sourceLine pos) instance Show RuntimeFailure where show err = show (runtimeFailureDoc err) -- | Internal error codes data InternalCode = NotLinear deriving Eq throwInternalFailure code = throwRuntimeFailure (InternalFailure code) noPos {- Expressions -} -- | Semantics of unary operators unOp :: UnOp -> Value -> Value unOp Neg (IntValue n) = IntValue (-n) unOp Not (BoolValue b) = BoolValue (not b) -- | Semi-strict semantics of binary operators: -- 'binOpLazy' @op lhs@ : returns the value of @lhs op@ if already defined, otherwise Nothing binOpLazy :: BinOp -> Value -> Maybe Value binOpLazy And (BoolValue False) = Just $ BoolValue False binOpLazy Or (BoolValue True) = Just $ BoolValue True binOpLazy Implies (BoolValue False) = Just $ BoolValue True binOpLazy Explies (BoolValue True) = Just $ BoolValue True binOpLazy _ _ = Nothing -- | Strict semantics of binary operators binOp :: SourcePos -> BinOp -> Value -> Value -> Execution Value binOp pos Plus (IntValue n1) (IntValue n2) = return $ IntValue (n1 + n2) binOp pos Minus (IntValue n1) (IntValue n2) = return $ IntValue (n1 - n2) binOp pos Times (IntValue n1) (IntValue n2) = return $ IntValue (n1 * n2) binOp pos Div (IntValue n1) (IntValue n2) = if n2 == 0 then throwRuntimeFailure DivisionByZero pos else return $ IntValue (fst (n1 `euclidean` n2)) binOp pos Mod (IntValue n1) (IntValue n2) = if n2 == 0 then throwRuntimeFailure DivisionByZero pos else return $ IntValue (snd (n1 `euclidean` n2)) binOp pos Leq (IntValue n1) (IntValue n2) = return $ BoolValue (n1 <= n2) binOp pos Ls (IntValue n1) (IntValue n2) = return $ BoolValue (n1 < n2) binOp pos Geq (IntValue n1) (IntValue n2) = return $ BoolValue (n1 >= n2) binOp pos Gt (IntValue n1) (IntValue n2) = return $ BoolValue (n1 > n2) binOp pos And (BoolValue b1) (BoolValue b2) = return $ BoolValue (b1 && b2) binOp pos Or (BoolValue b1) (BoolValue b2) = return $ BoolValue (b1 || b2) binOp pos Implies (BoolValue b1) (BoolValue b2) = return $ BoolValue (b1 <= b2) binOp pos Explies (BoolValue b1) (BoolValue b2) = return $ BoolValue (b1 >= b2) binOp pos Equiv (BoolValue b1) (BoolValue b2) = return $ BoolValue (b1 == b2) binOp pos Eq v1 v2 = return $ BoolValue (v1 == v2) binOp pos Neq v1 v2 = return $ BoolValue (v1 /= v2) binOp pos Lc v1 v2 = throwRuntimeFailure (UnsupportedConstruct "orders") pos -- | Euclidean division used by Boogie for integer division and modulo euclidean :: Integer -> Integer -> (Integer, Integer) a `euclidean` b = case a `quotRem` b of (q, r) | r >= 0 -> (q, r) | b > 0 -> (q - 1, r + b) | otherwise -> (q + 1, r - b) -- | Evaluate an expression; -- can have a side-effect of initializing variables that were not previously defined eval :: Expression -> Execution Value eval expr = case node expr of TT -> return $ BoolValue True FF -> return $ BoolValue False Numeral n -> return $ IntValue n Var id -> evalVar id (position expr) Application id args -> evalApplication id args (position expr) Nothing MapSelection m args -> evalMapSelection m args (position expr) MapUpdate m args new -> evalMapUpdate m args new Old e -> old $ eval e IfExpr cond e1 e2 -> evalIf cond e1 e2 Coercion e t -> evalCoercion e t UnaryExpression op e -> unOp op <$> eval e BinaryExpression op e1 e2 -> evalBinary op e1 e2 Quantified Lambda _ _ _ -> throwRuntimeFailure (UnsupportedConstruct "lambda expressions") (position expr) Quantified Forall tv vars e -> vnot <$> evalExists tv vars (enot e) (position expr) where vnot (BoolValue b) = BoolValue (not b) Quantified Exists tv vars e -> evalExists tv vars e (position expr) evalVar id pos = do tc <- gets envTypeContext case M.lookup id (localScope tc) of Just t -> lookup envLocals setLocal t Nothing -> case M.lookup id (ctxGlobals tc) of Just t -> lookup envGlobals setGlobal t Nothing -> case M.lookup id (ctxConstants tc) of Just t -> do constants <- gets envConstants case M.lookup id constants of Just e -> eval e Nothing -> return $ defaultValue t -- ToDo: cache constant value? Nothing -> (error . show) (text "encountered unknown identifier during execution:" <+> text id) where lookup getter setter t = do vars <- gets getter case M.lookup id vars of Just val -> return val Nothing -> generateValue t (modify . setter id) (checkWhere id pos) evalApplication name args pos mRetType = do defs <- gets (lookupFunction name) evalDefs defs where -- | If the guard of one of function definitions evaluates to true, apply that definition; otherwise return the default value evalDefs :: [FDef] -> Execution Value evalDefs [] = defaultValue . returnType <$> gets envTypeContext evalDefs (FDef formals guard body : defs) = do argsV <- mapM eval args applicable <- evalLocally formals argsV guard `catchError` addStackFrame frame case applicable of BoolValue True -> evalLocally formals argsV body `catchError` addStackFrame frame BoolValue False -> evalDefs defs evalLocally formals actuals expr = do sig <- funSig name <$> gets envTypeContext executeLocally (enterFunction sig formals args mRetType) formals actuals (eval expr) returnType tc = case mRetType of Nothing -> exprType tc (gen $ Application name args) Just t -> t frame = StackFrame pos name evalMapSelection m args pos = do tc <- gets envTypeContext let rangeType = exprType tc (gen $ MapSelection m args) mV <- eval m argsV <- mapM eval args case mV of MapValue map -> case M.lookup argsV map of Nothing -> case mapVariable tc (node m) of Nothing -> return $ defaultValue rangeType -- The underlying map comes from a constant or function, nothing to check Just v -> generateValue rangeType (\_ -> return ()) (checkWhere v pos) -- The underlying map comes from a variable: check the where clause -- Decided not to cache map access so far, because it leads to strange effects when the map is passed as an argument and can take a lot of memory -- Just v -> generateValue rangeType (cache v map argsV) (checkWhere v pos) -- The underlying map comes from a variable: check the where clause and cache the value Just v -> return v where mapVariable tc (Var v) = if M.member v (allVars tc) then Just v else Nothing mapVariable tc (MapUpdate m _ _) = mapVariable tc (node m) mapVariable tc _ = Nothing -- cache m map args val = setV m (MapValue (M.insert args val map)) evalMapUpdate m args new = do mV <- eval m argsV <- mapM eval args newV <- eval new case mV of MapValue map -> return $ MapValue (M.insert argsV newV map) evalIf cond e1 e2 = do v <- eval cond case v of BoolValue True -> eval e1 BoolValue False -> eval e2 evalCoercion (Pos pos (Application f args)) t = do c <- gets envTypeContext let t' = resolve c t evalApplication f args pos (Just t') evalCoercion e _ = eval e evalBinary op e1 e2 = do left <- eval e1 case binOpLazy op left of Just result -> return result Nothing -> do right <- eval e2 binOp (position e1) op left right -- | Finite domain type Domain = [Value] evalExists :: [Id] -> [IdType] -> Expression -> SourcePos -> Execution Value evalExists tv vars e pos = do tc <- gets envTypeContext case node $ normalize tc (attachPos pos $ Quantified Exists tv vars e) of Quantified Exists tv' vars' e' -> evalExists' tv' vars' e' evalExists' :: [Id] -> [IdType] -> Expression -> Execution Value evalExists' tv vars e = do results <- executeLocally (enterQuantified tv vars) [] [] evalWithDomains return $ BoolValue (any isTrue results) where evalWithDomains = do doms <- domains e varNames evalForEach varNames doms -- | evalForEach vars domains: evaluate e for each combination of possible values of vars, drown from respective domains evalForEach :: [Id] -> [Domain] -> Execution [Value] evalForEach [] [] = replicate 1 <$> eval e evalForEach (var : vars) (dom : doms) = concat <$> forM dom (fixOne vars doms var) -- | Fix the value of var to val, then evaluate e for each combination of values for the rest of vars fixOne :: [Id] -> [Domain] -> Id -> Value -> Execution [Value] fixOne vars doms var val = do setV var val evalForEach vars doms isTrue (BoolValue b) = b varNames = map fst vars {- Statements -} -- | Execute a basic statement -- (no jump, if or while statements allowed) exec :: Statement -> Execution () exec stmt = case node stmt of Predicate specClause -> execPredicate specClause (position stmt) Havoc ids -> execHavoc ids (position stmt) Assign lhss rhss -> execAssign lhss rhss Call lhss name args -> execCall lhss name args (position stmt) CallForall name args -> return () -- ToDo: assume (forall args :: pre ==> post)? execPredicate specClause pos = do b <- eval $ specExpr specClause case b of BoolValue True -> return () BoolValue False -> throwRuntimeFailure (SpecViolation specClause) pos execHavoc ids pos = do tc <- gets envTypeContext mapM_ (havoc tc) ids where havoc tc id = generateValue (exprType tc . gen . Var $ id) (setV id) (checkWhere id pos) execAssign lhss rhss = do rVals <- mapM eval rhss' setAll lhss' rVals where lhss' = map fst (zipWith simplifyLeft lhss rhss) rhss' = map snd (zipWith simplifyLeft lhss rhss) simplifyLeft (id, []) rhs = (id, rhs) simplifyLeft (id, argss) rhs = (id, mapUpdate (gen $ Var id) argss rhs) mapUpdate e [args] rhs = gen $ MapUpdate e args rhs mapUpdate e (args1 : argss) rhs = gen $ MapUpdate e args1 (mapUpdate (gen $ MapSelection e args1) argss rhs) execCall lhss name args pos = do tc <- gets envTypeContext defs <- gets (lookupProcedure name) case defs of [] -> throwRuntimeFailure (NoImplementation name) pos def : _ -> do let lhssExpr = map (attachPos (ctxPos tc) . Var) lhss retsV <- execProcedure (procSig name tc) def args lhssExpr `catchError` addStackFrame frame setAll lhss retsV where frame = StackFrame pos name -- | Execute program consisting of blocks starting from the block labeled label. -- Return the location of the exit point. execBlock :: Map Id [Statement] -> Id -> Execution SourcePos execBlock blocks label = let block = blocks ! label statements = init block in do mapM exec statements case last block of Pos pos Return -> return pos Pos _ (Goto lbs) -> tryOneOf blocks lbs -- | tryOneOf blocks labels: try executing blocks starting with each of labels, -- until we find one that does not result in an assumption violation tryOneOf :: Map Id [Statement] -> [Id] -> Execution SourcePos tryOneOf blocks (l : lbs) = execBlock blocks l `catchError` retry where retry err | failureKind err == Unreachable && not (null lbs) = tryOneOf blocks lbs | otherwise = throwError err -- | 'execProcedure' @sig def args lhss@ : -- Execute definition @def@ of procedure @sig@ with actual arguments @args@ and call left-hand sides @lhss@ execProcedure :: PSig -> PDef -> [Expression] -> [Expression] -> Execution [Value] execProcedure sig def args lhss = let ins = pdefIns def outs = pdefOuts def blocks = snd (pdefBody def) exitPoint pos = if pos == noPos then pdefPos def -- Fall off the procedure body: take the procedure definition location else pos -- A return statement inside the body execBody = do checkPreconditions sig def olds <- saveOld pos <- exitPoint <$> execBlock blocks startLabel checkPostonditions sig def pos restoreOld olds mapM (eval . attachPos (pdefPos def) . Var) outs in do argsV <- mapM eval args executeLocally (enterProcedure sig def args lhss) ins argsV execBody {- Specs -} -- | Assert preconditions of definition def of procedure sig checkPreconditions sig def = mapM_ (exec . attachPos (pdefPos def) . Predicate . subst sig) (psigRequires sig) where subst sig (SpecClause t f e) = SpecClause t f (paramSubst sig def e) -- | Assert postconditions of definition def of procedure sig at exitPoint checkPostonditions sig def exitPoint = mapM_ (exec . attachPos exitPoint . Predicate . subst sig) (psigEnsures sig) where subst sig (SpecClause t f e) = SpecClause t f (paramSubst sig def e) -- | Assume where clause of variable at a program location pos -- (pos will be reported as the location of the failure instead of the location of the variable definition). checkWhere id pos = do whereClauses <- ctxWhere <$> gets envTypeContext case M.lookup id whereClauses of Nothing -> return () Just w -> (exec . attachPos pos . Predicate . SpecClause Where True) w {- Preprocessing -} -- | Collect constant, function and procedure definitions from the program collectDefinitions :: Program -> SafeExecution () collectDefinitions (Program decls) = mapM_ processDecl decls where processDecl (Pos _ (FunctionDecl name _ args _ (Just body))) = processFunctionBody name args body processDecl (Pos pos (ProcedureDecl name _ args rets _ (Just body))) = processProcedureBody name pos (map noWhere args) (map noWhere rets) body processDecl (Pos pos (ImplementationDecl name _ args rets bodies)) = mapM_ (processProcedureBody name pos args rets) bodies processDecl (Pos _ (AxiomDecl expr)) = processAxiom expr processDecl _ = return () processFunctionBody name args body = let formals = map (formalName . fst) args guard = gen TT in modify $ addFunctionDefs name [FDef formals guard body] where formalName Nothing = dummyFArg formalName (Just n) = n processProcedureBody name pos args rets body = do sig <- procSig name <$> gets envTypeContext modify $ addProcedureDef name (PDef argNames retNames (paramsRenamed sig) (flatten body) pos) where argNames = map fst args retNames = map fst rets flatten (locals, statements) = (concat locals, M.fromList (toBasicBlocks statements)) paramsRenamed sig = map itwId (psigParams sig) /= (argNames ++ retNames) processAxiom expr = do extractConstantDefs expr extractFunctionDefs expr [] {- Constant and function definitions -} -- | Extract constant definitions from a boolean expression bExpr extractConstantDefs :: Expression -> SafeExecution () extractConstantDefs bExpr = case node bExpr of BinaryExpression Eq (Pos _ (Var c)) rhs -> modify $ addConstantDef c rhs -- c == rhs: remember rhs as a definition for c _ -> return () -- | Extract function definitions from a boolean expression bExpr, using guards extracted from the exclosing expression. -- bExpr of the form "(forall x :: P(x, c) ==> f(x, c) == rhs(x, c) && B) && A", -- with zero or more bound variables x and zero or more constants c, -- produces a definition "f(x, x') = rhs(x, x')" with a guard "P(x) && x' == c" extractFunctionDefs :: Expression -> [Expression] -> SafeExecution () extractFunctionDefs bExpr guards = extractFunctionDefs' (node bExpr) guards extractFunctionDefs' (BinaryExpression Eq (Pos _ (Application f args)) rhs) outerGuards = do c <- gets envTypeContext -- Only possible if each argument is either a variables or does not involve variables and there are no extra variables in rhs: if all (simple c) args && closedRhs c then do let (formals, guards) = unzip (extractArgs c) let allGuards = concat guards ++ outerGuards let guard = if null allGuards then gen TT else foldl1 (|&|) allGuards modify $ addFunctionDefs f [FDef formals guard rhs] else return () where simple _ (Pos p (Var _)) = True simple c e = null $ freeVars e `intersect` M.keys (ctxIns c) closedRhs c = null $ (freeVars rhs \\ concatMap freeVars args) `intersect` M.keys (ctxIns c) extractArgs c = zipWith (extractArg c) args [0..] -- | Formal argument name and guards extracted from an actual argument at position i extractArg :: Context -> Expression -> Integer -> (String, [Expression]) extractArg c (Pos p e) i = let x = freshArgName i xExpr = attachPos p $ Var x in case e of Var arg -> if arg `M.member` ctxIns c then (arg, []) -- Bound variable of the enclosing quantifier: use variable name as formal, no additional guards else (x, [xExpr |=| Pos p e]) -- Constant: use fresh variable as formal (will only appear in the guard), add equality guard _ -> (x, [xExpr |=| Pos p e]) freshArgName i = f ++ (nonIdChar : show i) extractFunctionDefs' (BinaryExpression Implies cond bExpr) outerGuards = extractFunctionDefs bExpr (cond : outerGuards) extractFunctionDefs' (BinaryExpression And bExpr1 bExpr2) outerGuards = do extractFunctionDefs bExpr1 outerGuards extractFunctionDefs bExpr2 outerGuards extractFunctionDefs' (Quantified Forall tv vars bExpr) outerGuards = executeLocally (enterQuantified tv vars) [] [] (extractFunctionDefs bExpr outerGuards) extractFunctionDefs' _ _ = return () {- Quantification -} -- | Sets of interval constraints on integer variables type Constraints = Map Id Interval -- | The set of domains for each variable in vars, outside which boolean expression boolExpr is always false. -- Fails if any of the domains are infinite or cannot be found. domains :: Expression -> [Id] -> Execution [Domain] domains boolExpr vars = do initC <- foldM initConstraints M.empty vars finalC <- inferConstraints boolExpr initC forM vars (domain finalC) where initConstraints c var = do tc <- gets envTypeContext case M.lookup var (allVars tc) of Just BoolType -> return c Just IntType -> return $ M.insert var top c _ -> throwRuntimeFailure (UnsupportedConstruct "quantification over a map or user-defined type") (position boolExpr) domain c var = do tc <- gets envTypeContext case M.lookup var (allVars tc) of Just BoolType -> return $ map BoolValue [True, False] Just IntType -> do case c ! var of int | isBottom int -> return [] Interval (Finite l) (Finite u) -> return $ map IntValue [l..u] int -> throwRuntimeFailure (InfiniteDomain var int) (position boolExpr) -- | Starting from initial constraints, refine them with the information from boolExpr, -- until fixpoint is reached or the domain for one of the variables is empty. -- This function terminates because the interval for each variable can only become smaller with each iteration. inferConstraints :: Expression -> Constraints -> Execution Constraints inferConstraints boolExpr constraints = do constraints' <- foldM refineVar constraints (M.keys constraints) if bot `elem` M.elems constraints' then return $ M.map (const bot) constraints' -- if boolExpr does not have a satisfying assignment to one variable, then it has none to all variables else if constraints == constraints' then return constraints' -- if a fixpoint is reached, return it else inferConstraints boolExpr constraints' -- otherwise do another iteration where refineVar :: Constraints -> Id -> Execution Constraints refineVar c id = do int <- inferInterval boolExpr c id return $ M.insert id (meet (c ! id) int) c -- | Infer an interval for variable x, outside which boolean expression booExpr is always false, -- assuming all other quantified variables satisfy constraints; -- boolExpr has to be in negation-prenex normal form. inferInterval :: Expression -> Constraints -> Id -> Execution Interval inferInterval boolExpr constraints x = (case node boolExpr of FF -> return bot BinaryExpression And be1 be2 -> liftM2 meet (inferInterval be1 constraints x) (inferInterval be2 constraints x) BinaryExpression Or be1 be2 -> liftM2 join (inferInterval be1 constraints x) (inferInterval be2 constraints x) BinaryExpression Eq ae1 ae2 -> do (a, b) <- toLinearForm (ae1 |-| ae2) constraints x if 0 <: a && 0 <: b then return top else return $ -b // a BinaryExpression Leq ae1 ae2 -> do (a, b) <- toLinearForm (ae1 |-| ae2) constraints x if isBottom a || isBottom b then return bot else if 0 <: a && not (isBottom (meet b nonPositives)) then return top else return $ join (lessEqual (-b // meet a positives)) (greaterEqual (-b // meet a negatives)) BinaryExpression Ls ae1 ae2 -> inferInterval (ae1 |<=| (ae2 |-| num 1)) constraints x BinaryExpression Geq ae1 ae2 -> inferInterval (ae2 |<=| ae1) constraints x BinaryExpression Gt ae1 ae2 -> inferInterval (ae2 |<=| (ae1 |-| num 1)) constraints x -- Quantifier can only occur here if it is alternating with the enclosing one, hence no domain can be inferred _ -> return top ) `catchError` handleNotLinear where lessEqual int | isBottom int = bot | otherwise = Interval NegInf (upper int) greaterEqual int | isBottom int = bot | otherwise = Interval (lower int) Inf handleNotLinear err = case rtfSource err of InternalFailure NotLinear -> return top _ -> throwError err -- | Linear form (A, B) represents a set of expressions a*x + b, where a in A and b in B type LinearForm = (Interval, Interval) -- | If possible, convert arithmetic expression aExpr into a linear form over variable x, -- assuming all other quantified variables satisfy constraints. toLinearForm :: Expression -> Constraints -> Id -> Execution LinearForm toLinearForm aExpr constraints x = case node aExpr of Numeral n -> return (0, fromInteger n) Var y -> if x == y then return (1, 0) else case M.lookup y constraints of Just int -> return (0, int) Nothing -> const aExpr Application name args -> if null $ M.keys constraints `intersect` freeVars aExpr then const aExpr else throwInternalFailure NotLinear MapSelection m args -> if null $ M.keys constraints `intersect` freeVars aExpr then const aExpr else throwInternalFailure NotLinear Old e -> old $ toLinearForm e constraints x UnaryExpression Neg e -> do (a, b) <- toLinearForm e constraints x return (-a, -b) BinaryExpression op e1 e2 -> do left <- toLinearForm e1 constraints x right <- toLinearForm e2 constraints x combineBinOp op left right where const e = do v <- eval e case v of IntValue n -> return (0, fromInteger n) combineBinOp Plus (a1, b1) (a2, b2) = return (a1 + a2, b1 + b2) combineBinOp Minus (a1, b1) (a2, b2) = return (a1 - a2, b1 - b2) combineBinOp Times (a, b) (0, k) = return (k * a, k * b) combineBinOp Times (0, k) (a, b) = return (k * a, k * b) combineBinOp _ _ _ = throwInternalFailure NotLinear