diff --git a/language/evm/move-to-yul/src/experiments.rs b/language/evm/move-to-yul/src/experiments.rs index 0e8063acf5..be2312a1c4 100644 --- a/language/evm/move-to-yul/src/experiments.rs +++ b/language/evm/move-to-yul/src/experiments.rs @@ -24,4 +24,9 @@ impl Experiment { /// during tests this is off. /// Retention: permanent pub const CAPTURE_SOURCE_INFO: &'static str = "capture-source-info"; + + /// Transform control flow graph to structured control flow. + /// This is off by default for now, we might want to make it default + /// if the performance improvement is significant. + pub const APPLY_CFG_TO_SCF: &'static str = "apply-cfg-to-scf"; } diff --git a/language/evm/move-to-yul/src/functions.rs b/language/evm/move-to-yul/src/functions.rs index 9cb5e29cb8..e9889c1720 100644 --- a/language/evm/move-to-yul/src/functions.rs +++ b/language/evm/move-to-yul/src/functions.rs @@ -14,6 +14,7 @@ use move_stackless_bytecode::{ function_target_pipeline::FunctionVariant, stackless_bytecode::{Bytecode, Constant, Label, Operation}, stackless_control_flow_graph::{BlockContent, BlockId, StacklessControlFlowGraph}, + stackless_structured_control_flow::{compute_label_map, generate_scg_vec, StacklessSCG, StacklessSCGBlockKind}, }; use sha3::{Digest, Keccak256}; use std::collections::{btree_map::Entry, BTreeMap}; @@ -107,48 +108,153 @@ impl<'a> FunctionGenerator<'a> { // Compute control flow graph, entry block, and label map let code = target.data.code.as_slice(); let cfg = StacklessControlFlowGraph::new_forward(code); - let entry_bb = Self::get_actual_entry_block(&cfg); - let label_map = Self::compute_label_map(&cfg, code); - // Emit state machine to represent control flow. - // TODO: Eliminate the need for this, see also - // https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2 - if cfg.successors(entry_bb).iter().all(|b| cfg.is_dummmy(*b)) { - // In this trivial case, we have only one block and can omit the state machine - if let BlockContent::Basic { lower, upper } = cfg.content(entry_bb) { - for offs in *lower..*upper + 1 { - self.bytecode(ctx, fun_id, target, &label_map, &code[offs as usize], false); - } - } else { - panic!("effective entry block is not basic") + if !ctx.options.apply_cfg_to_scf() { + self.emit_cfg(&cfg, code, ctx, fun_id, target); + } else { + self.emit_scf_from_cfg(&cfg, code, ctx, fun_id, target); + } + }); + emitln!(ctx.writer) + } + + fn emit_cfg( + &mut self, + cfg: &StacklessControlFlowGraph, + code: &[Bytecode], + ctx: &Context, + fun_id: &QualifiedInstId, + target: &FunctionTarget, + ) { + let entry_bb = Self::get_actual_entry_block(cfg); + let label_map = compute_label_map(cfg, code); + // Emit state machine to represent control flow. + // TODO: Eliminate the need for this, see also + // https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2 + if cfg.successors(entry_bb).iter().all(|b| cfg.is_dummmy(*b)) { + // In this trivial case, we have only one block and can omit the state machine + if let BlockContent::Basic { lower, upper } = cfg.content(entry_bb) { + for offs in *lower..*upper + 1 { + self.bytecode( + ctx, + fun_id, + target, + &label_map, + &code[offs as usize], + false, + false, + ); } } else { - emitln!(ctx.writer, "let $block := {}", entry_bb); - emit!(ctx.writer, "for {} true {} "); + panic!("effective entry block is not basic") + } + } else { + emitln!(ctx.writer, "let $block := {}", entry_bb); + emit!(ctx.writer, "for {} true {} "); + ctx.emit_block(|| { + emitln!(ctx.writer, "switch $block"); + for blk_id in &cfg.blocks() { + if let BlockContent::Basic { lower, upper } = cfg.content(*blk_id) { + // Emit code for this basic block. + emit!(ctx.writer, "case {} ", blk_id); + ctx.emit_block(|| { + for offs in *lower..*upper + 1 { + self.bytecode( + ctx, + fun_id, + target, + &label_map, + &code[offs as usize], + true, + false, + ); + } + }) + } + } + }) + } + } + + fn emit_scf_from_cfg( + &mut self, + cfg: &StacklessControlFlowGraph, + code: &[Bytecode], + ctx: &Context, + fun_id: &QualifiedInstId, + target: &FunctionTarget, + ) { + let scg_vec = generate_scg_vec(cfg, code); + for scg in scg_vec { + self.emit_scg(&scg, &cfg, code, ctx, fun_id, target); + } + } + + pub fn emit_scg( + &mut self, + scg: &StacklessSCG, + cfg: &StacklessControlFlowGraph, + code: &[Bytecode], + ctx: &Context, + fun_id: &QualifiedInstId, + target: &FunctionTarget, + ) { + let label_map = compute_label_map(cfg, code); + match scg { + StacklessSCG::BasicBlock { + start_offset, + end_offset, + kind, + } => { ctx.emit_block(|| { - emitln!(ctx.writer, "switch $block"); - for blk_id in &cfg.blocks() { - if let BlockContent::Basic { lower, upper } = cfg.content(*blk_id) { - // Emit code for this basic block. - emit!(ctx.writer, "case {} ", blk_id); - ctx.emit_block(|| { - for offs in *lower..*upper + 1 { - self.bytecode( - ctx, - fun_id, - target, - &label_map, - &code[offs as usize], - true, - ); - } - }) + for offs in *start_offset..*end_offset + 1 { + self.bytecode( + ctx, + fun_id, + target, + &label_map, + &code[offs as usize], + false, + true, + ); + } + match kind { + StacklessSCGBlockKind::Break => { + emitln!(ctx.writer, "break"); } + StacklessSCGBlockKind::Continue => { + emitln!(ctx.writer, "continue"); + } + _ => {} } - }) + }); } - }); - emitln!(ctx.writer) + StacklessSCG::IfBlock { + cond, + if_true, + if_false, + } => { + emitln!(ctx.writer, "switch $t{} ", cond); + emit!(ctx.writer, "case 0 "); + self.emit_scg(if_false, cfg, code, ctx, fun_id, target); + emit!(ctx.writer, "default "); + self.emit_scg(if_true, cfg, code, ctx, fun_id, target); + } + // TODO: need to emit codes of loops based on StacklessSCG::LoopBlock + // based on new bytecodes of Break and Continue + StacklessSCG::LoopBlock { + loop_header, + loop_body, + } => { + self.emit_scg(&*loop_header, &cfg, code, ctx, fun_id, target); + emit!(ctx.writer, "for {} true {} "); + ctx.emit_block(|| { + for loop_body_scg in loop_body { + self.emit_scg(loop_body_scg, &cfg, code, ctx, fun_id, target); + } + }); + } + } } /// Compute the locals in the given function which are borrowed from and which are not @@ -179,22 +285,6 @@ impl<'a> FunctionGenerator<'a> { } entry_bb } - - /// Compute a map from labels to block ids which those labels start. - fn compute_label_map( - cfg: &StacklessControlFlowGraph, - code: &[Bytecode], - ) -> BTreeMap { - let mut map = BTreeMap::new(); - for id in cfg.blocks() { - if let BlockContent::Basic { lower, .. } = cfg.content(id) { - if let Bytecode::Label(_, l) = &code[*lower as usize] { - map.insert(*l, id); - } - } - } - map - } } // ================================================================================================ @@ -210,6 +300,7 @@ impl<'a> FunctionGenerator<'a> { label_map: &BTreeMap, bc: &Bytecode, has_flow: bool, + skip_block: bool, ) { use Bytecode::*; emitln!( @@ -283,19 +374,23 @@ impl<'a> FunctionGenerator<'a> { match bc { Jump(_, l) => { print_loc(); - emitln!(ctx.writer, "$block := {}", get_block(l)) + if !skip_block { + emitln!(ctx.writer, "$block := {}", get_block(l)) + } } Branch(_, if_t, if_f, cond) => { print_loc(); - emitln!( - ctx.writer, - "switch {}\n\ - case 0 {{ $block := {} }}\n\ - default {{ $block := {} }}", - local(cond), - get_block(if_f), - get_block(if_t), - ) + if !skip_block { + emitln!( + ctx.writer, + "switch {}\n\ + case 0 {{ $block := {} }}\n\ + default {{ $block := {} }}", + local(cond), + get_block(if_f), + get_block(if_t), + ) + } } Assign(_, dest, src, _) => { print_loc(); diff --git a/language/evm/move-to-yul/src/options.rs b/language/evm/move-to-yul/src/options.rs index 55f44b7d71..923a0f2b75 100644 --- a/language/evm/move-to-yul/src/options.rs +++ b/language/evm/move-to-yul/src/options.rs @@ -62,4 +62,9 @@ impl Options { pub fn generate_source_info(&self) -> bool { !self.testing || self.experiment_on(Experiment::CAPTURE_SOURCE_INFO) } + + /// Returns true if control flow graph to structured control flow is applied. + pub fn apply_cfg_to_scf(&self) -> bool { + self.experiment_on(Experiment::APPLY_CFG_TO_SCF) + } } diff --git a/language/evm/move-to-yul/tests/ControlStructures.exp.apply-cfg-to-scf b/language/evm/move-to-yul/tests/ControlStructures.exp.apply-cfg-to-scf new file mode 100644 index 0000000000..59de05abfd --- /dev/null +++ b/language/evm/move-to-yul/tests/ControlStructures.exp.apply-cfg-to-scf @@ -0,0 +1,288 @@ +/* ======================================= + * Generated by Move-To-Yul compiler v0.0 + * ======================================= */ + + +object "A2_M" { + code { + codecopy(0, dataoffset("A2_M_deployed"), datasize("A2_M_deployed")) + return(0, datasize("A2_M_deployed")) + } + object "A2_M_deployed" { + code { + mstore(0, memoryguard(160)) + if iszero(lt(calldatasize(), 4)) + { + let selector := $Shr(calldataload(0), 224) + switch selector + case 0x70e8dbcb + { + // f(uint64) + if callvalue() + { + $Abort(99) + } + let param_0 := abi_decode_tuple_$uint64$(4, calldatasize()) + A2_M_f(param_0) + let memPos := mload(0) + let memEnd := abi_encode_tuple_(memPos) + return(memPos, sub(memEnd, memPos)) + } + case 0xb3e8e519 + { + // g(uint64) + if callvalue() + { + $Abort(99) + } + let param_0 := abi_decode_tuple_$uint64$(4, calldatasize()) + A2_M_g(param_0) + let memPos := mload(0) + let memEnd := abi_encode_tuple_(memPos) + return(memPos, sub(memEnd, memPos)) + } + case 0xf5f96a87 + { + // h1(uint64) + if callvalue() + { + $Abort(99) + } + let param_0 := abi_decode_tuple_$uint64$(4, calldatasize()) + let ret_0 := A2_M_h1(param_0) + let memPos := mload(0) + let memEnd := abi_encode_tuple_$uint64$(memPos, ret_0) + return(memPos, sub(memEnd, memPos)) + } + case 0x410dac7e + { + // h2(uint64) + if callvalue() + { + $Abort(99) + } + let param_0 := abi_decode_tuple_$uint64$(4, calldatasize()) + A2_M_h2(param_0) + let memPos := mload(0) + let memEnd := abi_encode_tuple_(memPos) + return(memPos, sub(memEnd, memPos)) + } + default {} + } + $Abort(97) + function A2_M_f(x) { + let $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8 + { + // label L7 + // $t1 := 0 + $t1 := 0 + // $t2 := >($t0, $t1) + $t2 := $Gt(x, $t1) + // if ($t2) goto L0 else goto L2 + } + for {} true {} { + { + // label L0 + // $t3 := 2 + $t3 := 2 + // $t4 := %($t0, $t3) + $t4 := $Mod(x, $t3) + // $t5 := 0 + $t5 := 0 + // $t6 := ==($t4, $t5) + $t6 := $Eq($t4, $t5) + // if ($t6) goto L3 else goto L5 + } + switch $t6 + case 0 { + // label L5 + // $t8 := 2 + $t8 := 2 + // $t0 := -($t0, $t8) + x := $Sub(x, $t8) + // goto L6 + } + default { + // label L3 + // $t7 := 1 + $t7 := 1 + // $t0 := +($t0, $t7) + x := $AddU64(x, $t7) + // goto L6 + } + { + // label L6 + // goto L7 + } + } + { + // label L2 + // return () + } + } + + function A2_M_g(x) { + let $t1, $t2, $t3, $t4, $t5 + { + // label L3 + // $t1 := 1 + $t1 := 1 + // $t2 := >=($t0, $t1) + $t2 := $GtEq(x, $t1) + // if ($t2) goto L0 else goto L2 + } + for {} true {} { + { + // label L0 + // $t3 := 1 + $t3 := 1 + // $t0 := -($t0, $t3) + x := $Sub(x, $t3) + // goto L3 + } + { + // label L2 + // $t4 := 0 + $t4 := 0 + // $t5 := ==($t0, $t4) + $t5 := $Eq(x, $t4) + // if ($t5) goto L4 else goto L3 + } + switch $t5 + case 0 { + // label L3 + // $t1 := 1 + $t1 := 1 + // $t2 := >=($t0, $t1) + $t2 := $GtEq(x, $t1) + // if ($t2) goto L0 else goto L2 + continue + } + default { + // label L4 + // return () + break + } + } + } + + function A2_M_h1(x) -> $result { + let tmp_$1, $t2, $t3, $t4, $t5 + { + // $t2 := 0 + $t2 := 0 + // $t3 := >($t0, $t2) + $t3 := $Gt(x, $t2) + // if ($t3) goto L0 else goto L2 + } + switch $t3 + case 0 { + // label L2 + // $t5 := 2 + $t5 := 2 + // $t1 := $t5 + tmp_$1 := $t5 + // goto L3 + } + default { + // label L0 + // $t4 := 1 + $t4 := 1 + // $t1 := $t4 + tmp_$1 := $t4 + // goto L3 + } + { + // label L3 + // return $t1 + $result := tmp_$1 + } + } + + function A2_M_h2(x) { + let $t1, $t2, $t3 + { + // $t1 := 0 + $t1 := 0 + // $t2 := >($t0, $t1) + $t2 := $Gt(x, $t1) + // if ($t2) goto L0 else goto L2 + } + switch $t2 + case 0 { + // label L2 + // return () + } + default { + // label L0 + // $t3 := 1 + $t3 := 1 + // abort($t3) + $Abort($t3) + } + } + + function abi_encode_tuple_(headStart ) -> tail { + tail := add(headStart, 0) + } + function abi_decode_tuple_$uint64$(headStart, dataEnd) -> value_0 { + if slt(sub(dataEnd, headStart), 32) { $Abort(96) } + { + let offset := 0 + value_0 := abi_decode_uint64(add(headStart, offset), dataEnd) + } + } + function abi_decode_uint64(offset, end) -> value { + value := calldataload(offset) + validator_uint64(value) + } + function validator_uint64(value) { + if iszero(eq(value, cleanup_uint64(value))) { $Abort(95) } + } + function cleanup_uint64(value) -> cleaned { + cleaned := and(value, 0xffffffffffffffff) + } + function abi_encode_tuple_$uint64$(headStart ,value_0) -> tail { + tail := add(headStart, 32) + abi_encode_uint64(value_0, add(headStart, 0)) + } + function abi_encode_uint64(value, pos) { + mstore(pos, cleanup_uint64(value)) + } + function $Abort(code) { + mstore(0, code) + revert(24, 8) // TODO: store code as a string? + } + function $AbortBuiltin() { + $Abort(sub(0, 1)) + } + function $AddU64(x, y) -> r { + if lt(sub(0xffffffffffffffff, x), y) { $AbortBuiltin() } + r := add(x, y) + } + function $Sub(x, y) -> r { + if lt(x, y) { $AbortBuiltin() } + r := sub(x, y) + } + function $Mod(x, y) -> r { + if eq(y, 0) { $AbortBuiltin() } + r := mod(x, y) + } + function $Shr(x, y) -> r { + r := shr(y, x) + } + function $Gt(x, y) -> r { + r := gt(x, y) + } + function $GtEq(x, y) -> r { + r := or(gt(x, y), eq(x, y)) + } + function $Eq(x, y) -> r { + r := eq(x, y) + } + } + } +} + + +!! Succeeded compiling Yul diff --git a/language/evm/move-to-yul/tests/ControlStructures.move b/language/evm/move-to-yul/tests/ControlStructures.move index d08bb59bad..1bfb8a9523 100644 --- a/language/evm/move-to-yul/tests/ControlStructures.move +++ b/language/evm/move-to-yul/tests/ControlStructures.move @@ -1,4 +1,7 @@ // Tests basic control structures. +// +// experiment: apply-cfg-to-scf +// #[contract] module 0x2::M { #[callable] diff --git a/language/move-prover/bytecode/src/lib.rs b/language/move-prover/bytecode/src/lib.rs index 20b9833695..2007ef5f05 100644 --- a/language/move-prover/bytecode/src/lib.rs +++ b/language/move-prover/bytecode/src/lib.rs @@ -38,9 +38,11 @@ pub mod pipeline_factory; pub mod reaching_def_analysis; pub mod read_write_set_analysis; pub mod spec_instrumentation; +pub mod stackifier_topological_sort; pub mod stackless_bytecode; pub mod stackless_bytecode_generator; pub mod stackless_control_flow_graph; +pub mod stackless_structured_control_flow; pub mod usage_analysis; pub mod verification_analysis; pub mod verification_analysis_v2; diff --git a/language/move-prover/bytecode/src/stackifier_topological_sort.rs b/language/move-prover/bytecode/src/stackifier_topological_sort.rs new file mode 100644 index 0000000000..14bd5874bd --- /dev/null +++ b/language/move-prover/bytecode/src/stackifier_topological_sort.rs @@ -0,0 +1,168 @@ +// Copyright (c) The Diem Core Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::stackless_control_flow_graph::BlockId; +use std::collections::btree_map::Entry; +use std::collections::{BTreeMap, BTreeSet}; + +#[derive(Clone, Debug)] +pub struct StackifierDependency { + pub num_prec: usize, + pub succ_set: BTreeSet, +} + +#[derive(Clone, Debug)] +pub struct StackifierTopologicalSort { + pub last_popped_block: Option, + top: BTreeMap, + // complete topology will not change while popping + pub complete_top: BTreeMap, +} + +impl StackifierDependency { + pub fn new() -> StackifierDependency { + StackifierDependency { + num_prec: 0, + succ_set: BTreeSet::new(), + } + } +} + +impl StackifierTopologicalSort { + #[inline] + pub fn new() -> StackifierTopologicalSort { + StackifierTopologicalSort { + last_popped_block: Option::None, + top: BTreeMap::new(), + complete_top: BTreeMap::new(), + } + } + + #[inline] + pub fn len(&self) -> usize { + self.top.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.top.is_empty() + } + + pub fn add_dependency(&mut self, prec: BlockId, succ: BlockId) { + Self::add_dependency_impl(&mut self.top, prec, succ); + Self::add_dependency_impl(&mut self.complete_top, prec, succ); + } + + fn add_dependency_impl( + top: &mut BTreeMap, + prec: BlockId, + succ: BlockId, + ) { + match top.entry(prec) { + Entry::Vacant(e) => { + let mut dep = StackifierDependency::new(); + dep.succ_set.insert(succ); + e.insert(dep); + } + Entry::Occupied(e) => { + e.into_mut().succ_set.insert(succ); + } + } + match top.entry(succ) { + Entry::Vacant(e) => { + let mut dep = StackifierDependency::new(); + dep.num_prec = 1; + e.insert(dep); + } + Entry::Occupied(e) => { + e.into_mut().num_prec += 1; + } + } + } + + pub fn pop(&mut self) -> Option> { + if let Some(peek_blocks) = self.peek() { + for peek_block in &peek_blocks { + self.remove(*peek_block); + self.last_popped_block = Some(*peek_block); + } + return Some(peek_blocks); + } + None + } + + pub fn remove(&mut self, prec: BlockId) -> Option { + let result = self.top.remove(&prec); + if let Some(ref p) = result { + for s in &p.succ_set { + if let Some(y) = self.top.get_mut(s) { + y.num_prec -= 1; + } + } + } + result + } + + /// Instead of picking random block w/o precessor after popping the last block, + /// we prioritize blocks with last popped block as their only precessor. + /// Also if the prioritized block is also the only successor of last popped block, + /// we will keep nesting them into one vector until either condition goes false. + pub fn peek(&self) -> Option> { + let mut priority_succ: Option = None; + if let Some(last_block) = self.last_popped_block { + priority_succ = self.find_priority_succ(last_block); + } + + // prioritize priority_succ if any, otherwise + // run original topological sorting. + let mut nested_blocks: Vec = vec![]; + if let Some(priority_succ_blk_id) = priority_succ { + nested_blocks.push(priority_succ_blk_id); + let mut curr_block = priority_succ_blk_id; + if let Some(curr_dependency) = self.complete_top.get(&curr_block) { + let mut succ_set = &curr_dependency.succ_set; + // nest blocks iff 1. curr_block has only one succ; + // && 2. the succ has only one precessor + while let Some(next_priority_succ_blk_id) = self.find_priority_succ(curr_block) { + if succ_set.len() != 1 { + break; + } + nested_blocks.push(next_priority_succ_blk_id); + curr_block = next_priority_succ_blk_id; + if let Some(dependency) = self.complete_top.get(&curr_block) { + succ_set = &dependency.succ_set; + } + } + } + } else if let Some(next_succ) = self + .top + .iter() + .filter(|&(_, v)| v.num_prec == 0) + .map(|(k, _)| k) + .next() + .cloned() + { + nested_blocks.push(next_succ); + } else { + return None; + } + Some(nested_blocks) + } + + //? Priority succ is a block whose only precessor is last block. + pub fn find_priority_succ(&self, last_block: BlockId) -> Option { + let mut priority_succ = None; + if let Some(last_block_dependency) = self.complete_top.get(&last_block) { + for succ in &last_block_dependency.succ_set { + if let Some(complete_succ_dependency) = self.complete_top.get(succ) { + let succ_prec_num = complete_succ_dependency.num_prec; + if succ_prec_num == 1 { + priority_succ = Some(*succ); + break; + } + } + } + } + priority_succ + } +} diff --git a/language/move-prover/bytecode/src/stackless_control_flow_graph.rs b/language/move-prover/bytecode/src/stackless_control_flow_graph.rs index ea582703bc..4ba6f097fe 100644 --- a/language/move-prover/bytecode/src/stackless_control_flow_graph.rs +++ b/language/move-prover/bytecode/src/stackless_control_flow_graph.rs @@ -6,20 +6,22 @@ use crate::{ function_target::FunctionTarget, + graph::{Graph as ProverGraph, NaturalLoop}, stackless_bytecode::{Bytecode, Label}, }; use move_binary_format::file_format::CodeOffset; use petgraph::{dot::Dot, graph::Graph}; use std::collections::{BTreeMap, BTreeSet}; +use std::collections::btree_map::Entry; type Map = BTreeMap; type Set = BTreeSet; pub type BlockId = CodeOffset; -#[derive(Debug)] -struct Block { - successors: Vec, - content: BlockContent, +#[derive(Clone, Debug)] +pub(crate) struct Block { + pub(crate) successors: Vec, + pub(crate) content: BlockContent, } #[derive(Copy, Clone, Debug)] @@ -31,10 +33,11 @@ pub enum BlockContent { Dummy, } +#[derive(Clone, Debug)] pub struct StacklessControlFlowGraph { - entry_block_id: BlockId, - blocks: Map, - backward: bool, + pub(crate) entry_block_id: BlockId, + pub(crate) blocks: Map, + pub(crate) backward: bool, } const DUMMY_ENTRANCE: BlockId = 0; @@ -232,6 +235,43 @@ impl StacklessControlFlowGraph { } } +impl StacklessControlFlowGraph { + pub fn to_prover_graph(&self) -> ProverGraph { + let nodes: Vec = self.blocks.keys().copied().collect(); + let mut edges: Vec<(BlockId, BlockId)> = vec![]; + for (block_id, block) in &self.blocks { + for successor_id in &block.successors { + edges.push((*block_id, *successor_id)); + } + } + ProverGraph::new(DUMMY_ENTRANCE, nodes, edges) + } + + pub fn reduce_cfg_loop( + &mut self, + graph: &ProverGraph, + ) -> Map>> { + let mut loop_map: Map>> = Map::new(); + if let Some(loops) = graph.compute_reducible() { + for one_loop in loops { + for loop_body_block in &one_loop.loop_body { + if *loop_body_block == one_loop.loop_header {continue;} + self.blocks.remove(loop_body_block); + } + match loop_map.entry(one_loop.loop_header) { + Entry::Vacant(e) => { + e.insert(vec![one_loop]); + } + Entry::Occupied(e) => { + e.into_mut().push(one_loop); + } + } + } + } + loop_map + } +} + // CFG dot graph generation struct DotCFGBlock<'env> { block_id: BlockId, diff --git a/language/move-prover/bytecode/src/stackless_structured_control_flow.rs b/language/move-prover/bytecode/src/stackless_structured_control_flow.rs new file mode 100644 index 0000000000..81d7497fa1 --- /dev/null +++ b/language/move-prover/bytecode/src/stackless_structured_control_flow.rs @@ -0,0 +1,331 @@ +// Copyright (c) The Diem Core Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::stackifier_topological_sort::StackifierTopologicalSort; +use crate::stackless_bytecode::{Bytecode, Label}; +use crate::stackless_control_flow_graph::{BlockContent, BlockId, StacklessControlFlowGraph}; +use move_model::ast::TempIndex; +use std::collections::{BTreeMap, BTreeSet}; +use std::vec::Vec; +use crate::graph::NaturalLoop; +use std::option::Option; + +pub struct StacklessStructuredControlFlow { + pub top_sort: StackifierTopologicalSort, +} + +impl StacklessStructuredControlFlow { + pub fn new(cfg: &StacklessControlFlowGraph) -> Self { + let mut topological_sort = StackifierTopologicalSort::new(); + for (block_id, block) in &cfg.blocks { + for successor_id in &block.successors { + topological_sort.add_dependency(*block_id, *successor_id); + } + } + Self { + top_sort: topological_sort, + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum StacklessSCGBlockKind { + Basic, // for non-loop blocks + Break, + Continue, +} + +#[derive(Debug)] +pub enum StacklessSCG { + BasicBlock{start_offset: usize, end_offset: usize, kind: StacklessSCGBlockKind}, + IfBlock{cond: TempIndex, if_true: Box, if_false: Box}, + LoopBlock{loop_header: Box, loop_body: Vec} +} + +impl StacklessSCG { + pub fn new(block_id: BlockId, cfg: &StacklessControlFlowGraph, kind: StacklessSCGBlockKind) -> Self { + if let BlockContent::Basic { lower, upper } = cfg.content(block_id) { + Self::BasicBlock { + start_offset: *lower as usize, + end_offset: *upper as usize, + kind, + } + } else { + panic!(); + } + } +} + +pub fn generate_scg_vec( + cfg: &StacklessControlFlowGraph, + code: &[Bytecode], +) -> Vec { + let prover_graph = cfg.to_prover_graph(); + let mut reduced_cfg = cfg.clone(); + let loop_map = reduced_cfg.reduce_cfg_loop(&prover_graph); + let mut scf_top_sort = StacklessStructuredControlFlow::new(&reduced_cfg).top_sort; + let scg_block_map = compute_scg_block_map(&loop_map, &mut scf_top_sort.clone()); + + let mut scg_vec = vec![]; + let mut visited_blocks = BTreeSet::new(); + + while let Some(blocks) = scf_top_sort.pop() { + for blk_id in &blocks { + push_scg(&mut scg_vec, blk_id, &cfg, &mut visited_blocks, code, &loop_map, &scg_block_map); + } + } + println!("the scg vec is {:?}", &scg_vec); + scg_vec +} + +pub fn push_scg( + scg_vec: &mut Vec, + blk_id: &BlockId, + cfg: &StacklessControlFlowGraph, + visited_blocks: &mut BTreeSet, + code: &[Bytecode], + loop_map: &BTreeMap>>, + scg_block_map: &BTreeMap, +) { + if visited_blocks.contains(blk_id) { + return; + } + if let Some(loops) = loop_map.get(&blk_id) { + push_loop_scg(loops, scg_vec, &cfg, visited_blocks, code, &loop_map, &scg_block_map); + visited_blocks.insert(*blk_id); + } else { + push_non_loop_scg(scg_vec, *blk_id, &cfg, visited_blocks, code, &scg_block_map); + } +} + +pub fn push_loop_scg( + loops: &Vec>, + scg_vec: &mut Vec, + // blk_id: BlockId, + cfg: &StacklessControlFlowGraph, + visited_blocks: &mut BTreeSet, + code: &[Bytecode], + loop_map: &BTreeMap>>, + scg_block_map: &BTreeMap, +) { + let mut loop_scg_vec = vec![]; + let mut loop_header = None; + + println!("visited before loop {:?}", &visited_blocks); + for one_loop in loops { + for loop_body_blk_id in &one_loop.loop_body { + if loop_header.is_none() { + loop_header = Some(one_loop.loop_header); + } + if *loop_body_blk_id == one_loop.loop_header {continue;} + println!("push scg of block {:?} and vec {:?}", loop_body_blk_id, loop_scg_vec); + push_scg( + &mut loop_scg_vec, + loop_body_blk_id, + &cfg, + visited_blocks, + code, + loop_map, + scg_block_map, + ); + println!("visited after loop {:?} and vec {:?}", &visited_blocks, loop_scg_vec); + } + } + + + scg_vec.push(StacklessSCG::LoopBlock { + /// Branch is always at the end of a block, here this scg is just a wrapper, so it should be a "basic". + loop_header: Box::new(StacklessSCG::new(loop_header.unwrap(), &cfg, StacklessSCGBlockKind::Basic)), + loop_body: loop_scg_vec, + }); +} + + +pub fn push_non_loop_scg( + scg_vec: &mut Vec, + blk_id: BlockId, + cfg: &StacklessControlFlowGraph, + visited_blocks: &mut BTreeSet, + code: &[Bytecode], + scg_block_map: &BTreeMap, +) { + if visited_blocks.contains(&blk_id) {return;} + let label_map = compute_label_map(cfg, code); + let get_block = |l| label_map.get(l).expect("label has corresponding block"); + if let BlockContent::Basic { lower, upper } = cfg.content(blk_id) { + let mut start = *lower; + for offs in *lower..*upper + 1 { + match &code[offs as usize] { + Bytecode::Branch(_, if_t, if_f, cond) => { + scg_vec.push(StacklessSCG::BasicBlock { + start_offset: start as usize, + end_offset: offs as usize, + kind: StacklessSCGBlockKind::Basic, + }); + start = offs; + + let if_block_id = get_block(if_t); + let else_block_id = get_block(if_f); + + let if_block_kind = get_block_kind(&blk_id, &if_block_id, &scg_block_map); + let else_block_kind = get_block_kind(&blk_id, &else_block_id, &scg_block_map); + + let if_else_scg = StacklessSCG::IfBlock { + cond: *cond, + if_true: Box::new(StacklessSCG::new(*if_block_id, &cfg, if_block_kind)), + if_false: Box::new(StacklessSCG::new(*else_block_id, &cfg, else_block_kind)), + }; + scg_vec.push(if_else_scg); + visited_blocks.insert(*if_block_id); + visited_blocks.insert(*else_block_id); + } + Bytecode::Jump(_, label) => { + let dest_blk_id = get_block(label); + let block_kind = get_block_kind(&blk_id, dest_blk_id, scg_block_map); + scg_vec.push(StacklessSCG::BasicBlock { + start_offset: start as usize, + end_offset: offs as usize, + kind: StacklessSCGBlockKind::Basic, + }); + start = offs; + } + _ => {} + } + } + if start != *upper { + scg_vec.push(StacklessSCG::BasicBlock { + start_offset: start as usize, + end_offset: *upper as usize, + kind: StacklessSCGBlockKind::Basic, + }); + } + visited_blocks.insert(blk_id); + } +} + +fn get_block_kind(src_block: &BlockId, dest_block: &BlockId, scg_block_map: &BTreeMap) -> StacklessSCGBlockKind { + let src_index = scg_block_map.get(&src_block).unwrap(); + let dest_index = scg_block_map.get(&dest_block).unwrap(); + match src_index { + StacklessSCGBlockIndex::LoopBody{index: _, header} => { + if let Some(header_index) = scg_block_map.get(&header) { + match header_index { + StacklessSCGBlockIndex::LoopHeader{start_index, end_index, body: _} => { + match dest_index { + StacklessSCGBlockIndex::Basic{index} => { + if *index == *start_index { + return StacklessSCGBlockKind::Continue; + } else if *index >= *end_index { + // Break to a certain loop point is not supported. + return StacklessSCGBlockKind::Break; + } else if *index < *start_index { + panic!("A go-to towards lines before loop is detected!"); + } + } + StacklessSCGBlockIndex::LoopBody{index, header: _} => { + if *index == *start_index { + return StacklessSCGBlockKind::Continue; + } else if *index >= *end_index { + // Break to a certain loop point is not supported. + return StacklessSCGBlockKind::Break; + } else if *index < *start_index { + panic!("A go-to towards lines before loop is detected!"); + } + }, + StacklessSCGBlockIndex::LoopHeader{start_index: _, end_index: _, body: _} => { + return StacklessSCGBlockKind::Continue; + } + } + } + _ => {panic!("Header label is of wrong type!")} + } + } else { + panic!("Cannot find header label index!"); + } + } + _ => { + return StacklessSCGBlockKind::Basic; + } + } + StacklessSCGBlockKind::Basic +} + +/// Compute a map from labels to block ids which those labels start. +pub fn compute_label_map( + cfg: &StacklessControlFlowGraph, + code: &[Bytecode], +) -> BTreeMap { + let mut map = BTreeMap::new(); + for id in cfg.blocks() { + if let Some(label) = get_label(id, cfg, code) { + map.insert(label, id); + } + } + map +} + +fn get_label( + id: BlockId, + cfg: &StacklessControlFlowGraph, + code: &[Bytecode] +) -> Option