Compare commits
12 Commits
95c17b563c
...
0.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
| d8fe9a0d7d | |||
|
089fe46d36
|
|||
|
14c641797a
|
|||
| fb5eacea02 | |||
|
9fd3a55182
|
|||
| 397aa47217 | |||
|
6f86563863
|
|||
|
352041746c
|
|||
|
5f4335dbcc
|
|||
|
2a5dfd9ab6
|
|||
|
2dfe36f8be
|
|||
|
d28cdfcc7b
|
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
@@ -80,10 +80,14 @@ cargo test --package compiler --lib -- test::tuple_literals::test::test_tuple_li
|
|||||||
|
|
||||||
### Quick Compilation
|
### Quick Compilation
|
||||||
|
|
||||||
|
!IMPORTANT: make sure you use these commands instead of creating temporary files.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd rust_compiler
|
cd rust_compiler
|
||||||
# Compile Slang code to IC10 using current compiler changes
|
# Compile Slang code to IC10 using current compiler changes
|
||||||
echo 'let x = 5;' | cargo run --bin slang
|
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
|
# Or from file
|
||||||
cargo run --bin slang -- input.slang -o output.ic10
|
cargo run --bin slang -- input.slang -o output.ic10
|
||||||
# Optimize the output with -z flag
|
# Optimize the output with -z flag
|
||||||
|
|||||||
16
Changelog.md
16
Changelog.md
@@ -1,11 +1,19 @@
|
|||||||
# Changelog
|
# 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]
|
[0.5.0]
|
||||||
|
|
||||||
- Added support for tuple types
|
- Added full tuple support: declarations, assignments, and returns
|
||||||
- Added support for tuple returns from functions
|
- Refactored optimizer into modular passes with improved code generation
|
||||||
- Added support for ignoring tuple values
|
- Enhanced peephole optimizations and pattern recognition
|
||||||
- Fixed various compiler bugs
|
- Comprehensive test coverage for edge cases and error handling
|
||||||
|
|
||||||
[0.4.7]
|
[0.4.7]
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
<ModMetadata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<Name>Slang</Name>
|
<Name>Slang</Name>
|
||||||
<Author>JoeDiertay</Author>
|
<Author>JoeDiertay</Author>
|
||||||
<Version>0.5.0</Version>
|
<Version>0.5.1</Version>
|
||||||
<Description>
|
<Description>
|
||||||
[h1]Slang: High-Level Programming for Stationeers[/h1]
|
[h1]Slang: High-Level Programming for Stationeers[/h1]
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Slang
|
|||||||
{
|
{
|
||||||
public const string PluginGuid = "com.biddydev.slang";
|
public const string PluginGuid = "com.biddydev.slang";
|
||||||
public const string PluginName = "Slang";
|
public const string PluginName = "Slang";
|
||||||
public const string PluginVersion = "0.5.0";
|
public const string PluginVersion = "0.5.1";
|
||||||
|
|
||||||
private static Harmony? _harmony;
|
private static Harmony? _harmony;
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +1,73 @@
|
|||||||
|
using System;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Slang;
|
namespace Slang;
|
||||||
|
|
||||||
public static class TextMeshProFormatter
|
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)
|
public static string FromMarkdown(string markdown)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(markdown))
|
if (string.IsNullOrEmpty(markdown))
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
// 1. Normalize Line Endings
|
// Normalize Line Endings
|
||||||
string text = markdown.Replace("\r\n", "\n");
|
string text = markdown.Replace("\r\n", "\n");
|
||||||
|
|
||||||
// 2. Handle Code Blocks (```)
|
// Process code blocks FIRST (``` ... ```)
|
||||||
text = Regex.Replace(
|
text = Regex.Replace(
|
||||||
text,
|
text,
|
||||||
@"```\s*(.*?)\s*```",
|
@"```[^\n]*\n(.*?)\n```",
|
||||||
match =>
|
match =>
|
||||||
{
|
{
|
||||||
var codeContent = match.Groups[1].Value;
|
var codeContent = match.Groups[1].Value;
|
||||||
return $"<color={CODE_COLOR}>{codeContent}</color>"; // Gold color for code
|
return $"<color={CODE_COLOR}>{codeContent}</color>";
|
||||||
},
|
},
|
||||||
RegexOptions.Singleline
|
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 = Regex.Replace(
|
||||||
text,
|
text,
|
||||||
@"^\s*##\s+(.+)$",
|
@"\[([^\]]+)\]\(([^\)]+)\)",
|
||||||
"<size=110%><color=#ffffff><b>$1</b></color></size>",
|
$"<color={LINK_COLOR}><u>$1</u></color>"
|
||||||
RegexOptions.Multiline
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Handle # Headers SECOND (General)
|
// Process inline code (`code`)
|
||||||
text = Regex.Replace(
|
|
||||||
text,
|
|
||||||
@"^\s*#\s+(.+)$",
|
|
||||||
"<size=120%><color=#ffffff><b>$1</b></color></size>",
|
|
||||||
RegexOptions.Multiline
|
|
||||||
);
|
|
||||||
|
|
||||||
// 4. Handle Inline Code (`code`)
|
|
||||||
text = Regex.Replace(text, @"`([^`]+)`", $"<color={CODE_COLOR}>$1</color>");
|
text = Regex.Replace(text, @"`([^`]+)`", $"<color={CODE_COLOR}>$1</color>");
|
||||||
|
|
||||||
// 5. Handle Bold (**text**)
|
// Process bold (**text**)
|
||||||
text = Regex.Replace(text, @"\*\*(.+?)\*\*", "<b>$1</b>");
|
text = Regex.Replace(text, @"\*\*(.+?)\*\*", "<b>$1</b>");
|
||||||
|
|
||||||
// 6. Handle Italics (*text*)
|
// Process italics (*text*)
|
||||||
text = Regex.Replace(text, @"\*(.+?)\*", "<i>$1</i>");
|
text = Regex.Replace(text, @"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)", "<i>$1</i>");
|
||||||
|
|
||||||
// 7. Convert Newlines to TMP Line Breaks
|
// Process block quotes (> text)
|
||||||
// Stationpedia needs <br> or explicit newlines.
|
text = Regex.Replace(
|
||||||
// Often just ensuring \n is preserved is enough, but <br> is safer for HTML-like parsers.
|
text,
|
||||||
text = text.Replace("\n", "<br>");
|
@"^>\s+(.+)$",
|
||||||
|
$"<color={QUOTE_COLOR}><i>$1</i></color>",
|
||||||
|
RegexOptions.Multiline
|
||||||
|
);
|
||||||
|
|
||||||
|
// Process unordered lists (- items)
|
||||||
|
text = Regex.Replace(
|
||||||
|
text,
|
||||||
|
@"^-\s+(.+)$",
|
||||||
|
" • $1",
|
||||||
|
RegexOptions.Multiline
|
||||||
|
);
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyName>StationeersSlang</AssemblyName>
|
<AssemblyName>StationeersSlang</AssemblyName>
|
||||||
<Description>Slang Compiler Bridge</Description>
|
<Description>Slang Compiler Bridge</Description>
|
||||||
<Version>0.5.0</Version>
|
<Version>0.5.1</Version>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
2
rust_compiler/Cargo.lock
generated
2
rust_compiler/Cargo.lock
generated
@@ -1039,7 +1039,7 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slang"
|
name = "slang"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "slang"
|
name = "slang"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crc32fast::hash as crc32_hash;
|
use crc32fast::hash as crc32_hash;
|
||||||
|
|
||||||
/// This function takes an input which is meant to be hashed via the CRC32 algorithm, but it then
|
/// 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.
|
/// converts the generated UNSIGNED number into it's SIGNED counterpart.
|
||||||
pub fn crc_hash_signed(input: &str) -> i128 {
|
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
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod helper_funcs;
|
mod helper_funcs;
|
||||||
|
pub use helper_funcs::dedent;
|
||||||
mod macros;
|
mod macros;
|
||||||
mod syscall;
|
mod syscall;
|
||||||
|
|
||||||
|
|||||||
@@ -99,12 +99,12 @@ macro_rules! documented {
|
|||||||
),*
|
),*
|
||||||
];
|
];
|
||||||
|
|
||||||
doc_lines.iter()
|
let combined = doc_lines.iter()
|
||||||
.filter_map(|&d| d)
|
.filter_map(|&d| d)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n")
|
.join("\n");
|
||||||
.trim()
|
|
||||||
.to_string()
|
$crate::dedent(&combined).trim().to_string()
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
@@ -122,12 +122,13 @@ macro_rules! documented {
|
|||||||
documented!(@doc_filter #[ $($variant_attr)* ])
|
documented!(@doc_filter #[ $($variant_attr)* ])
|
||||||
),*
|
),*
|
||||||
];
|
];
|
||||||
doc_lines.iter()
|
|
||||||
|
let combined = doc_lines.iter()
|
||||||
.filter_map(|&d| d)
|
.filter_map(|&d| d)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n")
|
.join("\n");
|
||||||
.trim()
|
|
||||||
.to_string()
|
$crate::dedent(&combined).trim().to_string()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),*
|
),*
|
||||||
@@ -136,4 +137,3 @@ macro_rules! documented {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ mod tests {
|
|||||||
let compiler = Compiler::new(parser, None);
|
let compiler = Compiler::new(parser, None);
|
||||||
let result = compiler.compile();
|
let result = compiler.compile();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.errors.is_empty(),
|
||||||
|
"Compilation errors: {:?}",
|
||||||
|
result.errors
|
||||||
|
);
|
||||||
|
|
||||||
// Get unoptimized output
|
// Get unoptimized output
|
||||||
let mut unoptimized_writer = std::io::BufWriter::new(Vec::new());
|
let mut unoptimized_writer = std::io::BufWriter::new(Vec::new());
|
||||||
result
|
result
|
||||||
@@ -212,4 +218,27 @@ 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_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 158
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -34,15 +35,11 @@ pop r9
|
|||||||
pop r10
|
pop r10
|
||||||
push sp
|
push sp
|
||||||
push ra
|
push ra
|
||||||
add r1 r10 r10
|
add r11 r10 r10
|
||||||
move r11 r1
|
move r12 r9
|
||||||
move r2 r9
|
move r13 r8
|
||||||
move r12 r2
|
|
||||||
move r3 r8
|
|
||||||
move r13 r3
|
|
||||||
add r4 r11 r12
|
add r4 r11 r12
|
||||||
add r5 r4 r13
|
add r15 r4 r13
|
||||||
move r15 r5
|
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 103
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -24,8 +25,8 @@ j main
|
|||||||
pop r8
|
pop r8
|
||||||
push sp
|
push sp
|
||||||
push ra
|
push ra
|
||||||
add r1 r8 1
|
move r9 20
|
||||||
move r15 r1
|
add r15 r8 1
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 70
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -31,13 +32,12 @@ j ra
|
|||||||
|
|
||||||
## Optimized Output
|
## Optimized Output
|
||||||
|
|
||||||
j 10
|
j 9
|
||||||
pop r8
|
pop r8
|
||||||
pop r9
|
pop r9
|
||||||
push sp
|
push sp
|
||||||
push ra
|
push ra
|
||||||
add r1 r9 r8
|
add r15 r9 r8
|
||||||
move r15 r1
|
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
@@ -46,6 +46,7 @@ push ra
|
|||||||
push 5
|
push 5
|
||||||
push 10
|
push 10
|
||||||
jal 1
|
jal 1
|
||||||
|
move r8 r15
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ __internal_L12:
|
|||||||
|
|
||||||
## Optimized Output
|
## Optimized Output
|
||||||
|
|
||||||
j 71
|
j 77
|
||||||
push sp
|
push sp
|
||||||
push ra
|
push ra
|
||||||
yield
|
yield
|
||||||
@@ -139,8 +139,10 @@ push sp
|
|||||||
push ra
|
push ra
|
||||||
s d0 Setting 1
|
s d0 Setting 1
|
||||||
jal 1
|
jal 1
|
||||||
|
move r1 r15
|
||||||
s d0 Activate 1
|
s d0 Activate 1
|
||||||
jal 1
|
jal 1
|
||||||
|
move r2 r15
|
||||||
s d1 Open 0
|
s d1 Open 0
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
@@ -152,53 +154,58 @@ sle r1 r8 1
|
|||||||
ls r15 d0 255 Seeding
|
ls r15 d0 255 Seeding
|
||||||
slt r2 r15 1
|
slt r2 r15 1
|
||||||
or r3 r1 r2
|
or r3 r1 r2
|
||||||
beqz r3 30
|
beqz r3 32
|
||||||
j 68
|
j 74
|
||||||
ls r15 d0 255 Mature
|
ls r15 d0 255 Mature
|
||||||
beqz r15 35
|
beqz r15 37
|
||||||
yield
|
yield
|
||||||
s d0 Activate 1
|
s d0 Activate 1
|
||||||
j 30
|
j 32
|
||||||
ls r15 d0 255 Occupied
|
ls r9 d0 255 Occupied
|
||||||
move r9 r15
|
|
||||||
s d0 Setting 1
|
s d0 Setting 1
|
||||||
push r8
|
push r8
|
||||||
push r9
|
push r9
|
||||||
jal 1
|
jal 1
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
|
move r4 r15
|
||||||
push r8
|
push r8
|
||||||
push r9
|
push r9
|
||||||
jal 11
|
jal 11
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
beqz r9 54
|
move r5 r15
|
||||||
|
beqz r9 58
|
||||||
push r8
|
push r8
|
||||||
push r9
|
push r9
|
||||||
jal 11
|
jal 11
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
|
move r6 r15
|
||||||
s d0 Setting r8
|
s d0 Setting r8
|
||||||
push r8
|
push r8
|
||||||
push r9
|
push r9
|
||||||
jal 1
|
jal 1
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
|
move r6 r15
|
||||||
ls r15 d0 0 Occupied
|
ls r15 d0 0 Occupied
|
||||||
beqz r15 63
|
beqz r15 68
|
||||||
s d0 Activate 1
|
s d0 Activate 1
|
||||||
push r8
|
push r8
|
||||||
push r9
|
push r9
|
||||||
jal 1
|
jal 1
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
|
move r7 r15
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
|
move r8 0
|
||||||
yield
|
yield
|
||||||
l r1 d0 Idle
|
l r1 d0 Idle
|
||||||
bne r1 0 75
|
bne r1 0 82
|
||||||
j 71
|
j 78
|
||||||
add r3 r8 1
|
add r3 r8 1
|
||||||
sgt r4 r3 19
|
sgt r4 r3 19
|
||||||
add r5 r8 1
|
add r5 r8 1
|
||||||
@@ -207,8 +214,10 @@ move r9 r6
|
|||||||
push r8
|
push r8
|
||||||
push r9
|
push r9
|
||||||
push r8
|
push r8
|
||||||
jal 21
|
jal 23
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
|
move r7 r15
|
||||||
s d0 Setting r9
|
s d0 Setting r9
|
||||||
j 71
|
move r8 r9
|
||||||
|
j 78
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 144
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -20,4 +21,10 @@ j ra
|
|||||||
|
|
||||||
j main
|
j main
|
||||||
pop r8
|
pop r8
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
add r1 r8 1
|
||||||
|
move r8 r1
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 173
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -66,8 +67,7 @@ pop r8
|
|||||||
pop r9
|
pop r9
|
||||||
push sp
|
push sp
|
||||||
push ra
|
push ra
|
||||||
add r1 r9 r8
|
add r15 r9 r8
|
||||||
move r15 r1
|
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
@@ -75,8 +75,7 @@ pop r8
|
|||||||
pop r9
|
pop r9
|
||||||
push sp
|
push sp
|
||||||
push ra
|
push ra
|
||||||
add r1 r9 r9
|
add r15 r9 r9
|
||||||
move r15 r1
|
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
@@ -97,7 +96,7 @@ push r9
|
|||||||
push r10
|
push r10
|
||||||
push r10
|
push r10
|
||||||
push 2
|
push 2
|
||||||
jal 10
|
jal 9
|
||||||
pop r10
|
pop r10
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 116
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -24,5 +25,10 @@ j ra
|
|||||||
j main
|
j main
|
||||||
pop r8
|
pop r8
|
||||||
pop r9
|
pop r9
|
||||||
ble r9 r8 4
|
push sp
|
||||||
|
push ra
|
||||||
|
ble r9 r8 7
|
||||||
|
move r10 1
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 133
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -29,8 +30,7 @@ j main
|
|||||||
pop r8
|
pop r8
|
||||||
push sp
|
push sp
|
||||||
push ra
|
push ra
|
||||||
select r9 r8 10 20
|
select r15 r8 10 20
|
||||||
move r15 r9
|
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 60
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -17,4 +18,9 @@ j ra
|
|||||||
## Optimized Output
|
## Optimized Output
|
||||||
|
|
||||||
j main
|
j main
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
move r8 10
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 91
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -23,8 +24,7 @@ j main
|
|||||||
pop r8
|
pop r8
|
||||||
push sp
|
push sp
|
||||||
push ra
|
push ra
|
||||||
add r1 r8 r8
|
add r15 r8 r8
|
||||||
move r15 r1
|
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: libs/integration_tests/src/lib.rs
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 206
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
## Unoptimized Output
|
## Unoptimized Output
|
||||||
@@ -54,12 +55,11 @@ __internal_L4:
|
|||||||
|
|
||||||
## Optimized Output
|
## Optimized Output
|
||||||
|
|
||||||
j 25
|
j 23
|
||||||
pop r8
|
pop r8
|
||||||
push sp
|
push sp
|
||||||
push ra
|
push ra
|
||||||
add r1 r8 1
|
add r15 r8 1
|
||||||
move r15 r1
|
|
||||||
pop ra
|
pop ra
|
||||||
pop sp
|
pop sp
|
||||||
j ra
|
j ra
|
||||||
@@ -74,21 +74,20 @@ jal 1
|
|||||||
move r3 r15
|
move r3 r15
|
||||||
push r3
|
push r3
|
||||||
sub r0 sp 5
|
sub r0 sp 5
|
||||||
get r0 db r0
|
get r15 db r0
|
||||||
move r15 r0
|
|
||||||
sub r0 sp 4
|
sub r0 sp 4
|
||||||
get ra db r0
|
get ra db r0
|
||||||
j ra
|
j ra
|
||||||
yield
|
yield
|
||||||
jal 9
|
jal 8
|
||||||
pop r0
|
pop r0
|
||||||
pop r9
|
pop r9
|
||||||
pop r8
|
pop r8
|
||||||
move sp r15
|
move sp r15
|
||||||
jal 9
|
jal 8
|
||||||
pop r0
|
pop r0
|
||||||
pop r0
|
pop r0
|
||||||
pop r9
|
pop r9
|
||||||
move sp r15
|
move sp r15
|
||||||
s db Setting r9
|
s db Setting r9
|
||||||
j 25
|
j 23
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -25,24 +25,12 @@ pub fn constant_propagation<'a>(
|
|||||||
Instruction::Add(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x + y),
|
Instruction::Add(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x + y),
|
||||||
Instruction::Sub(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x - y),
|
Instruction::Sub(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x - y),
|
||||||
Instruction::Mul(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x * y),
|
Instruction::Mul(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x * y),
|
||||||
Instruction::Div(dst, a, b) => {
|
Instruction::Div(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| {
|
||||||
try_fold_math(
|
if y.is_zero() { Decimal::ZERO } else { x / y }
|
||||||
dst,
|
}),
|
||||||
a,
|
Instruction::Mod(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| {
|
||||||
b,
|
if y.is_zero() { Decimal::ZERO } else { x % y }
|
||||||
®isters,
|
}),
|
||||||
|x, y| if y.is_zero() { x } else { x / y },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Instruction::Mod(dst, a, b) => {
|
|
||||||
try_fold_math(
|
|
||||||
dst,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
®isters,
|
|
||||||
|x, y| if y.is_zero() { x } else { x % y },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Instruction::BranchEq(a, b, l) => {
|
Instruction::BranchEq(a, b, l) => {
|
||||||
try_resolve_branch(a, b, l, ®isters, |x, y| x == y)
|
try_resolve_branch(a, b, l, ®isters, |x, y| x == y)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,20 @@ use std::collections::HashMap;
|
|||||||
pub fn dead_store_elimination<'a>(
|
pub fn dead_store_elimination<'a>(
|
||||||
input: Vec<InstructionNode<'a>>,
|
input: Vec<InstructionNode<'a>>,
|
||||||
) -> (Vec<InstructionNode<'a>>, bool) {
|
) -> (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 last_write: HashMap<u8, usize> = HashMap::new();
|
||||||
let mut to_remove = Vec::new();
|
let mut to_remove = Vec::new();
|
||||||
|
|
||||||
@@ -31,7 +44,6 @@ pub fn dead_store_elimination<'a>(
|
|||||||
if !was_used {
|
if !was_used {
|
||||||
// Previous write was dead
|
// Previous write was dead
|
||||||
to_remove.push(prev_idx);
|
to_remove.push(prev_idx);
|
||||||
changed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,34 +51,31 @@ pub fn dead_store_elimination<'a>(
|
|||||||
last_write.insert(dest_reg, i);
|
last_write.insert(dest_reg, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before clearing on labels/calls, check if current tracked writes are dead
|
// Handle control flow instructions
|
||||||
if matches!(
|
match &node.instruction {
|
||||||
node.instruction,
|
// JumpAndLink (function calls) only clobbers the return register (r15)
|
||||||
Instruction::LabelDef(_) | Instruction::JumpAndLink(_)
|
// We can continue tracking other registers across function calls
|
||||||
) {
|
Instruction::JumpAndLink(_) => {
|
||||||
// Check all currently tracked writes to see if they're dead
|
last_write.remove(&15);
|
||||||
for (®, &idx) in &last_write {
|
|
||||||
// Don't remove writes to r15 (return register)
|
|
||||||
if reg == 15 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
last_write.clear();
|
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
|
let output = input
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
|||||||
@@ -125,6 +125,9 @@ pub fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
|
|||||||
| Instruction::Pow(_, a, b) => check(a) || check(b),
|
| Instruction::Pow(_, a, b) => check(a) || check(b),
|
||||||
Instruction::Load(_, a, _) => check(a),
|
Instruction::Load(_, a, _) => check(a),
|
||||||
Instruction::Store(a, _, b) => check(a) || check(b),
|
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::BranchEq(a, b, _)
|
||||||
| Instruction::BranchNe(a, b, _)
|
| Instruction::BranchNe(a, b, _)
|
||||||
| Instruction::BranchGt(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) => {
|
Instruction::Atan2(_, a, b) | Instruction::Max(_, a, b) | Instruction::Min(_, a, b) => {
|
||||||
check(a) || check(b)
|
check(a) || check(b)
|
||||||
}
|
}
|
||||||
|
Instruction::JumpRelative(a) => check(a),
|
||||||
|
Instruction::Alias(_, a) => check(a),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,150 +1,41 @@
|
|||||||
use crate::leaf_function::find_leaf_functions;
|
use crate::leaf_function::find_leaf_functions;
|
||||||
use il::{Instruction, InstructionNode, Operand};
|
use il::InstructionNode;
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pass: Leaf Function Optimization
|
/// Pass: Leaf Function Optimization
|
||||||
/// If a function makes no calls (is a leaf), it doesn't need to save/restore `ra`.
|
/// 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>(
|
pub fn optimize_leaf_functions<'a>(
|
||||||
input: Vec<InstructionNode<'a>>,
|
input: Vec<InstructionNode<'a>>,
|
||||||
) -> (Vec<InstructionNode<'a>>, bool) {
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
let leaves = find_leaf_functions(&input);
|
// Optimization disabled - returns input unchanged
|
||||||
if leaves.is_empty() {
|
#[allow(unused)]
|
||||||
return (input, false);
|
let _leaves = find_leaf_functions(&input);
|
||||||
}
|
(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) = ¤t_function
|
|
||||||
&& leaves.contains(func)
|
|
||||||
{
|
|
||||||
to_remove.insert(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::Get(Operand::ReturnAddress, _, Operand::Register(_)) => {
|
|
||||||
if let Some(func) = ¤t_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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,11 +358,12 @@ fn find_matching_ra_pop<'a>(
|
|||||||
return Some((idx, &instructions[1..idx]));
|
return Some((idx, &instructions[1..idx]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop searching if we hit a jump (different control flow)
|
// Stop searching if we hit a jump (different control flow) or a function label
|
||||||
// Labels are OK - they're just markers
|
// Labels are OK - they're just markers EXCEPT for user-defined function labels
|
||||||
|
// which indicate a function boundary
|
||||||
if matches!(
|
if matches!(
|
||||||
node.instruction,
|
node.instruction,
|
||||||
Instruction::Jump(_) | Instruction::JumpRelative(_)
|
Instruction::Jump(_) | Instruction::JumpRelative(_) | Instruction::LabelDef(_)
|
||||||
) {
|
) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::helpers::{get_destination_reg, reg_is_read, set_destination_reg};
|
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
|
/// Pass: Register Forwarding
|
||||||
/// Eliminates intermediate moves by writing directly to the final destination.
|
/// 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 changed = false;
|
||||||
let mut i = 0;
|
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) {
|
while i < input.len().saturating_sub(1) {
|
||||||
let next_idx = i + 1;
|
let next_idx = i + 1;
|
||||||
|
|
||||||
@@ -48,23 +63,51 @@ pub fn register_forwarding<'a>(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conservative: assume liveness might leak at labels/jumps
|
// Function calls (jal) clobber the return register (r15)
|
||||||
if matches!(
|
// So if we're tracking r15 and hit a function call, the old value is dead
|
||||||
node.instruction,
|
if matches!(node.instruction, Instruction::JumpAndLink(_)) && temp_reg == 15 {
|
||||||
Instruction::LabelDef(_) | Instruction::Jump(_) | Instruction::JumpAndLink(_)
|
|
||||||
) {
|
|
||||||
temp_is_dead = false;
|
|
||||||
break;
|
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 {
|
if temp_is_dead {
|
||||||
// Rewrite to use final destination directly
|
// Safety check: ensure final_reg is not used as an operand in the current instruction.
|
||||||
if let Some(new_instr) = set_destination_reg(&input[i].instruction, final_reg) {
|
// This prevents generating invalid instructions like `sub r5 r0 r5` (read and write same register).
|
||||||
input[i].instruction = new_instr;
|
if !reg_is_read(&input[i].instruction, final_reg) {
|
||||||
input.remove(next_idx);
|
// Rewrite to use final destination directly
|
||||||
changed = true;
|
if let Some(new_instr) = set_destination_reg(&input[i].instruction, final_reg) {
|
||||||
continue;
|
input[i].instruction = new_instr;
|
||||||
|
input.remove(next_idx);
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user