Skip to content

Commit 5cc0f6f

Browse files
committed
control flow graph to structured control flow
1 parent 4b969d2 commit 5cc0f6f

File tree

6 files changed

+303
-40
lines changed

6 files changed

+303
-40
lines changed

language/evm/move-to-yul/src/generator.rs

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use move_stackless_bytecode::{
2222
reaching_def_analysis::ReachingDefProcessor,
2323
stackless_bytecode::{Bytecode, Constant, Label, Operation},
2424
stackless_control_flow_graph::{BlockContent, BlockId, StacklessControlFlowGraph},
25+
stackless_structured_control_flow::{StacklessStructuredControlFlow},
2526
};
2627

2728
use crate::{
@@ -32,6 +33,7 @@ use crate::{
3233
const CONTRACT_ATTR: &str = "contract";
3334
const CREATE_ATTR: &str = "create";
3435
const CALLABLE_ATTR: &str = "callable";
36+
const CFG_TO_SCF: bool = false;
3537

3638
/// Immutable context passed through the compilation.
3739
struct Context<'a> {
@@ -144,10 +146,7 @@ impl Generator {
144146
fn objects(&mut self, ctx: &Context) {
145147
emitln!(
146148
ctx.writer,
147-
"\
148-
/* =======================================
149-
* Generated by Move-To-Yul compiler v{}
150-
* ======================================= */",
149+
"{}",
151150
ctx.options.version(),
152151
);
153152
emitln!(ctx.writer);
@@ -321,43 +320,58 @@ impl Generator {
321320
// Compute control flow graph, entry block, and label map
322321
let code = target.data.code.as_slice();
323322
let cfg = StacklessControlFlowGraph::new_forward(code);
324-
let entry_bb = Self::get_actual_entry_block(&cfg);
325-
let label_map = Self::compute_label_map(&cfg, code);
326-
327-
// Emit state machine to represent control flow.
328-
// TODO: Eliminate the need for this, see also
329-
// https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2
330-
if cfg.successors(entry_bb).iter().all(|b| cfg.is_dummmy(*b)) {
331-
// In this trivial case, we have only one block and can omit the state machine
332-
if let BlockContent::Basic { lower, upper } = cfg.content(entry_bb) {
333-
for offs in *lower..*upper + 1 {
334-
self.bytecode(ctx, &target, &label_map, &code[offs as usize], false);
323+
324+
if !CFG_TO_SCF {
325+
let entry_bb = Self::get_actual_entry_block(&cfg);
326+
let label_map = Self::compute_label_map(&cfg, code);
327+
328+
// Emit state machine to represent control flow.
329+
// TODO: Eliminate the need for this, see also
330+
// https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2
331+
if cfg.successors(entry_bb).iter().all(|b| cfg.is_dummmy(*b)) {
332+
// In this trivial case, we have only one block and can omit the state machine
333+
if let BlockContent::Basic { lower, upper } = cfg.content(entry_bb) {
334+
for offs in *lower..*upper + 1 {
335+
self.bytecode(ctx, &target, &label_map, &code[offs as usize], false);
336+
}
337+
} else {
338+
panic!("effective entry block is not basic")
335339
}
336340
} else {
337-
panic!("effective entry block is not basic")
338-
}
339-
} else {
340-
emitln!(ctx.writer, "let $block := {}", entry_bb);
341-
emitln!(ctx.writer, "for {} true {} {");
342-
ctx.writer.indent();
343-
emitln!(ctx.writer, "switch $block");
344-
for blk_id in &cfg.blocks() {
345-
if let BlockContent::Basic { lower, upper } = cfg.content(*blk_id) {
346-
// Emit code for this basic block.
347-
emitln!(ctx.writer, "case {} {{", blk_id);
348-
ctx.writer.indent();
349-
for offs in *lower..*upper + 1 {
350-
self.bytecode(ctx, &target, &label_map, &code[offs as usize], true);
341+
emitln!(ctx.writer, "let $block := {}", entry_bb);
342+
emitln!(ctx.writer, "for {} true {} {");
343+
ctx.writer.indent();
344+
emitln!(ctx.writer, "switch $block");
345+
for blk_id in &cfg.blocks() {
346+
if let BlockContent::Basic { lower, upper } = cfg.content(*blk_id) {
347+
// Emit code for this basic block.
348+
emitln!(ctx.writer, "case {} {{", blk_id);
349+
ctx.writer.indent();
350+
for offs in *lower..*upper + 1 {
351+
self.bytecode(ctx, &target, &label_map, &code[offs as usize], true);
352+
}
353+
ctx.writer.unindent();
354+
emitln!(ctx.writer, "}")
351355
}
352-
ctx.writer.unindent();
353-
emitln!(ctx.writer, "}")
354356
}
357+
ctx.writer.unindent();
358+
emitln!(ctx.writer, "}"); // for
355359
}
356360
ctx.writer.unindent();
357-
emitln!(ctx.writer, "}"); // for
361+
emitln!(ctx.writer, "}\n"); // function
362+
} else {
363+
let scf_top_sort = StacklessStructuredControlFlow::new(cfg).top_sort;
364+
365+
while let block_id_vec = scf_top_sort.pop() {
366+
let blocks = block_id_vec.unwrap();
367+
let block_contents: Vec<BlockContent> = blocks.iter().map(|block_id| cfg.content(*block_id)).collect();
368+
369+
370+
}
371+
372+
// TODO emit new style of codes based on the transformed structured control flow
373+
// based on while, if/else and block
358374
}
359-
ctx.writer.unindent();
360-
emitln!(ctx.writer, "}\n"); // function
361375
}
362376

363377
/// Get the actual entry block, skipping trailing dummy blocks.

language/move-prover/bytecode/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ once_cell = "1.7.2"
3131
paste = "1.0.5"
3232
petgraph = "0.5.1"
3333
workspace-hack = { version = "0.1", path = "../../../crates/workspace-hack" }
34+
topological-sort = "0.1"
3435

3536
[dev-dependencies]
3637
move-stdlib = { path = "../../move-stdlib" }

language/move-prover/bytecode/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ pub mod pipeline_factory;
3838
pub mod reaching_def_analysis;
3939
pub mod read_write_set_analysis;
4040
pub mod spec_instrumentation;
41+
pub mod stackifier_topological_sort;
4142
pub mod stackless_bytecode;
4243
pub mod stackless_bytecode_generator;
4344
pub mod stackless_control_flow_graph;
45+
pub mod stackless_structured_control_flow;
4446
pub mod usage_analysis;
4547
pub mod verification_analysis;
4648
pub mod verification_analysis_v2;
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
2+
use std::collections::{HashMap, HashSet};
3+
use std::collections::hash_map::Entry;
4+
use crate::stackless_control_flow_graph::BlockId;
5+
6+
#[derive(Clone, Debug)]
7+
pub struct StackifierDependency {
8+
pub num_prec: usize,
9+
pub succ_set: HashSet<BlockId>,
10+
}
11+
12+
#[derive(Clone, Debug)]
13+
pub struct StackifierTopologicalSort {
14+
pub last_popped_block: Option<BlockId>,
15+
top: HashMap<BlockId, StackifierDependency>,
16+
/// complete topology will not change while popping
17+
complete_top: HashMap<BlockId, StackifierDependency>,
18+
}
19+
20+
impl StackifierDependency {
21+
pub fn new() -> StackifierDependency {
22+
StackifierDependency {
23+
num_prec: 0,
24+
succ_set: HashSet::new(),
25+
}
26+
}
27+
}
28+
29+
impl StackifierTopologicalSort {
30+
#[inline]
31+
pub fn new() -> StackifierTopologicalSort {
32+
StackifierTopologicalSort {
33+
last_popped_block: Option::None,
34+
top: HashMap::new(),
35+
complete_top: HashMap::new(),
36+
}
37+
}
38+
39+
#[inline]
40+
pub fn len(&self) -> usize {
41+
self.top.len()
42+
}
43+
44+
#[inline]
45+
pub fn is_empty(&self) -> bool {
46+
self.top.is_empty()
47+
}
48+
49+
pub fn add_dependency(&self, prec: BlockId, succ: BlockId) {
50+
Self::add_dependency_impl(&mut self.top, prec, succ);
51+
Self::add_dependency_impl(&mut self.complete_top, prec, succ);
52+
}
53+
54+
fn add_dependency_impl(top: &mut HashMap<BlockId, StackifierDependency>, prec: BlockId, succ: BlockId) {
55+
match top.entry(prec) {
56+
Entry::Vacant(e) => {
57+
let mut dep = StackifierDependency::new();
58+
dep.succ_set.insert(succ);
59+
e.insert(dep);
60+
}
61+
Entry::Occupied(e) => {
62+
if !e.into_mut().succ_set.insert(succ) {
63+
return;
64+
}
65+
}
66+
}
67+
}
68+
69+
/// Here is the main difference of this topological sort,
70+
/// instead of picking random node w/o precessor after popping the last node,
71+
/// we prioritize nodes with last popped node as their only precessor.
72+
/// Also if the prioritized node is also the only successor of last popped node,
73+
/// we will keep popping until either condition becomes false.
74+
pub fn pop(&mut self) -> Option<Vec<BlockId>> {
75+
let peek_blocks = self.peek();
76+
match self.peek() {
77+
Some(peek_blocks) => {
78+
for peek_block in peek_blocks {
79+
self.remove(peek_block);
80+
self.last_popped_block = Some(peek_block);
81+
}
82+
},
83+
None => {}
84+
}
85+
peek_blocks
86+
}
87+
88+
pub fn remove(&mut self, prec: BlockId) -> Option<StackifierDependency> {
89+
let result = self.top.remove(&prec);
90+
if let Some(ref p) = result {
91+
for s in &p.succ_set {
92+
if let Some(y) = self.top.get_mut(s) {
93+
y.num_prec -= 1;
94+
}
95+
}
96+
}
97+
result
98+
}
99+
100+
pub fn peek(&self) -> Option<Vec<BlockId>> {
101+
let priority_succ = None;
102+
if let last_block = self.last_popped_block {
103+
priority_succ = self.find_priority_succ(last_block.unwrap());
104+
}
105+
// if last_popped_block has no other successor other than
106+
let nested_blocks = vec![];
107+
if let curr_block_option = priority_succ {
108+
let curr_block = curr_block_option.unwrap();
109+
nested_blocks.push(curr_block);
110+
if let curr_dependency = self.complete_top.get(&curr_block) {
111+
let succ_set = curr_dependency.unwrap().succ_set;
112+
let next_priority_succ = self.find_priority_succ(curr_block);
113+
// nest blocks when 1. curr_block has only one succ;
114+
// && 2. the succ has only one precessor
115+
while succ_set.len() == 1 && next_priority_succ.is_some() {
116+
nested_blocks.push(next_priority_succ.unwrap());
117+
curr_block = next_priority_succ.unwrap();
118+
priority_succ = next_priority_succ;
119+
120+
if let dependency = self.complete_top.get(&curr_block) {
121+
succ_set = dependency.unwrap().succ_set;
122+
next_priority_succ = self.find_priority_succ(curr_block);
123+
}
124+
}
125+
}
126+
} else {
127+
let next_succ = self.top
128+
.iter()
129+
.filter(|&(_, v)| v.num_prec == 0)
130+
.map(|(k, _)| k)
131+
.next()
132+
.cloned();
133+
if next_succ.is_none() {
134+
return None;
135+
} else {
136+
nested_blocks.push(next_succ.unwrap());
137+
}
138+
}
139+
Some(nested_blocks)
140+
}
141+
142+
pub fn find_priority_succ(&self, last_block: BlockId) -> Option<BlockId> {
143+
let priority_succ = None;
144+
if let last_block_dependency = self.complete_top.get(&last_block) {
145+
for succ in last_block_dependency.unwrap().succ_set {
146+
if let complete_succ_dependency = self.complete_top.get(&succ) {
147+
let succ_prec_num = complete_succ_dependency.unwrap().num_prec;
148+
if succ_prec_num > 1 {continue}
149+
} else {continue}
150+
// priority succ is a block whose only precessor is last_popped_block
151+
priority_succ = Some(succ);
152+
break;
153+
}
154+
}
155+
priority_succ
156+
}
157+
}

language/move-prover/bytecode/src/stackless_control_flow_graph.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use crate::{
88
function_target::FunctionTarget,
99
stackless_bytecode::{Bytecode, Label},
10+
graph::Graph as ProverGraph,
1011
};
1112
use move_binary_format::file_format::CodeOffset;
1213
use petgraph::{dot::Dot, graph::Graph};
@@ -17,9 +18,9 @@ type Set<V> = BTreeSet<V>;
1718
pub type BlockId = CodeOffset;
1819

1920
#[derive(Debug)]
20-
struct Block {
21-
successors: Vec<BlockId>,
22-
content: BlockContent,
21+
pub struct Block {
22+
pub successors: Vec<BlockId>,
23+
pub content: BlockContent,
2324
}
2425

2526
#[derive(Copy, Clone, Debug)]
@@ -32,9 +33,10 @@ pub enum BlockContent {
3233
}
3334

3435
pub struct StacklessControlFlowGraph {
35-
entry_block_id: BlockId,
36-
blocks: Map<BlockId, Block>,
37-
backward: bool,
36+
// TODO: think if we should leave them as public
37+
pub entry_block_id: BlockId,
38+
pub blocks: Map<BlockId, Block>,
39+
pub backward: bool,
3840
}
3941

4042
const DUMMY_ENTRANCE: BlockId = 0;
@@ -232,6 +234,16 @@ impl StacklessControlFlowGraph {
232234
}
233235
}
234236

237+
impl StacklessControlFlowGraph {
238+
pub fn to_prover_graph(&self) -> Option<ProverGraph<BlockId>> {
239+
240+
}
241+
242+
pub fn from_prover_graph(graph: ProverGraph<BlockId>) -> Self {
243+
244+
}
245+
}
246+
235247
// CFG dot graph generation
236248
struct DotCFGBlock<'env> {
237249
block_id: BlockId,

0 commit comments

Comments
 (0)