g2-0.2.0.0: src/G2/QuasiQuotes/QuasiQuotes.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
module G2.QuasiQuotes.QuasiQuotes (g2) where
import G2.Config
import G2.Execution.Interface
import G2.Execution.Memory
import G2.Execution.Reducer
import G2.Execution.Rules
import G2.Initialization.MkCurrExpr
import G2.Interface
import G2.Language as G2
import qualified G2.Language.PathConds as PC
import qualified G2.Language.Typing as Ty
import G2.Solver
import G2.Translation.Cabal.Cabal
import G2.Translation.Haskell
import G2.Translation.Interface
import G2.Translation.TransTypes
import G2.QuasiQuotes.FloodConsts
import G2.QuasiQuotes.G2Rep
import G2.QuasiQuotes.Support
import G2.QuasiQuotes.Parser
import qualified Control.Concurrent.Lock as Lock
import Control.Monad.IO.Class
import Data.Data
import qualified Data.HashSet as HS
import qualified Data.HashMap.Lazy as HM
import Data.List
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Sequence as Seq
import Data.IORef
import qualified Data.Text as T
import Language.Haskell.TH.Lib
import Language.Haskell.TH.Syntax as TH
import Language.Haskell.TH.Quote
import System.Directory
import System.IO
import System.IO.Temp
import System.IO.Unsafe
g2 :: QuasiQuoter
g2 = QuasiQuoter { quoteExp = parseHaskellQ
, quotePat = error "g2: No QuasiQuoter for patterns."
, quoteType = error "g2: No QuasiQuoter for types."
, quoteDec = error "g2: No QuasiQuoter for declarations." }
-- If we compile multiple G2 quasiquoters at the same time, we can get errors.
-- This is a hack to prevent that from happening. The IORef is global, and
-- aquired/released by each quasiquoter's compilation
oneByOne :: IORef Lock.Lock
oneByOne = unsafePerformIO $ newIORef =<< Lock.new
{-# NOINLINE oneByOne #-}
acquireIORefLock :: IO ()
acquireIORefLock = do
lock <- readIORef oneByOne
Lock.acquire lock
releaseIORefLock :: IO ()
releaseIORefLock = do
lock <- readIORef oneByOne
Lock.release lock
parseHaskellQ :: String -> Q Exp
parseHaskellQ str = do
runIO $ acquireIORefLock
-- runIO $ putStrLn $ "CWD is: " ++ cwd
-- Get names for the lambdas for the regular inputs
let qext = extractQuotedData str
-- let regs = grabRegVars str
let regs = map fst $ concVars qext
ns <- mapM newName regs
let ns_pat = map varP ns
-- Get names for the lambdas for the regular inputs
exG2 <- parseHaskellQ' qext
config <- runIO qqConfig
let (init_s, init_b) = initStateWithCall' exG2 (T.pack functionName) (Just $ T.pack moduleName)
(mkCurrExpr Nothing Nothing) (mkArgTys) config
runIO $ releaseIORefLock
ex_out <- runExecutionQ init_s init_b config
state_name <- newName "state"
tenv_name <- newName "tenv_parse"
cleaned_names_name <- newName "cleaned"
bindings_name <- newName "bindings"
(ex, state_exp, tenv, bindings_final) <- case ex_out of
Completed xs b -> do
case elimUnusedCompleted xs b of
(xs'@(s:_), b') -> do
let xs'' = listE $ map (moveOutStatePieces tenv_name) xs'
xs''' = addCompRegVarPasses (varE state_name) tenv_name cleaned_names_name ns (inputIds s b') b'
b'' = b' { input_names = drop (length regs) (input_names b') }
sol = solveStates xs''' (varE bindings_name)
ars = extractArgs (inputIds s b'') (cleaned_names b'') tenv_name sol
return (foldr (\n -> lamE [n]) ars ns_pat, xs'', type_env s, b'')
([], _) ->
let
b' = init_b { input_names = drop (length regs) (input_names init_b) }
ts = map (toTHType (cleaned_names b') . Ty.typeOf) $ inputIds init_s b'
tup_t = case ts of
[t] -> t
_ -> foldr appT (tupleT (length ts)) ts
in
return (foldr (\n -> lamE [n]) [| return (Nothing :: Maybe $(tup_t)) |] ns_pat
, [| return [] :: IO [State ()] |]
, HM.empty
, b')
NonCompleted s b -> do
let
(s', b') = elimUnusedNonCompleted s b
s'' = moveOutStatePieces tenv_name s'
s''' = addedNonCompRegVarBinds (varE state_name) tenv_name cleaned_names_name ns (inputIds s' b') b'
b'' = b' { input_names = drop (length regs) (input_names b') }
sol = executeAndSolveStates s''' (varE bindings_name)
ars = extractArgs (inputIds s b'') (cleaned_names b'') tenv_name sol
return (foldr (\n -> lamE [n]) ars ns_pat, s'', type_env s', b'')
-- foldr (\n -> lamE [n]) [|do putStrLn "NONCOMPLETED"; return Nothing;|] ns_pat
let tenv_exp = liftDataT tenv `sigE` [t| TypeEnv |]
bindings_exp = liftDataT bindings_final
letE [ valD (varP state_name) (normalB state_exp) []
, valD (varP tenv_name) (normalB tenv_exp) []
, valD (varP bindings_name) (normalB bindings_exp) []
, valD (varP cleaned_names_name) (normalB (varE 'cleaned_names `appE` varE bindings_name)) []] ex
liftDataT :: Data a => a -> Q Exp
liftDataT = dataToExpQ (\a -> case liftText <$> cast a of
je@(Just _) -> je
Nothing -> case liftLoc <$> cast a of
jl@(Just _) -> jl
Nothing -> liftMaybeSpan <$> cast a)
where
liftText txt = appE (varE 'T.pack) (stringE (T.unpack txt))
liftLoc (G2.Loc l c f) = conE 'G2.Loc `appE` intE l `appE` intE c `appE` stringE f
liftMaybeSpan :: Maybe Span -> Q Exp
liftMaybeSpan _ = conE 'Nothing
intE i = [| i |]
parseHaskellQ' :: QuotedExtract-> Q ExtractedG2
parseHaskellQ' qext = do
(ModuleInfo mods) <- reifyModule =<< thisModule
-- runIO $ mapM putStrLn =<< guessModules mods
-- runIO $ putStrLn "-----"
runIO $ parseHaskellIO mods qext
-- | Turn the Haskell into a G2 Expr. All variables- both those that the user
-- marked to be passed into the Expr as real values, and those that the user
-- wants to solve for- are treated as symbolic here.
parseHaskellIO :: [Module] -> QuotedExtract -> IO ExtractedG2
parseHaskellIO mods qext = do
-- cwd <- getCurrentDirectory
-- let cwd' = cwd ++ "/quasiquote/"
let hskStr = quotedEx2Hsk qext
(_, exG2) <- withSystemTempFile fileName
(\filepath handle -> do
let contents = "module " ++ moduleName ++ " where\n"
++ intercalate "\n" modImports ++ "\n"
++ functionName ++ " = " ++ hskStr
hPutStrLn handle contents
hFlush handle
hClose handle
-- We guess based on the cwd because who knows where temp
-- files will get written to.
cwd <- getCurrentDirectory
cabal <- findCabal cwd
let cabal' = maybe (error "No cabal file found") id cabal
projs <- cabalSrcDirs cabal'
config <- qqConfig
translateLoaded projs [filepath]
(simplTranslationConfig { interpreter = True })
config)
return exG2
where
modImports = map ("import " ++)
. filter (`notElem` badImports)
. map (\(Module _ (ModName n)) -> n) $ mods
badImports = ["G2.QuasiQuotes.QuasiQuotes"]
-- allDirectories :: FilePath -> IO [FilePath]
-- allDirectories fp = do
-- all <- listDirectory fp
-- fs <- filterM doesDirectoryExist all
-- fs' <- mapM allDirectories fs
-- mapM makeAbsolute $ fp:fs ++ concat fs'
-- | If a State has been completely symbolically executed (i.e. no states were
-- discarded by a Halter) we encoded it as Completed.
-- Otherwise, we encode the original State and Bindings as NonCompleted
data ExecOut = Completed [State ()] Bindings
| NonCompleted (State ()) Bindings
runExecutionQ :: State () -> Bindings -> Config -> Q ExecOut
runExecutionQ s b config = do
runIO $ do
let (s', b') = addAssume s b
SomeSolver solver <- initSolverInfinite config
let simplifier = IdSimplifier
case qqRedHaltOrd config solver simplifier of
(SomeReducer red, SomeHalter hal, SomeOrderer ord) -> do
let (s'', b'') = runG2Pre emptyMemConfig s' b'
hal' = hal <~> zeroHalter 2000 <~> lemmingsHalter
(xs, b''') <- runExecutionToProcessed red hal' ord s'' b''
case xs of
Processed { accepted = acc, discarded = [] } -> do
let acc' = filter (trueCurrExpr) acc
return $ Completed acc' b'''
_ -> do
return $ NonCompleted s'' b''
where
trueCurrExpr (State { curr_expr = CurrExpr _ e
, known_values = kv }) = e == mkTrue kv
_ = False
-- | As soon as one States has been discarded, discard all States
lemmingsHalter :: Monad m => Halter m () t
lemmingsHalter =
(mkSimpleHalter
(const ())
(\_ _ _ -> ())
(\_ _ _ -> return Continue)
(\_ _ _ _ -> ())) { discardOnStart = discard }
where
discard _ pr _ = not . null . discarded $ pr
fileName :: String
fileName = "THTemp.hs"
moduleName :: String
moduleName = "THTemp"
functionName :: String
functionName = "g2Expr"
qqRedHaltOrd :: (MonadIO m, Solver solver, Simplifier simplifier) => Config -> solver -> simplifier -> (SomeReducer m (), SomeHalter m (), SomeOrderer m ())
qqRedHaltOrd config solver simplifier =
let
share = sharing config
state_name = G2.Name "state" Nothing 0 Nothing
in
( taggerRed state_name :== Finished .--> nonRedPCRed :== Finished --> stdRed share retReplaceSymbFuncVar solver simplifier
, SomeHalter
(discardIfAcceptedTagHalter state_name
<~> acceptIfViolatedHalter)
, SomeOrderer nextOrderer)
addAssume :: State t -> Bindings -> (State t, Bindings)
addAssume s@(State { curr_expr = CurrExpr er e }) b@(Bindings { name_gen = ng }) =
let
(v, ng') = freshId (Ty.typeOf e) ng
e' = Let [(v, e)] (Assume Nothing (Var v) (Var v))
in
(s { curr_expr = CurrExpr er e' }, b { name_gen = ng' })
type TypeEnvName = TH.Name
type CleanedNamesName = TH.Name
-- We have the TypeEnv separately from the state, and it is a waste to lift it to TH twice.
-- Further, we cannot directly apply Data to the PathConds, since they use and IORef
moveOutStatePieces :: Data t => TypeEnvName -> State t -> Q Exp
moveOutStatePieces tenv_name s = do
let -- s' = s { type_env = M.empty, path_conds = undefined }
-- s_exp = liftDataT s'
expr_env_exp = liftDataT (expr_env s)
curr_expr_exp = liftDataT (curr_expr s)
non_red_path_conds_exp = liftDataT (non_red_path_conds s)
true_assert_exp = liftDataT (true_assert s)
assert_ids_exp = liftDataT (assert_ids s)
type_classes_exp = liftDataT (type_classes s)
exec_stack_exp = liftDataT (exec_stack s)
model_exp = liftDataT (model s)
known_values_exp = liftDataT (known_values s)
rules_exp = liftDataT (rules s)
num_steps_exp = liftDataT (num_steps s)
tags_exp = liftDataT (tags s)
track_exp = liftDataT (track s)
pc_exp = liftDataT . PC.toList $ path_conds s
[| State { expr_env = $(expr_env_exp)
, type_env = $(varE tenv_name)
, curr_expr = $(curr_expr_exp)
, path_conds = PC.fromList $(pc_exp)
, non_red_path_conds = $(non_red_path_conds_exp)
, true_assert = $(true_assert_exp)
, assert_ids = $(assert_ids_exp)
, type_classes = $(type_classes_exp)
, exec_stack = $(exec_stack_exp)
, model = $(model_exp)
, known_values = $(known_values_exp)
, rules = $(rules_exp)
, num_steps = $(num_steps_exp)
, tags = $(tags_exp)
, sym_gens = Seq.empty
, track = $(track_exp) } |]
-- Returns an Q Exp represeting a [(Name, Expr)] list
regVarBindings :: [TH.Name] -> TypeEnvName -> CleanedNamesName -> InputIds -> Bindings -> Q Exp
regVarBindings ns tenv_name cleaned_name is (Bindings { input_names = ins, cleaned_names = cleaned }) = do
let ns_exp = map varE ns
ty_ns_exp = map (\(n, i) -> sigE n (toTHType cleaned (Ty.typeOf i))) $ zip ns_exp is
ins_exp = liftDataT ins
g2Rep_exp = [| g2Rep $(varE tenv_name) $(varE cleaned_name) |]
ns_expr = map (appE g2Rep_exp) ty_ns_exp
zip_exp = [| zip $(ins_exp) $(listE ns_expr) |]
zip_exp
-- | Adds the appropriate number of lambda bindings to the Exp,
-- and sets up a conversion from TH Exp's to G2 Expr's.
-- The returned Exp should have a function type and return type (State t).
addCompRegVarPasses :: StateListExp -> TypeEnvName -> CleanedNamesName -> [TH.Name] -> InputIds -> Bindings -> Q Exp
addCompRegVarPasses xs_exp tenv_name cleaned_name ns in_ids b = do
let zip_exp = regVarBindings ns tenv_name cleaned_name in_ids b
flooded_exp = appE (varE 'mapMaybe) (appE (varE 'floodConstantsChecking) zip_exp)
appE flooded_exp xs_exp
addedNonCompRegVarBinds :: StateExp -> TypeEnvName -> CleanedNamesName -> [TH.Name] -> InputIds -> Bindings -> Q Exp
addedNonCompRegVarBinds state_exp tenv_name cleaned_name ns in_ids b = do
let zip_exp = regVarBindings ns tenv_name cleaned_name in_ids b
flooded_exp = [| case floodConstantsChecking $(zip_exp) $(state_exp) of
Just s' -> s'
Nothing -> error "addedNonCompRegVarBinds: Nothing"|]
flooded_exp
elimUnusedCompleted :: Named t => [State t] -> Bindings -> ([State t], Bindings)
elimUnusedCompleted xs b =
let
b' = b { deepseq_walkers = M.empty
, higher_order_inst = HS.empty }
xs' = map (\s -> s { type_classes = initTypeClasses []
, rules = [] }) xs
xs'' = map (fst . flip markAndSweepIgnoringKnownValues b') xs'
in
(xs'', b')
elimUnusedNonCompleted :: Named t => State t -> Bindings -> (State t, Bindings)
elimUnusedNonCompleted s b =
let
b' = b { deepseq_walkers = M.empty
, higher_order_inst = HS.empty }
s' = s { type_classes = initTypeClasses []
, rules = [] }
in
markAndSweepIgnoringKnownValues s' b'
type StateExp = Q Exp
type StateListExp = Q Exp
type BindingsExp = Q Exp
errorHalter :: Monad m => Halter m () t
errorHalter = mkSimpleHalter
(const ())
(\_ _ _ -> ())
stop
(\_ _ _ _ -> ())
where
stop _ _ (State { curr_expr = CurrExpr _ (G2.Prim Error _)}) = return Discard
stop _ _ _ = return Continue
executeAndSolveStates :: StateExp -> BindingsExp -> Q Exp
executeAndSolveStates s b = do
varE 'executeAndSolveStates' `appE` b `appE` s
executeAndSolveStates' :: Bindings -> State () -> IO (Maybe (ExecRes ()))
executeAndSolveStates' b s = do
config <- qqConfig
SomeSolver solver <- initSolverInfinite config
let simplifier = IdSimplifier
case qqRedHaltOrd config solver simplifier of
(SomeReducer red, SomeHalter hal, _) -> do
let hal' = hal <~> errorHalter <~> varLookupLimitHalter 3 <~> maxOutputsHalter (Just 1)
ord = incrAfterN 2000 (adtHeightOrderer 0 Nothing) <-> bucketSizeOrderer 6
(res, _) <- runG2Post red hal' ord solver simplifier s b
case res of
exec_res:_ -> return $ Just exec_res
_ -> return Nothing
-- Takes an Exp representing a list of States, and returns an Exp representing an ExecRes
solveStates :: StateExp -> BindingsExp -> Q Exp
solveStates xs b = do
varE 'solveStates' `appE` b `appE` xs
solveStates' :: ( Named t
, ASTContainer t Expr
, ASTContainer t G2.Type) => Bindings -> [State t] -> IO (Maybe (ExecRes t))
solveStates' b xs = do
config <- qqConfig
SomeSolver solver <- initSolverInfinite config
let simplifier = IdSimplifier
solveStates'' solver simplifier b xs
solveStates'' :: ( Named t
, ASTContainer t Expr
, ASTContainer t G2.Type
, Solver solver
, Simplifier simplifier) => solver -> simplifier -> Bindings -> [State t] -> IO (Maybe (ExecRes t))
solveStates'' _ _ _ [] = return Nothing
solveStates'' sol simplifier b (s:xs) = do
m_ex_res <- runG2Solving sol simplifier b s
case m_ex_res of
Just _ -> do
return m_ex_res
Nothing -> solveStates'' sol simplifier b xs
-- | Get the values of the symbolic arguments, and returns them in a tuple
extractArgs :: InputIds -> CleanedNames -> TypeEnvName -> Q Exp -> Q Exp
extractArgs in_ids cleaned tenv es =
[|do
r <- $(es)
case r of
Just r' -> return . Just . $(toSymbArgsTuple in_ids cleaned tenv) $ conc_args r'
Nothing -> return Nothing |]
-- | If (length of InputIds) is greater than 1, returns a function to turn the first (length of InputIds) elements of
-- a list into a tuple.
-- Otherwise, simply returns the singular value directly.
toSymbArgsTuple :: InputIds -> CleanedNames -> TypeEnvName -> Q Exp
toSymbArgsTuple in_ids cleaned tenv_name = do
let mkTup = if length in_ids > 1 then tupE else head
lst <- newName "lst"
lamE [varP lst]
(mkTup $ map (\(i, n) -> [| g2UnRep $(varE tenv_name) ($(varE lst) !! n) :: $(toTHType cleaned (Ty.typeOf i)) |]) $ zip in_ids ([0..] :: [Int]))
qqConfig :: IO Config
qqConfig = do
homedir <- getHomeDirectory
let config = mkConfigDirect homedir [] M.empty
return $ config { extraDefaultMods = [homedir ++ "/.g2/G2Stubs/src/G2/QuasiQuotes/G2Rep.hs"] }