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)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)) r5354defSolver :: IO SMT.Solver55defSolver = do56 -- l <- SMT.newLogger 057 (solver, args) <- findSolver58 s <- SMT.newSolver solver args Nothing59 SMT.setLogic s logic60 return s6162logSolver :: Handle -> IO SMT.Solver63logSolver handle = do64 l <- SMT.newLoggerWithHandle handle 065 s <-66 SMT.newSolverWithConfig67 (SMT.defaultConfig "bitwuzla" [])68 { SMT.solverLogger = SMT.smtSolverLogger l69 }70 SMT.setLogic s logic71 return s7273------------------------------------------------------------------------7475data PathResult76 = PathResult77 { pathErr :: Maybe EvalError,78 pathTrace :: T.ExecTrace,79 pathVars :: ST.Assign80 }81 deriving (Show, Eq)8283initPath :: PathResult84initPath = PathResult Nothing [] Map.empty8586data Engine87 = Engine88 { expSolver :: SMT.Solver,89 expPathSel :: PathSel,90 expEnv :: Env,91 expLastPath :: PathResult92 }9394newEngine :: Env -> SMT.Solver -> Engine95newEngine env solver =96 Engine97 { expSolver = solver,98 expPathSel = newPathSel,99 expEnv = env,100 expLastPath = initPath101 }102103findNext :: [SMT.SExpr] -> T.ExecTrace -> StateT Engine IO (Maybe Model)104findNext symVars eTrace = do105 engine <- get106107 let pathSel = trackTrace (expPathSel engine) eTrace108 (model, nextPathSel) <-109 liftIO $ findUnexplored (expSolver engine) symVars pathSel110111 put $ engine {expPathSel = nextPathSel}112 pure model113114-- TODO: Consider modelling changes of the PathSel (via findNext) and115-- changes of the Store (via ST.finalize and ST.setModel) as a StateT.116explorePath :: SimState a -> StateT Engine IO Bool117explorePath simState = do118 engine@(Engine {expEnv = env}) <- get119 maybePath <- try $ run env120 let (mayErr, eTrace, nStore) =121 case maybePath of122 Left (err :: ErrorPath) ->123 let st = pathInput err124 in (Just $ pathError err, errTracer st, errStore st)125 Right (t, s) -> (Nothing, t, s)126127 -- Before finalizing the store, we can extract the variables we encountered128 -- during this concrete execution, as well as the concrete values used for129 -- these variables during the execution.130 let inputVars = ST.sexprs nStore131 varAssign = ST.cValues nStore132 put $ engine {expLastPath = PathResult mayErr eTrace varAssign}133134 -- Finalize the store (declare new symbolic vars in solver) and then,135 -- based on the new solver state, solve constraints to find a new input.136 store <- liftIO $ ST.finalize (expSolver engine) nStore137 model <- findNext inputVars eTrace138 case model of139 Nothing -> pure False140 Just newModel -> do141 let nEnv = env {envStore = ST.setModel store newModel}142 in modify (\e -> e {expEnv = nEnv})143 pure True144 where145 run env = lift $ evalStateT (unSimState $ runPath simState) env146147------------------------------------------------------------------------148149exploreFunc ::150 Engine ->151 QBE.FuncDef ->152 [(String, QBE.ExtType)] ->153 IO [PathResult]154exploreFunc engine entry params = do155 let funcState = mapM (uncurry makeConcolic) params >>= execFunc entry156 evalStateT (exploreFunc' funcState) engine157 where158 exploreFunc' st = do159 morePaths <- explorePath st160 curEngine <- get161162 let ret = expLastPath curEngine163 in if morePaths164 then (ret :) <$> exploreFunc' st165 else pure [ret]