Skip to content

Commit

Permalink
[move-prover] Move generation of well-formed assumptions into its own…
Browse files Browse the repository at this point in the history
… processor.

This moves the generation of entrypoint assumptions out of spec instrumentation into its own processor. The processor runs *after* global invariant instrumentation, so it sees all memory including that which is only used by injected invariants.

This fixes a bug unearthed during the development of specification variables. Namely, if a global invariant uses memory which is not used by the function in which it is injected, we missed to generate the right assumptions for this memory, because usage analysis happens before invariants are injected. In the new model, we run usage analysis again after all specs have been injected, in the new pipeline step `EntryPointInstrumentationProcessor`.

Updates in baselines are caused by slightly different source locations resulting from the new processor and change in order of processing.

Closes: aptos-labs#9215
  • Loading branch information
wrwg authored and bors-libra committed Sep 20, 2021
1 parent 3b5e28b commit c30944b
Show file tree
Hide file tree
Showing 36 changed files with 677 additions and 741 deletions.
5 changes: 5 additions & 0 deletions language/move-prover/bytecode/src/function_data_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ impl<'env> FunctionDataBuilder<'env> {
self.emit(f(attr_id))
}

/// Emits a Bytecode::Prop based on given kind and expression.
pub fn emit_prop(&mut self, kind: PropKind, exp: Exp) {
self.emit_with(move |id| Bytecode::Prop(id, kind, exp));
}

