From 3efaf5b7e26fa635df1f4121a6a7880e2cef1bae Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Sun, 1 Oct 2023 12:06:13 +0200 Subject: [PATCH] :sparkles: Clean optimizations up --- Cargo.lock | 23 ++ Cargo.toml | 3 + src/common.rs | 29 +- src/compile/mod.rs | 65 +++- src/compile/optimize.rs | 407 ------------------- src/compile/optimize/constant.rs | 129 ++++++ src/compile/optimize/dead.rs | 74 ++++ src/compile/optimize/jump.rs | 196 ++++++++++ src/compile/optimize/lookaround.rs | 54 +++ src/compile/optimize/mod.rs | 24 ++ src/compile/optimize/replace.rs | 22 ++ src/main.rs | 6 +- src/parse/ast.rs | 10 +- src/parse/mod.rs | 10 + src/parse/tokenize.rs | 10 +- tests/examples.rs | 10 +- tests/examples/procedural-waves.0.mlog | 519 ++++++++++++------------- tests/examples/procedural-waves.1.mlog | 76 ++-- 18 files changed, 930 insertions(+), 737 deletions(-) delete mode 100644 src/compile/optimize.rs create mode 100644 src/compile/optimize/constant.rs create mode 100644 src/compile/optimize/dead.rs create mode 100644 src/compile/optimize/jump.rs create mode 100644 src/compile/optimize/lookaround.rs create mode 100644 src/compile/optimize/mod.rs create mode 100644 src/compile/optimize/replace.rs diff --git a/Cargo.lock b/Cargo.lock index aaf75f5..c62c8ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,15 +15,32 @@ dependencies = [ name = "basic-to-mindustry" version = "0.1.0" dependencies = [ + "pretty_assertions", "regex", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "regex" version = "1.9.5" @@ -52,3 +69,9 @@ name = "regex-syntax" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index 9a6cbeb..c881a25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,6 @@ edition = "2021" [dependencies] regex = "1.9.5" + +[dev-dependencies] +pretty_assertions = "1.4.0" diff --git a/src/common.rs b/src/common.rs index 0c8b9ed..e7434cf 100644 --- a/src/common.rs +++ b/src/common.rs @@ -101,26 +101,33 @@ pub(crate) fn format_unary_operator(operator: UnaryOperator) -> &'static str { #[derive(Clone, Debug)] pub struct Config { - pub builtin_functions: HashMap, + pub builtin_functions: HashMap, } impl Default for Config { fn default() -> Self { macro_rules! builtin_function { - ( $name:expr, $target_name:expr, $n_args:expr ) => { - (String::from($name), (String::from($target_name), $n_args)) + ( $name:expr, $target_name:expr, $mutating:expr, $n_args:expr ) => { + ( + String::from($name), + (String::from($target_name), $mutating, $n_args), + ) }; } Self { builtin_functions: HashMap::from([ - builtin_function!("print_flush", "printflush", 1), - builtin_function!("print_message_mission", "message mission", 0), - builtin_function!("read", "read", 3), - builtin_function!("write", "write", 3), - builtin_function!("wait", "wait", 1), - builtin_function!("set_flag", "setflag", 2), - builtin_function!("get_flag", "getflag", 2), - builtin_function!("spawn", "spawn", 6), + builtin_function!("print_flush", "printflush", false, 1), + // TODO: write a special case for message + builtin_function!("print_message_mission", "message mission", false, 0), + builtin_function!("read", "read", true, 3), + // TODO: don't use a generic operation here + builtin_function!("write", "write", false, 3), + builtin_function!("wait", "wait", false, 1), + // TODO: don't use a generic operation here either + builtin_function!("set_flag", "setflag", false, 2), + builtin_function!("get_flag", "getflag", true, 2), + // TODO: same thing + builtin_function!("spawn", "spawn", false, 6), ]), } } diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 174bc3c..20971e9 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -35,8 +35,18 @@ pub enum MindustryOperation { JumpIf(String, Operator, Operand, Operand), Operator(String, Operator, Operand, Operand), UnaryOperator(String, UnaryOperator, Operand), + // TODO: add RandOperator Set(String, Operand), + /// A generic operation, with the following invariants: + /// - all of the operands are read-only + /// - there is no external dependency to other variables + /// - no external variable is modified Generic(String, Vec), + /// A generic, mutating operation `(name, out_name, operands)`, with the following invariants: + /// - all of the operands are read-only + /// - there is no external dependency to other variables, except `out_name` + /// - only `out_name` is modified + GenericMut(String, String, Vec), } impl MindustryOperation { @@ -48,6 +58,9 @@ impl MindustryOperation { Self::Generic(_name, operands) => { operands.iter().collect::>().into_boxed_slice() } + Self::GenericMut(_name, _out_name, operands) => { + operands.iter().collect::>().into_boxed_slice() + } _ => Box::new([]), } } @@ -58,9 +71,24 @@ impl MindustryOperation { Self::Operator(_target, _operator, lhs, rhs) => vec![lhs, rhs], Self::Set(_target, value) => vec![value], Self::Generic(_name, operands) => operands.iter_mut().collect::>(), + Self::GenericMut(_name, _out_name, operands) => operands.iter_mut().collect::>(), _ => vec![], } } + + fn mutates(&self, var_name: &str) -> bool { + match self { + MindustryOperation::JumpLabel(_) + | MindustryOperation::Jump(_) + | MindustryOperation::JumpIf(_, _, _, _) + | MindustryOperation::Generic(_, _) => false, + + MindustryOperation::Operator(out_name, _, _, _) + | MindustryOperation::UnaryOperator(out_name, _, _) + | MindustryOperation::Set(out_name, _) + | MindustryOperation::GenericMut(_, out_name, _) => out_name == var_name, + } + } } #[derive(Debug, Clone)] @@ -244,10 +272,17 @@ pub fn translate_ast( } } Instr::CallBuiltin(name, arguments) => { - let argument_names = (0..arguments.len()) + let Some((target_name, mutating, _)) = config.builtin_functions.get(name) else { + unreachable!("CallBuilting constructed with unknown function name"); + }; + let mutating = *mutating; + + let first_index = mutating as usize; + + let argument_names = (first_index..arguments.len()) .map(|_| namer.temporary()) .collect::>(); - for (i, argument) in arguments.iter().enumerate() { + for (i, argument) in arguments.iter().skip(first_index).enumerate() { res.append(&mut translate_expression( argument, namer, @@ -255,7 +290,23 @@ pub fn translate_ast( )); } - if let Some((target_name, _)) = config.builtin_functions.get(name) { + if mutating { + let BasicAstExpression::Variable(out_name) = arguments[0].clone() else { + unreachable!( + "First argument to {} isn't a variable, got {:?}", + name, arguments[0] + ); + }; + + res.push(MindustryOperation::GenericMut( + target_name.clone(), + out_name, + argument_names + .into_iter() + .map(|name| Operand::Variable(name)) + .collect(), + )); + } else { res.push(MindustryOperation::Generic( target_name.clone(), argument_names @@ -354,6 +405,14 @@ impl std::fmt::Display for MindustryProgram { } write!(f, "\n")?; } + MindustryOperation::GenericMut(name, out_name, operands) => { + write!(f, "{}", name)?; + write!(f, " {}", out_name)?; + for operand in operands { + write!(f, " {}", operand)?; + } + write!(f, "\n")?; + } } } diff --git a/src/compile/optimize.rs b/src/compile/optimize.rs deleted file mode 100644 index 87aebd4..0000000 --- a/src/compile/optimize.rs +++ /dev/null @@ -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::>() - // .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::>(); - - 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::>(); - - 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 -} diff --git a/src/compile/optimize/constant.rs b/src/compile/optimize/constant.rs new file mode 100644 index 0000000..7ef12a3 --- /dev/null +++ b/src/compile/optimize/constant.rs @@ -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::>() + // .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::>(); + + 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" + ) +} diff --git a/src/compile/optimize/dead.rs b/src/compile/optimize/dead.rs new file mode 100644 index 0000000..2ae558c --- /dev/null +++ b/src/compile/optimize/dead.rs @@ -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 + } + }) +} diff --git a/src/compile/optimize/jump.rs b/src/compile/optimize/jump.rs new file mode 100644 index 0000000..aac12bc --- /dev/null +++ b/src/compile/optimize/jump.rs @@ -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::>(); + + 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) +} diff --git a/src/compile/optimize/lookaround.rs b/src/compile/optimize/lookaround.rs new file mode 100644 index 0000000..13367e6 --- /dev/null +++ b/src/compile/optimize/lookaround.rs @@ -0,0 +1,54 @@ +//! Lookaround helpers for quickly implementing optimizations + +use super::*; + +pub(crate) enum Lookaround { + 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, + F: FnMut(&'a MindustryOperation) -> Lookaround, +{ + 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, + ::IntoIter: ExactSizeIterator + DoubleEndedIterator, + F: FnMut(&'a MindustryOperation) -> Lookaround, +{ + 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 +} diff --git a/src/compile/optimize/mod.rs b/src/compile/optimize/mod.rs new file mode 100644 index 0000000..4e2498e --- /dev/null +++ b/src/compile/optimize/mod.rs @@ -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 diff --git a/src/compile/optimize/replace.rs b/src/compile/optimize/replace.rs new file mode 100644 index 0000000..6ee53fe --- /dev/null +++ b/src/compile/optimize/replace.rs @@ -0,0 +1,22 @@ +use super::*; + + +pub(crate) fn replace_if(program: MindustryProgram, callback: F) -> MindustryProgram +where + F: for<'c> Fn(&'c Vec, &'c MindustryOperation, usize) -> Option> +{ + 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 +} diff --git a/src/main.rs b/src/main.rs index 0cbfdd4..eab7600 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use basic_to_mindustry::{ common::Config, - compile::{optimize_jump_always, optimize_jump_op, optimize_set_use, translate_ast, Namer}, + compile::{optimize_constant, optimize_jump_always, optimize_jump_op, translate_ast, Namer}, parse::{build_ast, tokenize}, }; @@ -13,9 +13,9 @@ fn main() { let parsed = build_ast(&tokens, &config).unwrap(); let transformed = translate_ast(&parsed, &mut Namer::default(), &config); - // println!("{}", transformed); + println!("{}", transformed); - let optimized = optimize_set_use(transformed); + let optimized = optimize_constant(transformed); let optimized = optimize_jump_op(optimized); let optimized = optimize_jump_always(optimized); diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 8d166ae..4755f5f 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -189,7 +189,15 @@ pub fn build_ast(tokens: &[BasicToken], config: &Config) -> Result Result, ParseError> { macro_rules! match_token { diff --git a/tests/examples.rs b/tests/examples.rs index b75021e..a662060 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use basic_to_mindustry::common::Config; -use basic_to_mindustry::compile::{optimize_set_use, translate_ast, optimize_jump_op, optimize_jump_always}; +use basic_to_mindustry::compile::{optimize_constant, translate_ast, optimize_jump_op, optimize_jump_always}; use basic_to_mindustry::parse::{build_ast, tokenize}; fn read_basic_examples() -> impl Iterator { @@ -39,7 +39,7 @@ fn test_examples() { panic!("Error parsing {:?}: {:?}", file_name, e); }); let translated = translate_ast(&parsed, &mut Default::default(), &config); - let optimized = optimize_set_use(translated); + let optimized = optimize_constant(translated); let _ = optimized; } @@ -67,14 +67,14 @@ fn test_examples_opt() { panic!("Couldn't open tests/examples/{}.0.mlog: {:?}", program_name, e); }); - assert_eq!(opt_0.trim(), format!("{}", translated).trim()); + pretty_assertions::assert_eq!(opt_0.trim(), format!("{}", translated).trim()); - let optimized = optimize_jump_always(optimize_jump_op(optimize_set_use(translated))); + let optimized = optimize_jump_always(optimize_jump_op(optimize_constant(translated))); let opt_1 = std::fs::read_to_string(format!("tests/examples/{}.1.mlog", program_name)).unwrap_or_else(|e| { panic!("Couldn't open tests/examples/{}.1.mlog: {:?}", program_name, e); }); - assert_eq!(opt_1.trim(), format!("{}", optimized).trim()); + pretty_assertions::assert_eq!(opt_1.trim(), format!("{}", optimized).trim()); } } diff --git a/tests/examples/procedural-waves.0.mlog b/tests/examples/procedural-waves.0.mlog index d2f04be..10cd1fd 100644 --- a/tests/examples/procedural-waves.0.mlog +++ b/tests/examples/procedural-waves.0.mlog @@ -85,140 +85,139 @@ set main__tmp_43 wave set main__tmp_44 cell1 set main__tmp_45 1 write main__tmp_43 main__tmp_44 main__tmp_45 -set main__tmp_46 timeout -set main__tmp_47 cell1 -set main__tmp_48 0 -read main__tmp_46 main__tmp_47 main__tmp_48 -set main__tmp_49 @time -set main__tmp_51 timeout -set main__tmp_52 1000 -op mul main__tmp_50 main__tmp_51 main__tmp_52 -op add timeout main__tmp_49 main__tmp_50 +set main__tmp_46 cell1 +set main__tmp_47 0 +read timeout main__tmp_46 main__tmp_47 +set main__tmp_48 @time +set main__tmp_50 timeout +set main__tmp_51 1000 +op mul main__tmp_49 main__tmp_50 main__tmp_51 +op add timeout main__tmp_48 main__tmp_49 main_wait: -set main__tmp_53 timeout -set main__tmp_54 @time -op sub remaining main__tmp_53 main__tmp_54 -set main__tmp_56 remaining -set main__tmp_57 0 -op lessThanEq main__tmp_55 main__tmp_56 main__tmp_57 -jump main__label_1_endif notEqual main__tmp_55 true +set main__tmp_52 timeout +set main__tmp_53 @time +op sub remaining main__tmp_52 main__tmp_53 +set main__tmp_55 remaining +set main__tmp_56 0 +op lessThanEq main__tmp_54 main__tmp_55 main__tmp_56 +jump main__label_1_endif notEqual main__tmp_54 true jump main always 0 0 main__label_1_endif: -set main__tmp_58 "[yellow]Wave " +set main__tmp_57 "[yellow]Wave " +print main__tmp_57 +set main__tmp_58 wave print main__tmp_58 -set main__tmp_59 wave +set main__tmp_59 "[white] - " print main__tmp_59 -set main__tmp_60 "[white] - " +set main__tmp_60 "Next wave: " print main__tmp_60 -set main__tmp_61 "Next wave: " +set main__tmp_62 remaining +set main__tmp_63 1000 +op div main__tmp_61 main__tmp_62 main__tmp_63 +op ceil main__tmp_61 main__tmp_61 0 print main__tmp_61 -set main__tmp_63 remaining -set main__tmp_64 1000 -op div main__tmp_62 main__tmp_63 main__tmp_64 -op ceil main__tmp_62 main__tmp_62 0 -print main__tmp_62 -set main__tmp_65 " s" -print main__tmp_65 +set main__tmp_64 " s" +print main__tmp_64 message mission -set main__tmp_66 0.5 -wait main__tmp_66 +set main__tmp_65 0.5 +wait main__tmp_65 jump main_wait always 0 0 spawn_tank: set spawned 0 spawn_tank_loop: set roll progression op rand roll roll 0 -set main__tmp_68 roll -set main__tmp_69 3 -op greaterThanEq main__tmp_67 main__tmp_68 main__tmp_69 -jump main__label_2_else notEqual main__tmp_67 true -set main__tmp_71 roll -set main__tmp_72 4 -op greaterThanEq main__tmp_70 main__tmp_71 main__tmp_72 -jump main__label_4_else notEqual main__tmp_70 true -set main__tmp_73 @conquer -set main__tmp_74 spawnx -set main__tmp_75 spawny -set main__tmp_76 0 -set main__tmp_77 @crux -set main__tmp_78 _ -spawn main__tmp_73 main__tmp_74 main__tmp_75 main__tmp_76 main__tmp_77 main__tmp_78 -set main__tmp_79 spawnx -set main__tmp_80 5.75 -op sub spawnx main__tmp_79 main__tmp_80 +set main__tmp_67 roll +set main__tmp_68 3 +op greaterThanEq main__tmp_66 main__tmp_67 main__tmp_68 +jump main__label_2_else notEqual main__tmp_66 true +set main__tmp_70 roll +set main__tmp_71 4 +op greaterThanEq main__tmp_69 main__tmp_70 main__tmp_71 +jump main__label_4_else notEqual main__tmp_69 true +set main__tmp_72 @conquer +set main__tmp_73 spawnx +set main__tmp_74 spawny +set main__tmp_75 0 +set main__tmp_76 @crux +set main__tmp_77 _ +spawn main__tmp_72 main__tmp_73 main__tmp_74 main__tmp_75 main__tmp_76 main__tmp_77 +set main__tmp_78 spawnx +set main__tmp_79 5.75 +op sub spawnx main__tmp_78 main__tmp_79 jump main__label_5_endif always 0 0 main__label_4_else: -set main__tmp_81 @vanquish -set main__tmp_82 spawnx -set main__tmp_83 spawny -set main__tmp_84 0 -set main__tmp_85 @crux -set main__tmp_86 _ -spawn main__tmp_81 main__tmp_82 main__tmp_83 main__tmp_84 main__tmp_85 main__tmp_86 -set main__tmp_87 spawnx -set main__tmp_88 3.5 -op sub spawnx main__tmp_87 main__tmp_88 +set main__tmp_80 @vanquish +set main__tmp_81 spawnx +set main__tmp_82 spawny +set main__tmp_83 0 +set main__tmp_84 @crux +set main__tmp_85 _ +spawn main__tmp_80 main__tmp_81 main__tmp_82 main__tmp_83 main__tmp_84 main__tmp_85 +set main__tmp_86 spawnx +set main__tmp_87 3.5 +op sub spawnx main__tmp_86 main__tmp_87 main__label_5_endif: jump main__label_3_endif always 0 0 main__label_2_else: -set main__tmp_90 roll -set main__tmp_91 2 -op greaterThanEq main__tmp_89 main__tmp_90 main__tmp_91 -jump main__label_6_else notEqual main__tmp_89 true -set main__tmp_92 @precept -set main__tmp_93 spawnx -set main__tmp_94 spawny -set main__tmp_95 0 -set main__tmp_96 @crux -set main__tmp_97 _ -spawn main__tmp_92 main__tmp_93 main__tmp_94 main__tmp_95 main__tmp_96 main__tmp_97 -set main__tmp_98 spawnx -set main__tmp_99 3.25 -op sub spawnx main__tmp_98 main__tmp_99 +set main__tmp_89 roll +set main__tmp_90 2 +op greaterThanEq main__tmp_88 main__tmp_89 main__tmp_90 +jump main__label_6_else notEqual main__tmp_88 true +set main__tmp_91 @precept +set main__tmp_92 spawnx +set main__tmp_93 spawny +set main__tmp_94 0 +set main__tmp_95 @crux +set main__tmp_96 _ +spawn main__tmp_91 main__tmp_92 main__tmp_93 main__tmp_94 main__tmp_95 main__tmp_96 +set main__tmp_97 spawnx +set main__tmp_98 3.25 +op sub spawnx main__tmp_97 main__tmp_98 jump main__label_7_endif always 0 0 main__label_6_else: -set main__tmp_101 roll -set main__tmp_102 1 -op greaterThanEq main__tmp_100 main__tmp_101 main__tmp_102 -jump main__label_8_else notEqual main__tmp_100 true -set main__tmp_103 @locus -set main__tmp_104 spawnx -set main__tmp_105 spawny -set main__tmp_106 0 -set main__tmp_107 @crux -set main__tmp_108 _ -spawn main__tmp_103 main__tmp_104 main__tmp_105 main__tmp_106 main__tmp_107 main__tmp_108 -set main__tmp_109 spawnx -set main__tmp_110 1 -op sub spawnx main__tmp_109 main__tmp_110 +set main__tmp_100 roll +set main__tmp_101 1 +op greaterThanEq main__tmp_99 main__tmp_100 main__tmp_101 +jump main__label_8_else notEqual main__tmp_99 true +set main__tmp_102 @locus +set main__tmp_103 spawnx +set main__tmp_104 spawny +set main__tmp_105 0 +set main__tmp_106 @crux +set main__tmp_107 _ +spawn main__tmp_102 main__tmp_103 main__tmp_104 main__tmp_105 main__tmp_106 main__tmp_107 +set main__tmp_108 spawnx +set main__tmp_109 1 +op sub spawnx main__tmp_108 main__tmp_109 jump main__label_9_endif always 0 0 main__label_8_else: -set main__tmp_111 @stell -set main__tmp_112 spawnx -set main__tmp_113 spawny -set main__tmp_114 0 -set main__tmp_115 @crux -set main__tmp_116 _ -spawn main__tmp_111 main__tmp_112 main__tmp_113 main__tmp_114 main__tmp_115 main__tmp_116 -set main__tmp_117 spawnx -set main__tmp_118 1 -op sub spawnx main__tmp_117 main__tmp_118 +set main__tmp_110 @stell +set main__tmp_111 spawnx +set main__tmp_112 spawny +set main__tmp_113 0 +set main__tmp_114 @crux +set main__tmp_115 _ +spawn main__tmp_110 main__tmp_111 main__tmp_112 main__tmp_113 main__tmp_114 main__tmp_115 +set main__tmp_116 spawnx +set main__tmp_117 1 +op sub spawnx main__tmp_116 main__tmp_117 main__label_9_endif: main__label_7_endif: main__label_3_endif: -set main__tmp_120 spawnx -set main__tmp_121 10 -op lessThan main__tmp_119 main__tmp_120 main__tmp_121 -jump main__label_10_endif notEqual main__tmp_119 true +set main__tmp_119 spawnx +set main__tmp_120 10 +op lessThan main__tmp_118 main__tmp_119 main__tmp_120 +jump main__label_10_endif notEqual main__tmp_118 true set spawnx 10 main__label_10_endif: -set main__tmp_122 spawned -set main__tmp_123 1 -op add spawned main__tmp_122 main__tmp_123 -set main__tmp_125 spawned -set main__tmp_126 tank_units -op lessThan main__tmp_124 main__tmp_125 main__tmp_126 -jump main__label_11_endif notEqual main__tmp_124 true +set main__tmp_121 spawned +set main__tmp_122 1 +op add spawned main__tmp_121 main__tmp_122 +set main__tmp_124 spawned +set main__tmp_125 tank_units +op lessThan main__tmp_123 main__tmp_124 main__tmp_125 +jump main__label_11_endif notEqual main__tmp_123 true jump spawn_tank_loop always 0 0 main__label_11_endif: jump spawn_tank_end always 0 0 @@ -227,97 +226,97 @@ set spawned 0 spawn_mech_loop: set roll progression op rand roll roll 0 -set main__tmp_128 roll -set main__tmp_129 3 -op greaterThanEq main__tmp_127 main__tmp_128 main__tmp_129 -jump main__label_12_else notEqual main__tmp_127 true -set main__tmp_131 roll -set main__tmp_132 4 -op greaterThanEq main__tmp_130 main__tmp_131 main__tmp_132 -jump main__label_14_else notEqual main__tmp_130 true -set main__tmp_133 @collaris -set main__tmp_134 spawnx -set main__tmp_135 spawny -set main__tmp_136 0 -set main__tmp_137 @crux -set main__tmp_138 _ -spawn main__tmp_133 main__tmp_134 main__tmp_135 main__tmp_136 main__tmp_137 main__tmp_138 -set main__tmp_139 spawnx -set main__tmp_140 5.5 -op sub spawnx main__tmp_139 main__tmp_140 +set main__tmp_127 roll +set main__tmp_128 3 +op greaterThanEq main__tmp_126 main__tmp_127 main__tmp_128 +jump main__label_12_else notEqual main__tmp_126 true +set main__tmp_130 roll +set main__tmp_131 4 +op greaterThanEq main__tmp_129 main__tmp_130 main__tmp_131 +jump main__label_14_else notEqual main__tmp_129 true +set main__tmp_132 @collaris +set main__tmp_133 spawnx +set main__tmp_134 spawny +set main__tmp_135 0 +set main__tmp_136 @crux +set main__tmp_137 _ +spawn main__tmp_132 main__tmp_133 main__tmp_134 main__tmp_135 main__tmp_136 main__tmp_137 +set main__tmp_138 spawnx +set main__tmp_139 5.5 +op sub spawnx main__tmp_138 main__tmp_139 jump main__label_15_endif always 0 0 main__label_14_else: -set main__tmp_141 @tecta -set main__tmp_142 spawnx -set main__tmp_143 spawny -set main__tmp_144 0 -set main__tmp_145 @crux -set main__tmp_146 _ -spawn main__tmp_141 main__tmp_142 main__tmp_143 main__tmp_144 main__tmp_145 main__tmp_146 -set main__tmp_147 spawnx -set main__tmp_148 2.87 -op sub spawnx main__tmp_147 main__tmp_148 +set main__tmp_140 @tecta +set main__tmp_141 spawnx +set main__tmp_142 spawny +set main__tmp_143 0 +set main__tmp_144 @crux +set main__tmp_145 _ +spawn main__tmp_140 main__tmp_141 main__tmp_142 main__tmp_143 main__tmp_144 main__tmp_145 +set main__tmp_146 spawnx +set main__tmp_147 2.87 +op sub spawnx main__tmp_146 main__tmp_147 main__label_15_endif: jump main__label_13_endif always 0 0 main__label_12_else: -set main__tmp_150 roll -set main__tmp_151 2 -op greaterThanEq main__tmp_149 main__tmp_150 main__tmp_151 -jump main__label_16_else notEqual main__tmp_149 true -set main__tmp_152 @anthicus -set main__tmp_153 spawnx -set main__tmp_154 spawny -set main__tmp_155 0 -set main__tmp_156 @crux -set main__tmp_157 _ -spawn main__tmp_152 main__tmp_153 main__tmp_154 main__tmp_155 main__tmp_156 main__tmp_157 -set main__tmp_158 spawnx -set main__tmp_159 2.62 -op sub spawnx main__tmp_158 main__tmp_159 +set main__tmp_149 roll +set main__tmp_150 2 +op greaterThanEq main__tmp_148 main__tmp_149 main__tmp_150 +jump main__label_16_else notEqual main__tmp_148 true +set main__tmp_151 @anthicus +set main__tmp_152 spawnx +set main__tmp_153 spawny +set main__tmp_154 0 +set main__tmp_155 @crux +set main__tmp_156 _ +spawn main__tmp_151 main__tmp_152 main__tmp_153 main__tmp_154 main__tmp_155 main__tmp_156 +set main__tmp_157 spawnx +set main__tmp_158 2.62 +op sub spawnx main__tmp_157 main__tmp_158 jump main__label_17_endif always 0 0 main__label_16_else: -set main__tmp_161 roll -set main__tmp_162 1 -op greaterThanEq main__tmp_160 main__tmp_161 main__tmp_162 -jump main__label_18_else notEqual main__tmp_160 true -set main__tmp_163 @cleroi -set main__tmp_164 spawnx -set main__tmp_165 spawny -set main__tmp_166 0 -set main__tmp_167 @crux -set main__tmp_168 _ -spawn main__tmp_163 main__tmp_164 main__tmp_165 main__tmp_166 main__tmp_167 main__tmp_168 -set main__tmp_169 spawnx -set main__tmp_170 1 -op sub spawnx main__tmp_169 main__tmp_170 +set main__tmp_160 roll +set main__tmp_161 1 +op greaterThanEq main__tmp_159 main__tmp_160 main__tmp_161 +jump main__label_18_else notEqual main__tmp_159 true +set main__tmp_162 @cleroi +set main__tmp_163 spawnx +set main__tmp_164 spawny +set main__tmp_165 0 +set main__tmp_166 @crux +set main__tmp_167 _ +spawn main__tmp_162 main__tmp_163 main__tmp_164 main__tmp_165 main__tmp_166 main__tmp_167 +set main__tmp_168 spawnx +set main__tmp_169 1 +op sub spawnx main__tmp_168 main__tmp_169 jump main__label_19_endif always 0 0 main__label_18_else: -set main__tmp_171 @merui -set main__tmp_172 spawnx -set main__tmp_173 spawny -set main__tmp_174 0 -set main__tmp_175 @crux -set main__tmp_176 _ -spawn main__tmp_171 main__tmp_172 main__tmp_173 main__tmp_174 main__tmp_175 main__tmp_176 -set main__tmp_177 spawnx -set main__tmp_178 1 -op sub spawnx main__tmp_177 main__tmp_178 +set main__tmp_170 @merui +set main__tmp_171 spawnx +set main__tmp_172 spawny +set main__tmp_173 0 +set main__tmp_174 @crux +set main__tmp_175 _ +spawn main__tmp_170 main__tmp_171 main__tmp_172 main__tmp_173 main__tmp_174 main__tmp_175 +set main__tmp_176 spawnx +set main__tmp_177 1 +op sub spawnx main__tmp_176 main__tmp_177 main__label_19_endif: main__label_17_endif: main__label_13_endif: -set main__tmp_180 spawnx -set main__tmp_181 10 -op lessThan main__tmp_179 main__tmp_180 main__tmp_181 -jump main__label_20_endif notEqual main__tmp_179 true +set main__tmp_179 spawnx +set main__tmp_180 10 +op lessThan main__tmp_178 main__tmp_179 main__tmp_180 +jump main__label_20_endif notEqual main__tmp_178 true set spawnx 10 main__label_20_endif: -set main__tmp_182 spawned -set main__tmp_183 1 -op add spawned main__tmp_182 main__tmp_183 -set main__tmp_185 spawned -set main__tmp_186 mech_units -op lessThan main__tmp_184 main__tmp_185 main__tmp_186 -jump main__label_21_endif notEqual main__tmp_184 true +set main__tmp_181 spawned +set main__tmp_182 1 +op add spawned main__tmp_181 main__tmp_182 +set main__tmp_184 spawned +set main__tmp_185 mech_units +op lessThan main__tmp_183 main__tmp_184 main__tmp_185 +jump main__label_21_endif notEqual main__tmp_183 true jump spawn_mech_loop always 0 0 main__label_21_endif: jump spawn_mech_end always 0 0 @@ -326,97 +325,97 @@ set spawned 0 spawn_air_loop: set roll progression op rand roll roll 0 -set main__tmp_188 roll -set main__tmp_189 3 -op greaterThanEq main__tmp_187 main__tmp_188 main__tmp_189 -jump main__label_22_else notEqual main__tmp_187 true -set main__tmp_191 roll -set main__tmp_192 4 -op greaterThanEq main__tmp_190 main__tmp_191 main__tmp_192 -jump main__label_24_else notEqual main__tmp_190 true -set main__tmp_193 @disrupt -set main__tmp_194 spawnx -set main__tmp_195 spawny -set main__tmp_196 0 -set main__tmp_197 @crux -set main__tmp_198 _ -spawn main__tmp_193 main__tmp_194 main__tmp_195 main__tmp_196 main__tmp_197 main__tmp_198 -set main__tmp_199 spawnx -set main__tmp_200 5.75 -op sub spawnx main__tmp_199 main__tmp_200 +set main__tmp_187 roll +set main__tmp_188 3 +op greaterThanEq main__tmp_186 main__tmp_187 main__tmp_188 +jump main__label_22_else notEqual main__tmp_186 true +set main__tmp_190 roll +set main__tmp_191 4 +op greaterThanEq main__tmp_189 main__tmp_190 main__tmp_191 +jump main__label_24_else notEqual main__tmp_189 true +set main__tmp_192 @disrupt +set main__tmp_193 spawnx +set main__tmp_194 spawny +set main__tmp_195 0 +set main__tmp_196 @crux +set main__tmp_197 _ +spawn main__tmp_192 main__tmp_193 main__tmp_194 main__tmp_195 main__tmp_196 main__tmp_197 +set main__tmp_198 spawnx +set main__tmp_199 5.75 +op sub spawnx main__tmp_198 main__tmp_199 jump main__label_25_endif always 0 0 main__label_24_else: -set main__tmp_201 @quell -set main__tmp_202 spawnx -set main__tmp_203 spawny -set main__tmp_204 0 -set main__tmp_205 @crux -set main__tmp_206 _ -spawn main__tmp_201 main__tmp_202 main__tmp_203 main__tmp_204 main__tmp_205 main__tmp_206 -set main__tmp_207 spawnx -set main__tmp_208 4.5 -op sub spawnx main__tmp_207 main__tmp_208 +set main__tmp_200 @quell +set main__tmp_201 spawnx +set main__tmp_202 spawny +set main__tmp_203 0 +set main__tmp_204 @crux +set main__tmp_205 _ +spawn main__tmp_200 main__tmp_201 main__tmp_202 main__tmp_203 main__tmp_204 main__tmp_205 +set main__tmp_206 spawnx +set main__tmp_207 4.5 +op sub spawnx main__tmp_206 main__tmp_207 main__label_25_endif: jump main__label_23_endif always 0 0 main__label_22_else: -set main__tmp_210 roll -set main__tmp_211 2 -op greaterThanEq main__tmp_209 main__tmp_210 main__tmp_211 -jump main__label_26_else notEqual main__tmp_209 true -set main__tmp_212 @obviate -set main__tmp_213 spawnx -set main__tmp_214 spawny -set main__tmp_215 0 -set main__tmp_216 @crux -set main__tmp_217 _ -spawn main__tmp_212 main__tmp_213 main__tmp_214 main__tmp_215 main__tmp_216 main__tmp_217 -set main__tmp_218 spawnx -set main__tmp_219 3.12 -op sub spawnx main__tmp_218 main__tmp_219 +set main__tmp_209 roll +set main__tmp_210 2 +op greaterThanEq main__tmp_208 main__tmp_209 main__tmp_210 +jump main__label_26_else notEqual main__tmp_208 true +set main__tmp_211 @obviate +set main__tmp_212 spawnx +set main__tmp_213 spawny +set main__tmp_214 0 +set main__tmp_215 @crux +set main__tmp_216 _ +spawn main__tmp_211 main__tmp_212 main__tmp_213 main__tmp_214 main__tmp_215 main__tmp_216 +set main__tmp_217 spawnx +set main__tmp_218 3.12 +op sub spawnx main__tmp_217 main__tmp_218 jump main__label_27_endif always 0 0 main__label_26_else: -set main__tmp_221 roll -set main__tmp_222 1 -op greaterThanEq main__tmp_220 main__tmp_221 main__tmp_222 -jump main__label_28_else notEqual main__tmp_220 true -set main__tmp_223 @avert -set main__tmp_224 spawnx -set main__tmp_225 spawny -set main__tmp_226 0 -set main__tmp_227 @crux -set main__tmp_228 _ -spawn main__tmp_223 main__tmp_224 main__tmp_225 main__tmp_226 main__tmp_227 main__tmp_228 -set main__tmp_229 spawnx -set main__tmp_230 1 -op sub spawnx main__tmp_229 main__tmp_230 +set main__tmp_220 roll +set main__tmp_221 1 +op greaterThanEq main__tmp_219 main__tmp_220 main__tmp_221 +jump main__label_28_else notEqual main__tmp_219 true +set main__tmp_222 @avert +set main__tmp_223 spawnx +set main__tmp_224 spawny +set main__tmp_225 0 +set main__tmp_226 @crux +set main__tmp_227 _ +spawn main__tmp_222 main__tmp_223 main__tmp_224 main__tmp_225 main__tmp_226 main__tmp_227 +set main__tmp_228 spawnx +set main__tmp_229 1 +op sub spawnx main__tmp_228 main__tmp_229 jump main__label_29_endif always 0 0 main__label_28_else: -set main__tmp_231 @elude -set main__tmp_232 spawnx -set main__tmp_233 spawny -set main__tmp_234 0 -set main__tmp_235 @crux -set main__tmp_236 _ -spawn main__tmp_231 main__tmp_232 main__tmp_233 main__tmp_234 main__tmp_235 main__tmp_236 -set main__tmp_237 spawnx -set main__tmp_238 1 -op sub spawnx main__tmp_237 main__tmp_238 +set main__tmp_230 @elude +set main__tmp_231 spawnx +set main__tmp_232 spawny +set main__tmp_233 0 +set main__tmp_234 @crux +set main__tmp_235 _ +spawn main__tmp_230 main__tmp_231 main__tmp_232 main__tmp_233 main__tmp_234 main__tmp_235 +set main__tmp_236 spawnx +set main__tmp_237 1 +op sub spawnx main__tmp_236 main__tmp_237 main__label_29_endif: main__label_27_endif: main__label_23_endif: -set main__tmp_240 spawnx -set main__tmp_241 10 -op lessThan main__tmp_239 main__tmp_240 main__tmp_241 -jump main__label_30_endif notEqual main__tmp_239 true +set main__tmp_239 spawnx +set main__tmp_240 10 +op lessThan main__tmp_238 main__tmp_239 main__tmp_240 +jump main__label_30_endif notEqual main__tmp_238 true set spawnx 10 main__label_30_endif: -set main__tmp_242 spawned -set main__tmp_243 1 -op add spawned main__tmp_242 main__tmp_243 -set main__tmp_245 spawned -set main__tmp_246 air_units -op lessThan main__tmp_244 main__tmp_245 main__tmp_246 -jump main__label_31_endif notEqual main__tmp_244 true +set main__tmp_241 spawned +set main__tmp_242 1 +op add spawned main__tmp_241 main__tmp_242 +set main__tmp_244 spawned +set main__tmp_245 air_units +op lessThan main__tmp_243 main__tmp_244 main__tmp_245 +jump main__label_31_endif notEqual main__tmp_243 true jump spawn_air_loop always 0 0 main__label_31_endif: jump spawn_air_end always 0 0 diff --git a/tests/examples/procedural-waves.1.mlog b/tests/examples/procedural-waves.1.mlog index dba2954..1e6c566 100644 --- a/tests/examples/procedural-waves.1.mlog +++ b/tests/examples/procedural-waves.1.mlog @@ -52,12 +52,12 @@ jump spawn_air always 0 0 spawn_air_end: write wave cell1 1 read timeout cell1 0 -set main__tmp_49 @time -op mul main__tmp_50 timeout 1000 -op add timeout main__tmp_49 main__tmp_50 +set main__tmp_48 @time +op mul main__tmp_49 timeout 1000 +op add timeout main__tmp_48 main__tmp_49 main_wait: -set main__tmp_54 @time -op sub remaining timeout main__tmp_54 +set main__tmp_53 @time +op sub remaining timeout main__tmp_53 jump main__label_1_endif greaterThan remaining 0 jump main always 0 0 main__label_1_endif: @@ -65,9 +65,9 @@ print "[yellow]Wave " print wave print "[white] - " print "Next wave: " -op div main__tmp_62 remaining 1000 -op ceil main__tmp_62 main__tmp_62 0 -print main__tmp_62 +op div main__tmp_61 remaining 1000 +op ceil main__tmp_61 main__tmp_61 0 +print main__tmp_61 print " s" message mission wait 0.5 @@ -79,30 +79,30 @@ set roll progression op rand roll roll 0 jump main__label_2_else lessThan roll 3 jump main__label_4_else lessThan roll 4 -set main__tmp_77 @crux -spawn @conquer spawnx spawny 0 main__tmp_77 _ +set main__tmp_76 @crux +spawn @conquer spawnx spawny 0 main__tmp_76 _ op sub spawnx spawnx 5.75 jump main__label_3_endif always 0 0 main__label_4_else: -set main__tmp_85 @crux -spawn @vanquish spawnx spawny 0 main__tmp_85 _ +set main__tmp_84 @crux +spawn @vanquish spawnx spawny 0 main__tmp_84 _ op sub spawnx spawnx 3.5 jump main__label_3_endif always 0 0 main__label_2_else: jump main__label_6_else lessThan roll 2 -set main__tmp_96 @crux -spawn @precept spawnx spawny 0 main__tmp_96 _ +set main__tmp_95 @crux +spawn @precept spawnx spawny 0 main__tmp_95 _ op sub spawnx spawnx 3.25 jump main__label_7_endif always 0 0 main__label_6_else: jump main__label_8_else lessThan roll 1 -set main__tmp_107 @crux -spawn @locus spawnx spawny 0 main__tmp_107 _ +set main__tmp_106 @crux +spawn @locus spawnx spawny 0 main__tmp_106 _ op sub spawnx spawnx 1 jump main__label_9_endif always 0 0 main__label_8_else: -set main__tmp_115 @crux -spawn @stell spawnx spawny 0 main__tmp_115 _ +set main__tmp_114 @crux +spawn @stell spawnx spawny 0 main__tmp_114 _ op sub spawnx spawnx 1 main__label_9_endif: main__label_7_endif: @@ -121,30 +121,30 @@ set roll progression op rand roll roll 0 jump main__label_12_else lessThan roll 3 jump main__label_14_else lessThan roll 4 -set main__tmp_137 @crux -spawn @collaris spawnx spawny 0 main__tmp_137 _ +set main__tmp_136 @crux +spawn @collaris spawnx spawny 0 main__tmp_136 _ op sub spawnx spawnx 5.5 jump main__label_13_endif always 0 0 main__label_14_else: -set main__tmp_145 @crux -spawn @tecta spawnx spawny 0 main__tmp_145 _ +set main__tmp_144 @crux +spawn @tecta spawnx spawny 0 main__tmp_144 _ op sub spawnx spawnx 2.87 jump main__label_13_endif always 0 0 main__label_12_else: jump main__label_16_else lessThan roll 2 -set main__tmp_156 @crux -spawn @anthicus spawnx spawny 0 main__tmp_156 _ +set main__tmp_155 @crux +spawn @anthicus spawnx spawny 0 main__tmp_155 _ op sub spawnx spawnx 2.62 jump main__label_17_endif always 0 0 main__label_16_else: jump main__label_18_else lessThan roll 1 -set main__tmp_167 @crux -spawn @cleroi spawnx spawny 0 main__tmp_167 _ +set main__tmp_166 @crux +spawn @cleroi spawnx spawny 0 main__tmp_166 _ op sub spawnx spawnx 1 jump main__label_19_endif always 0 0 main__label_18_else: -set main__tmp_175 @crux -spawn @merui spawnx spawny 0 main__tmp_175 _ +set main__tmp_174 @crux +spawn @merui spawnx spawny 0 main__tmp_174 _ op sub spawnx spawnx 1 main__label_19_endif: main__label_17_endif: @@ -163,30 +163,30 @@ set roll progression op rand roll roll 0 jump main__label_22_else lessThan roll 3 jump main__label_24_else lessThan roll 4 -set main__tmp_197 @crux -spawn @disrupt spawnx spawny 0 main__tmp_197 _ +set main__tmp_196 @crux +spawn @disrupt spawnx spawny 0 main__tmp_196 _ op sub spawnx spawnx 5.75 jump main__label_23_endif always 0 0 main__label_24_else: -set main__tmp_205 @crux -spawn @quell spawnx spawny 0 main__tmp_205 _ +set main__tmp_204 @crux +spawn @quell spawnx spawny 0 main__tmp_204 _ op sub spawnx spawnx 4.5 jump main__label_23_endif always 0 0 main__label_22_else: jump main__label_26_else lessThan roll 2 -set main__tmp_216 @crux -spawn @obviate spawnx spawny 0 main__tmp_216 _ +set main__tmp_215 @crux +spawn @obviate spawnx spawny 0 main__tmp_215 _ op sub spawnx spawnx 3.12 jump main__label_27_endif always 0 0 main__label_26_else: jump main__label_28_else lessThan roll 1 -set main__tmp_227 @crux -spawn @avert spawnx spawny 0 main__tmp_227 _ +set main__tmp_226 @crux +spawn @avert spawnx spawny 0 main__tmp_226 _ op sub spawnx spawnx 1 jump main__label_29_endif always 0 0 main__label_28_else: -set main__tmp_235 @crux -spawn @elude spawnx spawny 0 main__tmp_235 _ +set main__tmp_234 @crux +spawn @elude spawnx spawny 0 main__tmp_234 _ op sub spawnx spawnx 1 main__label_29_endif: main__label_27_endif: