Skip to content
215 changes: 215 additions & 0 deletions src/driver/commands/get_pre_models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
use std::{ops::DerefMut, process::ExitCode, sync::Arc};

use crate::{
ast::{BinOpKind, Direction, ExprBuilder, FileId, Span, TyKind},
driver::{
commands::{mk_cli_server, print_timings, verify::VerifyCommand},
core_heyvl::{lower_core_heyvl_task, CoreHeyVLTask},
error::{finalize_caesar_result, CaesarError},
front::parse_and_tycheck,
item::Item,
quant_proof::BoolVcProveTask,
smt_proof::{run_smt_check_sat, set_global_z3_params},
},
resource_limits::{await_with_resource_limits, LimitsRef},
servers::{Server, SharedServer},
};
use z3::SatResult;

pub async fn run_nontrivial_model_command(options: VerifyCommand) -> ExitCode {
let (user_files, server) = match mk_cli_server(&options.input_options) {
Ok(value) => value,
Err(value) => return value,
};
let options = Arc::new(options);
let nontrivial_models_result = nontrivial_model_files(&options, &server, user_files).await;

if options.debug_options.timing {
print_timings();
}

finalize_caesar_result(server, &options.rlimit_options, nontrivial_models_result)
}

/// This is just like verify_files()
pub async fn nontrivial_model_files(
options: &Arc<VerifyCommand>,
server: &SharedServer,
user_files: Vec<FileId>,
) -> Result<bool, CaesarError> {
let handle = |limits_ref: LimitsRef| {
let options = options.clone();
let server = server.clone();
tokio::task::spawn_blocking(move || {
// execute the verifier with a larger stack size of 50MB. the
// default stack size might be quite small and we need to do quite a
// lot of recursion.
let stack_size = 50 * 1024 * 1024;
stacker::maybe_grow(stack_size, stack_size, move || {
let mut server = server.lock().unwrap();
nontrivial_model_files_main(&options, limits_ref, server.deref_mut(), &user_files)
})
})
};
// Unpacking lots of Results with `.await??` :-)
await_with_resource_limits(
Some(options.rlimit_options.timeout()),
Some(options.rlimit_options.mem_limit()),
handle,
)
.await??
}

fn nontrivial_model_files_main(
options: &VerifyCommand,
limits_ref: LimitsRef,
server: &mut dyn Server,
user_files: &[FileId],
) -> Result<bool, CaesarError> {
let (mut module, mut tcx) = parse_and_tycheck(
&options.input_options,
&options.debug_options,
server,
user_files,
)?;

// Register all relevant source units with the server
module.register_with_server(server)?;

// explain high-level HeyVL if requested
if options.lsp_options.explain_vc {
module.explain_high_level_vc(&tcx, &limits_ref, server)?;
}

// Desugar encodings from source units.
module.apply_encodings(&mut tcx, server)?;

if options.debug_options.print_core_procs {
println!("HeyVL get nontrivial model task with generated procs:");
println!("{module}");
}

// generate dependency graph to determine which declarations are needed for
// the SMT translation later
let mut depgraph = module.generate_depgraph(&options.opt_options.function_encoding)?;

// translate program to relevant preexpectation conditions
let mut verify_units: Vec<Item<CoreHeyVLTask>> = module
.items
.into_iter()
.flat_map(|item| {
item.flat_map(|unit| CoreHeyVLTask::from_proc_pre_model(unit, &mut depgraph))
})
.collect();

if options.debug_options.z3_trace && verify_units.len() > 1 {
tracing::warn!("Z3 tracing is enabled with multiple units. Intermediate tracing results will be overwritten.");
}
// set requested global z3 options
set_global_z3_params(options, &limits_ref);

let mut num_sat: usize = 0;
let mut num_unsat: usize = 0;

for verify_unit in &mut verify_units {
limits_ref.check_limits()?;

let (name, mut verify_unit) = verify_unit.enter_with_name();

// Set the current unit as ongoing
server.set_ongoing_unit(name)?;

// no slicing
let mut new_options = VerifyCommand::default();
new_options.slice_options.no_slice_error = true;

let (vc_expr, _slice_vars) = lower_core_heyvl_task(
&mut tcx,
name,
&new_options,
&limits_ref,
server,
&mut verify_unit,
)?;

// Lowering the quantitative task to a Boolean one.
let mut quant_task = vc_expr;

// Unfolding (applies substitutions)
quant_task.unfold(options, &limits_ref, &tcx)?;

quant_task.trace_expr_stats();

// Turn quantitative formula into a Boolean one
let builder = ExprBuilder::new(Span::dummy_span());
let expr = quant_task.expr.clone();

// Construct the condition based on quantifier direction
let res = match quant_task.direction {
Direction::Up => {
// For coprocs, check if expr < top
let top = builder.top_lit(quant_task.expr.ty.as_ref().unwrap());
builder.binary(BinOpKind::Lt, Some(TyKind::Bool), expr, top)
}
Direction::Down => {
// For procs, check if expr > bot
let bot = builder.bot_lit(quant_task.expr.ty.as_ref().unwrap());
builder.binary(BinOpKind::Gt, Some(TyKind::Bool), expr, bot)
}
};

// Create a Boolean verification task with the comparison
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a BoolVcProveTask was intended to be used only in the context of verification (i.e. vc == top/bot), but the operations should be sound here as well, because they're all equivalence transformations (I think).

Please make a note of this in the PR description, I'll refactor that at some point :)

let mut bool_task = BoolVcProveTask {
quant_vc: quant_task,
vc: res,
};

// Optimizations
if !options.opt_options.no_boolify || options.opt_options.opt_rel {
bool_task.remove_parens();
}
if !options.opt_options.no_boolify {
bool_task.opt_boolify();
}
if options.opt_options.opt_rel {
bool_task.opt_relational();
}

if options.debug_options.print_theorem {
bool_task.print_theorem(name);
}

// Running the SMT check sat task: translating to Z3, running the solver.
// This also handles printing the result
let sat_result = run_smt_check_sat(
&options,
&limits_ref,
&tcx,
&depgraph,
&name,
bool_task,
server,
)?;

// Handle reasons to stop the verifier.
match sat_result {
SatResult::Unknown => return Err(CaesarError::Interrupted),
_ => {}
}

// Increment counters.
match sat_result {
SatResult::Sat => num_sat += 1,
SatResult::Unsat | SatResult::Unknown => num_unsat += 1,
}
}

if !options.lsp_options.language_server {
println!();
println!(
"Found models for {num_sat} (co)procedures, No models for {num_unsat} (co)procedures."
);
}

Ok(num_unsat == 0)
}
6 changes: 6 additions & 0 deletions src/driver/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! (CLI) command and option declarations to run Caesar with.

