Skip to content

Commit a80f9d5

Browse files
committed
control flow graph to structured control flow
1 parent 06e9b0e commit a80f9d5

File tree

9 files changed

+697
-46
lines changed

9 files changed

+697
-46
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: 142 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ 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::StacklessStructuredControlFlow,
1718
};
1819
use sha3::{Digest, Keccak256};
1920
use std::collections::{btree_map::Entry, BTreeMap};
@@ -105,48 +106,147 @@ impl<'a> FunctionGenerator<'a> {
105106
// Compute control flow graph, entry block, and label map
106107
let code = target.data.code.as_slice();
107108
let cfg = StacklessControlFlowGraph::new_forward(code);
108-
let entry_bb = Self::get_actual_entry_block(&cfg);
109-
let label_map = Self::compute_label_map(&cfg, code);
110109

111-
// Emit state machine to represent control flow.
112-
// TODO: Eliminate the need for this, see also
113-
// https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2
114-
if cfg.successors(entry_bb).iter().all(|b| cfg.is_dummmy(*b)) {
115-
// In this trivial case, we have only one block and can omit the state machine
116-
if let BlockContent::Basic { lower, upper } = cfg.content(entry_bb) {
117-
for offs in *lower..*upper + 1 {
118-
self.bytecode(ctx, fun_id, target, &label_map, &code[offs as usize], false);
119-
}
120-
} else {
121-
panic!("effective entry block is not basic")
110+
if !ctx.options.apply_cfg_to_scf() {
111+
self.emit_cfg(&cfg, code, ctx, fun_id, target);
112+
} else {
113+
self.emit_scf_from_cfg(&cfg, code, ctx, fun_id, target);
114+
}
115+
});
116+
emitln!(ctx.writer)
117+
}
118+
119+
fn emit_cfg(
120+
&mut self,
121+
cfg: &StacklessControlFlowGraph,
122+
code: &[Bytecode],
123+
ctx: &Context,
124+
fun_id: &QualifiedInstId<FunId>,
125+
target: &FunctionTarget,
126+
) {
127+
let entry_bb = Self::get_actual_entry_block(cfg);
128+
let label_map = Self::compute_label_map(cfg, code);
129+
// Emit state machine to represent control flow.
130+
// TODO: Eliminate the need for this, see also
131+
// https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2
132+
if cfg.successors(entry_bb).iter().all(|b| cfg.is_dummmy(*b)) {
133+
// In this trivial case, we have only one block and can omit the state machine
134+
if let BlockContent::Basic { lower, upper } = cfg.content(entry_bb) {
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+
&"block",
143+
false,
144+
);
122145
}
123146
} else {
124-
emitln!(ctx.writer, "let $block := {}", entry_bb);
125-
emit!(ctx.writer, "for {} true {} ");
126-
ctx.emit_block(|| {
127-
emitln!(ctx.writer, "switch $block");
128-
for blk_id in &cfg.blocks() {
129-
if let BlockContent::Basic { lower, upper } = cfg.content(*blk_id) {
130-
// Emit code for this basic block.
131-
emit!(ctx.writer, "case {} ", blk_id);
132-
ctx.emit_block(|| {
133-
for offs in *lower..*upper + 1 {
134-
self.bytecode(
135-
ctx,
136-
fun_id,
137-
target,
138-
&label_map,
139-
&code[offs as usize],
140-
true,
141-
);
147+
panic!("effective entry block is not basic")
148+
}
149+
} else {
150+
emitln!(ctx.writer, "let $block := {}", entry_bb);
151+
emit!(ctx.writer, "for {} true {} ");
152+
ctx.emit_block(|| {
153+
emitln!(ctx.writer, "switch $block");
154+
for blk_id in &cfg.blocks() {
155+
if let BlockContent::Basic { lower, upper } = cfg.content(*blk_id) {
156+
// Emit code for this basic block.
157+
emit!(ctx.writer, "case {} ", blk_id);
158+
ctx.emit_block(|| {
159+
for offs in *lower..*upper + 1 {
160+
self.bytecode(
161+
ctx,
162+
fun_id,
163+
target,
164+
&label_map,
165+
&code[offs as usize],
166+
&"block",
167+
true,
168+
);
169+
}
170+
})
171+
}
172+
}
173+
})
174+
}
175+
}
176+
177+
fn emit_scf_from_cfg(
178+
&mut self,
179+
cfg: &StacklessControlFlowGraph,
180+
code: &[Bytecode],
181+
ctx: &Context,
182+
fun_id: &QualifiedInstId<FunId>,
183+
target: &FunctionTarget,
184+
) {
185+
let entry_bb = Self::get_actual_entry_block(cfg);
186+
let label_map = Self::compute_label_map(cfg, code);
187+
188+
let prover_graph = cfg.to_prover_graph();
189+
let mut reduced_cfg = cfg.clone();
190+
let loop_map = reduced_cfg.reduce_cfg_loop(&prover_graph);
191+
192+
let mut scf_top_sort = StacklessStructuredControlFlow::new(&reduced_cfg).top_sort;
193+
emitln!(ctx.writer, "let $block := {}", entry_bb);
194+
emitln!(ctx.writer, "switch $block");
195+
while let Some(blocks) = scf_top_sort.pop() {
196+
for blk_id in blocks {
197+
if let Some(one_loop) = loop_map.get(&blk_id) {
198+
emit!(ctx.writer, "case {} ", blk_id);
199+
ctx.emit_block(|| {
200+
emitln!(
201+
ctx.writer,
202+
"let $block{} := {}",
203+
one_loop.loop_header,
204+
one_loop.loop_header
205+
);
206+
emit!(ctx.writer, "for {} true {} ");
207+
ctx.emit_block(|| {
208+
let block_str = format!("block{}", one_loop.loop_header);
209+
emitln!(ctx.writer, "switch ${}", block_str);
210+
for loop_body_blk_id in &one_loop.loop_body {
211+
if let BlockContent::Basic { lower, upper } =
212+
cfg.content(*loop_body_blk_id)
213+
{
214+
emit!(ctx.writer, "case {} ", loop_body_blk_id);
215+
ctx.emit_block(|| {
216+
for offs in *lower..*upper + 1 {
217+
self.bytecode(
218+
ctx,
219+
fun_id,
220+
target,
221+
&label_map,
222+
&code[offs as usize],
223+
block_str.as_str(),
224+
true,
225+
);
226+
}
227+
})
142228
}
143-
})
229+
}
230+
});
231+
})
232+
} else if let BlockContent::Basic { lower, upper } = cfg.content(blk_id) {
233+
emit!(ctx.writer, "case {} ", blk_id);
234+
ctx.emit_block(|| {
235+
for offs in *lower..*upper + 1 {
236+
self.bytecode(
237+
ctx,
238+
fun_id,
239+
target,
240+
&label_map,
241+
&code[offs as usize],
242+
&"block".to_string(),
243+
true,
244+
);
144245
}
145-
}
146-
})
246+
})
247+
}
147248
}
148-
});
149-
emitln!(ctx.writer)
249+
}
150250
}
151251

152252
/// Compute the locals in the given function which are borrowed from and which are not
@@ -207,6 +307,7 @@ impl<'a> FunctionGenerator<'a> {
207307
target: &FunctionTarget,
208308
label_map: &BTreeMap<Label, BlockId>,
209309
bc: &Bytecode,
310+
block_name: &str,
210311
has_flow: bool,
211312
) {
212313
use Bytecode::*;
@@ -281,17 +382,19 @@ impl<'a> FunctionGenerator<'a> {
281382
match bc {
282383
Jump(_, l) => {
283384
print_loc();
284-
emitln!(ctx.writer, "$block := {}", get_block(l))
385+
emitln!(ctx.writer, "${} := {}", block_name, get_block(l))
285386
}
286387
Branch(_, if_t, if_f, cond) => {
287388
print_loc();
288389
emitln!(
289390
ctx.writer,
290391
"switch {}\n\
291-
case 0 {{ $block := {} }}\n\
292-
default {{ $block := {} }}",
392+
case 0 {{ ${} := {} }}\n\
393+
default {{ ${} := {} }}",
293394
local(cond),
395+
block_name,
294396
get_block(if_f),
397+
block_name,
295398
get_block(if_t),
296399
)
297400
}

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)