15 Commits

Author SHA1 Message Date
d8fe9a0d7d Merge pull request 'optimizer-bug' (#14) from optimizer-bug into master
All checks were successful
CI/CD Pipeline / test (push) Successful in 36s
CI/CD Pipeline / build (push) Successful in 1m44s
CI/CD Pipeline / release (push) Successful in 4s
Reviewed-on: #14
2026-01-01 12:41:11 -07:00
089fe46d36 update changelog and version bump
All checks were successful
CI/CD Pipeline / test (pull_request) Successful in 37s
CI/CD Pipeline / build (pull_request) Has been skipped
CI/CD Pipeline / release (pull_request) Has been skipped
2026-01-01 12:38:53 -07:00
14c641797a Fixed optimizer bug where certain syscalls were not included in register liveness analysis 2026-01-01 12:35:57 -07:00
fb5eacea02 Merge pull request 'Updated auto-doc formatting for markdown files' (#13) from doc-formatting-fixes into master
All checks were successful
CI/CD Pipeline / test (push) Successful in 36s
CI/CD Pipeline / build (push) Successful in 1m44s
CI/CD Pipeline / release (push) Has been skipped
Reviewed-on: #13
2026-01-01 02:43:50 -07:00
9fd3a55182 Updated auto-doc formatting for markdown files
All checks were successful
CI/CD Pipeline / test (pull_request) Successful in 37s
CI/CD Pipeline / build (pull_request) Has been skipped
CI/CD Pipeline / release (pull_request) Has been skipped
2026-01-01 02:43:07 -07:00
397aa47217 Merge pull request '0.5.0 -- tuples and more optimizations' (#12) from 43-tuple-return into master
All checks were successful
CI/CD Pipeline / test (push) Successful in 37s
CI/CD Pipeline / build (push) Successful in 1m45s
CI/CD Pipeline / release (push) Successful in 5s
Reviewed-on: #12
2025-12-31 17:03:50 -07:00
6f86563863 Update changelog
All checks were successful
CI/CD Pipeline / test (pull_request) Successful in 36s
CI/CD Pipeline / build (pull_request) Has been skipped
CI/CD Pipeline / release (pull_request) Has been skipped
2025-12-31 17:01:26 -07:00
352041746c More tests, renamed files from slang -> stlg
All checks were successful
CI/CD Pipeline / test (pull_request) Successful in 37s
CI/CD Pipeline / build (pull_request) Has been skipped
CI/CD Pipeline / release (pull_request) Has been skipped
2025-12-31 14:39:38 -07:00
5f4335dbcc Added another complex stlg test file 2025-12-31 14:12:19 -07:00
2a5dfd9ab6 Ready for in-game testing
All checks were successful
CI/CD Pipeline / test (pull_request) Successful in 37s
CI/CD Pipeline / build (pull_request) Has been skipped
CI/CD Pipeline / release (pull_request) Has been skipped
2025-12-31 03:08:41 -07:00
2dfe36f8be more optimizations 2025-12-31 02:39:57 -07:00
d28cdfcc7b Improve dead code elimination in optimizer
- Refactored dead_store_elimination to separate forward and backward passes
- Improved register forwarding to better detect backward jumps
- Fixed handling of JumpAndLink instructions in register tracking
- Updated optimizer snapshots to reflect improved code generation

The forward pass now correctly eliminates writes that are immediately overwritten.
Register forwarding now better handles conditional branches and loops.

Note: Backward pass for dead code elimination disabled for now - it needs
additional work to properly handle return values and call site analysis.
2025-12-31 02:37:26 -07:00
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
42 changed files with 1017 additions and 422 deletions

View File

@@ -80,10 +80,14 @@ cargo test --package compiler --lib -- test::tuple_literals::test::test_tuple_li
### Quick Compilation
!IMPORTANT: make sure you use these commands instead of creating temporary files.
```bash
cd rust_compiler
# Compile Slang code to IC10 using current compiler changes
echo 'let x = 5;' | cargo run --bin slang
# Compile Slang code to IC10 with optimization
echo 'let x = 5;' | cargo run --bin slang -z
# Or from file
cargo run --bin slang -- input.slang -o output.ic10
# Optimize the output with -z flag

1
.gitignore vendored
View File

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

View File

@@ -1,11 +1,19 @@
# Changelog
[0.5.1]
- Fixed optimizer bug where `StoreBatch` and `StoreBatchNamed` instructions
were not recognized as reading operands, causing incorrect elimination of
necessary device property loads
- Added comprehensive register read tracking for `StoreSlot`, `JumpRelative`,
and `Alias` instructions in the optimizer
[0.5.0]
- Added support for tuple types
- Added support for tuple returns from functions
- Added support for ignoring tuple values
- Fixed various compiler bugs
- Added full tuple support: declarations, assignments, and returns
- Refactored optimizer into modular passes with improved code generation
- Enhanced peephole optimizations and pattern recognition
- Comprehensive test coverage for edge cases and error handling
[0.4.7]

View File

@@ -2,7 +2,7 @@
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Slang</Name>
<Author>JoeDiertay</Author>
<Version>0.5.0</Version>
<Version>0.5.1</Version>
<Description>
[h1]Slang: High-Level Programming for Stationeers[/h1]

View File

@@ -39,7 +39,7 @@ namespace Slang
{
public const string PluginGuid = "com.biddydev.slang";
public const string PluginName = "Slang";
public const string PluginVersion = "0.5.0";
public const string PluginVersion = "0.5.1";
private static Harmony? _harmony;

View File

@@ -1,59 +1,73 @@
using System;
using System.Text.RegularExpressions;
namespace Slang;
public static class TextMeshProFormatter
{
private const string CODE_COLOR = "#FFD700";
private const string CODE_COLOR = "#FFD700"; // Gold
private const string LINK_COLOR = "#0099FF"; // Blue
private const string QUOTE_COLOR = "#90EE90"; // Light Green
public static string FromMarkdown(string markdown)
{
if (string.IsNullOrEmpty(markdown))
return "";
// 1. Normalize Line Endings
// Normalize Line Endings
string text = markdown.Replace("\r\n", "\n");
// 2. Handle Code Blocks (```)
// Process code blocks FIRST (``` ... ```)
text = Regex.Replace(
text,
@"```\s*(.*?)\s*```",
@"```[^\n]*\n(.*?)\n```",
match =>
{
var codeContent = match.Groups[1].Value;
return $"<color={CODE_COLOR}>{codeContent}</color>"; // Gold color for code
return $"<color={CODE_COLOR}>{codeContent}</color>";
},
RegexOptions.Singleline
);
// Process headers - check for 1-6 hashes
text = Regex.Replace(text, @"^#{1}\s+(.+)$", "<size=120%><b>$1</b></size>", RegexOptions.Multiline);
text = Regex.Replace(text, @"^#{2}\s+(.+)$", "<size=110%><b>$1</b></size>", RegexOptions.Multiline);
text = Regex.Replace(text, @"^#{3}\s+(.+)$", "<size=100%><b>$1</b></size>", RegexOptions.Multiline);
text = Regex.Replace(text, @"^#{4}\s+(.+)$", "<size=90%><b>$1</b></size>", RegexOptions.Multiline);
text = Regex.Replace(text, @"^#{5}\s+(.+)$", "<size=80%><b>$1</b></size>", RegexOptions.Multiline);
text = Regex.Replace(text, @"^#{6}\s+(.+)$", "<size=70%><b>$1</b></size>", RegexOptions.Multiline);
// Process markdown links [text](url)
text = Regex.Replace(
text,
@"^\s*##\s+(.+)$",
"<size=110%><color=#ffffff><b>$1</b></color></size>",
RegexOptions.Multiline
@"\[([^\]]+)\]\(([^\)]+)\)",
$"<color={LINK_COLOR}><u>$1</u></color>"
);
// 3. Handle # Headers SECOND (General)
text = Regex.Replace(
text,
@"^\s*#\s+(.+)$",
"<size=120%><color=#ffffff><b>$1</b></color></size>",
RegexOptions.Multiline
);
// 4. Handle Inline Code (`code`)
// Process inline code (`code`)
text = Regex.Replace(text, @"`([^`]+)`", $"<color={CODE_COLOR}>$1</color>");
// 5. Handle Bold (**text**)
// Process bold (**text**)
text = Regex.Replace(text, @"\*\*(.+?)\*\*", "<b>$1</b>");
// 6. Handle Italics (*text*)
text = Regex.Replace(text, @"\*(.+?)\*", "<i>$1</i>");
// Process italics (*text*)
text = Regex.Replace(text, @"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)", "<i>$1</i>");
// 7. Convert Newlines to TMP Line Breaks
// Stationpedia needs <br> or explicit newlines.
// Often just ensuring \n is preserved is enough, but <br> is safer for HTML-like parsers.
text = text.Replace("\n", "<br>");
// Process block quotes (> text)
text = Regex.Replace(
text,
@"^>\s+(.+)$",
$"<color={QUOTE_COLOR}><i>$1</i></color>",
RegexOptions.Multiline
);
// Process unordered lists (- items)
text = Regex.Replace(
text,
@"^-\s+(.+)$",
" • $1",
RegexOptions.Multiline
);
return text;
}

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<AssemblyName>StationeersSlang</AssemblyName>
<Description>Slang Compiler Bridge</Description>
<Version>0.5.0</Version>
<Version>0.5.1</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
</PropertyGroup>

View File

@@ -1039,7 +1039,7 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "slang"
version = "0.5.0"
version = "0.5.1"
dependencies = [
"anyhow",
"clap",

View File

@@ -1,6 +1,6 @@
[package]
name = "slang"
version = "0.5.0"
version = "0.5.1"
edition = "2021"
[workspace]

View File

@@ -1,4 +1,5 @@
use crc32fast::hash as crc32_hash;
/// This function takes an input which is meant to be hashed via the CRC32 algorithm, but it then
/// converts the generated UNSIGNED number into it's SIGNED counterpart.
pub fn crc_hash_signed(input: &str) -> i128 {
@@ -9,3 +10,38 @@ pub fn crc_hash_signed(input: &str) -> i128 {
hash_value_i32 as i128
}
/// Removes common leading whitespace from all lines in a string (dedent).
/// This is useful for cleaning up documentation strings that have uniform indentation.
pub fn dedent(text: &str) -> String {
let lines: Vec<&str> = text.lines().collect();
// Find minimum indentation (excluding empty lines)
let mut min_indent = usize::MAX;
for line in &lines {
if !line.trim().is_empty() {
let indent = line.len() - line.trim_start().len();
min_indent = min_indent.min(indent);
}
}
// If no lines or all empty, return as-is
if min_indent == usize::MAX {
return text.to_string();
}
// Remove the common indentation
lines
.iter()
.map(|line| {
if line.trim().is_empty() {
""
} else if line.len() >= min_indent {
&line[min_indent..]
} else {
line
}
})
.collect::<Vec<_>>()
.join("\n")
}

View File

@@ -1,4 +1,5 @@
mod helper_funcs;
pub use helper_funcs::dedent;
mod macros;
mod syscall;

View File

@@ -99,12 +99,12 @@ macro_rules! documented {
),*
];
doc_lines.iter()
let combined = doc_lines.iter()
.filter_map(|&d| d)
.collect::<Vec<_>>()
.join("\n")
.trim()
.to_string()
.join("\n");
$crate::dedent(&combined).trim().to_string()
}
)*
}
@@ -122,12 +122,13 @@ macro_rules! documented {
documented!(@doc_filter #[ $($variant_attr)* ])
),*
];
doc_lines.iter()
let combined = doc_lines.iter()
.filter_map(|&d| d)
.collect::<Vec<_>>()
.join("\n")
.trim()
.to_string()
.join("\n");
$crate::dedent(&combined).trim().to_string()
}
)
),*
@@ -136,4 +137,3 @@ macro_rules! documented {
}
};
}

