1-- SPDX-FileCopyrightText: 2025-2026 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: GPL-3.0-only4module Language.QBE.Simulator.Explorer5 ( defSolver,6 logSolver,7 PathResult (..),8 Engine (expLastPath),9 newEngine,10 explorePath,11 exploreFunc,12 )13where1415import Control.Applicative (empty, (<|>))16import Control.Monad.Catch (try)17import Control.Monad.IO.Class (liftIO)18import Control.Monad.State.Strict (StateT, evalStateT, get, lift, modify, put)19import Data.Map qualified as Map20import Language.QBE.Backend.DFS (PathSel, findUnexplored, newPathSel, trackTrace)21import Language.QBE.Backend.Model (Model)22import Language.QBE.Backend.Store qualified as ST23import Language.QBE.Backend.Tracer qualified as T24import Language.QBE.Simulator (execFunc)25import Language.QBE.Simulator.Concolic.State26 ( Env (envStore),27 ErrorPath (pathError, pathInput),28 ErrorState (errStore, errTracer),29 SimState (..),30 makeConcolic,31 runPath,32 )33import Language.QBE.Simulator.Error (EvalError (TerminatePath))34import Language.QBE.Types qualified as QBE35import SimpleBV qualified as SMT36import System.Directory (findExecutable)37import System.IO (Handle)3839logic :: String40logic = "QF_BV"4142findSolver :: IO (String, [String])43findSolver =44 solver "bitwuzla" []45 <|> solver "z3" ["-smt2", "-in"]46 <|> solver "cvc5" ["--incremental"]47 <|> fail "no suitable sover found in PATH"48 where49 solver :: String -> [String] -> IO (String, [String])50 solver exec args = do51 r <- findExecutable exec52 maybe empty (\_ -> pure (exec, args)) r5354setupSolver :: SMT.Solver -> IO ()55setupSolver s = do56 -- Do not tie declarations to the current assertion stack.57 SMT.setOption s ":global-declarations" "true"58 SMT.setLogic s logic5960defSolver :: IO SMT.Solver61defSolver = do62 -- l <- SMT.newLogger 063 (solver, args) <- findSolver64 s <- SMT.newSolver solver args Nothing65 setupSolver s >> return s6667logSolver :: Handle -> IO SMT.Solver68logSolver handle = do69 l <- SMT.newLoggerWithHandle handle 070 (solver, args) <- findSolver71 s <-72 SMT.newSolverWithConfig73 (SMT.defaultConfig solver args)74 { SMT.solverLogger = SMT.smtSolverLogger l75 }76 setupSolver s >> return s7778------------------------------------------------------------------------7980data PathResult81 = PathResult82 { pathErr :: Maybe EvalError,83 pathTrace :: T.ExecTrace,84 pathVars :: ST.Assign85 }86 deriving (Show, Eq)8788initPath :: PathResult89initPath = PathResult Nothing [] Map.empty9091data Engine92 = Engine93 { expSolver :: SMT.Solver,94 expPathSel :: PathSel,95 expEnv :: Env,96 expLastPath :: PathResult97 }9899newEngine :: Env -> SMT.Solver -> Engine100newEngine env solver =101 Engine102 { expSolver = solver,103 expPathSel = newPathSel,104 expEnv = env,105 expLastPath = initPath106 }107108findNext :: [SMT.SExpr] -> T.ExecTrace -> StateT Engine IO (Maybe Model)109findNext symVars eTrace = do110 engine <- get111112 let pathSel = trackTrace (expPathSel engine) eTrace113 (model, nextPathSel) <-114 liftIO $ findUnexplored (expSolver engine) symVars pathSel115116 put $ engine {expPathSel = nextPathSel}117 pure model118119-- TODO: Consider modelling changes of the PathSel (via findNext) and120-- changes of the Store (via ST.finalize and ST.setModel) as a StateT.121explorePath :: SimState a -> StateT Engine IO Bool122explorePath simState = do123 engine@(Engine {expEnv = env}) <- get124 maybePath <- try $ run env125 let (mayErr, eTrace, nStore) =126 case maybePath of127 Left (err :: ErrorPath) ->128 let st = pathInput err129 pe = case pathError err of130 TerminatePath -> Nothing131 errKind -> Just errKind132 in (pe, errTracer st, errStore st)133 Right (t, s) -> (Nothing, t, s)134135 -- Before finalizing the store, we can extract the variables we encountered136 -- during this concrete execution, as well as the concrete values used for137 -- these variables during the execution.138 let inputVars = ST.sexprs nStore139 varAssign = ST.cValues nStore140 put $ engine {expLastPath = PathResult mayErr eTrace varAssign}141142 -- Finalize the store (declare new symbolic vars in solver) and then,143 -- based on the new solver state, solve constraints to find a new input.144 store <- liftIO $ ST.finalize (expSolver engine) nStore145 model <- findNext inputVars eTrace146 case model of147 Nothing -> pure False148 Just newModel -> do149 let nEnv = env {envStore = ST.setModel store newModel}150 in modify (\e -> e {expEnv = nEnv})151 pure True152 where153 run env = lift $ evalStateT (unSimState $ runPath simState) env154155------------------------------------------------------------------------156157exploreFunc ::158 Engine ->159 QBE.FuncDef ->160 [(String, QBE.ExtType)] ->161 IO [PathResult]162exploreFunc engine entry params = do163 let funcState = mapM (uncurry makeConcolic) params >>= execFunc entry164 evalStateT (exploreFunc' funcState) engine165 where166 exploreFunc' st = do167 morePaths <- explorePath st168 curEngine <- get169170 let ret = expLastPath curEngine171 in if morePaths172 then (ret :) <$> exploreFunc' st173 else pure [ret]