Skip to content

Commit e39629b

Browse files
committed
[compiler-v2] Making v2 the basis of the prover (step #1)
1 parent e3cdf20 commit e39629b

File tree

236 files changed

+2363
-2702
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

236 files changed

+2363
-2702
lines changed

Cargo.lock

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

third_party/move/move-compiler-v2/src/env_pipeline/mod.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
77
// TODO: move all other `&mut GlobalEnv` processors into this module.
88

9-
use log::debug;
9+
use log::trace;
1010
use move_model::model::GlobalEnv;
1111
use std::fmt::Write;
1212

1313
pub mod lambda_lifter;
14+
pub mod rewrite_target;
15+
pub mod spec_checker;
16+
pub mod spec_rewriter;
1417

1518
/// Represents a pipeline of processors working on the global environment.
1619
#[derive(Default)]
@@ -32,10 +35,10 @@ impl<'a> EnvProcessorPipeline<'a> {
3235
/// Runs the pipeline. Running will be ended if any of the steps produces an error.
3336
/// The function returns true if all steps succeeded without errors.
3437
pub fn run(&self, env: &mut GlobalEnv) -> bool {
35-
debug!("before env processor pipeline: {}", env.dump_env());
38+
trace!("before env processor pipeline: {}", env.dump_env());
3639
for (name, proc) in &self.processors {
3740
proc(env);
38-
debug!("after env processor {}", name);
41+
trace!("after env processor {}", name);
3942
if env.has_errors() {
4043
return false;
4144
}
@@ -47,13 +50,13 @@ impl<'a> EnvProcessorPipeline<'a> {
4750
/// only.
4851
pub fn run_and_record(&self, env: &mut GlobalEnv, w: &mut impl Write) -> anyhow::Result<bool> {
4952
let msg = format!("before env processor pipeline:\n{}\n", env.dump_env());
50-
debug!("{}", msg);
53+
trace!("{}", msg);
5154
writeln!(w, "// -- Model dump {}", msg)?;
5255
for (name, proc) in &self.processors {
5356
proc(env);
5457
if !env.has_errors() {
5558
let msg = format!("after env processor {}:\n{}\n", name, env.dump_env());
56-
debug!("{}", msg);
59+
trace!("{}", msg);
5760
writeln!(w, "// -- Model dump {}", msg)?;
5861
} else {
5962
return Ok(false);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Copyright © Aptos Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use move_model::{
5+
ast::{Exp, Spec, SpecBlockTarget},
6+
model::{FunId, GlobalEnv, NodeId, QualifiedId, SpecFunId},
7+
};
8+
use std::{
9+
collections::{BTreeMap, BTreeSet},
10+
mem,
11+
};
12+
13+
/// Represents a target for rewriting.
14+
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone)]
15+
pub enum RewriteTarget {
16+
MoveFun(QualifiedId<FunId>),
17+
SpecFun(QualifiedId<SpecFunId>),
18+
SpecBlock(SpecBlockTarget),
19+
}
20+
21+
/// Represents the state of a rewriting target.
22+
#[derive(Debug, Clone, Eq, PartialEq)]
23+
pub enum RewriteState {
24+
Unchanged,
25+
Def(Exp),
26+
Spec(Spec),
27+
Abstract,
28+
}
29+
30+
/// Scope for collecting targets.
31+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
32+
pub enum RewritingScope {
33+
CompilationTarget,
34+
Everything,
35+
}
36+
37+
/// Represents a set of rewriting targets in the given state.
38+
#[derive(Clone)]
39+
pub struct RewriteTargets {
40+
pub targets: BTreeMap<RewriteTarget, RewriteState>,
41+
}
42+
43+
impl RewriteTargets {
44+
/// Create a new set of rewrite targets, collecting them as specified by `scope`.
45+
/// Those targets are initially associated with `Unchanged` state.
46+
pub fn create(env: &GlobalEnv, scope: RewritingScope) -> Self {
47+
let mut targets = vec![];
48+
let add_spec =
49+
|targets: &mut Vec<RewriteTarget>, sb_target: SpecBlockTarget, spec: &Spec| {
50+
if !spec.is_empty() {
51+
targets.push(RewriteTarget::SpecBlock(sb_target))
52+
}
53+
};
54+
for module in env.get_modules() {
55+
if scope == RewritingScope::Everything || module.is_target() {
56+
for func in module.get_functions() {
57+
let id = func.get_qualified_id();
58+
targets.push(RewriteTarget::MoveFun(id));
59+
add_spec(
60+
&mut targets,
61+
SpecBlockTarget::Function(id.module_id, id.id),
62+
&func.get_spec(),
63+
);
64+
}
65+
for (spec_fun_id, _) in module.get_spec_funs() {
66+
targets.push(RewriteTarget::SpecFun(
67+
module.get_id().qualified(*spec_fun_id),
68+
));
69+
}
70+
for struct_env in module.get_structs() {
71+
add_spec(
72+
&mut targets,
73+
SpecBlockTarget::Struct(module.get_id(), struct_env.get_id()),
74+
&struct_env.get_spec(),
75+
)
76+
}
77+
if !module.get_spec().is_empty() {
78+
add_spec(
79+
&mut targets,
80+
SpecBlockTarget::Module(module.get_id()),
81+
&module.get_spec(),
82+
);
83+
}
84+
}
85+
}
86+
Self {
87+
targets: targets
88+
.into_iter()
89+
.map(|target| (target, RewriteState::Unchanged))
90+
.collect(),
91+
}
92+
}
93+
94+
/// Filters the targets according to the predicate.
95+
pub fn filter(&mut self, pred: impl Fn(&RewriteTarget, &RewriteState) -> bool) {
96+
self.targets = mem::take(&mut self.targets)
97+
.into_iter()
98+
.filter(|(t, s)| pred(t, s))
99+
.collect();
100+
}
101+
102+
pub fn iter(&self) -> impl Iterator<Item = (&RewriteTarget, &RewriteState)> + '_ {
103+
self.targets.iter()
104+
}
105+
106+
/// Returns an iteration of the target keys.
107+
pub fn keys(&self) -> impl Iterator<Item = RewriteTarget> + '_ {
108+
self.targets.keys().cloned()
109+
}
110+
111+
/// Adds a new rewrite target in state `Unchanged` if it doesn't exist yet. Returns
112+
/// a boolean whether the entry is new and a mutable reference to the state.
113+
pub fn entry(&mut self, target: RewriteTarget) -> (bool, &mut RewriteState) {
114+
let mut is_new = false;
115+
let state = self.targets.entry(target).or_insert_with(|| {
116+
is_new = true;
117+
RewriteState::Unchanged
118+
});
119+
(is_new, state)
120+
}
121+
122+
/// Gets the current state of the target.
123+
pub fn state(&self, target: &RewriteTarget) -> &RewriteState {
124+
self.targets.get(target).expect("state defined")
125+
}
126+
127+
/// Gets the mutable current state of the target.
128+
pub fn state_mut(&mut self, target: &RewriteTarget) -> &mut RewriteState {
129+
self.targets.get_mut(target).expect("state defined")
130+
}
131+
132+
/// Updates the global env based on the current state. This consumes
133+
/// the rewrite targets.
134+
pub fn write_to_env(self, env: &mut GlobalEnv) {
135+
for (target, state) in self.targets {
136+
use RewriteState::*;
137+
use RewriteTarget::*;
138+
match (target, state) {
139+
(_, Unchanged) => {},
140+
(MoveFun(fnid), Def(def)) => env.set_function_def(fnid, def),
141+
(SpecFun(fnid), Def(def)) => env.get_spec_fun_mut(fnid).body = Some(def),
142+
(SpecBlock(sb_target), Spec(spec)) => {
143+
*env.get_spec_block_mut(&sb_target) = spec;
144+
},
145+
_ => panic!("unexpected rewrite target and result combination"),
146+
}
147+
}
148+
}
149+
}
150+
151+
impl RewriteTarget {
152+
/// Gets the call sites for the target.
153+
pub fn called_funs_with_call_sites(
154+
&self,
155+
env: &GlobalEnv,
156+
) -> BTreeMap<QualifiedId<FunId>, BTreeSet<NodeId>> {
157+
use RewriteTarget::*;
158+
match self {
159+
MoveFun(id) => env
160+
.get_function(*id)
161+
.get_def()
162+
.map(|e| e.called_funs_with_callsites())
163+
.unwrap_or_default(),
164+
SpecFun(id) => env
165+
.get_spec_fun(*id)
166+
.body
167+
.as_ref()
168+
.map(|e| e.called_funs_with_callsites())
169+
.unwrap_or_default(),
170+
SpecBlock(target) => {
171+
let spec = env.get_spec_block(target);
172+
spec.called_funs_with_callsites()
173+
},
174+
}
175+
}
176+
177+
/// Get the environment state of this target in form of a RewriteState.
178+
pub fn get_env_state(&self, env: &GlobalEnv) -> RewriteState {
179+
use RewriteState::*;
180+
use RewriteTarget::*;
181+
match self {
182+
MoveFun(fid) => env
183+
.get_function(*fid)
184+
.get_def()
185+
.map(|e| Def(e.clone()))
186+
.unwrap_or(Abstract),
187+
SpecFun(fid) => env
188+
.get_spec_fun(*fid)
189+
.body
190+
.clone()
191+
.map(Def)
192+
.unwrap_or(Abstract),
193+
SpecBlock(sb_target) => Spec(env.get_spec_block(sb_target).clone()),
194+
}
195+
}
196+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright © Aptos Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! The spec checker runs over the specifications of the target modules:
5+
//!
6+
//! - It checks whether the constructs they use are pure. If a specification
7+
//! expression calls a Move function it checks that for pureness as well.
8+
//! - It checks whether struct invariants do not depend on global state.
9+
10+
use crate::env_pipeline::rewrite_target::{
11+
RewriteState, RewriteTarget, RewriteTargets, RewritingScope,
12+
};
13+
use codespan_reporting::diagnostic::Severity;
14+
use log::info;
15+
use move_model::{
16+
ast::{Exp, Spec},
17+
model::{FunId, GlobalEnv, NodeId, QualifiedId},
18+
pureness_checker::{FunctionPurenessChecker, FunctionPurenessCheckerMode},
19+
};
20+
21+
pub fn run_spec_checker(env: &GlobalEnv) {
22+
info!("checking specifications");
23+
24+
// Targets are all spec functions and spec blocks, as well as functions to
25+
// process inline specs.
26+
let mut targets = RewriteTargets::create(env, RewritingScope::CompilationTarget);
27+
targets.filter(|target, _| {
28+
matches!(
29+
target,
30+
RewriteTarget::SpecFun(_) | RewriteTarget::SpecBlock(_) | RewriteTarget::MoveFun(_)
31+
)
32+
});
33+
34+
// Walk over those targets and check them for pureness.
35+
for target in targets.keys() {
36+
match (target.clone(), target.get_env_state(env)) {
37+
(RewriteTarget::MoveFun(_), RewriteState::Def(exp)) => {
38+
exp.visit_inline_specs(&mut |s| {
39+
check_spec(env, s);
40+
true
41+
})
42+
},
43+
(RewriteTarget::SpecFun(_), RewriteState::Def(exp)) => check_exp(env, &exp),
44+
(RewriteTarget::SpecBlock(_), RewriteState::Spec(spec)) => check_spec(env, &spec),
45+
_ => {},
46+
}
47+
}
48+
}
49+
50+
fn check_exp(env: &GlobalEnv, exp: &Exp) {
51+
let mut error_reported = false;
52+
let mut checker = FunctionPurenessChecker::new(
53+
FunctionPurenessCheckerMode::Specification,
54+
|node_id, msg, call_chain| report_error(env, &mut error_reported, node_id, msg, call_chain),
55+
);
56+
checker.check_exp(env, exp);
57+
}
58+
59+
fn check_spec(env: &GlobalEnv, spec: &Spec) {
60+
let mut error_reported = false;
61+
let mut checker = FunctionPurenessChecker::new(
62+
FunctionPurenessCheckerMode::Specification,
63+
|node_id, msg, call_chain| report_error(env, &mut error_reported, node_id, msg, call_chain),
64+
);
65+
checker.check_spec(env, spec);
66+
}
67+
68+
fn report_error(
69+
env: &GlobalEnv,
70+
error_reported: &mut bool,
71+
id: NodeId,
72+
msg: &str,
73+
call_chain: &[(QualifiedId<FunId>, NodeId)],
74+
) {
75+
// We report the first error only because otherwise the error messages can be
76+
// overwhelming, if the user e.g. accidentally calls a complex system function.
77+
if *error_reported {
78+
return;
79+
}
80+
// The first call in call_chain is the one from the specification function to
81+
// a Move function. We take this as the primary anchor for the error message
82+
let print_fun = |f: QualifiedId<FunId>| env.get_function(f).get_name_str();
83+
if call_chain.is_empty() {
84+
// Direct report
85+
env.diag_with_primary_and_labels(
86+
Severity::Error,
87+
&env.get_node_loc(id),
88+
"specification expression cannot use impure construct",
89+
msg,
90+
vec![],
91+
);
92+
} else {
93+
let (first_fun, first_id) = call_chain[0];
94+
let mut call_chain_info = vec![];
95+
// First print the sequence of calls leading us to the issue
96+
for i in 1..call_chain.len() {
97+
let previous_fun = print_fun(call_chain[i - 1].0);
98+
let this_fun = print_fun(call_chain[1].0);
99+
let this_loc = env.get_node_loc(call_chain[1].1);
100+
call_chain_info.push((
101+
this_loc,
102+
format!(
103+
"transitively calling `{}` from `{}` here",
104+
this_fun, previous_fun
105+
),
106+
))
107+
}
108+
// Next print the particular issue detected
109+
let last_fun = call_chain.last().unwrap().0;
110+
call_chain_info.push((
111+
env.get_node_loc(id),
112+
format!("in `{}`: {}", print_fun(last_fun), msg),
113+
));
114+
115+
env.diag_with_primary_and_labels(
116+
Severity::Error,
117+
&env.get_node_loc(first_id),
118+
&format!(
119+
"specification expression cannot call impure \
120+
Move function `{}`",
121+
env.get_function(first_fun).get_name_str()
122+
),
123+
"called here",
124+
call_chain_info,
125+
);
126+
}
127+
*error_reported = true;
128+
}

0 commit comments

Comments
 (0)