View File

@@ -18,6 +18,12 @@ mod tests {
let compiler = Compiler::new(parser, None);
let result = compiler.compile();
assert!(
result.errors.is_empty(),
"Compilation errors: {:?}",
result.errors
);
// Get unoptimized output
let mut unoptimized_writer = std::io::BufWriter::new(Vec::new());
result
@@ -172,4 +178,67 @@ mod tests {
let output = compile_with_and_without_optimization(source);
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);
}
#[test]
fn test_reagent_processing() {
let source = include_str!("./test_files/reagent_processing.stlg");
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
#[test]
fn test_setbatched_with_member_access() {
let source = indoc! {r#"
const SENSOR = 20088;
const PANELS = hash("StructureSolarPanelDual");
loop {
setBatched(PANELS, "Horizontal", SENSOR.Horizontal);
setBatched(PANELS, "Vertical", SENSOR.Vertical + 90);
yield();
}
"#};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
}

View File

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

View File

@@ -1,5 +1,6 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 158
expression: output
---
## Unoptimized Output
@@ -34,16 +35,11 @@ pop r9
pop r10
push sp
push ra
add r1 r10 r10
move r11 r1
move r2 r9
move r12 r2
move r3 r8
move r13 r3
add r11 r10 r10
move r12 r9
move r13 r8
add r4 r11 r12
add r5 r4 r13
move r15 r5
j 16
add r15 r4 r13
pop ra
pop sp
j ra

View File

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

View File

@@ -1,5 +1,6 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 103
expression: output
---
## Unoptimized Output
@@ -24,9 +25,8 @@ j main
pop r8
push sp
push ra
add r1 r8 1
move r15 r1
j 7
move r9 20
add r15 r8 1
pop ra
pop sp
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

@@ -1,5 +1,6 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 70
expression: output
---
## Unoptimized Output
@@ -31,14 +32,12 @@ j ra
## Optimized Output
j 11
j 9
pop r8
pop r9
push sp
push ra
add r1 r9 r8
move r15 r1
j 8
add r15 r9 r8
pop ra
pop sp
j ra
@@ -47,6 +46,7 @@ push ra
push 5
push 10
jal 1
move r8 r15
pop ra
pop sp
j ra

View File

@@ -0,0 +1,223 @@
---
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 77
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
move r1 r15
s d0 Activate 1
jal 1
move r2 r15
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 32
j 74
ls r15 d0 255 Mature
beqz r15 37
yield
s d0 Activate 1
j 32
ls r9 d0 255 Occupied
s d0 Setting 1
push r8
push r9
jal 1
pop r9
pop r8
move r4 r15
push r8
push r9
jal 11
pop r9
pop r8
move r5 r15
beqz r9 58
push r8
push r9
jal 11
pop r9
pop r8
move r6 r15
s d0 Setting r8
push r8
push r9
jal 1
pop r9
pop r8
move r6 r15
ls r15 d0 0 Occupied
beqz r15 68
s d0 Activate 1
push r8
push r9
jal 1
pop r9
pop r8
move r7 r15
pop ra
pop sp
j ra
move r8 0
yield
l r1 d0 Idle
bne r1 0 82
j 78
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 23
pop r9
pop r8
move r7 r15
s d0 Setting r9
move r8 r9
j 78

View File

@@ -1,5 +1,6 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 144
expression: output
---
## Unoptimized Output
@@ -19,4 +20,11 @@ j ra
## Optimized Output
j main
pop r8
push sp
push ra
add r1 r8 1
move r8 r1
pop ra
pop sp
j ra

View File

@@ -1,5 +1,6 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 173
expression: output
---
## Unoptimized Output
@@ -66,18 +67,15 @@ pop r8
pop r9
push sp
push ra
add r1 r9 r8
move r15 r1
j 8
add r15 r9 r8
pop ra
pop sp
j ra
pop r8
pop r9
push sp
push ra
add r1 r9 r9
move r15 r1
j 17
add r15 r9 r9
pop ra
pop sp
j ra
@@ -98,10 +96,12 @@ push r9
push r10
push r10
push 2
jal 11
jal 9
pop r10
pop r9
pop r8
move r11 r15
move r15 r11
j 41
pop ra
pop sp
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

@@ -1,5 +1,6 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 116
expression: output
---
## Unoptimized Output
@@ -24,5 +25,10 @@ j ra
j main
pop r8
pop r9
ble r9 r8 4
push sp
push ra
ble r9 r8 7
move r10 1
pop ra
pop sp
j ra

View File

@@ -0,0 +1,111 @@
---
source: libs/integration_tests/src/lib.rs
expression: output
---
## Unoptimized Output
j main
main:
s d2 Mode 1
s d2 On 0
move r8 0
move r9 0
__internal_L1:
yield
l r1 d0 Reagents
move r10 r1
sge r2 r10 100
sge r3 r9 2
or r4 r2 r3
beqz r4 __internal_L3
move r8 1
__internal_L3:
slt r5 r10 100
ls r15 d0 0 Occupied
seq r6 r15 0
and r7 r5 r6
add r1 r9 1
select r2 r7 r1 0
move r9 r2
l r3 d0 Rpm
slt r4 r3 1
and r5 r8 r4
beqz r5 __internal_L4
s d0 Open 1
seq r6 r10 0
ls r15 d0 0 Occupied
and r7 r6 r15
seq r1 r7 0
move r8 r1
__internal_L4:
seq r6 r8 0
s d0 On r6
ls r15 d1 0 Quantity
move r11 r15
l r7 d3 Pressure
sgt r1 r7 200
beqz r1 __internal_L5
j __internal_L1
__internal_L5:
sgt r2 r11 0
s d1 On r2
sgt r3 r11 0
s d1 Activate r3
l r4 d3 Pressure
sgt r5 r4 0
l r6 d1 Activate
or r7 r5 r6
s d2 On r7
l r1 d1 Activate
s db Setting r1
j __internal_L1
__internal_L2:
## Optimized Output
s d2 Mode 1
s d2 On 0
move r8 0
move r9 0
yield
l r10 d0 Reagents
sge r2 r10 100
sge r3 r9 2
or r4 r2 r3
beqz r4 11
move r8 1
slt r5 r10 100
ls r15 d0 0 Occupied
seq r6 r15 0
and r7 r5 r6
add r1 r9 1
select r2 r7 r1 0
move r9 r2
l r3 d0 Rpm
slt r4 r3 1
and r5 r8 r4
beqz r5 27
s d0 Open 1
seq r6 r10 0
ls r15 d0 0 Occupied
and r7 r6 r15
seq r8 r7 0
seq r6 r8 0
s d0 On r6
ls r15 d1 0 Quantity
move r11 r15
l r7 d3 Pressure
ble r7 200 34
j 4
sgt r2 r11 0
s d1 On r2
sgt r3 r11 0
s d1 Activate r3
l r4 d3 Pressure
sgt r5 r4 0
l r6 d1 Activate
or r7 r5 r6
s d2 On r7
l r1 d1 Activate
s db Setting r1
j 4

View File

@@ -1,5 +1,6 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 133
expression: output
---
## Unoptimized Output
@@ -29,9 +30,7 @@ j main
pop r8
push sp
push ra
select r9 r8 10 20
move r15 r9
j 7
select r15 r8 10 20
pop ra
pop sp
j ra

View File

@@ -0,0 +1,28 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 242
expression: output
---
## Unoptimized Output
j main
main:
__internal_L1:
l r1 20088 Horizontal
sb -539224550 Horizontal r1
l r2 20088 Vertical
add r3 r2 90
sb -539224550 Vertical r3
yield
j __internal_L1
__internal_L2:
## Optimized Output
l r1 20088 Horizontal
sb -539224550 Horizontal r1
l r2 20088 Vertical
add r3 r2 90
sb -539224550 Vertical r3
yield
j 0

View File

@@ -1,5 +1,6 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 60
expression: output
---
## Unoptimized Output
@@ -17,4 +18,9 @@ j ra
## Optimized Output
j main
push sp
push ra
move r8 10
pop ra
pop sp
j ra

View File

@@ -1,5 +1,6 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 91
expression: output
---
## Unoptimized Output
@@ -23,9 +24,7 @@ j main
pop r8
push sp
push ra
add r1 r8 r8
move r15 r1
j 7
add r15 r8 r8
pop ra
pop sp
j ra

View File

@@ -0,0 +1,93 @@
---
source: libs/integration_tests/src/lib.rs
assertion_line: 206
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 23
pop r8
push sp
push ra
add r15 r8 1
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 r15 db r0
sub r0 sp 4
get ra db r0
j ra
yield
jal 8
pop r0
pop r9
pop r8
move sp r15
jal 8
pop r0
pop r0
pop r9
move sp r15
s db Setting r9
j 23

View File

@@ -0,0 +1,49 @@
device combustion = "d0";
device furnace = "d1";
device vent = "d2";
device gasSensor = "d3";
device self = "db";
const MAX_WAIT_ITER = 2;
const STACK_SIZE = 100;
vent.Mode = 1; // Vent inward into pipes
vent.On = false;
let ejecting = false;
let combustionWaitIter = 0;
loop {
yield();
let reagentCount = combustion.Reagents;
if (reagentCount >= STACK_SIZE || combustionWaitIter >= MAX_WAIT_ITER) {
ejecting = true;
}
combustionWaitIter = (reagentCount < STACK_SIZE && !ls(combustion, 0, "Occupied"))
? combustionWaitIter + 1
: 0;
if (ejecting && combustion.Rpm < 1) {
combustion.Open = true;
ejecting = !(reagentCount == 0 && ls(combustion, 0, "Occupied"));
}
combustion.On = !ejecting;
let furnaceAmt = ls(furnace, 0, "Quantity");
if (gasSensor.Pressure > 200) {
// safety: don't turn this on if we have gas still to process
// This should prevent pipes from blowing. This will NOT hault
// The in-progress burn job, but it'll prevent new jobs from
// blowing the walls or pipes.
continue;
}
furnace.On = furnaceAmt > 0;
furnace.Activate = furnaceAmt > 0;
vent.On = gasSensor.Pressure > 0 || furnace.Activate;
self.Setting = furnace.Activate;
}

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

@@ -25,24 +25,12 @@ pub fn constant_propagation<'a>(
Instruction::Add(dst, a, b) => try_fold_math(dst, a, b, &registers, |x, y| x + y),
Instruction::Sub(dst, a, b) => try_fold_math(dst, a, b, &registers, |x, y| x - y),
Instruction::Mul(dst, a, b) => try_fold_math(dst, a, b, &registers, |x, y| x * y),
Instruction::Div(dst, a, b) => {
try_fold_math(
dst,
a,
b,
&registers,
|x, y| if y.is_zero() { x } else { x / y },
)
}
Instruction::Mod(dst, a, b) => {
try_fold_math(
dst,
a,
b,
&registers,
|x, y| if y.is_zero() { x } else { x % y },
)
}
Instruction::Div(dst, a, b) => try_fold_math(dst, a, b, &registers, |x, y| {
if y.is_zero() { Decimal::ZERO } else { x / y }
}),
Instruction::Mod(dst, a, b) => try_fold_math(dst, a, b, &registers, |x, y| {
if y.is_zero() { Decimal::ZERO } else { x % y }
}),
Instruction::BranchEq(a, b, l) => {
try_resolve_branch(a, b, l, &registers, |x, y| x == y)
}

View File

@@ -1,4 +1,4 @@
use il::{Instruction, InstructionNode};
use il::{Instruction, InstructionNode, Operand};
/// Pass: Redundant Move Elimination
/// Removes moves where source and destination are the same: `move rx rx`
@@ -43,6 +43,72 @@ pub fn remove_unreachable_code<'a>(
(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)]
mod tests {
use super::*;

View File

@@ -7,24 +7,43 @@ use std::collections::HashMap;
pub fn dead_store_elimination<'a>(
input: Vec<InstructionNode<'a>>,
) -> (Vec<InstructionNode<'a>>, bool) {
let mut changed = false;
// Forward pass: Remove writes that are immediately overwritten
let (input, forward_changed) = eliminate_overwritten_stores(input);
// Note: Backward pass disabled for now - it needs more work to handle all cases correctly
// The forward pass is sufficient for most common patterns
// (e.g., move r6 r15 immediately followed by move r6 r15 again)
(input, forward_changed)
}
/// Forward pass: Remove stores that are overwritten before being read
fn eliminate_overwritten_stores<'a>(
input: Vec<InstructionNode<'a>>,
) -> (Vec<InstructionNode<'a>>, bool) {
let mut last_write: HashMap<u8, usize> = HashMap::new();
let mut to_remove = Vec::new();
// Scan for dead writes
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 this register was written before and hasn't been read, previous write is dead
if let Some(&prev_idx) = last_write.get(&dest_reg) {
// Check if the value was ever used between prev_idx and current
let was_used = input[prev_idx + 1..i]
.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 {
// Previous write was dead
to_remove.push(prev_idx);
changed = true;
}
}
@@ -32,34 +51,31 @@ pub fn dead_store_elimination<'a>(
last_write.insert(dest_reg, i);
}
// Before clearing on labels/calls, check if current tracked writes are dead
if matches!(
node.instruction,
Instruction::LabelDef(_) | Instruction::JumpAndLink(_)
) {
// Check all currently tracked writes to see if they're dead
for (&reg, &idx) in &last_write {
// Don't remove writes to r15 (return register)
if reg == 15 {
continue;
// Handle control flow instructions
match &node.instruction {
// JumpAndLink (function calls) only clobbers the return register (r15)
// We can continue tracking other registers across function calls
Instruction::JumpAndLink(_) => {
last_write.remove(&15);
}
// Check if this write was used between write and now
let was_used = input[idx + 1..i]
.iter()
.any(|n| reg_is_read_or_affects_control(&n.instruction, reg));
if !was_used && !to_remove.contains(&idx) {
to_remove.push(idx);
changed = true;
}
}
// Other control flow instructions create complexity - clear all tracking
Instruction::Jump(_)
| Instruction::LabelDef(_)
| Instruction::BranchEq(_, _, _)
| Instruction::BranchNe(_, _, _)
| Instruction::BranchGt(_, _, _)
| Instruction::BranchLt(_, _, _)
| Instruction::BranchGe(_, _, _)
| Instruction::BranchLe(_, _, _)
| Instruction::BranchEqZero(_, _)
| Instruction::BranchNeZero(_, _) => {
last_write.clear();
}
_ => {}
}
}
if changed {
if !to_remove.is_empty() {
let output = input
.into_iter()
.enumerate()

View File

@@ -125,6 +125,9 @@ pub fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
| Instruction::Pow(_, a, b) => check(a) || check(b),
Instruction::Load(_, a, _) => check(a),
Instruction::Store(a, _, b) => check(a) || check(b),
Instruction::StoreBatch(a, _, b) => check(a) || check(b),
Instruction::StoreBatchNamed(a, b, _, c) => check(a) || check(b) || check(c),
Instruction::StoreSlot(a, b, _, c) => check(a) || check(b) || check(c),
Instruction::BranchEq(a, b, _)
| Instruction::BranchNe(a, b, _)
| Instruction::BranchGt(a, b, _)
@@ -167,6 +170,8 @@ pub fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
Instruction::Atan2(_, a, b) | Instruction::Max(_, a, b) | Instruction::Min(_, a, b) => {
check(a) || check(b)
}
Instruction::JumpRelative(a) => check(a),
Instruction::Alias(_, a) => check(a),
_ => false,
}
}

View File

@@ -1,150 +1,41 @@
use crate::leaf_function::find_leaf_functions;
use il::{Instruction, InstructionNode, Operand};
use rust_decimal::Decimal;
use std::collections::{HashMap, HashSet};
/// Helper: Check if a function body contains unsafe stack manipulation.
fn function_has_complex_stack_ops(
instructions: &[InstructionNode],
start_idx: usize,
end_idx: usize,
) -> bool {
for instruction in instructions.iter().take(end_idx).skip(start_idx) {
match instruction.instruction {
Instruction::Push(_) | Instruction::Pop(_) => return true,
Instruction::Add(Operand::StackPointer, _, _)
| Instruction::Sub(Operand::StackPointer, _, _)
| Instruction::Mul(Operand::StackPointer, _, _)
| Instruction::Div(Operand::StackPointer, _, _)
| Instruction::Move(Operand::StackPointer, _) => return true,
_ => {}
}
}
false
}
use il::InstructionNode;
/// Pass: Leaf Function Optimization
/// If a function makes no calls (is a leaf), it doesn't need to save/restore `ra`.
///
/// NOTE: This optimization is DISABLED due to correctness issues.
/// The optimization was designed for a specific calling convention (GET/PUT for RA)
/// but the compiler generates POP ra for return address restoration. Without proper
/// tracking of both conventions and validation of balanced push/pop pairs, this
/// optimization corrupts the stack frame by:
///
/// 1. Removing `push ra` but not `pop ra`, leaving unbalanced push/pop pairs
/// 2. Not accounting for parameter pops that occur before `push sp`
/// 3. Assuming all RA restoration uses GET instruction, but code uses POP
///
/// Example of broken optimization:
/// ```
/// Unoptimized: Optimized (BROKEN):
/// compare: pop r8
/// pop r8 pop r9
/// pop r9 ble r9 r8 5
/// push sp move r10 1
/// push ra j ra
/// sgt r1 r9 r8 ^ Missing stack frame!
/// ...
/// pop ra
/// pop sp
/// j ra
/// ```
///
/// Future work: Fix by handling both POP and GET calling conventions, validating
/// balanced push/pop pairs, and accounting for parameter pops.
pub fn optimize_leaf_functions<'a>(
input: Vec<InstructionNode<'a>>,
) -> (Vec<InstructionNode<'a>>, bool) {
let leaves = find_leaf_functions(&input);
if leaves.is_empty() {
return (input, false);
}
let mut changed = false;
let mut to_remove = HashSet::new();
let mut func_restore_indices = HashMap::new();
let mut func_ra_offsets = HashMap::new();
let mut current_function: Option<String> = None;
let mut function_start_indices = HashMap::new();
// First scan: Identify instructions to remove and capture RA offsets
for (i, node) in input.iter().enumerate() {
match &node.instruction {
Instruction::LabelDef(label) if !label.starts_with("__internal_L") => {
current_function = Some(label.to_string());
function_start_indices.insert(label.to_string(), i);
}
Instruction::Push(Operand::ReturnAddress) => {
if let Some(func) = &current_function
&& leaves.contains(func)
{
to_remove.insert(i);
}
}
Instruction::Get(Operand::ReturnAddress, _, Operand::Register(_)) => {
if let Some(func) = &current_function
&& leaves.contains(func)
{
to_remove.insert(i);
func_restore_indices.insert(func.clone(), i);
// Look back for the address calc: `sub r0 sp OFFSET`
if i > 0
&& let Instruction::Sub(_, Operand::StackPointer, Operand::Number(n)) =
&input[i - 1].instruction
{
func_ra_offsets.insert(func.clone(), *n);
to_remove.insert(i - 1);
}
}
}
_ => {}
}
}
// Safety Check: Verify functions don't have complex stack ops
let mut safe_functions = HashSet::new();
for (func, start_idx) in &function_start_indices {
if let Some(restore_idx) = func_restore_indices.get(func) {
let check_start = if to_remove.contains(&(start_idx + 1)) {
start_idx + 2
} else {
start_idx + 1
};
if !function_has_complex_stack_ops(&input, check_start, *restore_idx) {
safe_functions.insert(func.clone());
changed = true;
}
}
}
if !changed {
return (input, false);
}
// Second scan: Rebuild with adjustments
let mut output = Vec::with_capacity(input.len());
let mut processing_function: Option<String> = None;
for (i, mut node) in input.into_iter().enumerate() {
if to_remove.contains(&i)
&& let Some(func) = &processing_function
&& safe_functions.contains(func)
{
continue;
}
if let Instruction::LabelDef(l) = &node.instruction
&& !l.starts_with("__internal_L")
{
processing_function = Some(l.to_string());
}
// Apply Stack Adjustments
if let Some(func) = &processing_function
&& safe_functions.contains(func)
&& let Some(ra_offset) = func_ra_offsets.get(func)
{
// Stack Cleanup Adjustment
if let Instruction::Sub(
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(n),
) = &mut node.instruction
{
let new_n = *n - Decimal::from(1);
if new_n.is_zero() {
continue;
}
*n = new_n;
}
// Stack Variable Offset Adjustment
if let Instruction::Sub(_, Operand::StackPointer, Operand::Number(n)) =
&mut node.instruction
&& *n > *ra_offset
{
*n -= Decimal::from(1);
}
}
output.push(node);
}
(output, true)
// Optimization disabled - returns input unchanged
#[allow(unused)]
let _leaves = find_leaf_functions(&input);
(input, false)
}

