Skip to content

Commit fd1b741

Browse files
committed
control flow graph to structured control flow
1 parent 2cdfa0e commit fd1b741

9 files changed

+839
-53
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ impl Experiment {
2424
/// during tests this is off.
2525
/// Retention: permanent
2626
pub const CAPTURE_SOURCE_INFO: &'static str = "capture-source-info";
27+
28+
/// Transform control flow graph to structured control flow.
29+
/// This is off by default for now, we might want to make it default
30+
/// if the performance improvement is significant.
31+
pub const APPLY_CFG_TO_SCF: &'static str = "apply-cfg-to-scf";
2732
}

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

Lines changed: 264 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ use move_stackless_bytecode::{
1414
function_target_pipeline::FunctionVariant,
1515
stackless_bytecode::{Bytecode, Constant, Label, Operation},
1616
stackless_control_flow_graph::{BlockContent, BlockId, StacklessControlFlowGraph},
17+
stackless_structured_control_flow::{StacklessSCG, StacklessStructuredControlFlow},
1718
};
1819
use sha3::{Digest, Keccak256};
19-
use std::collections::{btree_map::Entry, BTreeMap};
20+
use std::collections::{btree_map::Entry, BTreeMap, BTreeSet};
2021

2122
/// Mutable state of the function generator.
2223
pub(crate) struct FunctionGenerator<'a> {
@@ -107,48 +108,260 @@ impl<'a> FunctionGenerator<'a> {
107108
// Compute control flow graph, entry block, and label map
108109
let code = target.data.code.as_slice();
109110
let cfg = StacklessControlFlowGraph::new_forward(code);
110-
let entry_bb = Self::get_actual_entry_block(&cfg);
111-
let label_map = Self::compute_label_map(&cfg, code);
112-
113-
// Emit state machine to represent control flow.
114-
// TODO: Eliminate the need for this, see also
115-
// https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2
116-
if cfg.successors(entry_bb).iter().all(|b| cfg.is_dummmy(*b)) {
117-
// In this trivial case, we have only one block and can omit the state machine
118-
if let BlockContent::Basic { lower, upper } = cfg.content(entry_bb) {
119-
for offs in *lower..*upper + 1 {
120-
self.bytecode(ctx, fun_id, target, &label_map, &code[offs as usize], false);
111+
112+
if !ctx.options.apply_cfg_to_scf() {
113+
self.emit_cfg(&cfg, code, ctx, fun_id, target);
114+
} else {
115+
self.emit_scf_from_cfg(&cfg, code, ctx, fun_id, target);
116+
}
117+
});
118+
emitln!(ctx.writer)
119+
}
120+
121+
fn emit_cfg(
122+
&mut self,
123+
cfg: &StacklessControlFlowGraph,
124+
code: &[Bytecode],
125+
ctx: &Context,
126+
fun_id: &QualifiedInstId<FunId>,
127+
target: &FunctionTarget,
128+
) {
129+
let entry_bb = Self::get_actual_entry_block(cfg);
130+
let label_map = Self::compute_label_map(cfg, code);
131+
// Emit state machine to represent control flow.
132+
// TODO: Eliminate the need for this, see also
133+
// https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2
134+
if cfg.successors(entry_bb).iter().all(|b| cfg.is_dummmy(*b)) {
135+
// In this trivial case, we have only one block and can omit the state machine
136+
if let BlockContent::Basic { lower, upper } = cfg.content(entry_bb) {
137+
for offs in *lower..*upper + 1 {
138+
self.bytecode(
139+
ctx,
140+
fun_id,
141+
target,
142+
&label_map,
143+
&code[offs as usize],
144+
false,
145+
false,
146+
);
147+
}
148+
} else {
149+
panic!("effective entry block is not basic")
150+
}
151+
} else {
152+
emitln!(ctx.writer, "let $block := {}", entry_bb);
153+
emit!(ctx.writer, "for {} true {} ");
154+
ctx.emit_block(|| {
155+
emitln!(ctx.writer, "switch $block");
156+
for blk_id in &cfg.blocks() {
157+
if let BlockContent::Basic { lower, upper } = cfg.content(*blk_id) {
158+
// Emit code for this basic block.
159+
emit!(ctx.writer, "case {} ", blk_id);
160+
ctx.emit_block(|| {
161+
for offs in *lower..*upper + 1 {
162+
self.bytecode(
163+
ctx,
164+
fun_id,
165+
target,
166+
&label_map,
167+
&code[offs as usize],
168+
true,
169+
false,
170+
);
171+
}
172+
})
121173
}
174+
}
175+
})
176+
}
177+
}
178+
179+
fn emit_scf_from_cfg(
180+
&mut self,
181+
cfg: &StacklessControlFlowGraph,
182+
code: &[Bytecode],
183+
ctx: &Context,
184+
fun_id: &QualifiedInstId<FunId>,
185+
target: &FunctionTarget,
186+
) {
187+
let prover_graph = cfg.to_prover_graph();
188+
let mut reduced_cfg = cfg.clone();
189+
let loop_map = reduced_cfg.reduce_cfg_loop(&prover_graph);
190+
191+
let mut scf_top_sort = StacklessStructuredControlFlow::new(&reduced_cfg).top_sort;
192+
193+
let mut scg_vec = vec![];
194+
let mut visited_blocks = BTreeSet::new();
195+
196+
while let Some(blocks) = scf_top_sort.pop() {
197+
for blk_id in &blocks {
198+
if visited_blocks.contains(blk_id) {
199+
continue;
200+
}
201+
if let Some(one_loop) = loop_map.get(&blk_id) {
202+
let mut loop_scg_vec = vec![];
203+
let mut loop_visited_blocks = BTreeSet::new();
204+
for loop_body_blk_id in &one_loop.loop_body {
205+
if loop_visited_blocks.contains(loop_body_blk_id) {
206+
continue;
207+
}
208+
Self::push_non_loop_scg(
209+
&mut loop_scg_vec,
210+
*loop_body_blk_id,
211+
&cfg,
212+
&mut loop_visited_blocks,
213+
code,
214+
);
215+
}
216+
scg_vec.push(StacklessSCG::LoopBlock {
217+
loop_header: Box::new(StacklessSCG::new(one_loop.loop_header, &cfg)),
218+
loop_body: loop_scg_vec,
219+
});
122220
} else {
123-
panic!("effective entry block is not basic")
221+
Self::push_non_loop_scg(&mut scg_vec, *blk_id, &cfg, &mut visited_blocks, code);
124222
}
125-
} else {
126-
emitln!(ctx.writer, "let $block := {}", entry_bb);
127-
emit!(ctx.writer, "for {} true {} ");
223+
}
224+
}
225+
226+
for scg in scg_vec {
227+
self.emit_scg(&scg, &cfg, code, ctx, fun_id, target);
228+
}
229+
}
230+
231+
pub fn emit_scg(
232+
&mut self,
233+
scg: &StacklessSCG,
234+
cfg: &StacklessControlFlowGraph,
235+
code: &[Bytecode],
236+
ctx: &Context,
237+
fun_id: &QualifiedInstId<FunId>,
238+
target: &FunctionTarget,
239+
) {
240+
let label_map = Self::compute_label_map(cfg, code);
241+
match scg {
242+
StacklessSCG::BasicBlock {
243+
start_offset,
244+
end_offset,
245+
} => {
128246
ctx.emit_block(|| {
129-
emitln!(ctx.writer, "switch $block");
130-
for blk_id in &cfg.blocks() {
131-
if let BlockContent::Basic { lower, upper } = cfg.content(*blk_id) {
132-
// Emit code for this basic block.
133-
emit!(ctx.writer, "case {} ", blk_id);
134-
ctx.emit_block(|| {
135-
for offs in *lower..*upper + 1 {
136-
self.bytecode(
137-
ctx,
138-
fun_id,
139-
target,
140-
&label_map,
141-
&code[offs as usize],
142-
true,
143-
);
144-
}
145-
})
247+
for offs in *start_offset..*end_offset + 1 {
248+
self.bytecode(
249+
ctx,
250+
fun_id,
251+
target,
252+
&label_map,
253+
&code[offs as usize],
254+
false,
255+
true,
256+
);
257+
}
258+
});
259+
}
260+
StacklessSCG::IfBlock {
261+
cond,
262+
if_true,
263+
if_false,
264+
} => {
265+
emitln!(ctx.writer, "switch $t{} ", cond);
266+
emit!(ctx.writer, "case 0 ");
267+
if let StacklessSCG::BasicBlock {
268+
start_offset,
269+
end_offset,
270+
} = **if_false
271+
{
272+
ctx.emit_block(|| {
273+
for offs in start_offset..end_offset + 1 {
274+
self.bytecode(
275+
ctx,
276+
fun_id,
277+
target,
278+
&label_map,
279+
&code[offs as usize],
280+
false,
281+
true,
282+
);
283+
}
284+
});
285+
}
286+
emit!(ctx.writer, "default ");
287+
if let StacklessSCG::BasicBlock {
288+
start_offset,
289+
end_offset,
290+
} = **if_true
291+
{
292+
ctx.emit_block(|| {
293+
for offs in start_offset..end_offset + 1 {
294+
self.bytecode(
295+
ctx,
296+
fun_id,
297+
target,
298+
&label_map,
299+
&code[offs as usize],
300+
false,
301+
true,
302+
);
146303
}
304+
});
305+
}
306+
}
307+
// TODO: need to emit codes of loops based on StacklessSCG::LoopBlock
308+
// based on new bytecodes of Break and Continue
309+
StacklessSCG::LoopBlock {
310+
loop_header,
311+
loop_body,
312+
} => {
313+
self.emit_scg(&*loop_header, &cfg, code, ctx, fun_id, target);
314+
emitln!(ctx.writer, "for {} true {} ");
315+
ctx.emit_block(|| {
316+
for loop_body_scg in loop_body {
317+
self.emit_scg(loop_body_scg, &cfg, code, ctx, fun_id, target);
147318
}
148-
})
319+
});
149320
}
150-
});
151-
emitln!(ctx.writer)
321+
}
322+
}
323+
324+
pub fn push_non_loop_scg(
325+
scg_vec: &mut Vec<StacklessSCG>,
326+
blk_id: BlockId,
327+
cfg: &StacklessControlFlowGraph,
328+
visited_blocks: &mut BTreeSet<BlockId>,
329+
code: &[Bytecode],
330+
) {
331+
let label_map = Self::compute_label_map(cfg, code);
332+
let get_block = |l| label_map.get(l).expect("label has corresponding block");
333+
if let BlockContent::Basic { lower, upper } = cfg.content(blk_id) {
334+
let mut start = *lower;
335+
for offs in *lower..*upper + 1 {
336+
match &code[offs as usize] {
337+
Bytecode::Branch(_, if_t, if_f, cond) => {
338+
scg_vec.push(StacklessSCG::BasicBlock {
339+
start_offset: start as usize,
340+
end_offset: offs as usize,
341+
});
342+
start = offs;
343+
344+
let if_block_id = get_block(if_t);
345+
let else_block_id = get_block(if_f);
346+
let if_else_scg = StacklessSCG::IfBlock {
347+
cond: *cond,
348+
if_true: Box::new(StacklessSCG::new(*if_block_id, &cfg)),
349+
if_false: Box::new(StacklessSCG::new(*else_block_id, &cfg)),
350+
};
351+
visited_blocks.insert(*if_block_id);
352+
visited_blocks.insert(*else_block_id);
353+
scg_vec.push(if_else_scg);
354+
}
355+
_ => {}
356+
}
357+
}
358+
if start != *upper {
359+
scg_vec.push(StacklessSCG::BasicBlock {
360+
start_offset: start as usize,
361+
end_offset: *upper as usize,
362+
});
363+
}
364+
}
152365
}
153366

