diff --git a/Cargo.lock b/Cargo.lock
index 150dcc59..1229f7f7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2676,6 +2676,7 @@ dependencies = [
"num",
"once_cell",
"ref-cast",
+ "regex",
"tempfile",
"thiserror 1.0.69",
"tracing",
diff --git a/src/driver.rs b/src/driver.rs
index 146ef65d..d075bef8 100644
--- a/src/driver.rs
+++ b/src/driver.rs
@@ -53,7 +53,7 @@ use crate::{
vcgen::Vcgen,
},
version::write_detailed_version_info,
- DebugOptions, SliceOptions, SliceVerifyMethod, VerifyCommand, VerifyError,
+ DebugOptions, SMTSolverType, SliceOptions, SliceVerifyMethod, VerifyCommand, VerifyError,
};
use ariadne::ReportKind;
@@ -65,7 +65,7 @@ use z3::{
use z3rro::{
model::InstrumentedModel,
probes::ProbeSummary,
- prover::{IncrementalMode, ProveResult, Prover},
+ prover::{IncrementalMode, ProveResult, Prover, SolverType},
smtlib::Smtlib,
util::{PrefixWriter, ReasonUnknown},
};
@@ -696,7 +696,13 @@ impl<'ctx> SmtVcUnit<'ctx> {
let span = info_span!("SAT check");
let _entered = span.enter();
- let prover = mk_valid_query_prover(limits_ref, ctx, translate, &self.vc);
+ let prover = mk_valid_query_prover(
+ limits_ref,
+ ctx,
+ translate,
+ &self.vc,
+ options.smt_solver_options.smt_solver.clone(),
+ );
if options.debug_options.probe {
let goal = Goal::new(ctx, false, false, false);
@@ -823,9 +829,18 @@ fn mk_valid_query_prover<'smt, 'ctx>(
ctx: &'ctx Context,
smt_translate: &TranslateExprs<'smt, 'ctx>,
valid_query: &Bool<'ctx>,
+ smt_solver: SMTSolverType,
) -> Prover<'ctx> {
+ let solver_type = match smt_solver {
+ SMTSolverType::InternalZ3 => SolverType::InternalZ3,
+ SMTSolverType::ExternalZ3 => SolverType::ExternalZ3,
+ SMTSolverType::Swine => SolverType::SWINE,
+ SMTSolverType::CVC5 => SolverType::CVC5,
+ SMTSolverType::Yices => SolverType::YICES,
+ };
+
// create the prover and set the params
- let mut prover = Prover::new(ctx, IncrementalMode::Native);
+ let mut prover = Prover::new(ctx, IncrementalMode::Native, solver_type);
if let Some(remaining) = limits_ref.time_left() {
prover.set_timeout(remaining);
}
diff --git a/src/main.rs b/src/main.rs
index 2c0b5932..da54383f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -39,7 +39,10 @@ use tokio::task::JoinError;
use tracing::{error, info, warn};
use vc::explain::VcExplanation;
-use z3rro::{prover::ProveResult, util::ReasonUnknown};
+use z3rro::{
+ prover::{ProveResult, ProverCommandError},
+ util::ReasonUnknown,
+};
pub mod ast;
mod driver;
@@ -138,6 +141,9 @@ pub struct VerifyCommand {
#[command(flatten)]
pub debug_options: DebugOptions,
+
+ #[command(flatten)]
+ pub smt_solver_options: SMTSolverOptions,
}
#[derive(Debug, Args)]
@@ -380,6 +386,28 @@ pub struct DebugOptions {
pub probe: bool,
}
+#[derive(Debug, Default, Args)]
+#[command(next_help_heading = "SMT Solver Options")]
+pub struct SMTSolverOptions {
+ #[arg(long, default_value = "default")]
+ pub smt_solver: SMTSolverType,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
+pub enum SMTSolverType {
+ #[default]
+ #[value(name = "default")]
+ InternalZ3,
+ #[value(name = "z3")]
+ ExternalZ3,
+ #[value(name = "swine")]
+ Swine,
+ #[value(name = "cvc5")]
+ CVC5,
+ #[value(name = "yices")]
+ Yices,
+}
+
#[derive(Debug, Default, Args)]
#[command(next_help_heading = "Slicing Options")]
pub struct SliceOptions {
@@ -533,6 +561,10 @@ fn finalize_verify_result(
tracing::error!("Interrupted");
ExitCode::from(130) // 130 seems to be a standard exit code for CTRL+C
}
+ Err(VerifyError::ProverError(err)) => {
+ eprintln!("{}", err.to_string());
+ ExitCode::from(1)
+ }
}
}
@@ -612,6 +644,8 @@ pub enum VerifyError {
/// The verifier was interrupted.
#[error("interrupted")]
Interrupted,
+ #[error("{0}")]
+ ProverError(#[from] ProverCommandError),
}
/// Verify a list of `user_files`. The `options.files` value is ignored here.
diff --git a/src/opt/fuzz_test.rs b/src/opt/fuzz_test.rs
index cdcbd81e..e264a913 100644
--- a/src/opt/fuzz_test.rs
+++ b/src/opt/fuzz_test.rs
@@ -2,7 +2,8 @@ use proptest::{
prelude::*,
test_runner::{TestCaseResult, TestRunner},
};
-use z3rro::prover::{IncrementalMode, ProveResult, Prover};
+
+use z3rro::prover::{IncrementalMode, ProveResult, Prover, SolverType};
use crate::{
ast::{
@@ -201,23 +202,24 @@ fn prove_equiv(expr: Expr, optimized: Expr, tcx: &TyCtx) -> TestCaseResult {
let smt_ctx = SmtCtx::new(&ctx, tcx);
let mut translate = TranslateExprs::new(&smt_ctx);
let eq_expr_z3 = translate.t_bool(&eq_expr);
- let mut prover = Prover::new(&ctx, IncrementalMode::Native);
+ let mut prover = Prover::new(&ctx, IncrementalMode::Native, SolverType::InternalZ3);
translate
.local_scope()
.add_assumptions_to_prover(&mut prover);
prover.add_provable(&eq_expr_z3);
let x = match prover.check_proof() {
- ProveResult::Proof => Ok(()),
- ProveResult::Counterexample => {
+ Ok(ProveResult::Proof) => Ok(()),
+ Ok(ProveResult::Counterexample) => {
let model = prover.get_model().unwrap();
Err(TestCaseError::fail(format!(
"rewrote {} ...into... {}, but those are not equivalent:\n{}",
expr, optimized, model
)))
}
- ProveResult::Unknown(reason) => {
+ Ok(ProveResult::Unknown(reason)) => {
Err(TestCaseError::fail(format!("unknown result ({})", reason)))
}
+ Err(err) => Err(TestCaseError::fail(format!("{}", err))),
};
x
}
diff --git a/src/opt/unfolder.rs b/src/opt/unfolder.rs
index 7cc15b18..d97146bb 100644
--- a/src/opt/unfolder.rs
+++ b/src/opt/unfolder.rs
@@ -25,7 +25,7 @@
use std::ops::DerefMut;
use z3::SatResult;
-use z3rro::prover::{IncrementalMode, Prover};
+use z3rro::prover::{IncrementalMode, Prover, SolverType};
use crate::{
ast::{
@@ -60,7 +60,7 @@ impl<'smt, 'ctx> Unfolder<'smt, 'ctx> {
// it's important that we use the native incremental mode here, because
// the performance benefit from the unfolder relies on many very fast
// SAT checks.
- let prover = Prover::new(ctx.ctx(), IncrementalMode::Native);
+ let prover = Prover::new(ctx.ctx(), IncrementalMode::Native, SolverType::InternalZ3);
Unfolder {
subst: Subst::new(ctx.tcx(), &limits_ref),
@@ -98,7 +98,7 @@ impl<'smt, 'ctx> Unfolder<'smt, 'ctx> {
// here we want to do a SAT check and not a proof search. if the
// expression is e.g. `false`, then we want to get `Unsat` from the
// solver and not `Proof`!
- if this.prover.check_sat() == SatResult::Unsat {
+ if this.prover.check_sat() == Ok(SatResult::Unsat) {
tracing::trace!(solver=?this.prover, "eliminated zero expr");
None
} else {
diff --git a/src/slicing/solver.rs b/src/slicing/solver.rs
index f0149f96..c4bcb7a7 100644
--- a/src/slicing/solver.rs
+++ b/src/slicing/solver.rs
@@ -9,16 +9,16 @@ use z3::{
};
use z3rro::{
model::{InstrumentedModel, ModelConsistency},
- prover::{ProveResult, Prover},
+ prover::{ProveResult, Prover, ProverCommandError},
util::ReasonUnknown,
};
use crate::{
ast::{ExprBuilder, Span},
- resource_limits::{LimitError, LimitsRef},
+ resource_limits::LimitsRef,
slicing::{
model::{SliceMode, SliceModel},
- util::{PartialMinimizeResult, SubsetExploration},
+ util::{at_most_k, PartialMinimizeResult, SubsetExploration},
},
smt::translate_exprs::TranslateExprs,
VerifyError,
@@ -218,7 +218,10 @@ impl<'ctx> SliceSolver<'ctx> {
options,
limits_ref,
)?;
- if exists_forall_solver.check_sat() == SatResult::Sat {
+ let sat_res = exists_forall_solver
+ .check_sat()
+ .map_err(|err| VerifyError::ProverError(err))?;
+ if sat_res == SatResult::Sat {
let model = exists_forall_solver.get_model().unwrap();
let slice_model =
SliceModel::from_model(SliceMode::Verify, &self.slice_stmts, selection, &model);
@@ -251,7 +254,7 @@ impl<'ctx> SliceSolver<'ctx> {
let res = self.prover.check_proof_assuming(&active_toggle_values);
let mut slice_searcher = SliceModelSearch::new(active_toggle_values.clone());
- if let ProveResult::Proof = res {
+ if let Ok(ProveResult::Proof) = res {
slice_searcher.found_active(self.prover.get_unsat_core());
}
@@ -351,7 +354,10 @@ impl<'ctx> SliceSolver<'ctx> {
self.prover.push();
slice_sat_binary_search(&mut self.prover, &active_toggle_values, options, limits_ref)?;
- let res = self.prover.check_proof();
+ let res = self
+ .prover
+ .check_proof()
+ .map_err(|err| VerifyError::ProverError(err))?;
let model = if let Some(model) = self.prover.get_model() {
assert!(matches!(
res,
@@ -461,16 +467,16 @@ fn slice_sat_binary_search<'ctx>(
) -> Result<(), VerifyError> {
assert_eq!(prover.level(), 2);
- let slice_vars: Vec<(&Bool<'ctx>, i32)> =
- active_slice_vars.iter().map(|value| (value, 1)).collect();
-
let set_at_most_true = |prover: &mut Prover<'ctx>, at_most_n: usize| {
prover.pop();
prover.push();
let ctx = prover.get_context();
- let at_most_n_true = Bool::pb_le(ctx, &slice_vars, at_most_n as i32);
- prover.add_assumption(&at_most_n_true);
+ if !active_slice_vars.is_empty() {
+ let at_most_n_true =
+ at_most_k(ctx, at_most_n, active_slice_vars, prover.get_solver_type());
+ prover.add_assumption(&at_most_n_true);
+ }
};
// TODO: we could have min_least_bound set to 1 if we could conclude for
@@ -485,7 +491,7 @@ fn slice_sat_binary_search<'ctx>(
// the fix would be to track explicitly whether we can make that assumption
// that min_least_bound is 1.
let min_least_bound = 0;
- let mut minimize = PartialMinimizer::new(min_least_bound..=slice_vars.len());
+ let mut minimize = PartialMinimizer::new(min_least_bound..=active_slice_vars.len());
let mut cur_solver_n = None;
let mut slice_searcher = SliceModelSearch::new(active_slice_vars.to_vec());
@@ -506,7 +512,10 @@ fn slice_sat_binary_search<'ctx>(
if let Some(timeout) = limits_ref.time_left() {
prover.set_timeout(timeout);
}
- let res = prover.check_sat();
+
+ let res = prover
+ .check_sat()
+ .map_err(|err| VerifyError::ProverError(err))?;
entered.record("res", tracing::field::debug(res));
@@ -571,7 +580,9 @@ fn slice_sat_binary_search<'ctx>(
if let Some(timeout) = limits_ref.time_left() {
prover.set_timeout(timeout);
}
- let res = prover.check_sat();
+ let res = prover
+ .check_sat()
+ .map_err(|err| VerifyError::ProverError(err))?;
if minimize.min_accept().is_some() {
assert!(res == SatResult::Sat || res == SatResult::Unknown);
} else if minimize.max_reject().is_some() {
@@ -593,7 +604,7 @@ pub fn slice_unsat_search<'ctx>(
prover: &mut Prover<'ctx>,
options: &SliceSolveOptions,
limits_ref: &LimitsRef,
-) -> Result