Skip to content

Commit

Permalink
control flow graph to structured control flow
Browse files Browse the repository at this point in the history
  • Loading branch information
gga0wp committed Mar 9, 2022
1 parent 2cdfa0e commit fd1b741
Show file tree
Hide file tree
Showing 9 changed files with 839 additions and 53 deletions.
5 changes: 5 additions & 0 deletions language/evm/move-to-yul/src/experiments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
310 changes: 264 additions & 46 deletions language/evm/move-to-yul/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ 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::{StacklessSCG, StacklessStructuredControlFlow},
};
use sha3::{Digest, Keccak256};
use std::collections::{btree_map::Entry, BTreeMap};
use std::collections::{btree_map::Entry, BTreeMap, BTreeSet};

/// Mutable state of the function generator.
pub(crate) struct FunctionGenerator<'a> {
Expand Down Expand Up @@ -107,48 +108,260 @@ 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);

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<FunId>,
target: &FunctionTarget,
) {
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,
false,
);
}
} else {
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<FunId>,
target: &FunctionTarget,
) {
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 mut scg_vec = vec![];
let mut visited_blocks = BTreeSet::new();

while let Some(blocks) = scf_top_sort.pop() {
for blk_id in &blocks {
if visited_blocks.contains(blk_id) {
continue;
}
if let Some(one_loop) = loop_map.get(&blk_id) {
let mut loop_scg_vec = vec![];
let mut loop_visited_blocks = BTreeSet::new();
for loop_body_blk_id in &one_loop.loop_body {
if loop_visited_blocks.contains(loop_body_blk_id) {
continue;
}
Self::push_non_loop_scg(
&mut loop_scg_vec,
*loop_body_blk_id,
&cfg,
&mut loop_visited_blocks,
code,
);
}
scg_vec.push(StacklessSCG::LoopBlock {
loop_header: Box::new(StacklessSCG::new(one_loop.loop_header, &cfg)),
loop_body: loop_scg_vec,
});
} else {
panic!("effective entry block is not basic")
Self::push_non_loop_scg(&mut scg_vec, *blk_id, &cfg, &mut visited_blocks, code);
}
} else {
emitln!(ctx.writer, "let $block := {}", entry_bb);
emit!(ctx.writer, "for {} true {} ");
}
}

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<FunId>,
target: &FunctionTarget,
) {
let label_map = Self::compute_label_map(cfg, code);
match scg {
StacklessSCG::BasicBlock {
start_offset,
end_offset,
} => {
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,
);
}
});
}
StacklessSCG::IfBlock {
cond,
if_true,
if_false,
} => {
emitln!(ctx.writer, "switch $t{} ", cond);
emit!(ctx.writer, "case 0 ");
if let StacklessSCG::BasicBlock {
start_offset,
end_offset,
} = **if_false
{
ctx.emit_block(|| {
for offs in start_offset..end_offset + 1 {
self.bytecode(
ctx,
fun_id,
target,
&label_map,
&code[offs as usize],
false,
true,
);
}
});
}
emit!(ctx.writer, "default ");
if let StacklessSCG::BasicBlock {
start_offset,
end_offset,
} = **if_true
{
ctx.emit_block(|| {
for offs in start_offset..end_offset + 1 {
self.bytecode(
ctx,
fun_id,
target,
&label_map,
&code[offs as usize],
false,
true,
);
}
});
}
}
// 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);
emitln!(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);
}
})
});
}
});
emitln!(ctx.writer)
}
}

pub fn push_non_loop_scg(
scg_vec: &mut Vec<StacklessSCG>,
blk_id: BlockId,
cfg: &StacklessControlFlowGraph,
visited_blocks: &mut BTreeSet<BlockId>,
code: &[Bytecode],
) {
let label_map = Self::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,
});
start = offs;

let if_block_id = get_block(if_t);
let else_block_id = get_block(if_f);
let if_else_scg = StacklessSCG::IfBlock {
cond: *cond,
if_true: Box::new(StacklessSCG::new(*if_block_id, &cfg)),
if_false: Box::new(StacklessSCG::new(*else_block_id, &cfg)),
};
visited_blocks.insert(*if_block_id);
visited_blocks.insert(*else_block_id);
scg_vec.push(if_else_scg);
}
_ => {}
}
}
if start != *upper {
scg_vec.push(StacklessSCG::BasicBlock {
start_offset: start as usize,
end_offset: *upper as usize,
});
}
}
}

/// Compute the locals in the given function which are borrowed from and which are not
Expand Down Expand Up @@ -210,6 +423,7 @@ impl<'a> FunctionGenerator<'a> {
label_map: &BTreeMap<Label, BlockId>,
bc: &Bytecode,
has_flow: bool,
skip_block: bool,
) {
use Bytecode::*;
emitln!(
Expand Down Expand Up @@ -283,19 +497,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();
Expand Down
5 changes: 5 additions & 0 deletions language/evm/move-to-yul/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading

0 comments on commit fd1b741

Please sign in to comment.