View File

@@ -17,7 +17,7 @@ mod strength_reduction;
use algebraic_simplification::algebraic_simplification;
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 function_call_optimization::optimize_function_calls;
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
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)
}

View File

@@ -358,11 +358,12 @@ fn find_matching_ra_pop<'a>(
return Some((idx, &instructions[1..idx]));
}
// Stop searching if we hit a jump (different control flow)
// Labels are OK - they're just markers
// Stop searching if we hit a jump (different control flow) or a function label
// Labels are OK - they're just markers EXCEPT for user-defined function labels
// which indicate a function boundary
if matches!(
node.instruction,
Instruction::Jump(_) | Instruction::JumpRelative(_)
Instruction::Jump(_) | Instruction::JumpRelative(_) | Instruction::LabelDef(_)
) {
return None;
}

View File

@@ -1,5 +1,6 @@
use crate::helpers::{get_destination_reg, reg_is_read, set_destination_reg};
use il::{Instruction, InstructionNode};
use il::{Instruction, InstructionNode, Operand};
use std::collections::HashMap;
/// Pass: Register Forwarding
/// Eliminates intermediate moves by writing directly to the final destination.
@@ -10,6 +11,20 @@ pub fn register_forwarding<'a>(
let mut changed = false;
let mut i = 0;
// Build a map of label positions to detect backward jumps
// Use String keys to avoid lifetime issues with references into input
let label_positions: HashMap<String, usize> = input
.iter()
.enumerate()
.filter_map(|(idx, node)| {
if let Instruction::LabelDef(label) = &node.instruction {
Some((label.to_string(), idx))
} else {
None
}
})
.collect();
while i < input.len().saturating_sub(1) {
let next_idx = i + 1;
@@ -48,17 +63,44 @@ pub fn register_forwarding<'a>(
break;
}
// Conservative: assume liveness might leak at labels/jumps
if matches!(
node.instruction,
Instruction::LabelDef(_) | Instruction::Jump(_) | Instruction::JumpAndLink(_)
) {
// Function calls (jal) clobber the return register (r15)
// So if we're tracking r15 and hit a function call, the old value is dead
if matches!(node.instruction, Instruction::JumpAndLink(_)) && temp_reg == 15 {
break;
}
// Labels are just markers - they don't affect register liveness
// But backward jumps create loops we need to analyze carefully
let jump_target = match &node.instruction {
Instruction::Jump(Operand::Label(target)) => Some(target.as_ref()),
Instruction::BranchEq(_, _, Operand::Label(target))
| Instruction::BranchNe(_, _, Operand::Label(target))
| Instruction::BranchGt(_, _, Operand::Label(target))
| Instruction::BranchLt(_, _, Operand::Label(target))
| Instruction::BranchGe(_, _, Operand::Label(target))
| Instruction::BranchLe(_, _, Operand::Label(target))
| Instruction::BranchEqZero(_, Operand::Label(target))
| Instruction::BranchNeZero(_, Operand::Label(target)) => Some(target.as_ref()),
_ => None,
};
if let Some(target) = jump_target {
// Check if this is a backward jump (target appears before current position)
if let Some(&target_pos) = label_positions.get(target) {
if target_pos < i {
// Backward jump - could loop back, register might be live
temp_is_dead = false;
break;
}
// Forward jump is OK - doesn't affect liveness before it
}
}
}
if temp_is_dead {
// Safety check: ensure final_reg is not used as an operand in the current instruction.
// This prevents generating invalid instructions like `sub r5 r0 r5` (read and write same register).
if !reg_is_read(&input[i].instruction, final_reg) {
// Rewrite to use final destination directly
if let Some(new_instr) = set_destination_reg(&input[i].instruction, final_reg) {
input[i].instruction = new_instr;
@@ -68,6 +110,7 @@ pub fn register_forwarding<'a>(
}
}
}
}
i += 1;
}