cache results of formal proofs
All checks were successful
/ test (push) Successful in 39m54s

This commit is contained in:
Jacob Lifshay 2024-10-07 23:31:24 -07:00
parent 99180eb3b4
commit bf907c3872
Signed by: programmerjake
SSH key fingerprint: SHA256:B1iRVvUJkvd7upMIiMqn6OyxvD2SgJkAH3ZnUOj6z+c
4 changed files with 175 additions and 16 deletions

48
Cargo.lock generated
View file

@ -69,6 +69,18 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -109,6 +121,20 @@ dependencies = [
"wyz", "wyz",
] ]
[[package]]
name = "blake3"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"serde",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -118,6 +144,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "cc"
version = "1.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
dependencies = [
"shlex",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -170,6 +205,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.12" version = "0.2.12"
@ -242,6 +283,7 @@ name = "fayalite"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"blake3",
"clap", "clap",
"eyre", "eyre",
"fayalite-proc-macros", "fayalite-proc-macros",
@ -521,6 +563,12 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"

View file

@ -19,6 +19,7 @@ fayalite-proc-macros-impl = { version = "=0.2.0", path = "crates/fayalite-proc-m
fayalite-visit-gen = { version = "=0.2.0", path = "crates/fayalite-visit-gen" } fayalite-visit-gen = { version = "=0.2.0", path = "crates/fayalite-visit-gen" }
base16ct = "0.2.0" base16ct = "0.2.0"
bitvec = { version = "1.0.1", features = ["serde"] } bitvec = { version = "1.0.1", features = ["serde"] }
blake3 = { version = "1.5.4", features = ["serde"] }
clap = { version = "4.5.9", features = ["derive", "env", "string"] } clap = { version = "4.5.9", features = ["derive", "env", "string"] }
eyre = "0.6.12" eyre = "0.6.12"
hashbrown = "0.14.3" hashbrown = "0.14.3"

View file

@ -15,6 +15,7 @@ version.workspace = true
[dependencies] [dependencies]
bitvec.workspace = true bitvec.workspace = true
blake3.workspace = true
clap.workspace = true clap.workspace = true
eyre.workspace = true eyre.workspace = true
fayalite-proc-macros.workspace = true fayalite-proc-macros.workspace = true

View file

@ -12,6 +12,7 @@ use clap::{
Parser, Subcommand, ValueEnum, ValueHint, Parser, Subcommand, ValueEnum, ValueHint,
}; };
use eyre::{eyre, Report}; use eyre::{eyre, Report};
use serde::{Deserialize, Serialize};
use std::{ use std::{
error, error,
ffi::OsString, ffi::OsString,
@ -197,8 +198,9 @@ impl BaseArgs {
&self, &self,
_acquired_job: &mut AcquiredJob, _acquired_job: &mut AcquiredJob,
mut command: process::Command, mut command: process::Command,
mut captured_output: Option<&mut String>,
) -> io::Result<process::ExitStatus> { ) -> io::Result<process::ExitStatus> {
if self.redirect_output_for_rust_test { if self.redirect_output_for_rust_test || captured_output.is_some() {
let (reader, writer) = os_pipe::pipe()?; let (reader, writer) = os_pipe::pipe()?;
let mut reader = io::BufReader::new(reader); let mut reader = io::BufReader::new(reader);
command.stderr(writer.try_clone()?); command.stderr(writer.try_clone()?);
@ -209,6 +211,9 @@ impl BaseArgs {
Ok(loop { Ok(loop {
let status = child.try_wait()?; let status = child.try_wait()?;
streaming_read_utf8(&mut reader, |s| { streaming_read_utf8(&mut reader, |s| {
if let Some(captured_output) = captured_output.as_deref_mut() {
captured_output.push_str(s);
}
// use print! so output goes to Rust test output capture // use print! so output goes to Rust test output capture
print!("{s}"); print!("{s}");
io::Result::Ok(()) io::Result::Ok(())
@ -369,6 +374,7 @@ pub struct VerilogArgs {
pub struct VerilogOutput { pub struct VerilogOutput {
pub firrtl: FirrtlOutput, pub firrtl: FirrtlOutput,
pub verilog_files: Vec<PathBuf>, pub verilog_files: Vec<PathBuf>,
pub contents_hash: Option<blake3::Hash>,
} }
impl VerilogOutput { impl VerilogOutput {
@ -386,6 +392,7 @@ impl VerilogArgs {
let file_separator_prefix = "\n// ----- 8< ----- FILE \""; let file_separator_prefix = "\n// ----- 8< ----- FILE \"";
let file_separator_suffix = "\" ----- 8< -----\n\n"; let file_separator_suffix = "\" ----- 8< -----\n\n";
let mut input = &*input; let mut input = &*input;
output.contents_hash = Some(blake3::hash(input.as_bytes()));
let main_verilog_file = output.main_verilog_file(); let main_verilog_file = output.main_verilog_file();
let mut file_name: Option<&Path> = Some(&main_verilog_file); let mut file_name: Option<&Path> = Some(&main_verilog_file);
loop { loop {
@ -428,6 +435,7 @@ impl VerilogArgs {
let output = VerilogOutput { let output = VerilogOutput {
firrtl: firrtl_output, firrtl: firrtl_output,
verilog_files: vec![], verilog_files: vec![],
contents_hash: None,
}; };
let mut cmd = process::Command::new(firtool); let mut cmd = process::Command::new(firtool);
cmd.arg(output.firrtl.firrtl_file()); cmd.arg(output.firrtl.firrtl_file());
@ -442,7 +450,7 @@ impl VerilogArgs {
} }
cmd.args(firtool_extra_args); cmd.args(firtool_extra_args);
cmd.current_dir(&output.firrtl.output_dir); cmd.current_dir(&output.firrtl.output_dir);
let status = firrtl.base.run_external_command(acquired_job, cmd)?; let status = firrtl.base.run_external_command(acquired_job, cmd, None)?;
if status.success() { if status.success() {
self.process_unadjusted_verilog_file(output) self.process_unadjusted_verilog_file(output)
} else { } else {
@ -541,7 +549,7 @@ pub struct FormalArgs {
)] )]
pub sby: PathBuf, pub sby: PathBuf,
#[arg(long)] #[arg(long)]
pub sby_extra_args: Vec<OsString>, pub sby_extra_args: Vec<String>,
#[arg(long, default_value_t)] #[arg(long, default_value_t)]
pub mode: FormalMode, pub mode: FormalMode,
#[arg(long, default_value_t = Self::DEFAULT_DEPTH)] #[arg(long, default_value_t = Self::DEFAULT_DEPTH)]
@ -550,6 +558,8 @@ pub struct FormalArgs {
pub solver: String, pub solver: String,
#[arg(long)] #[arg(long)]
pub smtbmc_extra_args: Vec<String>, pub smtbmc_extra_args: Vec<String>,
#[arg(long, default_value_t = true, env = "FAYALITE_CACHE_RESULTS")]
pub cache_results: bool,
#[command(flatten)] #[command(flatten)]
_formal_adjust_args: FormalAdjustArgs, _formal_adjust_args: FormalAdjustArgs,
} }
@ -564,16 +574,18 @@ impl fmt::Debug for FormalArgs {
depth, depth,
solver, solver,
smtbmc_extra_args, smtbmc_extra_args,
cache_results,
_formal_adjust_args: _, _formal_adjust_args: _,
} = self; } = self;
f.debug_struct("FormalArgs") f.debug_struct("FormalArgs")
.field("verilog", &verilog) .field("verilog", verilog)
.field("sby", &sby) .field("sby", sby)
.field("sby_extra_args", &sby_extra_args) .field("sby_extra_args", sby_extra_args)
.field("mode", &mode) .field("mode", mode)
.field("depth", &depth) .field("depth", depth)
.field("solver", &solver) .field("solver", solver)
.field("smtbmc_extra_args", &smtbmc_extra_args) .field("smtbmc_extra_args", smtbmc_extra_args)
.field("cache_results", cache_results)
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
@ -592,6 +604,48 @@ impl FormalOutput {
pub fn sby_file(&self) -> PathBuf { pub fn sby_file(&self) -> PathBuf {
self.verilog.firrtl.file_with_ext("sby") self.verilog.firrtl.file_with_ext("sby")
} }
pub fn cache_file(&self) -> PathBuf {
self.verilog.firrtl.file_with_ext("cache.json")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FormalCacheOutput {}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum FormalCacheVersion {
V1,
}
impl FormalCacheVersion {
pub const CURRENT: Self = Self::V1;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FormalCache {
pub version: FormalCacheVersion,
pub contents_hash: blake3::Hash,
pub stdout_stderr: String,
pub result: Result<FormalCacheOutput, String>,
}
impl FormalCache {
pub fn new(
version: FormalCacheVersion,
contents_hash: blake3::Hash,
stdout_stderr: String,
result: Result<FormalCacheOutput, String>,
) -> Self {
Self {
version,
contents_hash,
stdout_stderr,
result,
}
}
} }
impl FormalArgs { impl FormalArgs {
@ -604,6 +658,7 @@ impl FormalArgs {
depth, depth,
smtbmc_extra_args, smtbmc_extra_args,
solver, solver,
cache_results: _,
_formal_adjust_args: _, _formal_adjust_args: _,
} = self; } = self;
let smtbmc_options = smtbmc_extra_args.join(" "); let smtbmc_options = smtbmc_extra_args.join(" ");
@ -644,7 +699,19 @@ impl FormalArgs {
verilog: verilog_output, verilog: verilog_output,
}; };
let sby_file = output.sby_file(); let sby_file = output.sby_file();
std::fs::write(&sby_file, self.sby_contents(&output)?)?; let sby_contents = self.sby_contents(&output)?;
let contents_hash = output.verilog.contents_hash.map(|verilog_hash| {
let mut hasher = blake3::Hasher::new();
hasher.update(verilog_hash.as_bytes());
hasher.update(sby_contents.as_bytes());
hasher.update(&(self.sby_extra_args.len() as u64).to_le_bytes());
for sby_extra_arg in self.sby_extra_args.iter() {
hasher.update(&(sby_extra_arg.len() as u64).to_le_bytes());
hasher.update(sby_extra_arg.as_bytes());
}
hasher.finalize()
});
std::fs::write(&sby_file, sby_contents)?;
let mut cmd = process::Command::new(&self.sby); let mut cmd = process::Command::new(&self.sby);
cmd.arg("-f"); cmd.arg("-f");
cmd.arg(sby_file.file_name().unwrap()); cmd.arg(sby_file.file_name().unwrap());
@ -656,20 +723,62 @@ impl FormalArgs {
NonZeroUsize::new(1).unwrap() NonZeroUsize::new(1).unwrap()
}; };
let new_minimum_count = AcquiredJob::max_available_job_count().min(new_minimum_count); let new_minimum_count = AcquiredJob::max_available_job_count().min(new_minimum_count);
let mut captured_output = String::new();
let cache_file = output.cache_file();
let do_cache = if let Some(contents_hash) = contents_hash.filter(|_| self.cache_results) {
if let Some(FormalCache {
version: FormalCacheVersion::CURRENT,
contents_hash: cache_contents_hash,
stdout_stderr,
result,
}) = fs::read(&cache_file)
.ok()
.and_then(|v| serde_json::from_slice(&v).ok())
{
if cache_contents_hash == contents_hash {
println!("Using cached formal result:\n{stdout_stderr}");
return match result {
Ok(FormalCacheOutput {}) => Ok(output),
Err(error) => Err(CliError(eyre::Report::msg(error))),
};
}
}
true
} else {
false
};
let _ = fs::remove_file(&cache_file);
let status = acquired_job.increase_job_count(new_minimum_count, |acquired_job| { let status = acquired_job.increase_job_count(new_minimum_count, |acquired_job| {
self.verilog self.verilog.firrtl.base.run_external_command(
.firrtl acquired_job,
.base cmd,
.run_external_command(acquired_job, cmd) do_cache.then_some(&mut captured_output),
)
})?; })?;
if status.success() { let result = if status.success() {
Ok(output) Ok(output)
} else { } else {
Err(CliError(eyre!( Err(CliError(eyre!(
"running {} failed: {status}", "running {} failed: {status}",
self.sby.display() self.sby.display()
))) )))
};
if do_cache {
fs::write(
cache_file,
serde_json::to_string_pretty(&FormalCache {
version: FormalCacheVersion::CURRENT,
contents_hash: contents_hash.unwrap(),
stdout_stderr: captured_output,
result: match &result {
Ok(FormalOutput { verilog: _ }) => Ok(FormalCacheOutput {}),
Err(error) => Err(error.to_string()),
},
})
.expect("serialization shouldn't ever fail"),
)?;
} }
result
} }
} }