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 (solver, args) <- findSolver66 s <-67 SMT.newSolverWithConfig68 (SMT.defaultConfig solver args)69 { SMT.solverLogger = SMT.smtSolverLogger l70 }71 SMT.setLogic s logic72 return s7374------------------------------------------------------------------------7576data PathResult77 = PathResult78 { pathErr :: Maybe EvalError,79 pathTrace :: T.ExecTrace,80 pathVars :: ST.Assign81 }82 deriving (Show, Eq)8384initPath :: PathResult85initPath = PathResult Nothing [] Map.empty8687data Engine88 = Engine89 { expSolver :: SMT.Solver,90 expPathSel :: PathSel,91 expEnv :: Env,92 expLastPath :: PathResult93 }9495newEngine :: Env -> SMT.Solver -> Engine96newEngine env solver =97 Engine98 { expSolver = solver,99 expPathSel = newPathSel,100 expEnv = env,101 expLastPath = initPath102 }103104findNext :: [SMT.SExpr] -> T.ExecTrace -> StateT Engine IO (Maybe Model)105findNext symVars eTrace = do106 engine <- get107108 let pathSel = trackTrace (expPathSel engine) eTrace109 (model, nextPathSel) <-110 liftIO $ findUnexplored (expSolver engine) symVars pathSel111112 put $ engine {expPathSel = nextPathSel}113 pure model114115-- TODO: Consider modelling changes of the PathSel (via findNext) and116-- changes of the Store (via ST.finalize and ST.setModel) as a StateT.117explorePath :: SimState a -> StateT Engine IO Bool118explorePath simState = do119 engine@(Engine {expEnv = env}) <- get120 maybePath <- try $ run env121 let (mayErr, eTrace, nStore) =122 case maybePath of123 Left (err :: ErrorPath) ->124 let st = pathInput err125 in (Just $ pathError err, errTracer st, errStore st)126 Right (t, s) -> (Nothing, t, s)127128 -- Before finalizing the store, we can extract the variables we encountered129 -- during this concrete execution, as well as the concrete values used for130 -- these variables during the execution.131 let inputVars = ST.sexprs nStore132 varAssign = ST.cValues nStore133 put $ engine {expLastPath = PathResult mayErr eTrace varAssign}134135 -- Finalize the store (declare new symbolic vars in solver) and then,136 -- based on the new solver state, solve constraints to find a new input.137 store <- liftIO $ ST.finalize (expSolver engine) nStore138 model <- findNext inputVars eTrace139 case model of140 Nothing -> pure False141 Just newModel -> do142 let nEnv = env {envStore = ST.setModel store newModel}143 in modify (\e -> e {expEnv = nEnv})144 pure True145 where146 run env = lift $ evalStateT (unSimState $ runPath simState) env147148------------------------------------------------------------------------149150exploreFunc ::151 Engine ->152 QBE.FuncDef ->153 [(String, QBE.ExtType)] ->154 IO [PathResult]155exploreFunc engine entry params = do156 let funcState = mapM (uncurry makeConcolic) params >>= execFunc entry157 evalStateT (exploreFunc' funcState) engine158 where159 exploreFunc' st = do160 morePaths <- explorePath st161 curEngine <- get162163 let ret = expLastPath curEngine164 in if morePaths165 then (ret :) <$> exploreFunc' st166 else pure [ret]