/// Sets the debug comment which should be associated with the next instruction
/// emitted with `self.emit_with(|id| ..)`.
pub fn set_next_debug_comment(&mut self, comment: String) {
Expand Down
1 change: 1 addition & 0 deletions language/move-prover/bytecode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub mod stackless_control_flow_graph;
pub mod usage_analysis;
pub mod verification_analysis;
pub mod verification_analysis_v2;
pub mod well_formed_instrumentation;

/// Print function targets for testing and debugging.
pub fn print_targets_for_test(
Expand Down
5 changes: 4 additions & 1 deletion language/move-prover/bytecode/src/pipeline_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::{
spec_instrumentation::SpecInstrumentationProcessor,
usage_analysis::UsageProcessor,
verification_analysis::VerificationAnalysisProcessor,
well_formed_instrumentation::WellFormedInstrumentationProcessor,
};

pub fn default_pipeline_with_options(options: &ProverOptions) -> FunctionTargetPipeline {
Expand All @@ -41,9 +42,11 @@ pub fn default_pipeline_with_options(options: &ProverOptions) -> FunctionTargetP
LoopAnalysisProcessor::new(),
// spec instrumentation
SpecInstrumentationProcessor::new(),
DataInvariantInstrumentationProcessor::new(),
GlobalInvariantAnalysisProcessor::new(),
GlobalInvariantInstrumentationProcessor::new(),
WellFormedInstrumentationProcessor::new(),
DataInvariantInstrumentationProcessor::new(),
// monomorphization
MonoAnalysisProcessor::new(),
];

Expand Down
77 changes: 1 addition & 76 deletions language/move-prover/bytecode/src/spec_instrumentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ use crate::{
usage_analysis, verification_analysis,
};
use move_model::{
ast::{Exp, QuantKind},
ast::Exp,
exp_generator::ExpGenerator,
spec_translator::{SpecTranslator, TranslatedSpec},
};
use num::{BigUint, Zero};
use std::{
collections::{BTreeMap, BTreeSet},
fmt,
Expand Down Expand Up @@ -291,80 +290,6 @@ impl<'a> Instrumenter<'a> {
// Extract and clear current code
let old_code = std::mem::take(&mut self.builder.data.code);

if self.is_verified() {
// Inject well-formedness assumptions for parameters.
for param in 0..self.builder.fun_env.get_parameter_count() {
let exp = self.builder.mk_call(
&BOOL_TYPE,
ast::Operation::WellFormed,
vec![self.builder.mk_temporary(param)],
);
self.builder.emit_with(move |id| Prop(id, Assume, exp));
}

// Inject well-formedness assumption for used memory.
for mem in usage_analysis::get_memory_usage(&self.builder.get_target())
.accessed
.all
.clone()
{
// If this is native or intrinsic memory, skip this.
let struct_env = self
.builder
.global_env()
.get_struct_qid(mem.to_qualified_id());
if struct_env.is_native_or_intrinsic() {
continue;
}
let exp = self
.builder
.mk_inst_mem_quant_opt(QuantKind::Forall, &mem, &mut |val| {
Some(self.builder.mk_call(
&BOOL_TYPE,
ast::Operation::WellFormed,
vec![val],
))
})
.expect("quant defined");
self.builder.emit_with(move |id| Prop(id, Assume, exp));

// If this is ghost memory, assume it exists, and if it has an initializer,
// assume it has this value.
if let Some(spec_var) = struct_env.get_ghost_memory_spec_var() {
let mem_ty = mem.to_type();
let zero_addr = self.builder.mk_address_const(BigUint::zero());
let exists = self.builder.mk_call_with_inst(
&BOOL_TYPE,
vec![mem_ty.clone()],
ast::Operation::Exists(None),
vec![zero_addr.clone()],
);
self.builder.emit_with(move |id| Prop(id, Assume, exists));
let svar_module = self.builder.global_env().get_module(spec_var.module_id);
let svar = svar_module.get_spec_var(spec_var.id);
if let Some(init) = &svar.init {
let mem_val = self.builder.mk_call_with_inst(
&mem_ty,
mem.inst.clone(),
ast::Operation::Pack(mem.module_id, mem.id),
vec![init.clone()],
);
let mem_access = self.builder.mk_call_with_inst(
&mem_ty,
vec![mem_ty.clone()],
ast::Operation::Global(None),
vec![zero_addr],
);
let eq_with_init = self
.builder
.mk_bool_call(ast::Operation::Identical, vec![mem_access, mem_val]);
self.builder
.emit_with(move |id| Prop(id, Assume, eq_with_init));
}
}
}
}

// Emit `let` bindings.
self.emit_lets(spec, false);

Expand Down
2 changes: 1 addition & 1 deletion language/move-prover/bytecode/src/usage_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ impl<'a> TransferFunctions for MemoryUsageAnalysis<'a> {
}

impl<'a> MemoryUsageAnalysis<'a> {
/// Compute usage information for the given spec. This spec is injected in later
/// Compute usage information for the given spec. This spec maybe injected in later
/// phases into the code, but we need to account for it's memory usage already here
/// as spec injection itself depends on this information.
fn compute_spec_usage(&self, spec: &Spec, state: &mut UsageState) {
Expand Down
125 changes: 125 additions & 0 deletions language/move-prover/bytecode/src/well_formed_instrumentation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! Transformation which injects well-formed assumptions at top-level entry points of verified
//! functions. These assumptions are both about parameters and any memory referred to by
//! the code. For ghost memory, the transformation also assumes initial values if provided.
//!
//! This needs to be run *after* function specifications and global invariants have been
//! injected because only then we know all accessed memory.
//!
//! This phase need to be run *before* data invariant instrumentation, because the latter relies
//! on the well-formed assumptions, augmenting them with the data invariant.
//! Because data invariants cannot refer to global memory, they are not relevant for memory
//! usage, and their injection therefore can happen after this phase.
use crate::{
function_data_builder::FunctionDataBuilder,
function_target::FunctionData,
function_target_pipeline::{FunctionTargetProcessor, FunctionTargetsHolder},
stackless_bytecode::PropKind,
usage_analysis::UsageProcessor,
};
use move_model::{
ast::{Operation, QuantKind},
exp_generator::ExpGenerator,
model::FunctionEnv,
ty::BOOL_TYPE,
};
use num::{BigUint, Zero};

pub struct WellFormedInstrumentationProcessor {}

impl WellFormedInstrumentationProcessor {
pub fn new() -> Box<Self> {
Box::new(Self {})
}
}

impl FunctionTargetProcessor for WellFormedInstrumentationProcessor {
fn process(
&self,
targets: &mut FunctionTargetsHolder,
fun_env: &FunctionEnv<'_>,
data: FunctionData,
) -> FunctionData {
if !data.variant.is_verified() {
// only need to do this for verified functions
return data;
}
// Rerun usage analysis for this function.
let usage = UsageProcessor::analyze(targets, fun_env, &data);
let mut builder = FunctionDataBuilder::new(fun_env, data);
builder.set_loc(fun_env.get_loc().at_start());
let old_code = std::mem::take(&mut builder.data.code);

// Inject well-formedness assumptions for parameters.
for param in 0..builder.fun_env.get_parameter_count() {
let exp = builder.mk_call(
&BOOL_TYPE,
Operation::WellFormed,
vec![builder.mk_temporary(param)],
);
builder.emit_prop(PropKind::Assume, exp);
}

// Inject well-formedness assumption for used memory.
for mem in usage.accessed.all.clone() {
let struct_env = builder.global_env().get_struct_qid(mem.to_qualified_id());
if struct_env.is_native_or_intrinsic() {
// If this is native or intrinsic memory, skip this.
continue;
}
let exp = builder
.mk_inst_mem_quant_opt(QuantKind::Forall, &mem, &mut |val| {
Some(builder.mk_call(&BOOL_TYPE, Operation::WellFormed, vec![val]))
})
.expect("quant defined");
builder.emit_prop(PropKind::Assume, exp);

// If this is ghost memory, assume it exists, and if it has an initializer,
// assume it has this value.
if let Some(spec_var) = struct_env.get_ghost_memory_spec_var() {
let mem_ty = mem.to_type();
let zero_addr = builder.mk_address_const(BigUint::zero());
let exists = builder.mk_call_with_inst(
&BOOL_TYPE,
vec![mem_ty.clone()],
Operation::Exists(None),
vec![zero_addr.clone()],
);
builder.emit_prop(PropKind::Assume, exists);
let svar_module = builder.global_env().get_module(spec_var.module_id);
let svar = svar_module.get_spec_var(spec_var.id);
if let Some(init) = &svar.init {
let mem_val = builder.mk_call_with_inst(
&mem_ty,
mem.inst.clone(),
Operation::Pack(mem.module_id, mem.id),
vec![init.clone()],
);
let mem_access = builder.mk_call_with_inst(
&mem_ty,
vec![mem_ty.clone()],
Operation::Global(None),
vec![zero_addr],
);
let eq_with_init =
builder.mk_bool_call(Operation::Identical, vec![mem_access, mem_val]);
builder.emit_prop(PropKind::Assume, eq_with_init);
}
}
}

// Append the old code
for bc in old_code {
builder.emit(bc);
}

builder.data
}

fn name(&self) -> String {
"entry_point_instrumenter".to_string()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ fun DisableInv::foo($t0|s: signer) {
var $t1: bool
var $t2: DisableInv::R2
var $t3: num
0: assume WellFormed($t0)
1: assume forall $rsc: ResourceDomain<DisableInv::R2>(): WellFormed($rsc)
2: $t1 := false
3: $t2 := pack DisableInv::R2($t1)
4: move_to<DisableInv::R2>($t2, $t0) on_abort goto 7 with $t3
5: label L1
6: return ()
7: label L2
8: abort($t3)
0: $t1 := false
1: $t2 := pack DisableInv::R2($t1)
2: move_to<DisableInv::R2>($t2, $t0) on_abort goto 5 with $t3
3: label L1
4: return ()
5: label L2
6: abort($t3)
}


Expand All @@ -41,7 +39,7 @@ DisableInv::foo: [
]
]
}
4: move_to<DisableInv::R2>($t2, $t0) on_abort goto L2 with $t3 {}
2: move_to<DisableInv::R2>($t2, $t0) on_abort goto L2 with $t3 {}
exitpoint {
assert @0 = [
<> -> [
Expand Down
Loading

0 comments on commit c30944b

Please sign in to comment.