1-- SPDX-FileCopyrightText: 2024 University of Bremen2-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>3--4-- SPDX-License-Identifier: MIT AND GPL-3.0-only56module Language.QBE.Backend.Tracer7 ( Branch (Branch),8 newBranch,9 fromBranch,10 ExecTrace,11 newExecTrace,12 toSExprs,13 appendBranch,14 appendCons,15 solveTrace,16 )17where1819import Control.Exception (throwIO)20import Control.Monad (when)21import Language.QBE.Backend (SolverError (UnknownResult), prefixLength)22import Language.QBE.Backend.Model qualified as Model23import Language.QBE.Simulator.Symbolic.Expression qualified as SE24import SimpleBV qualified as SMT2526-- Represents a branch condition in the executed code27data Branch28 = Branch29 Bool -- Whether negation of the branch was attempted30 SE.BitVector -- The symbolic branch condition31 deriving (Show, Eq)3233-- Create a new branch condition.34newBranch :: SE.BitVector -> Branch35newBranch = Branch False3637-- Create a new branch from an existing branch, thereby updating its metadata.38-- It is assumed that the condition, encoded in the branches, is equal.39fromBranch :: Branch -> Branch -> Branch40fromBranch (Branch wasNeg' _) (Branch wasNeg ast) =41 Branch (wasNeg || wasNeg') ast4243------------------------------------------------------------------------4445-- Represents a single execution through a program, tracking for each46-- symbolic branch condition if it was 'True' or 'False'.47type ExecTrace = [(Bool, Branch)]4849-- Create a new empty execution tree.50newExecTrace :: ExecTrace51newExecTrace = []5253-- Return all branch conditions of an 'ExecTrace'.54toSExprs :: ExecTrace -> [SMT.SExpr]55toSExprs = map (\(_, Branch _ bv) -> SE.toSExpr bv)5657-- Append a branch to the execution trace, denoting via a 'Bool'58-- if the branch was taken or if it was not taken.59appendBranch :: ExecTrace -> Bool -> Branch -> ExecTrace60appendBranch trace wasTrue branch = trace ++ [(wasTrue, branch)]6162-- Append a constraint to the execution tree. This constraint must63-- be true and, contrary to appendBranch, negation will not be64-- attempted for it.65appendCons :: ExecTrace -> SE.BitVector -> ExecTrace66appendCons trace cons = trace ++ [(True, Branch True cons)]6768-- For a given execution trace, return an assignment (represented69-- as a 'Model') which statisfies all symbolic branch conditions.70-- If such an assignment does not exist, then 'Nothing' is returned.71--72-- Throws a 'SolverError' on an unknown solver result (e.g., on timeout).73solveTrace :: SMT.Solver -> [SMT.SExpr] -> ExecTrace -> ExecTrace -> IO (Maybe Model.Model)74solveTrace solver inputVars oldTrace newTrace = do75 -- Determine the common prefix of the current trace and the old trace76 -- drop constraints beyond this common prefix from the current solver77 -- context. Thereby, keeping the common prefix and making use of78 -- incremental solving capabilities.79 let prefix = prefixLength newTrace oldTrace80 let toDrop = length oldTrace - prefix8182 -- Micro optimization: When we don't have anything to drop, then83 -- don't call .popMany thereby avoiding communication with the solver.84 when (toDrop /= 0) $85 SMT.popMany solver (fromIntegral toDrop)8687 -- Only enforce new constraints, i.e. those beyond the common prefix.88 assertTrace (drop prefix newTrace)8990 isSat <- SMT.check solver91 case isSat of92 SMT.Sat -> Just <$> Model.getModel solver inputVars93 SMT.Unsat -> pure Nothing94 SMT.Unknown -> throwIO UnknownResult95 where96 -- Add all conditions enforced by the given 'ExecTrace' to the solver.97 -- Returns a list of all asserted conditions.98 assertTrace :: ExecTrace -> IO ()99 assertTrace [] = pure ()100 assertTrace t = do101 let conds = map (\(b, Branch _ c) -> SE.toCond b c) t102 mapM_ (\c -> SMT.push solver >> SMT.assert solver c) conds