pub mod get_pre_models;
pub mod model_check;
pub mod options;
pub mod refinement;
Expand All @@ -19,6 +20,7 @@ use clap::{crate_description, Args, CommandFactory, Parser, Subcommand};
use crate::{
ast::FileId,
driver::commands::{
get_pre_models::run_nontrivial_model_command,
model_check::{run_model_checking_command, ModelCheckCommand},
options::{DebugOptions, InputOptions},
refinement::run_verify_entailment_command,
Expand Down Expand Up @@ -65,6 +67,7 @@ impl CaesarCli {
CaesarCommand::Mc(options) => run_model_checking_command(options),
CaesarCommand::Lsp(options) => run_lsp_command(options).await,
CaesarCommand::ShellCompletions(options) => run_shell_completions_command(options),
CaesarCommand::GetPreModels(options) => run_nontrivial_model_command(options).await,
CaesarCommand::Other(_) => unreachable!(),
}
}
Expand Down Expand Up @@ -98,6 +101,8 @@ pub enum CaesarCommand {
Lsp(VerifyCommand),
/// Generate shell completions for the Caesar binary.
ShellCompletions(ShellCompletionsCommand),
/// Compute a non-trivial model for the preconditions of each (co)proc.
GetPreModels(VerifyCommand),
/// This is to support the default `verify` command.
#[command(external_subcommand)]
#[command(hide(true))]
Expand All @@ -112,6 +117,7 @@ impl CaesarCommand {
CaesarCommand::Lsp(verify_options) => Some(&verify_options.debug_options),
CaesarCommand::Mc(mc_options) => Some(&mc_options.debug_options),
CaesarCommand::ShellCompletions(_) => None,
CaesarCommand::GetPreModels(verify_options) => Some(&verify_options.debug_options),
CaesarCommand::Other(_vec) => unreachable!(),
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/driver/commands/refinement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::{
},
driver::{
commands::{mk_cli_server, print_timings, verify::VerifyCommand},
core_verify::{lower_core_verify_task, CoreVerifyTask},
core_heyvl::{lower_core_heyvl_task, CoreHeyVLTask},
error::{finalize_caesar_result, CaesarError},
front::{parse_and_tycheck, SourceUnit},
item::Item,
Expand Down Expand Up @@ -121,21 +121,21 @@ async fn verify_entailment(
};

let mut first_verify_unit = first
.flat_map(|u| CoreVerifyTask::from_source_unit(u, &mut depgraph))
.flat_map(|u| CoreHeyVLTask::from_proc_verify(u, &mut depgraph))
.unwrap();
let mut second_verify_unit = second
.flat_map(|u| CoreVerifyTask::from_source_unit(u, &mut depgraph))
.flat_map(|u| CoreHeyVLTask::from_proc_verify(u, &mut depgraph))
.unwrap();

let (first_vc, first_slice_stmts) = lower_core_verify_task(
let (first_vc, first_slice_stmts) = lower_core_heyvl_task(
&mut tcx,
&first_name,
options,
&limits_ref,
server,
&mut first_verify_unit.enter_mut(),
)?;
let (second_vc, second_slice_stmts) = lower_core_verify_task(
let (second_vc, second_slice_stmts) = lower_core_heyvl_task(
&mut tcx,
&first_name,
options,
Expand Down
8 changes: 4 additions & 4 deletions src/driver/commands/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
},
print_timings,
},
core_verify::{lower_core_verify_task, CoreVerifyTask},
core_heyvl::{lower_core_heyvl_task, CoreHeyVLTask},
error::{finalize_caesar_result, CaesarError},
front::parse_and_tycheck,
item::Item,
Expand Down Expand Up @@ -211,11 +211,11 @@ fn verify_files_main(
// the SMT translation later
let mut depgraph = module.generate_depgraph(&options.opt_options.function_encoding)?;

let mut verify_units: Vec<Item<CoreVerifyTask>> = module
let mut verify_units: Vec<Item<CoreHeyVLTask>> = module
.items
.into_iter()
.flat_map(|item| {
item.flat_map(|unit| CoreVerifyTask::from_source_unit(unit, &mut depgraph))
item.flat_map(|unit| CoreHeyVLTask::from_proc_verify(unit, &mut depgraph))
})
.collect();

Expand All @@ -240,7 +240,7 @@ fn verify_files_main(
// Lowering the core verify task to a quantitative prove task: applying
// spec call desugaring, preparing slicing, and verification condition
// generation.
let (vc_expr, slice_vars) = lower_core_verify_task(
let (vc_expr, slice_vars) = lower_core_heyvl_task(
&mut tcx,
name,
options,
Expand Down
Loading