Skip to content

Commit 673023c

Browse files
authored
[move-prover][cli] Add benchmark functionality (aptos-labs#15636)
This adds a new option `aptos move prove --benchmark` which lets verification run in the benchmark framework of the `move-prover/lab` tool. This tool verifies function by function and detects timeouts as well as measure verification time. The result of the benchmark will be stored in `<move-package>/prover_benchmark.fun_data`. A graphic representation will be stored as well in `prover_benchmark.svg`. The PR fixes also some other things on the way which came apparent when working on it, namely trying to identify the reasons for aptos-labs#15605: - Adds new debug print for which verification targets are generated. This is reachable via `move prove -v debug` - Reduces unnecessary verification of symmetric type instantiations `f<#1, #2>` and `f<#2, #1>` - Adds an option `--skip-instance-check` to completely turn off verification of type instantiations. - Removed legacy verification_analysis_v2 and global_invariant_instrumentation_v2. These are dead code since long and confusing when trying to understand what functions are verified
1 parent 2c9dece commit 673023c

File tree

16 files changed

+360
-1780
lines changed

16 files changed

+360
-1780
lines changed

Cargo.lock

Lines changed: 19 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,7 @@ move-prover = { path = "third_party/move/move-prover" }
875875
move-prover-boogie-backend = { path = "third_party/move/move-prover/boogie-backend" }
876876
move-prover-bytecode-pipeline = { path = "third_party/move/move-prover/bytecode-pipeline" }
877877
move-prover-test-utils = { path = "third_party/move/move-prover/test-utils" }
878+
move-prover-lab = { path = "third_party/move/move-prover/lab" }
878879
aptos-move-stdlib = { path = "aptos-move/framework/move-stdlib" }
879880
aptos-table-natives = { path = "aptos-move/framework/table-natives" }
880881
move-resource-viewer = { path = "third_party/move/tools/move-resource-viewer" }

aptos-move/framework/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ move-package = { workspace = true }
6060
move-prover = { workspace = true }
6161
move-prover-boogie-backend = { workspace = true }
6262
move-prover-bytecode-pipeline = { workspace = true }
63+
move-prover-lab = { workspace = true }
6364
move-stackless-bytecode = { workspace = true }
6465
move-vm-runtime = { workspace = true }
6566
move-vm-types = { workspace = true }
@@ -78,6 +79,7 @@ smallvec = { workspace = true }
7879
tempfile = { workspace = true }
7980
thiserror = { workspace = true }
8081
tiny-keccak = { workspace = true }
82+
toml = { workspace = true }
8183

8284
[dev-dependencies]
8385
aptos-aggregator = { workspace = true, features = ["testing"] }

aptos-move/framework/src/prover.rs

Lines changed: 168 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use crate::build_model;
5+
use anyhow::bail;
56
use codespan_reporting::{
67
diagnostic::Severity,
78
term::termcolor::{ColorChoice, StandardStream},
89
};
9-
use log::LevelFilter;
10+
use log::{info, LevelFilter};
1011
use move_core_types::account_address::AccountAddress;
11-
use move_model::metadata::{CompilerVersion, LanguageVersion};
12+
use move_model::{
13+
metadata::{CompilerVersion, LanguageVersion},
14+
model::{GlobalEnv, VerificationScope},
15+
};
16+
use move_prover::cli::Options;
1217
use std::{
1318
collections::{BTreeMap, BTreeSet},
19+
fs,
1420
path::Path,
1521
time::Instant,
1622
};
@@ -27,6 +33,12 @@ pub struct ProverOptions {
2733
#[clap(long, short)]
2834
pub filter: Option<String>,
2935

36+
/// Scopes verification to the specified function. This can either be a name of the
37+
/// form "mod::func" or simply "func", in the later case every matching function is
38+
/// taken.
39+
#[clap(long, short)]
40+
pub only: Option<String>,
41+
3042
/// Whether to display additional information in error reports. This may help
3143
/// debugging but also can make verification slower.
3244
#[clap(long, short)]
@@ -84,6 +96,24 @@ pub struct ProverOptions {
8496
#[clap(long)]
8597
pub dump: bool,
8698

99+
/// Whether to benchmark verification. If selected, each verification target in the
100+
/// current package will be verified independently with timing recorded. This attempts
101+
/// to detect timeouts. A benchmark report will be written to `prover_benchmark.fun_data` in the
102+
/// package directory. The command also writes a `prover_benchmark.svg` graphic, which
103+
/// is build from the data in the file above, comparing with any other `*.fun_data` files
104+
/// in the package directory. Thus, you can rename the data file to something like
105+
/// `prover_benchmark_v1.fun_data` and in the next run, compare benchmarks in the `.svg`
106+
/// file from multiple runs.
107+
#[clap(long = "benchmark")]
108+
pub benchmark: bool,
109+
110+
/// Whether to skip verification of type instantiations of functions. This may miss
111+
/// some verification conditions if different type instantiations can create
112+
/// different behavior via type reflection or storage access, but can speed up
113+
/// verification.
114+
#[clap(long = "skip-instance-check")]
115+
pub skip_instance_check: bool,
116+
87117
#[clap(skip)]
88118
pub for_test: bool,
89119
}
@@ -93,6 +123,7 @@ impl Default for ProverOptions {
93123
Self {
94124
verbosity: None,
95125
filter: None,
126+
only: None,
96127
trace: false,
97128
cvc5: false,
98129
stratification_depth: 6,
@@ -106,7 +137,9 @@ impl Default for ProverOptions {
106137
loop_unroll: None,
107138
stable_test_output: false,
108139
dump: false,
140+
benchmark: false,
109141
for_test: false,
142+
skip_instance_check: false,
110143
}
111144
}
112145
}
@@ -127,6 +160,7 @@ impl ProverOptions {
127160
) -> anyhow::Result<()> {
128161
let now = Instant::now();
129162
let for_test = self.for_test;
163+
let benchmark = self.benchmark;
130164
let mut model = build_model(
131165
dev_mode,
132166
package_path,
@@ -140,6 +174,15 @@ impl ProverOptions {
140174
experiments.to_vec(),
141175
)?;
142176
let mut options = self.convert_options();
177+
options.language_version = language_version;
178+
options.model_builder.language_version = language_version.unwrap_or_default();
179+
if compiler_version.unwrap_or_default() >= CompilerVersion::V2_0
180+
|| language_version
181+
.unwrap_or_default()
182+
.is_at_least(LanguageVersion::V2_0)
183+
{
184+
options.compiler_v2 = true;
185+
}
143186
// Need to ensure a distinct output.bpl file for concurrent execution. In non-test
144187
// mode, we actually want to use the static output.bpl for debugging purposes
145188
let _temp_holder = if for_test {
@@ -163,11 +206,21 @@ impl ProverOptions {
163206
template_bytes: include_bytes!("aptos-natives.bpl").to_vec(),
164207
module_instance_names: move_prover_boogie_backend::options::custom_native_options(),
165208
});
166-
let mut writer = StandardStream::stderr(ColorChoice::Auto);
167-
if compiler_version.unwrap_or_default() == CompilerVersion::V1 {
168-
move_prover::run_move_prover_with_model(&mut model, &mut writer, options, Some(now))?;
209+
if benchmark {
210+
// Special mode of benchmarking
211+
run_prover_benchmark(package_path, &mut model, options)?;
169212
} else {
170-
move_prover::run_move_prover_with_model_v2(&mut model, &mut writer, options, now)?;
213+
let mut writer = StandardStream::stderr(ColorChoice::Auto);
214+
if compiler_version.unwrap_or_default() == CompilerVersion::V1 {
215+
move_prover::run_move_prover_with_model(
216+
&mut model,
217+
&mut writer,
218+
options,
219+
Some(now),
220+
)?;
221+
} else {
222+
move_prover::run_move_prover_with_model_v2(&mut model, &mut writer, options, now)?;
223+
}
171224
}
172225
Ok(())
173226
}
@@ -184,6 +237,11 @@ impl ProverOptions {
184237
output_path: "".to_string(),
185238
verbosity_level,
186239
prover: move_prover_bytecode_pipeline::options::ProverOptions {
240+
verify_scope: if let Some(name) = self.only {
241+
VerificationScope::Only(name)
242+
} else {
243+
VerificationScope::All
244+
},
187245
stable_test_output: self.stable_test_output,
188246
auto_trace_level: if self.trace {
189247
move_prover_bytecode_pipeline::options::AutoTraceLevel::VerifiedFunction
@@ -215,6 +273,7 @@ impl ProverOptions {
215273
},
216274
custom_natives: None,
217275
loop_unroll: self.loop_unroll,
276+
skip_instance_check: self.skip_instance_check,
218277
..Default::default()
219278
},
220279
..Default::default()
@@ -234,3 +293,106 @@ impl ProverOptions {
234293
}
235294
}
236295
}
296+
297+
fn run_prover_benchmark(
298+
package_path: &Path,
299+
env: &mut GlobalEnv,
300+
mut options: Options,
301+
) -> anyhow::Result<()> {
302+
info!("starting prover benchmark");
303+
// Determine sources and dependencies from the env
304+
let mut sources = BTreeSet::new();
305+
let mut deps: Vec<String> = vec![];
306+
for module in env.get_modules() {
307+
let file_name = module.get_source_path().to_string_lossy().to_string();
308+
if module.is_primary_target() {
309+
sources.insert(module.get_source_path().to_string_lossy().to_string());
310+
} else if let Some(p) = Path::new(&file_name)
311+
.parent()
312+
.and_then(|p| p.canonicalize().ok())
313+
{
314+
// The prover doesn't like to have `p` and `p/s` as dep paths, filter those out
315+
let p = p.to_string_lossy().to_string();
316+
let mut done = false;
317+
for d in &mut deps {
318+
if p.starts_with(&*d) {
319+
// p is subsumed
320+
done = true;
321+
break;
322+
} else if d.starts_with(&p) {
323+
// p is more general or equal to d, swap it out
324+
*d = p.to_string();
325+
done = true;
326+
break;
327+
}
328+
}
329+
if !done {
330+
deps.push(p)
331+
}
332+
} else {
333+
bail!("invalid file path `{}`", file_name)
334+
}
335+
}
336+
337+
// Enrich the prover options by the aliases in the env
338+
for (alias, address) in env.get_address_alias_map() {
339+
options.move_named_address_values.push(format!(
340+
"{}={}",
341+
alias.display(env.symbol_pool()),
342+
address.to_hex_literal()
343+
))
344+
}
345+
346+
// Create or override a prover_benchmark.toml in the package dir, reflection `options`
347+
let config_file = package_path.join("prover_benchmark.toml");
348+
let toml = toml::to_string(&options)?;
349+
std::fs::write(&config_file, toml)?;
350+
351+
// Args for the benchmark API
352+
let mut args = vec![
353+
// Command name
354+
"bench".to_string(),
355+
// Benchmark by function not module
356+
"--func".to_string(),
357+
// Use as the config the file we derived from `options`
358+
"--config".to_string(),
359+
config_file.to_string_lossy().to_string(),
360+
];
361+
362+
// Add deps and sources to args and run the tool
363+
for dep in deps {
364+
args.push("-d".to_string());
365+
args.push(dep)
366+
}
367+
args.extend(sources);
368+
move_prover_lab::benchmark::benchmark(&args);
369+
370+
// The benchmark stores the result in `<config_file>.fun_data`, now plot it.
371+
// If there are any other `*.fun_data` files, add them to the plot.
372+
let mut args = vec![
373+
"plot".to_string(),
374+
format!(
375+
"--out={}",
376+
config_file
377+
.as_path()
378+
.with_extension("svg")
379+
.to_string_lossy()
380+
),
381+
"--sort".to_string(),
382+
];
383+
let main_data_file = config_file
384+
.as_path()
385+
.with_extension("fun_data")
386+
.to_string_lossy()
387+
.to_string();
388+
args.push(main_data_file.clone());
389+
let paths = fs::read_dir(package_path)?;
390+
for p in paths.flatten() {
391+
let p = p.path().as_path().to_string_lossy().to_string();
392+
// Only use this if its is not the main data file we already added
393+
if p.ends_with(".fun_data") && !p.ends_with("/prover_benchmark.fun_data") {
394+
args.push(p)
395+
}
396+
}
397+
move_prover_lab::plot::plot_svg(&args)
398+
}

crates/aptos/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
All notable changes to the Aptos CLI will be captured in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the format set out by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
44

55
# Unreleased
6+
- Add flag `--benchmark` to `aptos move prove`, which allows to benchmark verification times of individual functions in a package.
7+
- Add flag `--only <name>` to `aptos move prove`, which allows to scope verification to a function.
68

79
## [5.1.0] - 2024/12/13
810
- More optimizations are now default for compiler v2.

third_party/move/move-model/src/model.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,11 @@ impl GlobalEnv {
700700
self.address_alias_map = map
701701
}
702702

703+
/// Gets the global address alias map
704+
pub fn get_address_alias_map(&self) -> &BTreeMap<Symbol, AccountAddress> {
705+
&self.address_alias_map
706+
}
707+
703708
/// Indicates that all modules in the environment should be treated as
704709
/// target modules, i.e. `module.is_target()` returns true. This can be
705710
/// used to temporarily override the default which distinguishes

0 commit comments

Comments
 (0)