parent
8d33422f82
commit
3efaf5b7e2
@ -1,407 +0,0 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Optimizes away unnecessary `sets`
|
|
||||||
pub fn optimize_set_use(program: MindustryProgram) -> MindustryProgram {
|
|
||||||
let mut res = MindustryProgram::new();
|
|
||||||
let instructions = program.0;
|
|
||||||
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
|
|
||||||
|
|
||||||
// Find and replace references to constants
|
|
||||||
// TODO: multiple rounds?
|
|
||||||
for (use_index, instruction) in instructions.iter().enumerate() {
|
|
||||||
let optimizable_operands = instruction
|
|
||||||
.operands()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|operand| match operand {
|
|
||||||
Operand::Variable(name) => {
|
|
||||||
if tmp_regex.is_match(name) {
|
|
||||||
Some(name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
// PERF: check when it would be better to deduplicate operands
|
|
||||||
// .collect::<HashSet<_>>()
|
|
||||||
// .into_iter()
|
|
||||||
.filter_map(|name| {
|
|
||||||
for (index, instr) in instructions[0..use_index].iter().enumerate().rev() {
|
|
||||||
match instr {
|
|
||||||
MindustryOperation::Set(set_name, value) if set_name == name => {
|
|
||||||
return Some((name.clone(), value.clone(), index))
|
|
||||||
}
|
|
||||||
MindustryOperation::Operator(op_name, _op, _lhs, _rhs)
|
|
||||||
if op_name == name =>
|
|
||||||
{
|
|
||||||
// Not optimizable
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
MindustryOperation::JumpLabel(_label) => {
|
|
||||||
// Note: jump labels mark boundaries for constants. For instance:
|
|
||||||
// ```
|
|
||||||
// set __tmp_1 "this is not a constant"
|
|
||||||
// jump_label:
|
|
||||||
// set __tmp_2 "this is a constant"
|
|
||||||
// op add result __tmp_1 __tmp_2
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// gets optimized to:
|
|
||||||
// ```
|
|
||||||
// set __tmp_1 "this is not a constant"
|
|
||||||
// jump_label:
|
|
||||||
// op add result __tmp_1 "this is a constant"
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// A more complex algorithm could be used to check the flow of the program,
|
|
||||||
// but this usecase isn't needed yet.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.filter(|(_name, value, set_index)| {
|
|
||||||
// Don't optimize operands that refer to a mutating variable (either mutable @-variables or instructions that get updated in-between)
|
|
||||||
if let Operand::Variable(assigned_var) = value {
|
|
||||||
if matches!(
|
|
||||||
assigned_var.as_str(),
|
|
||||||
"@this" | "@thisx" | "@thisy" | "@links"
|
|
||||||
) || is_unit_constant(assigned_var.as_str())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if assigned_var.starts_with('@') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for instr_between in &instructions[*set_index..use_index] {
|
|
||||||
match instr_between {
|
|
||||||
MindustryOperation::Set(var_name, _)
|
|
||||||
| MindustryOperation::Operator(var_name, _, _, _) => {
|
|
||||||
if var_name == assigned_var {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if optimizable_operands.len() > 0 {
|
|
||||||
let mut instruction = instruction.clone();
|
|
||||||
for operand in instruction.operands_mut() {
|
|
||||||
if let Operand::Variable(use_name) = operand {
|
|
||||||
if let Some((_, optimized_into, _)) = optimizable_operands
|
|
||||||
.iter()
|
|
||||||
.find(|(set_name, _, _)| set_name == use_name)
|
|
||||||
{
|
|
||||||
*operand = optimized_into.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.push(instruction);
|
|
||||||
} else {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optimize_dead_code(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_unit_constant(name: &str) -> bool {
|
|
||||||
matches!(
|
|
||||||
name,
|
|
||||||
"@stell"
|
|
||||||
| "@locus"
|
|
||||||
| "@precept"
|
|
||||||
| "@vanquish"
|
|
||||||
| "@conquer"
|
|
||||||
| "@merui"
|
|
||||||
| "@cleroi"
|
|
||||||
| "@anthicus"
|
|
||||||
| "@tecta"
|
|
||||||
| "@collaris"
|
|
||||||
| "@elude"
|
|
||||||
| "@avert"
|
|
||||||
| "@obviate"
|
|
||||||
| "@quell"
|
|
||||||
| "@disrupt"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - optimize jump(1)-label(2)-...instr-label(1) into ...instr-jump(2)
|
|
||||||
// - shorten temporary variable names
|
|
||||||
|
|
||||||
/// Tries to merge the condition in an `op` into the `jump` itself
|
|
||||||
pub fn optimize_jump_op(program: MindustryProgram) -> MindustryProgram {
|
|
||||||
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
|
|
||||||
|
|
||||||
let mut res = MindustryProgram::new();
|
|
||||||
let instructions = program.0;
|
|
||||||
|
|
||||||
for (index, instruction) in instructions.iter().enumerate() {
|
|
||||||
match instruction {
|
|
||||||
MindustryOperation::JumpIf(label, operator, lhs, rhs) => {
|
|
||||||
let (truthiness, var_name) = match (
|
|
||||||
operator,
|
|
||||||
replace_constants(lhs.clone()),
|
|
||||||
replace_constants(rhs.clone()),
|
|
||||||
) {
|
|
||||||
(Operator::Neq, Operand::Variable(var_name), Operand::Integer(0))
|
|
||||||
| (Operator::Eq, Operand::Variable(var_name), Operand::Integer(1))
|
|
||||||
| (Operator::Neq, Operand::Integer(0), Operand::Variable(var_name))
|
|
||||||
| (Operator::Eq, Operand::Integer(1), Operand::Variable(var_name)) => {
|
|
||||||
(true, var_name)
|
|
||||||
}
|
|
||||||
(Operator::Eq, Operand::Variable(var_name), Operand::Integer(0))
|
|
||||||
| (Operator::Neq, Operand::Variable(var_name), Operand::Integer(1))
|
|
||||||
| (Operator::Eq, Operand::Integer(0), Operand::Variable(var_name))
|
|
||||||
| (Operator::Neq, Operand::Integer(1), Operand::Variable(var_name)) => {
|
|
||||||
(false, var_name)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !tmp_regex.is_match(&var_name) {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut last_op = None;
|
|
||||||
for prev_instruction in instructions[0..=index].iter().rev().skip(1) {
|
|
||||||
match prev_instruction {
|
|
||||||
MindustryOperation::Operator(name, operator, lhs, rhs)
|
|
||||||
if *name == var_name && is_condition_op(*operator) =>
|
|
||||||
{
|
|
||||||
last_op = Some((*operator, lhs.clone(), rhs.clone()));
|
|
||||||
}
|
|
||||||
MindustryOperation::JumpLabel(_) => break,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(last_op) = last_op else {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
continue
|
|
||||||
};
|
|
||||||
|
|
||||||
let (operator, lhs, rhs) = if truthiness {
|
|
||||||
last_op
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
match last_op.0 {
|
|
||||||
Operator::Gt => Operator::Lte,
|
|
||||||
Operator::Lt => Operator::Gte,
|
|
||||||
Operator::Gte => Operator::Lt,
|
|
||||||
Operator::Lte => Operator::Gt,
|
|
||||||
Operator::Eq => Operator::Neq,
|
|
||||||
Operator::Neq => Operator::Eq,
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
last_op.1,
|
|
||||||
last_op.2,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
res.push(MindustryOperation::JumpIf(
|
|
||||||
label.clone(),
|
|
||||||
operator,
|
|
||||||
lhs,
|
|
||||||
rhs,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return optimize_dead_code(res);
|
|
||||||
|
|
||||||
fn replace_constants(value: Operand) -> Operand {
|
|
||||||
if let Operand::Variable(var) = &value {
|
|
||||||
match var.as_str() {
|
|
||||||
"true" => Operand::Integer(1),
|
|
||||||
"false" | "null" => Operand::Integer(0),
|
|
||||||
_ => value,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_condition_op(op: Operator) -> bool {
|
|
||||||
matches!(
|
|
||||||
op,
|
|
||||||
Operator::Neq
|
|
||||||
| Operator::Eq
|
|
||||||
| Operator::Lt
|
|
||||||
| Operator::Lte
|
|
||||||
| Operator::Gt
|
|
||||||
| Operator::Gte
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to remove unnecessary `jump always` instructions
|
|
||||||
pub fn optimize_jump_always(mut program: MindustryProgram) -> MindustryProgram {
|
|
||||||
let instructions = &mut program.0;
|
|
||||||
|
|
||||||
let mut substitutions = Vec::new();
|
|
||||||
|
|
||||||
// Detect `label`-`jump always` pairs
|
|
||||||
for (index, instruction) in instructions.iter().enumerate() {
|
|
||||||
let MindustryOperation::JumpLabel(label_from) = instruction else {
|
|
||||||
continue
|
|
||||||
};
|
|
||||||
|
|
||||||
for future_instruction in instructions[index..].iter() {
|
|
||||||
match future_instruction {
|
|
||||||
MindustryOperation::JumpLabel(_) => {}
|
|
||||||
MindustryOperation::Jump(label_to) => {
|
|
||||||
substitutions.push((label_from.clone(), label_to.clone()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply transitivity to the pairs
|
|
||||||
let substitutions = substitutions
|
|
||||||
.iter()
|
|
||||||
.map(|(from, to)| {
|
|
||||||
let mut new_to = to;
|
|
||||||
let mut history = vec![to];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut found = false;
|
|
||||||
|
|
||||||
for (other_from, other_to) in substitutions.iter() {
|
|
||||||
if other_from == new_to {
|
|
||||||
// Leave cycles untouched
|
|
||||||
if history.contains(&other_to) {
|
|
||||||
return (from.clone(), to.clone());
|
|
||||||
}
|
|
||||||
new_to = other_to;
|
|
||||||
history.push(other_to);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(from.clone(), to.clone())
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for instruction in instructions.iter_mut() {
|
|
||||||
match instruction {
|
|
||||||
MindustryOperation::Jump(label) => {
|
|
||||||
if let Some((_, new_label)) = substitutions.iter().find(|(from, _)| from == label) {
|
|
||||||
*label = new_label.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MindustryOperation::JumpIf(label, _, _, _) => {
|
|
||||||
if let Some((_, new_label)) = substitutions.iter().find(|(from, _)| from == label) {
|
|
||||||
*label = new_label.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optimize_dead_code(program)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn optimize_dead_code(program: MindustryProgram) -> MindustryProgram {
|
|
||||||
let instructions = program.0;
|
|
||||||
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
|
|
||||||
let label_regex = Regex::new(r"__label_[0-9]+").unwrap();
|
|
||||||
let mut res = MindustryProgram::new();
|
|
||||||
|
|
||||||
let mut needed_vars = HashSet::new();
|
|
||||||
let mut needed_labels = HashSet::new();
|
|
||||||
let mut push_var = |operand: &Operand| match operand {
|
|
||||||
Operand::Variable(name) => {
|
|
||||||
needed_vars.insert(name.clone());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
for instruction in instructions.iter() {
|
|
||||||
match instruction {
|
|
||||||
MindustryOperation::JumpLabel(_) => {}
|
|
||||||
MindustryOperation::Jump(label) => {
|
|
||||||
needed_labels.insert(label.clone());
|
|
||||||
}
|
|
||||||
MindustryOperation::JumpIf(label, _, lhs, rhs) => {
|
|
||||||
needed_labels.insert(label.clone());
|
|
||||||
push_var(lhs);
|
|
||||||
push_var(rhs);
|
|
||||||
}
|
|
||||||
MindustryOperation::Operator(_, _, lhs, rhs) => {
|
|
||||||
push_var(lhs);
|
|
||||||
push_var(rhs);
|
|
||||||
}
|
|
||||||
MindustryOperation::UnaryOperator(_, _, value) => {
|
|
||||||
push_var(value);
|
|
||||||
}
|
|
||||||
MindustryOperation::Set(_, value) => {
|
|
||||||
push_var(value);
|
|
||||||
}
|
|
||||||
MindustryOperation::Generic(_, values) => {
|
|
||||||
values.iter().for_each(&mut push_var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove unneeded `set`s and `op`s
|
|
||||||
for instruction in instructions.iter() {
|
|
||||||
match instruction {
|
|
||||||
MindustryOperation::Set(name, _) | MindustryOperation::Operator(name, _, _, _) => {
|
|
||||||
if tmp_regex.is_match(name) {
|
|
||||||
if needed_vars.contains(name) {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
}
|
|
||||||
// else don't push
|
|
||||||
} else {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MindustryOperation::JumpLabel(label) => {
|
|
||||||
if label_regex.is_match(label) {
|
|
||||||
if needed_labels.contains(label) {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
}
|
|
||||||
// else don't push
|
|
||||||
} else {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
res.push(instruction.clone());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
@ -0,0 +1,129 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Optimizes away unnecessary `sets`
|
||||||
|
pub fn optimize_constant(program: MindustryProgram) -> MindustryProgram {
|
||||||
|
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
|
||||||
|
|
||||||
|
// Returns true if the variable is safe to substitute into an operand
|
||||||
|
let is_safe_variable = |name: &str| -> bool {
|
||||||
|
if matches!(name, "@this" | "@thisx" | "@thisy" | "@links") || is_unit_constant(name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.starts_with('@') {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = replace_if(program, |instructions, instruction, use_index| {
|
||||||
|
let optimizable_operands = instruction
|
||||||
|
.operands()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|operand| match operand {
|
||||||
|
Operand::Variable(name) if tmp_regex.is_match(name) => Some(name),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
// PERF: check when it would be better to deduplicate operands
|
||||||
|
// .collect::<HashSet<_>>()
|
||||||
|
// .into_iter()
|
||||||
|
.filter_map(|name| {
|
||||||
|
lookbehind(instructions, use_index, |instr| {
|
||||||
|
match instr {
|
||||||
|
MindustryOperation::Set(set_name, value) if set_name == name => {
|
||||||
|
Lookaround::Stop((name.clone(), value.clone()))
|
||||||
|
}
|
||||||
|
MindustryOperation::Operator(op_name, _op, _lhs, _rhs)
|
||||||
|
if op_name == name =>
|
||||||
|
{
|
||||||
|
Lookaround::Abort
|
||||||
|
}
|
||||||
|
MindustryOperation::JumpLabel(_label) => {
|
||||||
|
// Note: jump labels mark boundaries for constants. For instance:
|
||||||
|
// ```
|
||||||
|
// set __tmp_1 "this is not a constant"
|
||||||
|
// jump_label:
|
||||||
|
// set __tmp_2 "this is a constant"
|
||||||
|
// op add result __tmp_1 __tmp_2
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// gets optimized to:
|
||||||
|
// ```
|
||||||
|
// set __tmp_1 "this is not a constant"
|
||||||
|
// jump_label:
|
||||||
|
// op add result __tmp_1 "this is a constant"
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// A more complex algorithm could be used to check the flow of the program,
|
||||||
|
// but this usecase isn't needed yet.
|
||||||
|
Lookaround::Abort
|
||||||
|
}
|
||||||
|
_ => Lookaround::Continue,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(index, (name, value))| (name, value, index))
|
||||||
|
})
|
||||||
|
.filter(|(_name, value, set_index)| {
|
||||||
|
// Don't optimize operands that refer to a mutating variable (either mutable @-variables or instructions that get updated in-between)
|
||||||
|
if let Operand::Variable(assigned_var) = value {
|
||||||
|
if !is_safe_variable(&assigned_var) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for instr_between in &instructions[*set_index..use_index] {
|
||||||
|
if instr_between.mutates(&assigned_var) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(name, value, _index)| (name, value))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if optimizable_operands.len() == 0 {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut instruction = instruction.clone();
|
||||||
|
for operand in instruction.operands_mut() {
|
||||||
|
if let Operand::Variable(use_name) = operand {
|
||||||
|
if let Some((_, optimized_into)) = optimizable_operands
|
||||||
|
.iter()
|
||||||
|
.find(|(set_name, _)| set_name == use_name)
|
||||||
|
{
|
||||||
|
*operand = optimized_into.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(vec![instruction])
|
||||||
|
});
|
||||||
|
|
||||||
|
optimize_dead_code(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add serpulo units
|
||||||
|
fn is_unit_constant(name: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
name,
|
||||||
|
"@stell"
|
||||||
|
| "@locus"
|
||||||
|
| "@precept"
|
||||||
|
| "@vanquish"
|
||||||
|
| "@conquer"
|
||||||
|
| "@merui"
|
||||||
|
| "@cleroi"
|
||||||
|
| "@anthicus"
|
||||||
|
| "@tecta"
|
||||||
|
| "@collaris"
|
||||||
|
| "@elude"
|
||||||
|
| "@avert"
|
||||||
|
| "@obviate"
|
||||||
|
| "@quell"
|
||||||
|
| "@disrupt"
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(crate) fn optimize_dead_code(program: MindustryProgram) -> MindustryProgram {
|
||||||
|
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
|
||||||
|
let label_regex = Regex::new(r"__label_[0-9]+").unwrap();
|
||||||
|
|
||||||
|
let mut needed_vars = HashSet::new();
|
||||||
|
let mut needed_labels = HashSet::new();
|
||||||
|
let mut push_var = |operand: &Operand| match operand {
|
||||||
|
Operand::Variable(name) => {
|
||||||
|
needed_vars.insert(name.clone());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
for instruction in program.0.iter() {
|
||||||
|
match instruction {
|
||||||
|
MindustryOperation::JumpLabel(_) => {}
|
||||||
|
MindustryOperation::Jump(label) => {
|
||||||
|
needed_labels.insert(label.clone());
|
||||||
|
}
|
||||||
|
MindustryOperation::JumpIf(label, _, lhs, rhs) => {
|
||||||
|
needed_labels.insert(label.clone());
|
||||||
|
push_var(lhs);
|
||||||
|
push_var(rhs);
|
||||||
|
}
|
||||||
|
MindustryOperation::Operator(_, _, lhs, rhs) => {
|
||||||
|
push_var(lhs);
|
||||||
|
push_var(rhs);
|
||||||
|
}
|
||||||
|
MindustryOperation::UnaryOperator(_, _, value) => {
|
||||||
|
push_var(value);
|
||||||
|
}
|
||||||
|
MindustryOperation::Set(_, value) => {
|
||||||
|
push_var(value);
|
||||||
|
}
|
||||||
|
MindustryOperation::Generic(_, values) => {
|
||||||
|
values.iter().for_each(&mut push_var);
|
||||||
|
}
|
||||||
|
MindustryOperation::GenericMut(_, _, values) => {
|
||||||
|
values.iter().for_each(&mut push_var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove unneeded `set`s and `op`s
|
||||||
|
replace_if(program, |_instructions, instruction, _index| {
|
||||||
|
match instruction {
|
||||||
|
MindustryOperation::Set(name, _) | MindustryOperation::Operator(name, _, _, _) => {
|
||||||
|
if !tmp_regex.is_match(name) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
if needed_vars.contains(name) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(vec![])
|
||||||
|
}
|
||||||
|
MindustryOperation::JumpLabel(label) => {
|
||||||
|
if !label_regex.is_match(label) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
if needed_labels.contains(label) {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(vec![])
|
||||||
|
}
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,196 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Tries to merge the condition in an `op` into the `jump` itself
|
||||||
|
pub fn optimize_jump_op(program: MindustryProgram) -> MindustryProgram {
|
||||||
|
let tmp_regex = Regex::new(r"__tmp_[0-9]+$").unwrap();
|
||||||
|
|
||||||
|
let mut res = MindustryProgram::new();
|
||||||
|
let instructions = program.0;
|
||||||
|
|
||||||
|
for (index, instruction) in instructions.iter().enumerate() {
|
||||||
|
match instruction {
|
||||||
|
MindustryOperation::JumpIf(label, operator, lhs, rhs) => {
|
||||||
|
let (truthiness, var_name) = match (
|
||||||
|
operator,
|
||||||
|
replace_constants(lhs.clone()),
|
||||||
|
replace_constants(rhs.clone()),
|
||||||
|
) {
|
||||||
|
(Operator::Neq, Operand::Variable(var_name), Operand::Integer(0))
|
||||||
|
| (Operator::Eq, Operand::Variable(var_name), Operand::Integer(1))
|
||||||
|
| (Operator::Neq, Operand::Integer(0), Operand::Variable(var_name))
|
||||||
|
| (Operator::Eq, Operand::Integer(1), Operand::Variable(var_name)) => {
|
||||||
|
(true, var_name)
|
||||||
|
}
|
||||||
|
(Operator::Eq, Operand::Variable(var_name), Operand::Integer(0))
|
||||||
|
| (Operator::Neq, Operand::Variable(var_name), Operand::Integer(1))
|
||||||
|
| (Operator::Eq, Operand::Integer(0), Operand::Variable(var_name))
|
||||||
|
| (Operator::Neq, Operand::Integer(1), Operand::Variable(var_name)) => {
|
||||||
|
(false, var_name)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
res.push(instruction.clone());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !tmp_regex.is_match(&var_name) {
|
||||||
|
res.push(instruction.clone());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the last operation defining `var_name`
|
||||||
|
let Some((_, last_op)) =
|
||||||
|
lookbehind(
|
||||||
|
&*instructions,
|
||||||
|
index,
|
||||||
|
|prev_instruction| match prev_instruction {
|
||||||
|
MindustryOperation::Operator(name, operator, lhs, rhs)
|
||||||
|
if *name == var_name && is_condition_op(*operator) =>
|
||||||
|
{
|
||||||
|
Lookaround::Stop((*operator, lhs.clone(), rhs.clone()))
|
||||||
|
}
|
||||||
|
MindustryOperation::JumpLabel(_) => Lookaround::Abort,
|
||||||
|
x if x.mutates(&var_name) => Lookaround::Abort,
|
||||||
|
_ => Lookaround::Continue,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
res.push(instruction.clone());
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (operator, lhs, rhs) = if truthiness {
|
||||||
|
last_op
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
match last_op.0 {
|
||||||
|
Operator::Gt => Operator::Lte,
|
||||||
|
Operator::Lt => Operator::Gte,
|
||||||
|
Operator::Gte => Operator::Lt,
|
||||||
|
Operator::Lte => Operator::Gt,
|
||||||
|
Operator::Eq => Operator::Neq,
|
||||||
|
Operator::Neq => Operator::Eq,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
last_op.1,
|
||||||
|
last_op.2,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
res.push(MindustryOperation::JumpIf(
|
||||||
|
label.clone(),
|
||||||
|
operator,
|
||||||
|
lhs,
|
||||||
|
rhs,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
res.push(instruction.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return optimize_dead_code(res);
|
||||||
|
|
||||||
|
fn replace_constants(value: Operand) -> Operand {
|
||||||
|
if let Operand::Variable(var) = &value {
|
||||||
|
match var.as_str() {
|
||||||
|
"true" => Operand::Integer(1),
|
||||||
|
"false" | "null" => Operand::Integer(0),
|
||||||
|
_ => value,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_condition_op(op: Operator) -> bool {
|
||||||
|
matches!(
|
||||||
|
op,
|
||||||
|
Operator::Neq
|
||||||
|
| Operator::Eq
|
||||||
|
| Operator::Lt
|
||||||
|
| Operator::Lte
|
||||||
|
| Operator::Gt
|
||||||
|
| Operator::Gte
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to remove unnecessary `jump always` instructions
|
||||||
|
pub fn optimize_jump_always(mut program: MindustryProgram) -> MindustryProgram {
|
||||||
|
let instructions = &mut program.0;
|
||||||
|
|
||||||
|
let mut substitutions = Vec::new();
|
||||||
|
|
||||||
|
// Detect `label`-`jump always` pairs
|
||||||
|
for (index, instruction) in instructions.iter().enumerate() {
|
||||||
|
let MindustryOperation::JumpLabel(label_from) = instruction else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((_, label_to)) =
|
||||||
|
lookahead(
|
||||||
|
&*instructions,
|
||||||
|
index,
|
||||||
|
|future_instruction| match future_instruction {
|
||||||
|
MindustryOperation::JumpLabel(_) => Lookaround::Continue,
|
||||||
|
MindustryOperation::Jump(label_to) => Lookaround::Stop(label_to),
|
||||||
|
_ => Lookaround::Abort,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
{
|
||||||
|
substitutions.push((label_from.clone(), label_to.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply transitivity to the pairs
|
||||||
|
let substitutions = substitutions
|
||||||
|
.iter()
|
||||||
|
.map(|(from, to)| {
|
||||||
|
let mut new_to = to;
|
||||||
|
let mut history = vec![to];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut found = false;
|
||||||
|
|
||||||
|
for (other_from, other_to) in substitutions.iter() {
|
||||||
|
if other_from == new_to {
|
||||||
|
// Leave cycles untouched
|
||||||
|
if history.contains(&other_to) {
|
||||||
|
return (from.clone(), to.clone());
|
||||||
|
}
|
||||||
|
new_to = other_to;
|
||||||
|
history.push(other_to);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(from.clone(), to.clone())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for instruction in instructions.iter_mut() {
|
||||||
|
match instruction {
|
||||||
|
MindustryOperation::Jump(label) => {
|
||||||
|
if let Some((_, new_label)) = substitutions.iter().find(|(from, _)| from == label) {
|
||||||
|
*label = new_label.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MindustryOperation::JumpIf(label, _, _, _) => {
|
||||||
|
if let Some((_, new_label)) = substitutions.iter().find(|(from, _)| from == label) {
|
||||||
|
*label = new_label.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optimize_dead_code(program)
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
//! Lookaround helpers for quickly implementing optimizations
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(crate) enum Lookaround<T> {
|
||||||
|
Stop(T),
|
||||||
|
Abort,
|
||||||
|
Continue,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn lookahead<'a, I, F, T>(
|
||||||
|
instructions: I,
|
||||||
|
from: usize,
|
||||||
|
mut callback: F,
|
||||||
|
) -> Option<(usize, T)>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a MindustryOperation>,
|
||||||
|
F: FnMut(&'a MindustryOperation) -> Lookaround<T>,
|
||||||
|
{
|
||||||
|
for (index, instruction) in instructions.into_iter().enumerate().skip(from) {
|
||||||
|
match callback(instruction) {
|
||||||
|
Lookaround::Stop(value) => {
|
||||||
|
return Some((index, value));
|
||||||
|
}
|
||||||
|
Lookaround::Abort => return None,
|
||||||
|
Lookaround::Continue => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn lookbehind<'a, I, F, T>(
|
||||||
|
instructions: I,
|
||||||
|
from: usize,
|
||||||
|
mut callback: F,
|
||||||
|
) -> Option<(usize, T)>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a MindustryOperation>,
|
||||||
|
<I as IntoIterator>::IntoIter: ExactSizeIterator + DoubleEndedIterator,
|
||||||
|
F: FnMut(&'a MindustryOperation) -> Lookaround<T>,
|
||||||
|
{
|
||||||
|
for (index, instruction) in instructions.into_iter().enumerate().take(from).rev() {
|
||||||
|
match callback(instruction) {
|
||||||
|
Lookaround::Stop(value) => {
|
||||||
|
return Some((index, value));
|
||||||
|
}
|
||||||
|
Lookaround::Abort => return None,
|
||||||
|
Lookaround::Continue => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
mod constant;
|
||||||
|
pub use constant::*;
|
||||||
|
|
||||||
|
mod jump;
|
||||||
|
pub use jump::*;
|
||||||
|
|
||||||
|
mod dead;
|
||||||
|
pub(crate) use dead::optimize_dead_code;
|
||||||
|
|
||||||
|
mod lookaround;
|
||||||
|
pub(crate) use lookaround::*;
|
||||||
|
|
||||||
|
mod replace;
|
||||||
|
pub(crate) use replace::*;
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - optimize jump(1)-label(2)-...instr-label(1) into ...instr-jump(2)
|
||||||
|
// - shorten temporary variable names
|
||||||
|
// - jump normalization
|
||||||
|
// - variable normalization
|
@ -0,0 +1,22 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) fn replace_if<F>(program: MindustryProgram, callback: F) -> MindustryProgram
|
||||||
|
where
|
||||||
|
F: for<'c> Fn(&'c Vec<MindustryOperation>, &'c MindustryOperation, usize) -> Option<Vec<MindustryOperation>>
|
||||||
|
{
|
||||||
|
let mut res = MindustryProgram::new();
|
||||||
|
|
||||||
|
for (index, instruction) in program.0.iter().enumerate() {
|
||||||
|
match callback(&program.0, instruction, index) {
|
||||||
|
Some(mut to_replace) => {
|
||||||
|
res.0.append(&mut to_replace);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
res.push(instruction.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
Loading…
Reference in new issue