1-- SPDX-FileCopyrightText: 2025-2026 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: GPL-3.0-only45module Main (main) where67import Control.Monad (when)8import Control.Monad.State (evalStateT, gets, liftIO)9import Data.Binary (encodeFile)10import Data.KTest (KTest (KTest), KTestObj, fromAssign)11import Data.String (fromString)12import Language.QBE.CmdLine qualified as CMD13import Language.QBE.Simulator (execFunc)14import Language.QBE.Simulator.Concolic.State (mkEnv)15import Language.QBE.Simulator.Explorer16 ( Engine (expPathVars),17 defSolver,18 explorePath,19 logSolver,20 newEngine,21 )22import Language.QBE.Types qualified as QBE23import Options.Applicative qualified as OPT24import System.Directory (createDirectoryIfMissing)25import System.FilePath (addExtension, (</>))26import System.IO (IOMode (WriteMode), withFile)27import Text.Printf (printf)2829data Opts = Opts30 { optLog :: Maybe FilePath,31 optSeed :: Maybe Int,32 optTests :: Maybe FilePath,33 optVerbose :: Bool,34 optBase :: CMD.BasicArgs35 }3637optsParser :: OPT.Parser Opts38optsParser =39 Opts40 <$> OPT.optional41 ( OPT.strOption42 ( OPT.long "dump-smt2"43 <> OPT.short 'd'44 <> OPT.metavar "FILE"45 <> OPT.help "Output queries as an SMT-LIB file"46 )47 )48 <*> OPT.optional49 ( OPT.option50 OPT.auto51 ( OPT.long "random-seed"52 <> OPT.short 'r'53 <> OPT.help "Initial seed to for the random number generator"54 )55 )56 <*> OPT.optional57 ( OPT.strOption58 ( OPT.long "write-tests"59 <> OPT.short 't'60 <> OPT.metavar "DIR"61 <> OPT.help "Write .ktest files to the given directory"62 )63 )64 <*> OPT.switch65 ( OPT.long "verbose"66 <> OPT.short 'v'67 <> OPT.help "Enable more verbose output"68 )69 <*> CMD.basicArgs7071------------------------------------------------------------------------7273data KTestConf = KTestConf FilePath String74 deriving (Show)7576mkKTestConf :: FilePath -> String -> IO KTestConf77mkKTestConf directory name = do78 createDirectoryIfMissing True directory79 pure $ KTestConf directory name8081writeKTest :: KTestConf -> Int -> [KTestObj] -> IO ()82writeKTest (KTestConf directory name) pathID =83 writeKTest' pathID . KTest [fromString name]84 where85 writeKTest' :: Int -> KTest -> IO ()86 writeKTest' n ktest = do87 flip encodeFile ktest $88 addExtension89 (directory </> ("test" ++ printf "%06d" n))90 ".ktest"9192------------------------------------------------------------------------9394exploreEntry :: Opts -> Maybe KTestConf -> Engine -> QBE.FuncDef -> IO Int95exploreEntry opts ktest engine entry =96 evalStateT (go 1 $ execFunc entry []) engine97 where98 go n st = do99 when (optVerbose opts) $100 liftIO (putStrLn $ "Exploring path " ++ show n ++ "...")101102 morePaths <- explorePath st103 case ktest of104 Just conf -> do105 assign <- gets (fromAssign . expPathVars)106 liftIO $ writeKTest conf n assign107 Nothing -> pure ()108109 if morePaths110 then go (n + 1) st111 else pure n112113exploreFile :: Opts -> IO Int114exploreFile opts@Opts {optBase = base} = do115 (prog, func) <- CMD.parseEntryFile $ CMD.optQBEFile base116117 ktest <-118 case optTests opts of119 Just dir -> do120 Just <$> mkKTestConf dir (CMD.optQBEFile $ optBase opts)121 Nothing -> pure Nothing122123 env <- mkEnv prog (CMD.optMemStart base) (CMD.optMemSize base) (optSeed opts)124 case optLog opts of125 Just fn -> withFile fn WriteMode (exploreWithHandle ktest env func)126 Nothing -> do127 engine <- newEngine env <$> defSolver128 exploreEntry opts ktest engine func129 where130 exploreWithHandle ktest env func handle = do131 engine <- newEngine env <$> logSolver handle132 exploreEntry opts ktest engine func133134------------------------------------------------------------------------135136cmd :: OPT.ParserInfo Opts137cmd =138 OPT.info139 (optsParser OPT.<**> OPT.helper)140 ( OPT.fullDesc141 <> OPT.progDesc "Symbolic execution of programs in the QBE intermediate language"142 )143144main :: IO ()145main = do146 numPaths <- OPT.execParser cmd >>= exploreFile147 putStrLn $ "\n---\nAmount of paths: " ++ show numPaths