parent
eb0a48a481
commit
14fe23f780
@ -0,0 +1,158 @@
|
|||||||
|
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"
|
||||||
|
) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let instructions = res.0;
|
||||||
|
let mut res = MindustryProgram::new();
|
||||||
|
|
||||||
|
// Remove unneeded `set`s
|
||||||
|
// PERF: could be split into a search for all variable operands, and a removal of all unneeded `set`s
|
||||||
|
for instruction in instructions.iter() {
|
||||||
|
let MindustryOperation::Set(set_name, _) = instruction else {
|
||||||
|
res.push(instruction.clone());
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
if !tmp_regex.is_match(set_name) {
|
||||||
|
res.push(instruction.clone());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this will give false positives for temporary variable names that get re-used somewhere else
|
||||||
|
let mut needed = false;
|
||||||
|
for future_instruction in instructions.iter() {
|
||||||
|
if future_instruction.operands().iter().any(|operand| {
|
||||||
|
if let Operand::Variable(use_name) = operand {
|
||||||
|
if use_name == set_name {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}) {
|
||||||
|
needed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if needed {
|
||||||
|
res.push(instruction.clone());
|
||||||
|
}
|
||||||
|
// else don't push
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - optimize op-jumpif
|
||||||
|
// - optimize jump(1)-label(2)-...instr-label(1) into ...instr-jump(2)
|
||||||
|
// - shorten temporary variable names
|
Loading…
Reference in new issue