3 Commits

Author SHA1 Message Date
95c17b563c More optimizer fixes
All checks were successful
CI/CD Pipeline / test (pull_request) Successful in 2m2s
CI/CD Pipeline / build (pull_request) Has been skipped
CI/CD Pipeline / release (pull_request) Has been skipped
2025-12-30 23:34:14 -07:00
dbc4c72c3b Add .snap.new files to gitignore 2025-12-30 23:00:55 -07:00
964ad92077 More compiler optimizations 2025-12-30 23:00:17 -07:00
19 changed files with 510 additions and 157 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
target target
*.ic10 *.ic10
*.snap.new
release release
csharp_mod/bin csharp_mod/bin
obj obj

View File

@@ -172,4 +172,44 @@ mod tests {
let output = compile_with_and_without_optimization(source); let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output); insta::assert_snapshot!(output);
} }
#[test]
fn test_tuples() {
let source = indoc! {r#"
device self = "db";
device day = "d0";
fn getSomethingElse(input) {
return input + 1;
}
fn getSensorData() {
return (
day.Vertical,
day.Horizontal,
getSomethingElse(3)
);
}
loop {
yield();
let (vertical, horizontal, _) = getSensorData();
(horizontal, _, _) = getSensorData();
self.Setting = horizontal;
}
"#};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_larre_script() {
let source = include_str!("./test_files/test_larre_script.stlg");
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
} }

View File

@@ -12,7 +12,6 @@ move r9 r1
## Optimized Output ## Optimized Output
j 1
move r8 5 move r8 5
move r1 5 move r1 5
move r9 5 move r9 5

View File

@@ -43,7 +43,6 @@ move r13 r3
add r4 r11 r12 add r4 r11 r12
add r5 r4 r13 add r5 r4 r13
move r15 r5 move r15 r5
j 16
pop ra pop ra
pop sp pop sp
j ra j ra

View File

@@ -10,5 +10,4 @@ move r8 15
## Optimized Output ## Optimized Output
j 1
move r8 15 move r8 15

View File

@@ -26,7 +26,6 @@ push sp
push ra push ra
add r1 r8 1 add r1 r8 1
move r15 r1 move r15 r1
j 7
pop ra pop ra
pop sp pop sp
j ra j ra

View File

@@ -1,33 +0,0 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 103
expression: output
---
## Unoptimized Output
j main
compute:
pop r8
push sp
push ra
move r9 20
add r1 r8 1
move r15 r1
j __internal_L1
__internal_L1:
pop ra
pop sp
j ra
## Optimized Output
j main
pop r8
push sp
push ra
add r1 r8 1
move r15 r1
j 7
pop ra
pop sp
j ra

View File

@@ -31,14 +31,13 @@ j ra
## Optimized Output ## Optimized Output
j 11 j 10
pop r8 pop r8
pop r9 pop r9
push sp push sp
push ra push ra
add r1 r9 r8 add r1 r9 r8
move r15 r1 move r15 r1
j 8
pop ra pop ra
pop sp pop sp
j ra j ra

View File

