in rhai/src/optimizer.rs [159:383]
fn optimize_stmt_block(
mut statements: StmtBlockContainer,
state: &mut OptimizerState,
preserve_result: bool,
is_internal: bool,
reduce_return: bool,
) -> StmtBlockContainer {
if statements.is_empty() {
return statements;
}
let mut is_dirty = state.is_dirty();
let is_pure = if is_internal {
Stmt::is_internally_pure
} else {
Stmt::is_pure
};
// Flatten blocks
while let Some(n) = statements.iter().position(
|s| matches!(s, Stmt::Block(block, ..) if !block.iter().any(Stmt::is_block_dependent)),
) {
let (first, second) = statements.split_at_mut(n);
let stmt = second[0].take();
let mut stmts = match stmt {
Stmt::Block(block, ..) => block,
stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
};
statements = first
.iter_mut()
.map(mem::take)
.chain(stmts.iter_mut().map(mem::take))
.chain(second.iter_mut().skip(1).map(mem::take))
.collect();
is_dirty = true;
}
// Optimize
loop {
state.clear_dirty();
let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
let orig_propagate_constants = state.propagate_constants;
// Remove everything following control flow breaking statements
let mut dead_code = false;
statements.retain(|stmt| {
if dead_code {
state.set_dirty();
false
} else if stmt.is_control_flow_break() {
dead_code = true;
true
} else {
true
}
});
// Optimize each statement in the block
statements.iter_mut().for_each(|stmt| {
match stmt {
Stmt::Var(x, options, ..) => {
optimize_expr(&mut x.1, state, false);
let value = if options.contains(ASTFlags::CONSTANT) && x.1.is_constant() {
// constant literal
Some(x.1.get_literal_value().unwrap())
} else {
// variable
None
};
state.push_var(x.0.name.clone(), value);
}
// Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result),
}
});
// Remove all pure statements except the last one
let mut index = 0;
let mut first_non_constant = statements
.iter()
.rev()
.enumerate()
.find_map(|(i, stmt)| match stmt {
stmt if !is_pure(stmt) => Some(i),
Stmt::Var(x, ..) if x.1.is_constant() => Some(i),
Stmt::Expr(e) if !e.is_constant() => Some(i),
#[cfg(not(feature = "no_module"))]
Stmt::Import(x, ..) if !x.0.is_constant() => Some(i),
_ => None,
})
.map_or(0, |n| statements.len() - n - 1);
while index < statements.len() {
if preserve_result && index >= statements.len() - 1 {
break;
}
match statements[index] {
ref stmt if is_pure(stmt) && index >= first_non_constant => {
state.set_dirty();
statements.remove(index);
}
ref stmt if stmt.is_pure() => {
state.set_dirty();
if index < first_non_constant {
first_non_constant -= 1;
}
statements.remove(index);
}
_ => index += 1,
}
}
// Remove all pure statements that do not return values at the end of a block.
// We cannot remove anything for non-pure statements due to potential side-effects.
if preserve_result {
loop {
match statements[..] {
// { return; } -> {}
[Stmt::Return(None, options, ..)]
if reduce_return && !options.contains(ASTFlags::BREAK) =>
{
state.set_dirty();
statements.clear();
}
[ref stmt] if !stmt.returns_value() && is_pure(stmt) => {
state.set_dirty();
statements.clear();
}
// { ...; return; } -> { ... }
[.., ref last_stmt, Stmt::Return(None, options, ..)]
if reduce_return
&& !options.contains(ASTFlags::BREAK)
&& !last_stmt.returns_value() =>
{
state.set_dirty();
statements.pop().unwrap();
}
// { ...; return val; } -> { ...; val }
[.., Stmt::Return(ref mut expr, options, pos)]
if reduce_return && !options.contains(ASTFlags::BREAK) =>
{
state.set_dirty();
*statements.last_mut().unwrap() = expr
.as_mut()
.map_or_else(|| Stmt::Noop(pos), |e| Stmt::Expr(mem::take(e)));
}
// { ...; stmt; noop } -> done
[.., ref second_last_stmt, Stmt::Noop(..)]
if second_last_stmt.returns_value() =>
{
break
}
// { ...; stmt_that_returns; pure_non_value_stmt } -> { ...; stmt_that_returns; noop }
// { ...; stmt; pure_non_value_stmt } -> { ...; stmt }
[.., ref second_last_stmt, ref last_stmt]
if !last_stmt.returns_value() && is_pure(last_stmt) =>
{
state.set_dirty();
if second_last_stmt.returns_value() {
*statements.last_mut().unwrap() = Stmt::Noop(last_stmt.position());
} else {
statements.pop().unwrap();
}
}
_ => break,
}
}
} else {
loop {
match statements[..] {
[ref stmt] if is_pure(stmt) => {
state.set_dirty();
statements.clear();
}
// { ...; return; } -> { ... }
[.., Stmt::Return(None, options, ..)]
if reduce_return && !options.contains(ASTFlags::BREAK) =>
{
state.set_dirty();
statements.pop().unwrap();
}
// { ...; return pure_val; } -> { ... }
[.., Stmt::Return(Some(ref expr), options, ..)]
if reduce_return
&& !options.contains(ASTFlags::BREAK)
&& expr.is_pure() =>
{
state.set_dirty();
statements.pop().unwrap();
}
[.., ref last_stmt] if is_pure(last_stmt) => {
state.set_dirty();
statements.pop().unwrap();
}
_ => break,
}
}
}
// Pop the stack and remove all the local constants
state.rewind_var(orig_constants_len);
state.propagate_constants = orig_propagate_constants;
if !state.is_dirty() {
break;
}
is_dirty = true;
}
if is_dirty {
state.set_dirty();
}
statements.shrink_to_fit();
statements
}