module Compile (compile) where import Control.Monad.State hiding (State) import Data.List import Language.C import Language.C.Data.Ident import Error import Model hiding (CInteger) import qualified Model as M import Utils -- | Compiles a program to a model for analysis. compile :: String -> [CTranslUnit] -> IO Model compile function units = do m <- execStateT (evalStat initEnv $ rewrite function units) initMDB return (model m) { actions = reverse $ actions (model m) } none :: NodeInfo none = internalNode -- | Rewrites a program to a single statement. Requires no recursive functions and unique declarations for all top level declarations. rewrite :: String -> [CTranslUnit] -> CStat rewrite name units = if not $ null asms then notSupported (head asms) "inline assembly" else if not $ null duplicateNames then error $ "duplicate top-level names found (hiding names with static is not supported): " ++ show duplicateNames else CCompound [] (vars ++ funcs ++ [CBlockStmt call]) none where items = [ a | CTranslUnit items _ <- units, a <- items ] varDefs = [ a | CDeclExt a <- items ] vars = [ CBlockDecl (CDecl (CStorageSpec (CStatic none) : specs) a b) | CDecl specs a b <- varDefs ] -- Make top level vars static. funcDefs = [ a | CFDefExt a <- items ] funcs = map CNestedFunDef $ sortFunctions funcDefs asms = [ a | CAsmExt a <- items ] call :: CStat call = CExpr (Just $ CCall (CVar (Ident name 0 none) none) [] none) none duplicateNames = duplicates $ [ name | f <- funcDefs, let (_, (Ident name _ _), _, _) = functionInfo f ] ++ concat [ [ name | (Just (CDeclr (Just (Ident name _ _)) _ _ _ _), _, _) <- a ] | CDecl _ a _ <- varDefs ] duplicates :: Eq a => [a] -> [a] duplicates [] = [] duplicates (a:b) | elem a b = a : duplicates b | otherwise = duplicates b type M = StateT MDB IO data MDB = MDB { nextId :: Int , stack :: [Ident] , enabled :: E , model :: Model } initMDB :: MDB initMDB = MDB { nextId = 0 , stack = [] , enabled = true , model = Model { variables = [], actions = [] } } -- | Environment for resolving identifiers. data Env = Env { values :: [Value] } data Value = EnvFunction String Function | EnvVariable String V data Function = Function Int ([E] -> M E) -- | Looks of a variable in the environment. variable :: Env -> Ident -> V variable env i@(Ident name _ _) = if null m then err i $ "variable \"" ++ name ++ "\" not found" else head m where m = [ a | EnvVariable n a <- values env, n == name ] -- | Looks of a function in the environment. function :: Env -> Ident -> Function function env i@(Ident name _ _) = if null m then err i $ "function \"" ++ name ++ "\" not found" else head m where m = [ a | EnvFunction n a <- values env, n == name ] -- | Creates a branch. branch :: Position -> E -> M a -> M a -> M (a, a) branch n a onTrue onFalse = do m1 <- get put m1 { enabled = And (enabled m1) a n } r1 <- onTrue m2 <- get put m2 { enabled = And (enabled m1) (Not a n) n } r2 <- onFalse m3 <- get put m3 { enabled = enabled m1 } return (r1, r2) -- | Push an identifier onto the call stack, do something, then pop it off. callStack :: Ident -> M a -> M a callStack id a = do m <- get put m { stack = id : stack m } a <- a m <- get put m { stack = tail $ stack m } return a callPath :: M ([String], Position) callPath = do m <- get let s = stack m return ([ n | Ident n _ _ <- reverse s ], posOf $ head s) -- | The initial environment defines the assert and assume functions. initEnv :: Env initEnv = Env { values = [ EnvFunction "assert" $ Function 1 assert , EnvFunction "assume" $ Function 1 assume ] } where assert a = do (s, n) <- callPath m <- get let x = imply (enabled m) (head a) n newAction $ Assert x s n return x assume a = do (s, n) <- callPath m <- get let x = imply (enabled m) (head a) n newAction $ Assume x s n return x -- | Adds new action. newAction :: Action -> M () newAction a = do m <- get put m { model = (model m) { actions = a : actions (model m) }} -- | Adds new variable. addVar :: Env -> V -> M Env addVar env a = do case a of State a -> do m <- get put m { model = (model m) { variables = if elem a (variables $ model m) then variables $ model m else a : variables (model m) }} _ -> return () return env { values = EnvVariable name a : values env } where name = case a of State (VS a _ _ _) -> a Volatile a _ _ -> a Local a _ _ _ -> a Tmp _ _ _ -> error "Compile.addVar: should not call addVar with Tmp" evalStat :: Env -> CStat -> M () evalStat env a = case a of CLabel i a [] _ -> callStack i $ evalStat env a CExpr Nothing _ -> return () CExpr (Just a) _ -> evalExpr env a >> return () CCompound ids items _ -> f ids where f :: [Ident] -> M () f [] = foldM evalBlockItem env items >> return () f (a:b) = callStack a $ f b CIf a b Nothing n -> evalStat env $ CIf a b (Just $ CCompound [] [] n) n CIf a b (Just c) n -> do a <- evalExpr env a branch (posOf n) a (evalStat env b) (evalStat env c) return () _ -> notSupported a "statement" evalBlockItem :: Env -> CBlockItem -> M Env evalBlockItem env a = case a of CBlockStmt a -> evalStat env a >> return env CBlockDecl a -> evalDecl env a CNestedFunDef a -> evalFunc env a evalExpr :: Env -> CExpr -> M E evalExpr env a = latch (posOf a) =<< case a of CAssign op a b n -> case op of CAssignOp -> do a' <- evalExpr env a b' <- evalExpr env b case a' of Var v -> assign False (posOf n) v b' >> return a' _ -> unexpected a "non variable in left hand of assignment" CMulAssOp -> f CMulOp CDivAssOp -> f CDivOp CRmdAssOp -> f CRmdOp CAddAssOp -> f CAddOp CSubAssOp -> f CSubOp CShlAssOp -> f CShlOp CShrAssOp -> f CShrOp CAndAssOp -> f CAndOp CXorAssOp -> f CXorOp COrAssOp -> f COrOp where f :: CBinaryOp -> M E f op = evalExpr env (CAssign CAssignOp a (CBinary op a b n) n) CCond a (Just b) c n -> do a <- evalExpr env a (b, c) <- branch (posOf n) a (evalExpr env b) (evalExpr env c) return $ Mux a b c $ posOf n CCond a Nothing b n -> do a <- evalExpr env a (a, b) <- branch (posOf n) a (return a) (evalExpr env b) return $ Mux a a b $ posOf n CBinary op a b n -> case op of CMulOp -> f Mul CDivOp -> f Div CRmdOp -> f Mod CAddOp -> f Add CSubOp -> f Sub CShlOp -> notSupported a "(<<)" CShrOp -> notSupported a "(>>)" CLeOp -> f Lt CGrOp -> f $ \ a b n -> Lt b a n CLeqOp -> f $ \ a b n -> Not (Lt b a n) n CGeqOp -> f $ \ a b n -> Not (Lt a b n) n CEqOp -> f Eq CNeqOp -> f $ \ a b n -> Not (Eq a b n) n CAndOp -> notSupported a "(&)" CXorOp -> notSupported a "(^)" COrOp -> notSupported a "(|)" CLndOp -> do a <- evalExpr env a (b, a) <- branch n' a (evalExpr env b) (return a) return $ Mux a b a n' CLorOp -> do a <- evalExpr env a (a, b) <- branch n' a (return a) (evalExpr env b) return $ Mux a a b n' where n' = posOf n f :: (E -> E -> Position -> E) -> M E f op = do a <- evalExpr env a b <- evalExpr env b return $ op a b n' CUnary op a n -> do a <- evalExpr env a case (op, a) of (CPreIncOp, Var a) -> do assign False p a $ Add (Var a) one p return $ Var a (CPreDecOp, Var a) -> do assign False p a $ Sub (Var a) one p return $ Var a (CPostIncOp, Var a) -> do b <- latch p $ Var a assign False p a $ Add (Var a) one p return b (CPostDecOp, Var a) -> do b <- latch p $ Var a assign False p a $ Sub (Var a) one p return b (CPlusOp, a) -> return a (CMinOp, a) -> return $ Sub zero a p (CNegOp, a) -> return $ Not a p _ -> notSupported n "unary operator" where p = posOf n one = Const $ M.CInteger 1 p zero = Const $ M.CInteger 0 p CCall (CVar f _) args _ -> do args <- mapM (evalExpr env) args when (arity /= length args) $ unexpected f $ "function called with " ++ show (length args) ++ " arguments, but defined with " ++ show arity ++ " arguments" callStack f $ func args where Function arity func = function env f CCall _ _ _ -> notSupported a "non named function references" CVar i _ -> return $ Var $ variable env i CConst a -> case a of CIntConst (CInteger a _ _) n -> return $ Const $ M.CInteger a $ posOf n CFloatConst (CFloat a) n -> return $ Const $ CRational (toRational (read a :: Double)) $ posOf n _ -> notSupported a "char or string constant" _ -> notSupported a "expression" evalDecl :: Env -> CDecl -> M Env evalDecl env d@(CDecl specs decls _) = if isExtern typInfo then return env else foldM evalDecl' env decls where (typInfo, typ) = typeInfo specs evalDecl' :: Env -> (Maybe CDeclr, Maybe CInit, Maybe CExpr) -> M Env evalDecl' env (a, b, c) = case a of Just (CDeclr (Just i@(Ident name _ n)) [] Nothing [] _) -> case (b, c) of (Nothing, Nothing) -> evalDecl' env (a, Just $ CInitExpr (CConst (CIntConst (cInteger 0) n)) n, Nothing) (Just (CInitExpr (CConst const) n'), Nothing) | isStatic typInfo && not (isVolatile typInfo) -> addVar env v where v = State $ VS name typ init $ posOf n init = case typ of Void -> unexpected d "void type for variable declaration" Bool -> CBool (cInt /= 0) $ posOf n' Integer _ -> M.CInteger cInt $ posOf n' Rational _ -> CRational cRat $ posOf n' cInt :: Integer cInt = case const of CIntConst (CInteger a _ _) _ -> a _ -> unexpected const "non integer initialization" cRat :: Rational cRat = case const of CIntConst (CInteger a _ _) _ -> fromIntegral a CFloatConst (CFloat a) _ -> fromIntegral (read a :: Integer) _ -> unexpected const "non numeric initialization" (Just (CInitExpr c _), Nothing) -> evalDecl' env (a, Nothing, Just c) (Nothing, Just e') -> do e <- evalExpr env e' v <- if isVolatile typInfo then return $ Volatile name typ $ posOf n else do m <- get put m { nextId = nextId m + 1 } return $ Local name typ (nextId m) (posOf e') assign True (posOf e') v e addVar env v _ -> notSupported i "variable declaration" _ -> notSupported d "arrays, pointers, or functional pointers (So what good is this tool anyway?)" evalFunc :: Env -> CFunDef -> M Env evalFunc env f = do when (typ /= Void || not (null args)) $ notSupported f "non void f() function (How lame is this?)" return env { values = EnvFunction name (Function (length args) func) : values env } where (specs, (Ident name _ _), args, stat) = functionInfo f (_, typ) = typeInfo specs func _ = do --XXX evalStat env stat return false -- | Assgins a value to a variable. assign :: Bool -> Position -> V -> E -> M () assign decl n v a = case v of Volatile _ _ _ -> return () _ -> do m <- get newAction $ Assign v $ if decl then a else Mux (enabled m) a (Var v) n --XXX Is this correct for local and tmp variables? -- | Latch a value at a point in time and return the expression of the latch variable. latch :: Position -> E -> M E latch _ (Var a) = return (Var a) latch _ (Const a) = return (Const a) latch n a = do m <- get let v = Tmp (typeOf a) (nextId m) n put m { nextId = nextId m + 1 } assign True n v a return $ Var v -- | Logical implication. Assumes Bool inputs. imply :: E -> E -> Position -> E imply a b n = Or (Not a n) b n -- | Extract relavent info from a function declaration. functionInfo :: CFunDef -> ([CDeclSpec], Ident, [CDecl], CStat) functionInfo (CFunDef specs (CDeclr (Just ident) [(CFunDeclr (Right (args, False)) _ _)] Nothing [] _) [] stmt _) = (specs, ident, args, stmt) functionInfo f = notSupported f "function" -- | Topologically sorts functions based on dependencies. sortFunctions :: [CFunDef] -> [CFunDef] sortFunctions fs = case topo $ map functionDeps fs of Left a -> notSupported none $ "recursive functions somewhere among: " ++ show a Right a -> [ f | a <- a, f <- fs, functionName f == a ] topo :: Eq a => [(a, [a])] -> Either [a] [a] topo a = topo' [] a where topo' a [] = Right a topo' done waiting = if null next then Left $ fst $ unzip waiting else topo' (done ++ next) stillWaiting where next = [ a | (a, deps) <- waiting, all (flip elem done) deps ] stillWaiting = [ (a, deps) | (a, deps) <- waiting, notElem a next ] functionName :: CFunDef -> String functionName f = name where (_, Ident name _ _, _, _) = functionInfo f afvFunctionNames :: [String] afvFunctionNames = ["assert", "assume"] -- | Analyzes a function for dependencies. functionDeps :: CFunDef -> (String, [String]) functionDeps f = (functionName f, filter (flip notElem afvFunctionNames) $ nub $ fb [] [CNestedFunDef f]) where rewrite :: CFunDef -> CStat rewrite f = CCompound [] (map CBlockDecl args ++ [CBlockStmt stat]) none where (_, _, args, stat) = functionInfo f fs :: [String] -> CStat -> [String] fs env a = case a of CLabel _ a _ _ -> fs env a CCase a b _ -> fe env a ++ fs env b CCases a b c _ -> fe env a ++ fe env b ++ fs env c CDefault a _ -> fs env a CExpr (Just a ) _ -> fe env a CExpr Nothing _ -> [] CCompound _ a _ -> fb env a CIf a b Nothing _ -> fe env a ++ fs env b CIf a b (Just c) _ -> fe env a ++ fs env b ++ fs env c CSwitch a b _ -> fe env a ++ fs env b CWhile a b _ _ -> fe env a ++ fs env b CFor _ _ _ _ _ -> notSupported a "for loops" CGoto _ _ -> [] CGotoPtr a _ -> fe env a CCont _ -> [] CBreak _ -> [] CReturn Nothing _ -> [] CReturn (Just a) _ -> fe env a CAsm _ _ -> [] fe :: [String] -> CExpr -> [String] fe env a = case a of CComma a _ -> fe' a CAssign _ a b _ -> fe' [a, b] CCond a (Just b) c _ -> fe' [a, b, c] CCond a Nothing b _ -> fe' [a, b] CBinary _ a b _ -> fe' [a, b] CCast _ a _ -> fe env a --XXX does not check cast declaration. CUnary _ a _ -> fe env a --CSizeofExpr CExpr NodeInfo --CSizeofType CDecl NodeInfo --CAlignofExpr CExpr NodeInfo --CAlignofType CDecl NodeInfo --CComplexReal CExpr NodeInfo --CComplexImag CExpr NodeInfo CIndex a b _ -> fe' [a, b] CCall (CVar (Ident n _ _) _) args _ -> (if elem n env then [] else [n]) ++ fe' args CCall _ _ _ -> notSupported a "non-named function references" CMember a _ _ _ -> fe env a CVar _ _ -> [] CConst _ -> [] --CCompoundLit CDecl CInitList NodeInfo CStatExpr a _ -> fs env a CLabAddrExpr _ _ -> [] --CBuiltinExpr CBuiltin _ -> notSupported a "expression" where fe' = concatMap $ fe env fb :: [String] -> [CBlockItem] -> [String] fb _ [] = [] fb env (a:b) = case a of CBlockStmt a -> fs env a ++ fb env b CBlockDecl _ -> fb env b CNestedFunDef f -> fs env' (rewrite f) ++ fb env' b where env' = functionName f : env