@@ -0,0 +1,214 @@
---
source: libs/integration_tests/src/lib.rs
expression: output
---
## Unoptimized Output
j main
waitForIdle:
push sp
push ra
yield
__internal_L2:
l r1 d0 Idle
seq r2 r1 0
beqz r2 __internal_L3
yield
j __internal_L2
__internal_L3:
__internal_L1:
pop ra
pop sp
j ra
deposit:
push sp
push ra
s d0 Setting 1
jal waitForIdle
move r1 r15
s d0 Activate 1
jal waitForIdle
move r2 r15
s d1 Open 0
__internal_L4:
pop ra
pop sp
j ra
checkAndHarvest:
pop r8
push sp
push ra
sle r1 r8 1
ls r15 d0 255 Seeding
slt r2 r15 1
or r3 r1 r2
beqz r3 __internal_L6
j __internal_L5
__internal_L6:
__internal_L7:
ls r15 d0 255 Mature
beqz r15 __internal_L8
yield
s d0 Activate 1
j __internal_L7
__internal_L8:
ls r15 d0 255 Occupied
move r9 r15
s d0 Setting 1
push r8
push r9
jal waitForIdle
pop r9
pop r8
move r4 r15
push r8
push r9
jal deposit
pop r9
pop r8
move r5 r15
beqz r9 __internal_L9
push r8
push r9
jal deposit
pop r9
pop r8
move r6 r15
__internal_L9:
s d0 Setting r8
push r8
push r9
jal waitForIdle
pop r9
pop r8
move r6 r15
ls r15 d0 0 Occupied
beqz r15 __internal_L10
s d0 Activate 1
__internal_L10:
push r8
push r9
jal waitForIdle
pop r9
pop r8
move r7 r15
__internal_L5:
pop ra
pop sp
j ra
main:
move r8 0
__internal_L11:
yield
l r1 d0 Idle
seq r2 r1 0
beqz r2 __internal_L13
j __internal_L11
__internal_L13:
add r3 r8 1
sgt r4 r3 19
add r5 r8 1
select r6 r4 2 r5
move r9 r6
push r8
push r9
push r8
jal checkAndHarvest
pop r9
pop r8
move r7 r15
s d0 Setting r9
move r8 r9
j __internal_L11
__internal_L12:
## Optimized Output
j 71
push sp
push ra
yield
l r1 d0 Idle
bne r1 0 8
yield
j 4
pop ra
pop sp
j ra
push sp
push ra
s d0 Setting 1
jal 1
s d0 Activate 1
jal 1
s d1 Open 0
pop ra
pop sp
j ra
pop r8
push sp
push ra
sle r1 r8 1
ls r15 d0 255 Seeding
slt r2 r15 1
or r3 r1 r2
beqz r3 30
j 68
ls r15 d0 255 Mature
beqz r15 35
yield
s d0 Activate 1
j 30
ls r15 d0 255 Occupied
move r9 r15
s d0 Setting 1
push r8
push r9
jal 1
pop r9
pop r8
push r8
push r9
jal 11
pop r9
pop r8
beqz r9 54
push r8
push r9
jal 11
pop r9
pop r8
s d0 Setting r8
push r8
push r9
jal 1
pop r9
pop r8
ls r15 d0 0 Occupied
beqz r15 63
s d0 Activate 1
push r8
push r9
jal 1
pop r9
pop r8
pop ra
pop sp
j ra
yield
l r1 d0 Idle
bne r1 0 75
j 71
add r3 r8 1
sgt r4 r3 19
add r5 r8 1
select r6 r4 2 r5
move r9 r6
push r8
push r9
push r8
jal 21
pop r9
pop r8
s d0 Setting r9
j 71

View File

@@ -19,4 +19,5 @@ j ra
## Optimized Output ## Optimized Output
j main j main
pop r8
j ra j ra

View File

@@ -68,16 +68,15 @@ push sp
push ra push ra
add r1 r9 r8 add r1 r9 r8
move r15 r1 move r15 r1
j 8
pop ra pop ra
pop sp pop sp
j ra j ra
pop r8
pop r9 pop r9
push sp push sp
push ra push ra
add r1 r9 r9 add r1 r9 r9
move r15 r1 move r15 r1
j 17
pop ra pop ra
pop sp pop sp
j ra j ra
@@ -98,10 +97,12 @@ push r9
push r10 push r10
push r10 push r10
push 2 push 2
jal 11 jal 10
pop r10
pop r9
pop r8
move r11 r15 move r11 r15
move r15 r11 move r15 r11
j 41
pop ra pop ra
pop sp pop sp
j ra j ra

View File

@@ -1,108 +0,0 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 173
expression: output
---
## Unoptimized Output
j main
add:
pop r8
pop r9
push sp
push ra
add r1 r9 r8
move r15 r1
j __internal_L1
__internal_L1:
pop ra
pop sp
j ra
multiply:
pop r8
pop r9
push sp
push ra
mul r1 r9 2
move r15 r1
j __internal_L2
__internal_L2:
pop ra
pop sp
j ra
complex:
pop r8
pop r9
push sp
push ra
push r8
push r9
push r9
push r8
jal add
pop r9
pop r8
move r10 r15
push r8
push r9
push r10
push r10
push 2
jal multiply
pop r10
pop r9
pop r8
move r11 r15
move r15 r11
j __internal_L3
__internal_L3:
pop ra
pop sp
j ra
## Optimized Output
j main
pop r8
pop r9
push sp
push ra
add r1 r9 r8
move r15 r1
j 8
pop ra
pop sp
j ra
pop r9
push sp
push ra
add r1 r9 r9
move r15 r1
j 17
pop ra
pop sp
j ra
pop r8
pop r9
push sp
push ra
push r8
push r9
push r9
push r8
jal 1
pop r9
pop r8
move r10 r15
push r8
push r9
push r10
push r10
push 2
jal 11
move r11 r15
move r15 r11
j 41
pop ra
pop sp
j ra

View File

@@ -31,7 +31,6 @@ push sp
push ra push ra
select r9 r8 10 20 select r9 r8 10 20
move r15 r9 move r15 r9
j 7
pop ra pop ra
pop sp pop sp
j ra j ra

View File

@@ -25,7 +25,6 @@ push sp
push ra push ra
add r1 r8 r8 add r1 r8 r8
move r15 r1 move r15 r1
j 7
pop ra pop ra
pop sp pop sp
j ra j ra

View File

@@ -0,0 +1,94 @@
---
source: libs/integration_tests/src/lib.rs
expression: output
---
## Unoptimized Output
j main
getSomethingElse:
pop r8
push sp
push ra
add r1 r8 1
move r15 r1
j __internal_L1
__internal_L1:
pop ra
pop sp
j ra
getSensorData:
push sp
push ra
l r1 d0 Vertical
push r1
l r2 d0 Horizontal
push r2
push 3
jal getSomethingElse
move r3 r15
push r3
sub r0 sp 5
get r0 db r0
move r15 r0
j __internal_L2
__internal_L2:
sub r0 sp 4
get ra db r0
j ra
main:
__internal_L3:
yield
jal getSensorData
pop r0
pop r9
pop r8
move sp r15
jal getSensorData
pop r0
pop r0
pop r9
move sp r15
s db Setting r9
j __internal_L3
__internal_L4:
## Optimized Output
j 25
pop r8
push sp
push ra
add r1 r8 1
move r15 r1
pop ra
pop sp
j ra
push sp
push ra
l r1 d0 Vertical
push r1
l r2 d0 Horizontal
push r2
push 3
jal 1
move r3 r15
push r3
sub r0 sp 5
get r0 db r0
move r15 r0
sub r0 sp 4
get ra db r0
j ra
yield
jal 9
pop r0
pop r9
pop r8
move sp r15
jal 9
pop r0
pop r0
pop r9
move sp r15
s db Setting r9
j 25

View File

@@ -0,0 +1,72 @@
/// Laree script V1
device self = "db";
device larre = "d0";
device exportChute = "d1";
const TOTAL_SLOTS = 19;
const EXPORT_CHUTE = 1;
const START_STATION = 2;
let currentIndex = 0;
/// Waits for the larre to be idle before continuing
fn waitForIdle() {
yield();
while (!larre.Idle) {
yield();
}
}
/// Instructs the Larre to go to the chute and deposit
/// what is currently in its arm
fn deposit() {
larre.Setting = EXPORT_CHUTE;
waitForIdle();
larre.Activate = true;
waitForIdle();
exportChute.Open = false;
}
/// This function is responsible for checking the plant under
/// the larre at this index, and harvesting if applicable
fn checkAndHarvest(currentIndex) {
if (currentIndex <= EXPORT_CHUTE || ls(larre, 255, "Seeding") < 1) {
return;
}
// harvest from this device
while (ls(larre, 255, "Mature")) {
yield();
larre.Activate = true;
}
let hasRemainingPlant = ls(larre, 255, "Occupied");
// move to the export chute
larre.Setting = EXPORT_CHUTE;
waitForIdle();
deposit();
if (hasRemainingPlant) {
deposit();
}
larre.Setting = currentIndex;
waitForIdle();
if (ls(larre, 0, "Occupied")) {
larre.Activate = true;
}
waitForIdle();
}
loop {
yield();
if (!larre.Idle) {
continue;
}
let newIndex = currentIndex + 1 > TOTAL_SLOTS ? START_STATION : currentIndex + 1;
checkAndHarvest(currentIndex);
larre.Setting = newIndex;
currentIndex = newIndex;
}

View File

