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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
use std::mem;
use debris_common::{CompileContext, OptMode};
use crate::{
llir_nodes::{Branch, Condition, FastStoreFromResult, Node, VariableAccessMut},
minecraft_utils::{ScoreboardComparison, ScoreboardValue},
};
use super::variable_metadata::{Hint, ValueHints};
/// A just-in-time peephole optimizer.
///
/// This means, that the optimizer optimizes nodes as they are being added,
/// while also having access to the previously emitted nodes.
///
/// A single `PeepholeOptimizer` can only be active in a single context
/// (aka a single .mcfunction file)
#[derive(Debug)]
pub struct PeepholeOptimizer {
nodes: Vec<Node>,
opt_mode: OptMode,
/// Information about the possible values of runtime variables
value_hints: ValueHints,
}
impl PeepholeOptimizer {
pub fn from_compile_context(ctx: &CompileContext) -> Self {
PeepholeOptimizer {
nodes: Vec::new(),
opt_mode: ctx.config.opt_mode,
value_hints: Default::default(),
}
}
/// Adds this node to the collection and optimizes it on the fly
pub fn push(&mut self, node: Node) {
if self.opt_mode.disable_optimization() {
self.nodes.push(node);
} else {
self.optimize_and_insert(node);
}
}
pub fn _nodes(&self) -> &[Node] {
&self.nodes
}
/// Drops this instance and returns the wrapped nodes
pub fn take(&mut self) -> Vec<Node> {
mem::take(&mut self.nodes)
}
}
impl PeepholeOptimizer {
/// Optimizes a node and the previous nodes and pushes the new node to the collection of nodes
///
/// This operation may affect already pushed nodes.
fn optimize_and_insert(&mut self, node: Node) {
let node = self.optimize(node);
self.nodes.push(node);
}
fn optimize(&mut self, node: Node) -> Node {
self.value_hints.update_hints(&node, true);
match node {
Node::Branch(branch) => self.optimize_branch(branch),
mut other => {
other.variable_accesses_mut(&mut |access| {
if let VariableAccessMut::Read(value) = access {
if let ScoreboardValue::Scoreboard(id) = value {
if let Hint::Exact(exact_value) = self.value_hints.get_hint(*id) {
*value = ScoreboardValue::Static(exact_value);
}
}
}
});
other
}
}
}
fn optimize_branch(&mut self, branch: Branch) -> Node {
// Match this specific condition which is often emitted for if-statements
if let Branch {
condition:
Condition::Compare {
comparison: ScoreboardComparison::Equal,
lhs: ScoreboardValue::Scoreboard(id),
rhs: ScoreboardValue::Static(1),
},
..
} = &branch
{
if let Some(Node::FastStoreFromResult(FastStoreFromResult {
id: other_id,
command,
..
})) = self.nodes.last()
{
// Match only if the last statement has set the condition of the branch
if id == other_id {
// At last, check if the last statement was an assignment with of a condition
if let Node::Condition(condition) = &**command {
if condition.is_simple() {
// oof, is that really necessary?
let condition = {
match self.nodes.pop().unwrap() {
Node::FastStoreFromResult(FastStoreFromResult {
command,
..
}) => match *command {
Node::Condition(condition) => condition,
_other => unreachable!("Must be a condition"),
},
_other => unreachable!("Must be a FastStoreFromResult"),
}
};
let Branch {
pos_branch,
neg_branch,
..
} = branch;
return Node::Branch(Branch {
condition,
pos_branch,
neg_branch,
});
}
}
}
}
}
Node::Branch(branch)
}
}
impl Extend<Node> for PeepholeOptimizer {
fn extend<T: IntoIterator<Item = Node>>(&mut self, iter: T) {
let iter = iter.into_iter();
let size_hint = iter.size_hint();
self.nodes.reserve(size_hint.1.unwrap_or(size_hint.0));
for value in iter {
self.push(value);
}
}
}