154367
/// Compute the locals in the given function which are borrowed from and which are not
@@ -210,6 +423,7 @@ impl<'a> FunctionGenerator<'a> {
210423
label_map: &BTreeMap<Label, BlockId>,
211424
bc: &Bytecode,
212425
has_flow: bool,
426+
skip_block: bool,
213427
) {
214428
use Bytecode::*;
215429
emitln!(
@@ -283,19 +497,23 @@ impl<'a> FunctionGenerator<'a> {
283497
match bc {
284498
Jump(_, l) => {
285499
print_loc();
286-
emitln!(ctx.writer, "$block := {}", get_block(l))
500+
if !skip_block {
501+
emitln!(ctx.writer, "$block := {}", get_block(l))
502+
}
287503
}
288504
Branch(_, if_t, if_f, cond) => {
289505
print_loc();
290-
emitln!(
291-
ctx.writer,
292-
"switch {}\n\
293-
case 0 {{ $block := {} }}\n\
294-
default {{ $block := {} }}",
295-
local(cond),
296-
get_block(if_f),
297-
get_block(if_t),
298-
)
506+
if !skip_block {
507+
emitln!(
508+
ctx.writer,
509+
"switch {}\n\
510+
case 0 {{ $block := {} }}\n\
511+
default {{ $block := {} }}",
512+
local(cond),
513+
get_block(if_f),
514+
get_block(if_t),
515+
)
516+
}
299517
}
300518
Assign(_, dest, src, _) => {
301519
print_loc();

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,9 @@ impl Options {
6262
pub fn generate_source_info(&self) -> bool {
6363
!self.testing || self.experiment_on(Experiment::CAPTURE_SOURCE_INFO)
6464
}
65+
66+
/// Returns true if control flow graph to structured control flow is applied.
67+
pub fn apply_cfg_to_scf(&self) -> bool {
68+
self.experiment_on(Experiment::APPLY_CFG_TO_SCF)
69+
}
6570
}

0 commit comments

Comments
 (0)