@@ -1,4 +1,4 @@
use il::{Instruction, InstructionNode}; use il::{Instruction, InstructionNode, Operand};
/// Pass: Redundant Move Elimination /// Pass: Redundant Move Elimination
/// Removes moves where source and destination are the same: `move rx rx` /// Removes moves where source and destination are the same: `move rx rx`
@@ -43,6 +43,72 @@ pub fn remove_unreachable_code<'a>(
(output, changed) (output, changed)
} }
/// Pass: Remove Redundant Jumps
/// Removes jumps to the next instruction (after label resolution).
/// Must run AFTER label resolution since it needs line numbers.
/// This pass also adjusts all jump targets to account for removed instructions.
pub fn remove_redundant_jumps<'a>(
input: Vec<InstructionNode<'a>>,
) -> (Vec<InstructionNode<'a>>, bool) {
let mut output = Vec::with_capacity(input.len());
let mut changed = false;
let mut removed_lines = Vec::new();
// First pass: identify redundant jumps
for (i, node) in input.iter().enumerate() {
// Check if this is a jump to the next line number
if let Instruction::Jump(Operand::Number(target)) = &node.instruction {
// Current line number is i, next line number is i+1
// If jump target equals the next line, it's redundant
if target.to_string().parse::<usize>().ok() == Some(i + 1) {
changed = true;
removed_lines.push(i);
continue; // Skip this redundant jump
}
}
output.push(node.clone());
}
// Second pass: adjust all jump/branch targets to account for removed lines
if changed {
for node in &mut output {
// Helper to adjust a target line number
let adjust_target = |target_line: usize| -> usize {
// Count how many removed lines are before the target
let offset = removed_lines
.iter()
.filter(|&&removed| removed < target_line)
.count();
target_line.saturating_sub(offset)
};
match &mut node.instruction {
Instruction::Jump(Operand::Number(target))
| Instruction::JumpAndLink(Operand::Number(target)) => {
if let Ok(target_line) = target.to_string().parse::<usize>() {
*target = rust_decimal::Decimal::from(adjust_target(target_line));
}
}
Instruction::BranchEq(_, _, Operand::Number(target))
| Instruction::BranchNe(_, _, Operand::Number(target))
| Instruction::BranchGt(_, _, Operand::Number(target))
| Instruction::BranchLt(_, _, Operand::Number(target))
| Instruction::BranchGe(_, _, Operand::Number(target))
| Instruction::BranchLe(_, _, Operand::Number(target))
| Instruction::BranchEqZero(_, Operand::Number(target))
| Instruction::BranchNeZero(_, Operand::Number(target)) => {
if let Ok(target_line) = target.to_string().parse::<usize>() {
*target = rust_decimal::Decimal::from(adjust_target(target_line));
}
}
_ => {}
}
}
}
(output, changed)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -13,13 +13,20 @@ pub fn dead_store_elimination<'a>(
// Scan for dead writes // Scan for dead writes
for (i, node) in input.iter().enumerate() { for (i, node) in input.iter().enumerate() {
// Never remove Pop instructions - they have critical side effects on the stack pointer
if matches!(node.instruction, Instruction::Pop(_)) {
continue;
}
if let Some(dest_reg) = get_destination_reg(&node.instruction) { if let Some(dest_reg) = get_destination_reg(&node.instruction) {
// If this register was written before and hasn't been read, previous write is dead // If this register was written before and hasn't been read, previous write is dead
if let Some(&prev_idx) = last_write.get(&dest_reg) { if let Some(&prev_idx) = last_write.get(&dest_reg) {
// Check if the value was ever used between prev_idx and current // Check if the value was ever used between prev_idx and current
let was_used = input[prev_idx + 1..i] let was_used = input[prev_idx + 1..i]
.iter() .iter()
.any(|n| reg_is_read_or_affects_control(&n.instruction, dest_reg)); .any(|n| reg_is_read_or_affects_control(&n.instruction, dest_reg))
// Also check if current instruction reads the register before overwriting it
|| reg_is_read_or_affects_control(&node.instruction, dest_reg);
if !was_used { if !was_used {
// Previous write was dead // Previous write was dead

View File

@@ -17,7 +17,7 @@ mod strength_reduction;
use algebraic_simplification::algebraic_simplification; use algebraic_simplification::algebraic_simplification;
use constant_propagation::constant_propagation; use constant_propagation::constant_propagation;
use dead_code::{remove_redundant_moves, remove_unreachable_code}; use dead_code::{remove_redundant_jumps, remove_redundant_moves, remove_unreachable_code};
use dead_store_elimination::dead_store_elimination; use dead_store_elimination::dead_store_elimination;
use function_call_optimization::optimize_function_calls; use function_call_optimization::optimize_function_calls;
use label_resolution::resolve_labels; use label_resolution::resolve_labels;
@@ -91,5 +91,10 @@ pub fn optimize<'a>(instructions: Instructions<'a>) -> Instructions<'a> {
} }
// Final Pass: Resolve Labels to Line Numbers // Final Pass: Resolve Labels to Line Numbers
Instructions::new(resolve_labels(instructions)) let instructions = resolve_labels(instructions);
// Post-resolution Pass: Remove redundant jumps (must run after label resolution)
let (instructions, _) = remove_redundant_jumps(instructions);
Instructions::new(instructions)
} }