charon_lib/transform/duplicate_return.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
//! The MIR uses a unique `return` node, which can be an issue when reconstructing
//! the control-flow.
//!
//! For instance, it often leads to code of the following shape:
//! ```text
//! if b {
//! ...
//! x = 0;
//! }
//! else {
//! ...
//! x = 1;
//! }
//! return x;
//! ```
//!
//! while a more natural reconstruction would be:
//! ```text
//! if b {
//! ...
//! return 0;
//! }
//! else {
//! ...
//! return 1;
//! }
//! ```
use crate::ids::Generator;
use crate::transform::TransformCtx;
use crate::ullbc_ast::*;
use std::collections::HashMap;
use super::ctx::UllbcPass;
pub struct Transform;
impl UllbcPass for Transform {
fn transform_body(&self, _ctx: &mut TransformCtx, b: &mut ExprBody) {
// Find the return block id (there should be one).
let returns: HashMap<BlockId, Span> = b
.body
.iter_indexed()
.filter_map(|(bid, block)| {
if block.statements.is_empty() && block.terminator.content.is_return() {
Some((bid, block.terminator.span))
} else {
None
}
})
.collect();
// Whenever we find a goto the return block, introduce an auxiliary block
// for this (remark: in the end, it makes the return block dangling).
// We do this in two steps.
// First, introduce fresh ids.
assert!(usize::from(b.body.next_id()) == b.body.len());
let mut generator = Generator::new_with_init_value(b.body.len());
let mut new_spans = Vec::new();
b.body.dyn_visit_in_body_mut(|bid: &mut BlockId| {
if let Some(span) = returns.get(bid) {
*bid = generator.fresh_id();
new_spans.push(*span);
}
});
// Then introduce the new blocks
for span in new_spans {
let _ = b.body.push(BlockData {
statements: Vec::new(),
terminator: Terminator {
span,
content: RawTerminator::Return,
},
});
}
}
}