Skip to content

Commit f68cb51

Browse files
committed
[move-prover][cli] Add benchmark functionality
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 #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 cf5d790 commit f68cb51

File tree

16 files changed

+344
-1777
lines changed

16 files changed

+344
-1777
lines changed

Cargo.lock

+19-21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
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

+2
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

+156-6
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,
15+
};
16+
use move_prover::cli::Options;
1217
use std::{
1318
collections::{BTreeMap, BTreeSet},
19+
fs,
1420
path::Path,
1521
time::Instant,
1622
};
@@ -84,6 +90,24 @@ pub struct ProverOptions {
8490
#[clap(long)]
8591
pub dump: bool,
8692

93+
/// Whether to benchmark verification. If selected, each verification target in the
94+
/// current package will be verified independently with timing recorded. This attempts
95+
/// to detect timeouts. A benchmark report will be written to `prover_benchmark.fun_data` in the
96+
/// package directory. The command also writes a `prover_benchmark.svg` graphic, which
97+
/// is build from the data in the file above, comparing with any other `*.fun_data` files
98+
/// in the package directory. Thus, you can rename the data file to something like
99+
/// `prover_benchmark_v1.fun_data` and in the next run, compare benchmarks in the `.svg`
100+
/// file from multiple runs.
101+
#[clap(long = "benchmark")]
102+
pub benchmark: bool,
103+
104+
/// Whether to skip verification of type instantiations of functions. This may miss
105+
/// some verification conditions if different type instantiations can create
106+
/// different behavior via type reflection or storage access, but can speed up
107+
/// verification.
108+
#[clap(long = "skip-instance-check")]
109+
pub skip_instance_check: bool,
110+
87111
#[clap(skip)]
88112
pub for_test: bool,
89113
}
@@ -106,7 +130,9 @@ impl Default for ProverOptions {
106130
loop_unroll: None,
107131
stable_test_output: false,
108132
dump: false,
133+
benchmark: false,
109134
for_test: false,
135+
skip_instance_check: false,
110136
}
111137
}
112138
}
@@ -127,6 +153,7 @@ impl ProverOptions {
127153
) -> anyhow::Result<()> {
128154
let now = Instant::now();
129155
let for_test = self.for_test;
156+
let benchmark = self.benchmark;
130157
let mut model = build_model(
131158
dev_mode,
132159
package_path,
@@ -140,6 +167,15 @@ impl ProverOptions {
140167
experiments.to_vec(),
141168
)?;
142169
let mut options = self.convert_options();
170+
options.language_version = language_version;
171+
options.model_builder.language_version = language_version.unwrap_or_default();
172+
if compiler_version.unwrap_or_default() >= CompilerVersion::V2_0
173+
|| language_version
174+
.unwrap_or_default()
175+
.is_at_least(LanguageVersion::V2_0)
176+
{
177+
options.compiler_v2 = true;
178+
}
143179
// Need to ensure a distinct output.bpl file for concurrent execution. In non-test
144180
// mode, we actually want to use the static output.bpl for debugging purposes
145181
let _temp_holder = if for_test {
@@ -163,11 +199,21 @@ impl ProverOptions {
163199
template_bytes: include_bytes!("aptos-natives.bpl").to_vec(),
164200
module_instance_names: move_prover_boogie_backend::options::custom_native_options(),
165201
});
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))?;
202+
if benchmark {
203+
// Special mode of benchmarking
204+
run_prover_benchmark(package_path, &mut model, options)?;
169205
} else {
170-
move_prover::run_move_prover_with_model_v2(&mut model, &mut writer, options, now)?;
206+
let mut writer = StandardStream::stderr(ColorChoice::Auto);
207+
if compiler_version.unwrap_or_default() == CompilerVersion::V1 {
208+
move_prover::run_move_prover_with_model(
209+
&mut model,
210+
&mut writer,
211+
options,
212+
Some(now),
213+
)?;
214+
} else {
215+
move_prover::run_move_prover_with_model_v2(&mut model, &mut writer, options, now)?;
216+
}
171217
}
172218
Ok(())
173219
}
@@ -215,6 +261,7 @@ impl ProverOptions {
215261
},
216262
custom_natives: None,
217263
loop_unroll: self.loop_unroll,
264+
skip_instance_check: self.skip_instance_check,
218265
..Default::default()
219266
},
220267
..Default::default()
@@ -234,3 +281,106 @@ impl ProverOptions {
234281
}
235282
}
236283
}
284+
285+
fn run_prover_benchmark(
286+
package_path: &Path,
287+
env: &mut GlobalEnv,
288+
mut options: Options,
289+
) -> anyhow::Result<()> {
290+
info!("starting prover benchmark");
291+
// Determine sources and dependencies from the env
292+
let mut sources = BTreeSet::new();
293+
let mut deps: Vec<String> = vec![];
294+
for module in env.get_modules() {
295+
let file_name = module.get_source_path().to_string_lossy().to_string();
296+
if module.is_primary_target() {
297+
sources.insert(module.get_source_path().to_string_lossy().to_string());
298+
} else if let Some(p) = Path::new(&file_name)
299+
.parent()
300+
.and_then(|p| p.canonicalize().ok())
301+
{
302+
// The prover doesn't like to have `p` and `p/s` as dep paths, filter those out
303+
let p = p.to_string_lossy().to_string();
304+
let mut done = false;
305+
for d in &mut deps {
306+
if p.starts_with(&*d) {
307+
// p is subsumed
308+
done = true;
309+
break;
310+
} else if d.starts_with(&p) {
311+
// p is more general or equal to d, swap it out
312+
*d = p.to_string();
313+
done = true;
314+
break;
315+
}
316+
}
317+
if !done {
318+
deps.push(p)
319+
}
320+
} else {
321+
bail!("invalid file path `{}`", file_name)
322+
}
323+
}
324+
325+
// Enrich the prover options by the aliases in the env
326+
for (alias, address) in env.get_address_alias_map() {
327+
options.move_named_address_values.push(format!(
328+
"{}={}",
329+
alias.display(env.symbol_pool()),
330+
address.to_hex_literal()
331+
))
332+
}
333+
334+
// Create or override a prover_benchmark.toml in the package dir, reflection `options`
335+
let config_file = package_path.join("prover_benchmark.toml");
336+
let toml = toml::to_string(&options)?;
337+
std::fs::write(&config_file, toml)?;
338+
339+
// Args for the benchmark API
340+
let mut args = vec![
341+
// Command name
342+
"bench".to_string(),
343+
// Benchmark by function not module
344+
"--func".to_string(),
345+
// Use as the config the file we derived from `options`
346+
"--config".to_string(),
347+
config_file.to_string_lossy().to_string(),
348+
];
349+
350+
// Add deps and sources to args and run the tool
351+
for dep in deps {
352+
args.push("-d".to_string());
353+
args.push(dep)
354+
}
355+
args.extend(sources);
356+
move_prover_lab::benchmark::benchmark(&args);
357+
358+
// The benchmark stores the result in `<config_file>.fun_data`, now plot it.
359+
// If there are any other `*.fun_data` files, add them to the plot.
360+
let mut args = vec![
361+
"plot".to_string(),
362+
format!(
363+
"--out={}",
364+
config_file
365+
.as_path()
366+
.with_extension("svg")
367+
.to_string_lossy()
368+
),
369+
"--sort".to_string(),
370+
];
371+
let main_data_file = config_file
372+
.as_path()
373+
.with_extension("fun_data")
374+
.to_string_lossy()
375+
.to_string();
376+
args.push(main_data_file.clone());
377+
let paths = fs::read_dir(package_path)?;
378+
for p in paths.flatten() {
379+
let p = p.path().as_path().to_string_lossy().to_string();
380+
// Only use this if its is not the main data file we already added
381+
if p.ends_with(".fun_data") && !p.ends_with("/prover_benchmark.fun_data") {
382+
args.push(p)
383+
}
384+
}
385+
move_prover_lab::plot::plot_svg(&args)
386+
}

crates/aptos/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
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
67

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

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

+5
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)