Compare commits
35 Commits
72e6981176
...
bitwise
| Author | SHA1 | Date | |
|---|---|---|---|
|
b8521917b8
|
|||
|
4ff0ff1b66
|
|||
|
6dc4342ac3
|
|||
|
2070c2e4ca
|
|||
|
4c704b8960
|
|||
|
3c7300d2e1
|
|||
|
647c3d29d5
|
|||
|
e3a38ec110
|
|||
|
1f98ab8d75
|
|||
|
76c5df5dc2
|
|||
|
0999ae8aed
|
|||
|
072a6b9ea6
|
|||
| d8fe9a0d7d | |||
|
089fe46d36
|
|||
|
14c641797a
|
|||
|
f5a28dfd6d
|
|||
|
9966009500
|
|||
|
bc7c77846f
|
|||
|
76add65235
|
|||
|
e56414c251
|
|||
| fb5eacea02 | |||
|
9fd3a55182
|
|||
| 397aa47217 | |||
|
6f86563863
|
|||
|
352041746c
|
|||
|
5f4335dbcc
|
|||
|
2a5dfd9ab6
|
|||
|
2dfe36f8be
|
|||
|
d28cdfcc7b
|
|||
|
95c17b563c
|
|||
|
dbc4c72c3b
|
|||
|
964ad92077
|
|||
|
63f55b66cb
|
|||
|
d19a53bbee
|
|||
|
f87fdc1b0a
|
6
.github/copilot-instructions.md
vendored
6
.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
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
target
|
target
|
||||||
*.ic10
|
*.ic10
|
||||||
|
*.snap.new
|
||||||
release
|
release
|
||||||
csharp_mod/bin
|
csharp_mod/bin
|
||||||
obj
|
obj
|
||||||
|
|||||||
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]
|
||||||
|
|
||||||
|
|||||||
@@ -207,4 +207,34 @@ public static unsafe class SlangExtensions
|
|||||||
Ffi.free_docs_vec(vec);
|
Ffi.free_docs_vec(vec);
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static unsafe List<Symbol> ToList(this Vec_FfiSymbolInfo_t vec)
|
||||||
|
{
|
||||||
|
var toReturn = new List<Symbol>((int)vec.len);
|
||||||
|
|
||||||
|
var currentPtr = vec.ptr;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)vec.len; i++)
|
||||||
|
{
|
||||||
|
var item = currentPtr[i];
|
||||||
|
|
||||||
|
toReturn.Add(
|
||||||
|
new Slang.Symbol
|
||||||
|
{
|
||||||
|
Name = item.name.AsString(),
|
||||||
|
Kind = (SymbolKind)item.kind_data.kind,
|
||||||
|
Span = new Slang.Range
|
||||||
|
{
|
||||||
|
StartLine = item.span.start_line,
|
||||||
|
StartCol = item.span.start_col,
|
||||||
|
EndLine = item.span.end_line,
|
||||||
|
EndCol = item.span.end_col,
|
||||||
|
},
|
||||||
|
Description = item.description.AsString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,51 @@ public unsafe partial class Ffi {
|
|||||||
slice_ref_uint16_t input);
|
slice_ref_uint16_t input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 12)]
|
||||||
|
public unsafe struct FfiSymbolKindData_t {
|
||||||
|
public UInt32 kind;
|
||||||
|
|
||||||
|
public UInt32 arg_count;
|
||||||
|
|
||||||
|
public UInt32 syscall_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 80)]
|
||||||
|
public unsafe struct FfiSymbolInfo_t {
|
||||||
|
public Vec_uint8_t name;
|
||||||
|
|
||||||
|
public FfiSymbolKindData_t kind_data;
|
||||||
|
|
||||||
|
public FfiRange_t span;
|
||||||
|
|
||||||
|
public Vec_uint8_t description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Same as [<c>Vec<T></c>][<c>rust::Vec</c>], but with guaranteed <c>#[repr(C)]</c> layout
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 24)]
|
||||||
|
public unsafe struct Vec_FfiSymbolInfo_t {
|
||||||
|
public FfiSymbolInfo_t * ptr;
|
||||||
|
|
||||||
|
public UIntPtr len;
|
||||||
|
|
||||||
|
public UIntPtr cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 48)]
|
||||||
|
public unsafe struct FfiDiagnosticsAndSymbols_t {
|
||||||
|
public Vec_FfiDiagnostic_t diagnostics;
|
||||||
|
|
||||||
|
public Vec_FfiSymbolInfo_t symbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe partial class Ffi {
|
||||||
|
[DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
|
||||||
|
FfiDiagnosticsAndSymbols_t diagnose_source_with_symbols (
|
||||||
|
slice_ref_uint16_t input);
|
||||||
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 48)]
|
[StructLayout(LayoutKind.Sequential, Size = 48)]
|
||||||
public unsafe struct FfiDocumentedItem_t {
|
public unsafe struct FfiDocumentedItem_t {
|
||||||
public Vec_uint8_t item_name;
|
public Vec_uint8_t item_name;
|
||||||
@@ -184,6 +229,12 @@ public unsafe partial class Ffi {
|
|||||||
Vec_FfiDiagnostic_t v);
|
Vec_FfiDiagnostic_t v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe partial class Ffi {
|
||||||
|
[DllImport(RustLib, ExactSpelling = true)] public static unsafe extern
|
||||||
|
void free_ffi_diagnostics_and_symbols (
|
||||||
|
FfiDiagnosticsAndSymbols_t v);
|
||||||
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 64)]
|
[StructLayout(LayoutKind.Sequential, Size = 64)]
|
||||||
public unsafe struct FfiToken_t {
|
public unsafe struct FfiToken_t {
|
||||||
public Vec_uint8_t tooltip;
|
public Vec_uint8_t tooltip;
|
||||||
|
|||||||
@@ -171,18 +171,17 @@ public class SlangFormatter : ICodeFormatter
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Running this potentially CPU intensive work on a background thread.
|
// Running this potentially CPU intensive work on a background thread.
|
||||||
var dict = await Task.Run(
|
var (diagnostics, symbols) = await Task.Run(
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
return Marshal
|
return Marshal.DiagnoseSourceWithSymbols(inputSrc);
|
||||||
.DiagnoseSource(inputSrc)
|
|
||||||
.GroupBy(d => d.Range.StartLine)
|
|
||||||
.ToDictionary(g => g.Key);
|
|
||||||
},
|
},
|
||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
|
|
||||||
ApplyDiagnostics(dict);
|
var dict = diagnostics.GroupBy(d => d.Range.StartLine).ToDictionary(g => g.Key);
|
||||||
|
|
||||||
|
ApplyDiagnosticsAndSymbols(dict, symbols);
|
||||||
|
|
||||||
// If we have valid code, update the IC10 output
|
// If we have valid code, update the IC10 output
|
||||||
if (dict.Count > 0)
|
if (dict.Count > 0)
|
||||||
@@ -266,11 +265,11 @@ public class SlangFormatter : ICodeFormatter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Takes diagnostics from the Rust FFI compiler and applies it as semantic tokens to the
|
/// Takes diagnostics and symbols from the Rust FFI compiler and applies them as semantic tokens to the
|
||||||
/// source in this editor.
|
/// source in this editor.
|
||||||
/// This runs on the Main Thread
|
/// This runs on the Main Thread
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ApplyDiagnostics(Dictionary<uint, IGrouping<uint, Diagnostic>> dict)
|
private void ApplyDiagnosticsAndSymbols(Dictionary<uint, IGrouping<uint, Diagnostic>> dict, List<Symbol> symbols)
|
||||||
{
|
{
|
||||||
HashSet<uint> linesToRefresh;
|
HashSet<uint> linesToRefresh;
|
||||||
|
|
||||||
@@ -289,6 +288,12 @@ public class SlangFormatter : ICodeFormatter
|
|||||||
{
|
{
|
||||||
linesToRefresh = new HashSet<uint>(dict.Keys);
|
linesToRefresh = new HashSet<uint>(dict.Keys);
|
||||||
linesToRefresh.UnionWith(_linesWithErrors);
|
linesToRefresh.UnionWith(_linesWithErrors);
|
||||||
|
|
||||||
|
// Also add lines with symbols that may have been modified
|
||||||
|
foreach (var symbol in symbols)
|
||||||
|
{
|
||||||
|
linesToRefresh.Add(symbol.Span.StartLine);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastLineCount = this.Lines.Count;
|
_lastLineCount = this.Lines.Count;
|
||||||
@@ -328,9 +333,49 @@ public class SlangFormatter : ICodeFormatter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Add symbol tooltips for symbols on this line
|
||||||
|
foreach (var symbol in symbols)
|
||||||
|
{
|
||||||
|
if (symbol.Span.StartLine == lineIndex)
|
||||||
|
{
|
||||||
|
var column = (int)symbol.Span.StartCol;
|
||||||
|
var length = Math.Max(1, (int)(symbol.Span.EndCol - symbol.Span.StartCol));
|
||||||
|
|
||||||
|
// If there's already a token at this position (from syntax highlighting), use it
|
||||||
|
// Otherwise, create a new token for the symbol
|
||||||
|
if (allTokensDict.ContainsKey(column))
|
||||||
|
{
|
||||||
|
// Update existing token with symbol tooltip
|
||||||
|
var existingToken = allTokensDict[column];
|
||||||
|
allTokensDict[column] = new SemanticToken(
|
||||||
|
line: existingToken.Line,
|
||||||
|
column: existingToken.Column,
|
||||||
|
length: existingToken.Length,
|
||||||
|
type: existingToken.Type,
|
||||||
|
style: existingToken.Style,
|
||||||
|
data: symbol.Description, // Use symbol description as tooltip
|
||||||
|
isError: existingToken.IsError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create new token for symbol
|
||||||
|
allTokensDict[column] = new SemanticToken(
|
||||||
|
line: (int)lineIndex,
|
||||||
|
column,
|
||||||
|
length,
|
||||||
|
type: 0,
|
||||||
|
style: ColorIdentifier,
|
||||||
|
data: symbol.Description,
|
||||||
|
isError: false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var allTokens = allTokensDict.Values.ToList();
|
var allTokens = allTokensDict.Values.ToList();
|
||||||
|
|
||||||
// 3. Update the line (this clears existing tokens and uses the list we just built)
|
// 4. Update the line (this clears existing tokens and uses the list we just built)
|
||||||
line.Update(allTokens);
|
line.Update(allTokens);
|
||||||
|
|
||||||
ReattachMetadata(line, allTokens);
|
ReattachMetadata(line, allTokens);
|
||||||
@@ -339,6 +384,16 @@ public class SlangFormatter : ICodeFormatter
|
|||||||
_linesWithErrors = new HashSet<uint>(dict.Keys);
|
_linesWithErrors = new HashSet<uint>(dict.Keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes diagnostics from the Rust FFI compiler and applies it as semantic tokens to the
|
||||||
|
/// source in this editor.
|
||||||
|
/// This runs on the Main Thread
|
||||||
|
/// </summary>
|
||||||
|
private void ApplyDiagnostics(Dictionary<uint, IGrouping<uint, Diagnostic>> dict)
|
||||||
|
{
|
||||||
|
ApplyDiagnosticsAndSymbols(dict, new List<Symbol>());
|
||||||
|
}
|
||||||
|
|
||||||
// Helper to map SemanticToken data (tooltips/errors) back to the tokens in the line
|
// Helper to map SemanticToken data (tooltips/errors) back to the tokens in the line
|
||||||
private void ReattachMetadata(StyledLine line, List<SemanticToken> semanticTokens)
|
private void ReattachMetadata(StyledLine line, List<SemanticToken> semanticTokens)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,6 +47,33 @@ public struct SourceMapEntry
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct Symbol
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public Range Span;
|
||||||
|
public SymbolKind Kind;
|
||||||
|
public string Description;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Kind}: {Name} at {Span}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SymbolKind
|
||||||
|
{
|
||||||
|
Function = 0,
|
||||||
|
Syscall = 1,
|
||||||
|
Variable = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SymbolData
|
||||||
|
{
|
||||||
|
public uint Kind;
|
||||||
|
public uint ArgCount;
|
||||||
|
public uint SyscallType; // 0=System, 1=Math
|
||||||
|
}
|
||||||
|
|
||||||
public static class Marshal
|
public static class Marshal
|
||||||
{
|
{
|
||||||
private static IntPtr _libraryHandle = IntPtr.Zero;
|
private static IntPtr _libraryHandle = IntPtr.Zero;
|
||||||
@@ -164,6 +191,59 @@ public static class Marshal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static unsafe (List<Diagnostic>, List<Symbol>) DiagnoseSourceWithSymbols(string inputString)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(inputString) || !EnsureLibLoaded())
|
||||||
|
{
|
||||||
|
return (new(), new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (char* ptrInput = inputString)
|
||||||
|
{
|
||||||
|
var input = new slice_ref_uint16_t
|
||||||
|
{
|
||||||
|
ptr = (ushort*)ptrInput,
|
||||||
|
len = (UIntPtr)inputString.Length,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = Ffi.diagnose_source_with_symbols(input);
|
||||||
|
|
||||||
|
// Convert diagnostics
|
||||||
|
var diagnostics = result.diagnostics.ToList();
|
||||||
|
|
||||||
|
// Convert symbols
|
||||||
|
var symbols = new List<Symbol>();
|
||||||
|
var symbolPtr = result.symbols.ptr;
|
||||||
|
var symbolCount = (int)result.symbols.len;
|
||||||
|
|
||||||
|
for (int i = 0; i < symbolCount; i++)
|
||||||
|
{
|
||||||
|
var ffiSymbol = symbolPtr[i];
|
||||||
|
var kind = (SymbolKind)ffiSymbol.kind_data.kind;
|
||||||
|
|
||||||
|
// Use the actual description from the FFI (includes doc comments and syscall docs)
|
||||||
|
var description = ffiSymbol.description.AsString();
|
||||||
|
|
||||||
|
symbols.Add(new Symbol
|
||||||
|
{
|
||||||
|
Name = ffiSymbol.name.AsString(),
|
||||||
|
Kind = kind,
|
||||||
|
Span = new Range(
|
||||||
|
ffiSymbol.span.start_line,
|
||||||
|
ffiSymbol.span.start_col,
|
||||||
|
ffiSymbol.span.end_line,
|
||||||
|
ffiSymbol.span.end_col
|
||||||
|
),
|
||||||
|
Description = description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ffi.free_ffi_diagnostics_and_symbols(result);
|
||||||
|
|
||||||
|
return (diagnostics, symbols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static unsafe List<SemanticToken> TokenizeLine(string inputString)
|
public static unsafe List<SemanticToken> TokenizeLine(string inputString)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(inputString) || !EnsureLibLoaded())
|
if (string.IsNullOrEmpty(inputString) || !EnsureLibLoaded())
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
286
rust_compiler/Cargo.lock
generated
286
rust_compiler/Cargo.lock
generated
@@ -23,7 +23,7 @@ version = "0.7.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.16",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
@@ -73,7 +73,7 @@ version = "1.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -84,7 +84,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"once_cell_polyfill",
|
"once_cell_polyfill",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -135,6 +135,12 @@ version = "1.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitvec"
|
name = "bitvec"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -167,7 +173,7 @@ dependencies = [
|
|||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.111",
|
"syn 2.0.112",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -218,9 +224,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.53"
|
version = "4.5.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -228,9 +234,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.53"
|
version = "4.5.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -247,7 +253,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.111",
|
"syn 2.0.112",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -278,6 +284,18 @@ dependencies = [
|
|||||||
"tokenizer",
|
"tokenizer",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -293,12 +311,28 @@ version = "0.1.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ext-trait"
|
name = "ext-trait"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -334,13 +368,19 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23"
|
checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fluent-uri"
|
name = "fluent-uri"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
|
checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -366,6 +406,18 @@ dependencies = [
|
|||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.32.3"
|
version = "0.32.3"
|
||||||
@@ -428,6 +480,32 @@ dependencies = [
|
|||||||
"rustversion",
|
"rustversion",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "insta"
|
||||||
|
version = "1.45.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "983e3b24350c84ab8a65151f537d67afbbf7153bb9f1110e03e9fa9b07f67a5c"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"once_cell",
|
||||||
|
"similar",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "integration_tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"compiler",
|
||||||
|
"il",
|
||||||
|
"indoc",
|
||||||
|
"insta",
|
||||||
|
"optimizer",
|
||||||
|
"parser",
|
||||||
|
"tokenizer",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inventory"
|
name = "inventory"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
@@ -445,9 +523,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
@@ -465,6 +543,12 @@ version = "0.2.178"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "logos"
|
name = "logos"
|
||||||
version = "0.16.0"
|
version = "0.16.0"
|
||||||
@@ -487,7 +571,7 @@ dependencies = [
|
|||||||
"regex-automata",
|
"regex-automata",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"syn 2.0.111",
|
"syn 2.0.112",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -505,7 +589,7 @@ version = "0.97.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071"
|
checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.3.2",
|
||||||
"fluent-uri",
|
"fluent-uri",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -642,9 +726,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.103"
|
version = "1.0.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -678,6 +762,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radium"
|
name = "radium"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -711,7 +801,7 @@ version = "0.6.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -800,18 +890,25 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "safer-ffi"
|
name = "safer-ffi"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
@@ -889,20 +986,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.111",
|
"syn 2.0.112",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.145"
|
version = "1.0.148"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ryu",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -913,7 +1010,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.111",
|
"syn 2.0.112",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -928,9 +1025,15 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "similar"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slang"
|
name = "slang"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -999,9 +1102,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.111"
|
version = "2.0.112"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1014,6 +1117,19 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.17"
|
version = "2.0.17"
|
||||||
@@ -1031,7 +1147,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.111",
|
"syn 2.0.112",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1063,9 +1179,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.7.4+spec-1.0.0"
|
version = "0.7.5+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6"
|
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
@@ -1084,9 +1200,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_parser"
|
name = "toml_parser"
|
||||||
version = "1.0.5+spec-1.0.0"
|
version = "1.0.6+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c"
|
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
@@ -1140,6 +1256,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.1+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.106"
|
version = "0.2.106"
|
||||||
@@ -1172,7 +1297,7 @@ dependencies = [
|
|||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.111",
|
"syn 2.0.112",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1191,6 +1316,15 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -1200,6 +1334,70 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.14"
|
version = "0.7.14"
|
||||||
@@ -1209,6 +1407,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "with_builtin_macros"
|
name = "with_builtin_macros"
|
||||||
version = "0.0.3"
|
version = "0.0.3"
|
||||||
@@ -1261,5 +1465,11 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.111",
|
"syn 2.0.112",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "317f17ff091ac4515f17cc7a190d2769a8c9a96d227de5d64b500b01cda8f2cd"
|
||||||
|
|||||||
@@ -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,6 +1,8 @@
|
|||||||
|
pub mod symbols;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
mod v1;
|
mod v1;
|
||||||
mod variable_manager;
|
mod variable_manager;
|
||||||
|
|
||||||
|
pub use symbols::{CompilationMetadata, SymbolInfo, SymbolKind, SyscallType};
|
||||||
pub use v1::{CompilationResult, Compiler, CompilerConfig, Error};
|
pub use v1::{CompilationResult, Compiler, CompilerConfig, Error};
|
||||||
|
|||||||
343
rust_compiler/libs/compiler/src/symbols.rs
Normal file
343
rust_compiler/libs/compiler/src/symbols.rs
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
use helpers::Span;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// Represents a symbol (function, syscall, variable, etc.) that can be referenced in code.
|
||||||
|
/// Designed to be LSP-compatible for easy integration with language servers.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SymbolInfo<'a> {
|
||||||
|
/// The name of the symbol
|
||||||
|
pub name: Cow<'a, str>,
|
||||||
|
/// The kind of symbol and associated metadata
|
||||||
|
pub kind: SymbolKind<'a>,
|
||||||
|
/// The source location of this symbol (for IDE features)
|
||||||
|
pub span: Option<Span>,
|
||||||
|
/// Optional description for tooltips and documentation
|
||||||
|
pub description: Option<Cow<'a, str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SymbolInfo<'a> {
|
||||||
|
/// Converts to an LSP SymbolInformation for protocol compatibility.
|
||||||
|
pub fn to_lsp_symbol_information(&self, uri: lsp_types::Uri) -> lsp_types::SymbolInformation {
|
||||||
|
lsp_types::SymbolInformation {
|
||||||
|
name: self.name.to_string(),
|
||||||
|
kind: self.kind.to_lsp_symbol_kind(),
|
||||||
|
#[allow(deprecated)]
|
||||||
|
deprecated: None,
|
||||||
|
location: lsp_types::Location {
|
||||||
|
uri,
|
||||||
|
range: self.span.as_ref().map(|s| (*s).into()).unwrap_or_default(),
|
||||||
|
},
|
||||||
|
container_name: None,
|
||||||
|
tags: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts to an LSP CompletionItem for autocomplete.
|
||||||
|
pub fn to_lsp_completion_item(&self) -> lsp_types::CompletionItem {
|
||||||
|
lsp_types::CompletionItem {
|
||||||
|
label: self.name.to_string(),
|
||||||
|
kind: Some(self.kind.to_lsp_completion_kind()),
|
||||||
|
documentation: self
|
||||||
|
.description
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| lsp_types::Documentation::String(d.to_string())),
|
||||||
|
detail: Some(self.kind.detail_string()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Discriminates between different kinds of symbols.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SymbolKind<'a> {
|
||||||
|
/// A user-defined function
|
||||||
|
Function {
|
||||||
|
/// Names of parameters in order
|
||||||
|
parameters: Vec<Cow<'a, str>>,
|
||||||
|
/// Type hint for the return type (if applicable)
|
||||||
|
return_type: Option<Cow<'a, str>>,
|
||||||
|
},
|
||||||
|
/// A system or math syscall
|
||||||
|
Syscall {
|
||||||
|
/// Whether it's a System or Math syscall
|
||||||
|
syscall_type: SyscallType,
|
||||||
|
/// Number of expected arguments
|
||||||
|
argument_count: usize,
|
||||||
|
},
|
||||||
|
/// A variable declaration
|
||||||
|
Variable {
|
||||||
|
/// Type hint for the variable (if applicable)
|
||||||
|
type_hint: Option<Cow<'a, str>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SymbolKind<'a> {
|
||||||
|
/// Converts to LSP SymbolKind for protocol compatibility.
|
||||||
|
fn to_lsp_symbol_kind(&self) -> lsp_types::SymbolKind {
|
||||||
|
match self {
|
||||||
|
SymbolKind::Function { .. } => lsp_types::SymbolKind::FUNCTION,
|
||||||
|
SymbolKind::Syscall { .. } => lsp_types::SymbolKind::FUNCTION, // Syscalls are function-like
|
||||||
|
SymbolKind::Variable { .. } => lsp_types::SymbolKind::VARIABLE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts to LSP CompletionItemKind for autocomplete filtering.
|
||||||
|
fn to_lsp_completion_kind(&self) -> lsp_types::CompletionItemKind {
|
||||||
|
match self {
|
||||||
|
SymbolKind::Function { .. } => lsp_types::CompletionItemKind::FUNCTION,
|
||||||
|
SymbolKind::Syscall { .. } => lsp_types::CompletionItemKind::FUNCTION,
|
||||||
|
SymbolKind::Variable { .. } => lsp_types::CompletionItemKind::VARIABLE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a human-readable detail string for display in IDEs.
|
||||||
|
fn detail_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
SymbolKind::Function {
|
||||||
|
parameters,
|
||||||
|
return_type,
|
||||||
|
} => {
|
||||||
|
let params = parameters
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
let ret = return_type
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| format!(" -> {}", t))
|
||||||
|
.unwrap_or_default();
|
||||||
|
format!("fn({}){}", params, ret)
|
||||||
|
}
|
||||||
|
SymbolKind::Syscall {
|
||||||
|
syscall_type,
|
||||||
|
argument_count,
|
||||||
|
} => {
|
||||||
|
format!(
|
||||||
|
"{}(... {} args)",
|
||||||
|
match syscall_type {
|
||||||
|
SyscallType::System => "syscall",
|
||||||
|
SyscallType::Math => "math",
|
||||||
|
},
|
||||||
|
argument_count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SymbolKind::Variable { type_hint } => type_hint
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t.to_string())
|
||||||
|
.unwrap_or_else(|| "var".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Distinguishes between System and Math syscalls.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SyscallType {
|
||||||
|
System,
|
||||||
|
Math,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata collected during compilation, including all referenced symbols.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct CompilationMetadata<'a> {
|
||||||
|
/// All symbols encountered during compilation (functions, syscalls, variables)
|
||||||
|
pub symbols: Vec<SymbolInfo<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CompilationMetadata<'a> {
|
||||||
|
/// Creates a new empty compilation metadata.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
symbols: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a symbol to the metadata.
|
||||||
|
pub fn add_symbol(&mut self, symbol: SymbolInfo<'a>) {
|
||||||
|
self.symbols.push(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a function symbol.
|
||||||
|
pub fn add_function(
|
||||||
|
&mut self,
|
||||||
|
name: Cow<'a, str>,
|
||||||
|
parameters: Vec<Cow<'a, str>>,
|
||||||
|
span: Option<Span>,
|
||||||
|
) {
|
||||||
|
self.add_function_with_doc(name, parameters, span, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a function symbol with optional doc comment.
|
||||||
|
pub fn add_function_with_doc(
|
||||||
|
&mut self,
|
||||||
|
name: Cow<'a, str>,
|
||||||
|
parameters: Vec<Cow<'a, str>>,
|
||||||
|
span: Option<Span>,
|
||||||
|
description: Option<Cow<'a, str>>,
|
||||||
|
) {
|
||||||
|
self.add_symbol(SymbolInfo {
|
||||||
|
name,
|
||||||
|
kind: SymbolKind::Function {
|
||||||
|
parameters,
|
||||||
|
return_type: None,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a syscall symbol.
|
||||||
|
pub fn add_syscall(
|
||||||
|
&mut self,
|
||||||
|
name: Cow<'a, str>,
|
||||||
|
syscall_type: SyscallType,
|
||||||
|
argument_count: usize,
|
||||||
|
span: Option<Span>,
|
||||||
|
) {
|
||||||
|
self.add_syscall_with_doc(name, syscall_type, argument_count, span, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a syscall symbol with optional doc comment.
|
||||||
|
pub fn add_syscall_with_doc(
|
||||||
|
&mut self,
|
||||||
|
name: Cow<'a, str>,
|
||||||
|
syscall_type: SyscallType,
|
||||||
|
argument_count: usize,
|
||||||
|
span: Option<Span>,
|
||||||
|
description: Option<Cow<'a, str>>,
|
||||||
|
) {
|
||||||
|
self.add_symbol(SymbolInfo {
|
||||||
|
name,
|
||||||
|
kind: SymbolKind::Syscall {
|
||||||
|
syscall_type,
|
||||||
|
argument_count,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a variable symbol.
|
||||||
|
pub fn add_variable(&mut self, name: Cow<'a, str>, span: Option<Span>) {
|
||||||
|
self.add_variable_with_doc(name, span, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a variable symbol with optional doc comment.
|
||||||
|
pub fn add_variable_with_doc(
|
||||||
|
&mut self,
|
||||||
|
name: Cow<'a, str>,
|
||||||
|
span: Option<Span>,
|
||||||
|
description: Option<Cow<'a, str>>,
|
||||||
|
) {
|
||||||
|
self.add_symbol(SymbolInfo {
|
||||||
|
name,
|
||||||
|
kind: SymbolKind::Variable { type_hint: None },
|
||||||
|
span,
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all symbols of a specific kind.
|
||||||
|
pub fn symbols_of_kind(&self, kind: &str) -> Vec<&SymbolInfo<'a>> {
|
||||||
|
self.symbols
|
||||||
|
.iter()
|
||||||
|
.filter(|sym| match (&sym.kind, kind) {
|
||||||
|
(SymbolKind::Function { .. }, "function") => true,
|
||||||
|
(SymbolKind::Syscall { .. }, "syscall") => true,
|
||||||
|
(SymbolKind::Variable { .. }, "variable") => true,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts all symbols to LSP SymbolInformation for protocol compatibility.
|
||||||
|
pub fn to_lsp_symbols(&self, uri: lsp_types::Uri) -> Vec<lsp_types::SymbolInformation> {
|
||||||
|
self.symbols
|
||||||
|
.iter()
|
||||||
|
.map(|sym| sym.to_lsp_symbol_information(uri.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts all symbols to LSP CompletionItems for autocomplete.
|
||||||
|
pub fn to_lsp_completion_items(&self) -> Vec<lsp_types::CompletionItem> {
|
||||||
|
self.symbols
|
||||||
|
.iter()
|
||||||
|
.map(|sym| sym.to_lsp_completion_item())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_metadata_creation() {
|
||||||
|
let metadata = CompilationMetadata::new();
|
||||||
|
assert!(metadata.symbols.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_function_symbol() {
|
||||||
|
let mut metadata = CompilationMetadata::new();
|
||||||
|
metadata.add_function("test_func".into(), vec!["x".into(), "y".into()], None);
|
||||||
|
assert_eq!(metadata.symbols.len(), 1);
|
||||||
|
assert_eq!(metadata.symbols[0].name, "test_func");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_syscall_symbol() {
|
||||||
|
let mut metadata = CompilationMetadata::new();
|
||||||
|
metadata.add_syscall("hash".into(), SyscallType::System, 1, None);
|
||||||
|
assert_eq!(metadata.symbols.len(), 1);
|
||||||
|
assert_eq!(metadata.symbols[0].name, "hash");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_symbols_of_kind() {
|
||||||
|
let mut metadata = CompilationMetadata::new();
|
||||||
|
metadata.add_function("func1".into(), vec![], None);
|
||||||
|
metadata.add_syscall("hash".into(), SyscallType::System, 1, None);
|
||||||
|
metadata.add_variable("x".into(), None);
|
||||||
|
|
||||||
|
let functions = metadata.symbols_of_kind("function");
|
||||||
|
assert_eq!(functions.len(), 1);
|
||||||
|
|
||||||
|
let syscalls = metadata.symbols_of_kind("syscall");
|
||||||
|
assert_eq!(syscalls.len(), 1);
|
||||||
|
|
||||||
|
let variables = metadata.symbols_of_kind("variable");
|
||||||
|
assert_eq!(variables.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lsp_completion_items() {
|
||||||
|
let mut metadata = CompilationMetadata::new();
|
||||||
|
metadata.add_function("test_func".into(), vec![], None);
|
||||||
|
metadata.add_syscall("hash".into(), SyscallType::System, 1, None);
|
||||||
|
metadata.add_variable("x".into(), None);
|
||||||
|
|
||||||
|
let completions = metadata.to_lsp_completion_items();
|
||||||
|
assert_eq!(completions.len(), 3);
|
||||||
|
|
||||||
|
// Verify function
|
||||||
|
assert_eq!(completions[0].label, "test_func");
|
||||||
|
assert_eq!(
|
||||||
|
completions[0].kind,
|
||||||
|
Some(lsp_types::CompletionItemKind::FUNCTION)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify syscall
|
||||||
|
assert_eq!(completions[1].label, "hash");
|
||||||
|
assert_eq!(
|
||||||
|
completions[1].kind,
|
||||||
|
Some(lsp_types::CompletionItemKind::FUNCTION)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify variable
|
||||||
|
assert_eq!(completions[2].label, "x");
|
||||||
|
assert_eq!(
|
||||||
|
completions[2].kind,
|
||||||
|
Some(lsp_types::CompletionItemKind::VARIABLE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -272,3 +272,108 @@ fn device_property_with_underscore_name() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_index_read() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check "
|
||||||
|
device printer = \"d0\";
|
||||||
|
let value = printer[255];
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
compiled.errors.is_empty(),
|
||||||
|
"Expected no errors, got: {:?}",
|
||||||
|
compiled.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
compiled.output,
|
||||||
|
indoc! {
|
||||||
|
"
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
get r1 d0 255
|
||||||
|
move r8 r1
|
||||||
|
"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_index_write() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check "
|
||||||
|
device printer = \"d0\";
|
||||||
|
printer[255] = 42;
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
compiled.errors.is_empty(),
|
||||||
|
"Expected no errors, got: {:?}",
|
||||||
|
compiled.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
compiled.output,
|
||||||
|
indoc! {
|
||||||
|
"
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
put d0 255 42
|
||||||
|
"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_index_db_not_allowed() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check "
|
||||||
|
device stack = \"db\";
|
||||||
|
let x = stack[10];
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!compiled.errors.is_empty(),
|
||||||
|
"Expected error for db indexing"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
compiled.errors[0]
|
||||||
|
.to_string()
|
||||||
|
.contains("Direct stack access on 'db' is not yet supported"),
|
||||||
|
"Expected db restriction error"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_index_db_write_not_allowed() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check "
|
||||||
|
device stack = \"db\";
|
||||||
|
stack[10] = 42;
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!compiled.errors.is_empty(),
|
||||||
|
"Expected error for db indexing"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
compiled.errors[0]
|
||||||
|
.to_string()
|
||||||
|
.contains("Direct stack access on 'db' is not yet supported"),
|
||||||
|
"Expected db restriction error"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,15 @@ macro_rules! compile {
|
|||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
(metadata $source:expr) => {{
|
||||||
|
let compiler = crate::Compiler::new(
|
||||||
|
parser::Parser::new(tokenizer::Tokenizer::from($source)),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let res = compiler.compile();
|
||||||
|
res.metadata
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
mod binary_expression;
|
mod binary_expression;
|
||||||
mod branching;
|
mod branching;
|
||||||
@@ -61,5 +70,6 @@ mod loops;
|
|||||||
mod math_syscall;
|
mod math_syscall;
|
||||||
mod negation_priority;
|
mod negation_priority;
|
||||||
mod scoping;
|
mod scoping;
|
||||||
|
mod symbol_documentation;
|
||||||
mod syscall;
|
mod syscall;
|
||||||
mod tuple_literals;
|
mod tuple_literals;
|
||||||
|
|||||||
120
rust_compiler/libs/compiler/src/test/symbol_documentation.rs
Normal file
120
rust_compiler/libs/compiler/src/test/symbol_documentation.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_variable_doc_comment() -> Result<()> {
|
||||||
|
let metadata = compile!(metadata "/// this is a documented variable\nlet myVar = 42;");
|
||||||
|
|
||||||
|
let var_symbol = metadata
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.name == "myVar")
|
||||||
|
.expect("myVar symbol not found");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
var_symbol.description.as_ref().map(|d| d.as_ref()),
|
||||||
|
Some("this is a documented variable")
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_const_doc_comment() -> Result<()> {
|
||||||
|
let metadata = compile!(metadata "/// const documentation\nconst myConst = 100;");
|
||||||
|
|
||||||
|
let const_symbol = metadata
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.name == "myConst")
|
||||||
|
.expect("myConst symbol not found");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
const_symbol.description.as_ref().map(|d| d.as_ref()),
|
||||||
|
Some("const documentation")
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_doc_comment() -> Result<()> {
|
||||||
|
let metadata = compile!(metadata "/// device documentation\ndevice myDevice = \"d0\";");
|
||||||
|
|
||||||
|
let device_symbol = metadata
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.name == "myDevice")
|
||||||
|
.expect("myDevice symbol not found");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
device_symbol.description.as_ref().map(|d| d.as_ref()),
|
||||||
|
Some("device documentation")
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_function_doc_comment() -> Result<()> {
|
||||||
|
let metadata = compile!(metadata "/// function documentation\nfn test() { }");
|
||||||
|
|
||||||
|
let fn_symbol = metadata
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.name == "test")
|
||||||
|
.expect("test symbol not found");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
fn_symbol.description.as_ref().map(|d| d.as_ref()),
|
||||||
|
Some("function documentation")
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_syscall_documentation() -> Result<()> {
|
||||||
|
let metadata = compile!(metadata "fn test() { clr(d0); }");
|
||||||
|
|
||||||
|
let clr_symbol = metadata
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.name == "clr")
|
||||||
|
.expect("clr syscall not found");
|
||||||
|
|
||||||
|
// clr should have its built-in documentation
|
||||||
|
assert!(clr_symbol.description.is_some());
|
||||||
|
assert!(!clr_symbol.description.as_ref().unwrap().is_empty());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_variable_references_have_tooltips() -> Result<()> {
|
||||||
|
let metadata = compile!(metadata "/// documented variable\nlet myVar = 5;\nlet x = myVar + 2;\nmyVar = 10;");
|
||||||
|
|
||||||
|
// Count how many times 'myVar' appears in symbols
|
||||||
|
let myvar_symbols: Vec<_> = metadata
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.filter(|s| s.name == "myVar")
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// We should have at least 2: declaration + 1 reference (in myVar + 2)
|
||||||
|
// The assignment `myVar = 10` is a write, not a read, so doesn't create a reference
|
||||||
|
assert!(
|
||||||
|
myvar_symbols.len() >= 2,
|
||||||
|
"Expected at least 2 'myVar' symbols (declaration + reference), got {}",
|
||||||
|
myvar_symbols.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
// All should have the same description
|
||||||
|
let expected_desc = "documented variable";
|
||||||
|
for sym in &myvar_symbols {
|
||||||
|
assert_eq!(
|
||||||
|
sym.description.as_ref().map(|d| d.as_ref()),
|
||||||
|
Some(expected_desc),
|
||||||
|
"Symbol description mismatch at {:?}",
|
||||||
|
sym.span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -287,3 +287,68 @@ fn test_load_reagent() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_clr() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check
|
||||||
|
"
|
||||||
|
device stackDevice = \"d0\";
|
||||||
|
clr(stackDevice);
|
||||||
|
let deviceRef = 5;
|
||||||
|
clr(deviceRef);
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
compiled.errors.is_empty(),
|
||||||
|
"Expected no errors, got: {:?}",
|
||||||
|
compiled.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
compiled.output,
|
||||||
|
indoc! {
|
||||||
|
"
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
clr d0
|
||||||
|
move r8 5
|
||||||
|
clr r8
|
||||||
|
"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_rmap() -> anyhow::Result<()> {
|
||||||
|
let compiled = compile! {
|
||||||
|
check
|
||||||
|
"
|
||||||
|
device printer = \"d0\";
|
||||||
|
let reagentHash = 12345;
|
||||||
|
let itemHash = rmap(printer, reagentHash);
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
compiled.errors.is_empty(),
|
||||||
|
"Expected no errors, got: {:?}",
|
||||||
|
compiled.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
compiled.output,
|
||||||
|
indoc! {
|
||||||
|
"
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 12345
|
||||||
|
rmap r15 d0 r8
|
||||||
|
move r9 r15
|
||||||
|
"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1288,4 +1288,67 @@ mod test {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_all_forms_combined() -> anyhow::Result<()> {
|
||||||
|
// Test all three tuple forms in one test:
|
||||||
|
// 1. Tuple literal declaration: let (x, y) = (1, 2);
|
||||||
|
// 2. Tuple literal assignment: (x, y) = (3, 4);
|
||||||
|
// 3. Function return tuple: let (a, b) = func();
|
||||||
|
let compiled = compile!(
|
||||||
|
check
|
||||||
|
r#"
|
||||||
|
fn swap(x, y) {
|
||||||
|
return (y, x);
|
||||||
|
};
|
||||||
|
|
||||||
|
let (a, b) = (10, 20); // Literal declaration
|
||||||
|
(a, b) = (30, 40); // Literal assignment
|
||||||
|
let (c, d) = swap(a, b); // Function return
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
compiled.errors.is_empty(),
|
||||||
|
"Expected no errors, got: {:?}",
|
||||||
|
compiled.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
compiled.output,
|
||||||
|
indoc! {
|
||||||
|
"
|
||||||
|
j main
|
||||||
|
swap:
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
push r8
|
||||||
|
push r9
|
||||||
|
sub r0 sp 4
|
||||||
|
get r0 db r0
|
||||||
|
move r15 r0
|
||||||
|
j __internal_L1
|
||||||
|
__internal_L1:
|
||||||
|
sub r0 sp 3
|
||||||
|
get ra db r0
|
||||||
|
j ra
|
||||||
|
main:
|
||||||
|
move r8 10
|
||||||
|
move r9 20
|
||||||
|
move r8 30
|
||||||
|
move r9 40
|
||||||
|
push r8
|
||||||
|
push r9
|
||||||
|
jal swap
|
||||||
|
pop r11
|
||||||
|
pop r10
|
||||||
|
move sp r15
|
||||||
|
"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ use parser::{
|
|||||||
tree_node::{
|
tree_node::{
|
||||||
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
|
AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression,
|
||||||
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
|
DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression,
|
||||||
InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression,
|
IndexAccessExpression, InvocationExpression, Literal, LiteralOr, LiteralOrVariable,
|
||||||
LoopExpression, MemberAccessExpression, Spanned, TernaryExpression,
|
LogicalExpression, LoopExpression, MemberAccessExpression, Spanned, TernaryExpression,
|
||||||
TupleAssignmentExpression, TupleDeclarationExpression, WhileExpression,
|
TupleAssignmentExpression, TupleDeclarationExpression, WhileExpression,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -67,6 +67,9 @@ pub enum Error<'a> {
|
|||||||
#[error("Expected a {0}-tuple, but you're trying to destructure into {1} variables")]
|
#[error("Expected a {0}-tuple, but you're trying to destructure into {1} variables")]
|
||||||
TupleSizeMismatch(usize, usize, Span),
|
TupleSizeMismatch(usize, usize, Span),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
OperationNotSupported(String, Span),
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Unknown(String, Option<Span>),
|
Unknown(String, Option<Span>),
|
||||||
}
|
}
|
||||||
@@ -89,7 +92,8 @@ impl<'a> From<Error<'a>> for lsp_types::Diagnostic {
|
|||||||
| ConstAssignment(_, span)
|
| ConstAssignment(_, span)
|
||||||
| DeviceAssignment(_, span)
|
| DeviceAssignment(_, span)
|
||||||
| AgrumentMismatch(_, span)
|
| AgrumentMismatch(_, span)
|
||||||
| TupleSizeMismatch(_, _, span) => Diagnostic {
|
| TupleSizeMismatch(_, _, span)
|
||||||
|
| OperationNotSupported(_, span) => Diagnostic {
|
||||||
range: span.into(),
|
range: span.into(),
|
||||||
message: value.to_string(),
|
message: value.to_string(),
|
||||||
severity: Some(DiagnosticSeverity::ERROR),
|
severity: Some(DiagnosticSeverity::ERROR),
|
||||||
@@ -141,6 +145,7 @@ struct CompileLocation<'a> {
|
|||||||
pub struct CompilationResult<'a> {
|
pub struct CompilationResult<'a> {
|
||||||
pub errors: Vec<Error<'a>>,
|
pub errors: Vec<Error<'a>>,
|
||||||
pub instructions: Instructions<'a>,
|
pub instructions: Instructions<'a>,
|
||||||
|
pub metadata: crate::CompilationMetadata<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Metadata for the currently compiling function
|
/// Metadata for the currently compiling function
|
||||||
@@ -198,6 +203,8 @@ pub struct Compiler<'a> {
|
|||||||
pub source_map: HashMap<usize, Vec<Span>>,
|
pub source_map: HashMap<usize, Vec<Span>>,
|
||||||
/// Accumulative errors from the compilation process
|
/// Accumulative errors from the compilation process
|
||||||
pub errors: Vec<Error<'a>>,
|
pub errors: Vec<Error<'a>>,
|
||||||
|
/// Metadata about symbols encountered during compilation
|
||||||
|
pub metadata: crate::CompilationMetadata<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Compiler<'a> {
|
impl<'a> Compiler<'a> {
|
||||||
@@ -215,6 +222,7 @@ impl<'a> Compiler<'a> {
|
|||||||
loop_stack: Vec::new(),
|
loop_stack: Vec::new(),
|
||||||
source_map: HashMap::new(),
|
source_map: HashMap::new(),
|
||||||
errors: Vec::new(),
|
errors: Vec::new(),
|
||||||
|
metadata: crate::CompilationMetadata::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,6 +241,7 @@ impl<'a> Compiler<'a> {
|
|||||||
return CompilationResult {
|
return CompilationResult {
|
||||||
errors: self.errors,
|
errors: self.errors,
|
||||||
instructions: self.instructions,
|
instructions: self.instructions,
|
||||||
|
metadata: self.metadata,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -241,6 +250,7 @@ impl<'a> Compiler<'a> {
|
|||||||
return CompilationResult {
|
return CompilationResult {
|
||||||
errors: self.errors,
|
errors: self.errors,
|
||||||
instructions: self.instructions,
|
instructions: self.instructions,
|
||||||
|
metadata: self.metadata,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -266,6 +276,7 @@ impl<'a> Compiler<'a> {
|
|||||||
return CompilationResult {
|
return CompilationResult {
|
||||||
errors: self.errors,
|
errors: self.errors,
|
||||||
instructions: self.instructions,
|
instructions: self.instructions,
|
||||||
|
metadata: self.metadata,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +290,7 @@ impl<'a> Compiler<'a> {
|
|||||||
CompilationResult {
|
CompilationResult {
|
||||||
errors: self.errors,
|
errors: self.errors,
|
||||||
instructions: self.instructions,
|
instructions: self.instructions,
|
||||||
|
metadata: self.metadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,6 +399,26 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
Expression::Ternary(tern) => Ok(Some(self.expression_ternary(tern.node, scope)?)),
|
Expression::Ternary(tern) => Ok(Some(self.expression_ternary(tern.node, scope)?)),
|
||||||
Expression::Invocation(expr_invoke) => {
|
Expression::Invocation(expr_invoke) => {
|
||||||
|
// Special case: hash() with string literal can be evaluated at compile time
|
||||||
|
if expr_invoke.node.name.node == "hash" && expr_invoke.node.arguments.len() == 1 {
|
||||||
|
if let Expression::Literal(Spanned {
|
||||||
|
node: Literal::String(str_to_hash),
|
||||||
|
..
|
||||||
|
}) = &expr_invoke.node.arguments[0].node
|
||||||
|
{
|
||||||
|
// Evaluate hash at compile time
|
||||||
|
let hash_value = crc_hash_signed(str_to_hash);
|
||||||
|
return Ok(Some(CompileLocation {
|
||||||
|
location: VariableLocation::Constant(Literal::Number(Number::Integer(
|
||||||
|
hash_value,
|
||||||
|
Unit::None,
|
||||||
|
))),
|
||||||
|
temp_name: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-constant hash calls or other function calls
|
||||||
self.expression_function_invocation(expr_invoke, scope)?;
|
self.expression_function_invocation(expr_invoke, scope)?;
|
||||||
// Invocation returns result in r15 (RETURN_REGISTER).
|
// Invocation returns result in r15 (RETURN_REGISTER).
|
||||||
// If used as an expression, we must move it to a temp to avoid overwrite.
|
// If used as an expression, we must move it to a temp to avoid overwrite.
|
||||||
@@ -433,10 +465,23 @@ impl<'a> Compiler<'a> {
|
|||||||
},
|
},
|
||||||
Expression::Variable(name) => {
|
Expression::Variable(name) => {
|
||||||
match scope.get_location_of(&name.node, Some(name.span)) {
|
match scope.get_location_of(&name.node, Some(name.span)) {
|
||||||
Ok(loc) => Ok(Some(CompileLocation {
|
Ok(loc) => {
|
||||||
location: loc,
|
// Track this variable reference in metadata (for tooltips on all usages, not just declaration)
|
||||||
temp_name: None, // User variable, do not free
|
let doc_comment: Option<Cow<'a, str>> = self
|
||||||
})),
|
.parser
|
||||||
|
.get_declaration_doc(name.node.as_ref())
|
||||||
|
.map(|s| Cow::Owned(s) as Cow<'a, str>);
|
||||||
|
self.metadata.add_variable_with_doc(
|
||||||
|
name.node.clone(),
|
||||||
|
Some(name.span),
|
||||||
|
doc_comment,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Some(CompileLocation {
|
||||||
|
location: loc,
|
||||||
|
temp_name: None, // User variable, do not free
|
||||||
|
}))
|
||||||
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// fallback, check devices
|
// fallback, check devices
|
||||||
if let Some(device) = self.devices.get(&name.node) {
|
if let Some(device) = self.devices.get(&name.node) {
|
||||||
@@ -487,6 +532,50 @@ impl<'a> Compiler<'a> {
|
|||||||
temp_name: Some(result_name),
|
temp_name: Some(result_name),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
Expression::IndexAccess(access) => {
|
||||||
|
// "get" behavior (e.g. `let x = d0[255]`)
|
||||||
|
let IndexAccessExpression { object, index } = access.node;
|
||||||
|
|
||||||
|
// 1. Resolve the object to a device string
|
||||||
|
let (device, dev_cleanup) = self.resolve_device(*object, scope)?;
|
||||||
|
|
||||||
|
// Check if device is "db" (not allowed)
|
||||||
|
if let Operand::Device(ref dev_str) = device {
|
||||||
|
if dev_str.as_ref() == "db" {
|
||||||
|
return Err(Error::OperationNotSupported(
|
||||||
|
"Direct stack access on 'db' is not yet supported".to_string(),
|
||||||
|
expr.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Compile the index expression to get the address
|
||||||
|
let (addr, addr_cleanup) = self.compile_operand(*index, scope)?;
|
||||||
|
|
||||||
|
// 3. Allocate a temp register for the result
|
||||||
|
let result_name = self.next_temp_name();
|
||||||
|
let loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
|
||||||
|
let reg = self.resolve_register(&loc)?;
|
||||||
|
|
||||||
|
// 4. Emit get instruction: get rX device address
|
||||||
|
self.write_instruction(
|
||||||
|
Instruction::Get(Operand::Register(reg), device, addr),
|
||||||
|
Some(expr.span),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 5. Cleanup
|
||||||
|
if let Some(c) = dev_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
if let Some(c) = addr_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(CompileLocation {
|
||||||
|
location: loc,
|
||||||
|
temp_name: Some(result_name),
|
||||||
|
}))
|
||||||
|
}
|
||||||
Expression::MethodCall(call) => {
|
Expression::MethodCall(call) => {
|
||||||
// Methods are not yet fully supported (e.g. `d0.SomeFunc()`).
|
// Methods are not yet fully supported (e.g. `d0.SomeFunc()`).
|
||||||
// This would likely map to specialized syscalls or batch instructions.
|
// This would likely map to specialized syscalls or batch instructions.
|
||||||
@@ -525,6 +614,28 @@ impl<'a> Compiler<'a> {
|
|||||||
temp_name: Some(result_name),
|
temp_name: Some(result_name),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
Expression::BitwiseNot(inner_expr) => {
|
||||||
|
// Compile bitwise NOT using the NOT instruction
|
||||||
|
let (inner_str, cleanup) = self.compile_operand(*inner_expr, scope)?;
|
||||||
|
let result_name = self.next_temp_name();
|
||||||
|
let result_loc =
|
||||||
|
scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?;
|
||||||
|
let result_reg = self.resolve_register(&result_loc)?;
|
||||||
|
|
||||||
|
self.write_instruction(
|
||||||
|
Instruction::Not(Operand::Register(result_reg), inner_str),
|
||||||
|
Some(expr.span),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(name) = cleanup {
|
||||||
|
scope.free_temp(name, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(CompileLocation {
|
||||||
|
location: result_loc,
|
||||||
|
temp_name: Some(result_name),
|
||||||
|
}))
|
||||||
|
}
|
||||||
Expression::TupleDeclaration(tuple_decl) => {
|
Expression::TupleDeclaration(tuple_decl) => {
|
||||||
self.expression_tuple_declaration(tuple_decl.node, scope)?;
|
self.expression_tuple_declaration(tuple_decl.node, scope)?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -554,6 +665,14 @@ impl<'a> Compiler<'a> {
|
|||||||
if let Expression::Variable(ref name) = expr.node
|
if let Expression::Variable(ref name) = expr.node
|
||||||
&& let Some(device_id) = self.devices.get(&name.node)
|
&& let Some(device_id) = self.devices.get(&name.node)
|
||||||
{
|
{
|
||||||
|
// Track this device reference in metadata (for tooltips on all usages, not just declaration)
|
||||||
|
let doc_comment = self
|
||||||
|
.parser
|
||||||
|
.get_declaration_doc(name.node.as_ref())
|
||||||
|
.map(Cow::Owned);
|
||||||
|
self.metadata
|
||||||
|
.add_variable_with_doc(name.node.clone(), Some(expr.span), doc_comment);
|
||||||
|
|
||||||
return Ok((Operand::Device(device_id.clone()), None));
|
return Ok((Operand::Device(device_id.clone()), None));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,6 +725,14 @@ impl<'a> Compiler<'a> {
|
|||||||
let name_str = var_name.node;
|
let name_str = var_name.node;
|
||||||
let name_span = var_name.span;
|
let name_span = var_name.span;
|
||||||
|
|
||||||
|
// Track the variable in metadata
|
||||||
|
let doc_comment = self
|
||||||
|
.parser
|
||||||
|
.get_declaration_doc(name_str.as_ref())
|
||||||
|
.map(Cow::Owned);
|
||||||
|
self.metadata
|
||||||
|
.add_variable_with_doc(name_str.clone(), Some(name_span), doc_comment);
|
||||||
|
|
||||||
// optimization. Check for a negated numeric literal (including nested negations)
|
// optimization. Check for a negated numeric literal (including nested negations)
|
||||||
// e.g., -5, -(-5), -(-(5)), etc.
|
// e.g., -5, -(-5), -(-(5)), etc.
|
||||||
if let Some(num) = self.try_fold_negation(&expr.node) {
|
if let Some(num) = self.try_fold_negation(&expr.node) {
|
||||||
@@ -889,6 +1016,58 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
(var_loc, None)
|
(var_loc, None)
|
||||||
}
|
}
|
||||||
|
Expression::BitwiseNot(_) => {
|
||||||
|
// Compile the bitwise NOT expression
|
||||||
|
let result = self.expression(expr, scope)?;
|
||||||
|
let var_loc = scope.add_variable(
|
||||||
|
name_str.clone(),
|
||||||
|
LocationRequest::Persist,
|
||||||
|
Some(name_span),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(res) = result {
|
||||||
|
// Move result from temp to new persistent variable
|
||||||
|
let result_reg = self.resolve_register(&res.location)?;
|
||||||
|
self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?;
|
||||||
|
|
||||||
|
// Free the temp result
|
||||||
|
if let Some(name) = res.temp_name {
|
||||||
|
scope.free_temp(name, None)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::Unknown(
|
||||||
|
format!("`{name_str}` bitwise NOT expression did not produce a value"),
|
||||||
|
Some(name_span),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
(var_loc, None)
|
||||||
|
}
|
||||||
|
Expression::IndexAccess(_) => {
|
||||||
|
// Compile the index access expression
|
||||||
|
let result = self.expression(expr, scope)?;
|
||||||
|
let var_loc = scope.add_variable(
|
||||||
|
name_str.clone(),
|
||||||
|
LocationRequest::Persist,
|
||||||
|
Some(name_span),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(res) = result {
|
||||||
|
// Move result from temp to new persistent variable
|
||||||
|
let result_reg = self.resolve_register(&res.location)?;
|
||||||
|
self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?;
|
||||||
|
|
||||||
|
// Free the temp result
|
||||||
|
if let Some(name) = res.temp_name {
|
||||||
|
scope.free_temp(name, None)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::Unknown(
|
||||||
|
format!("`{name_str}` index access expression did not produce a value"),
|
||||||
|
Some(name_span),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
(var_loc, None)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Unknown(
|
return Err(Error::Unknown(
|
||||||
format!("`{name_str}` declaration of this type is not supported/implemented."),
|
format!("`{name_str}` declaration of this type is not supported/implemented."),
|
||||||
@@ -913,6 +1092,17 @@ impl<'a> Compiler<'a> {
|
|||||||
value: const_value,
|
value: const_value,
|
||||||
} = expr;
|
} = expr;
|
||||||
|
|
||||||
|
// Track the const variable in metadata
|
||||||
|
let doc_comment = self
|
||||||
|
.parser
|
||||||
|
.get_declaration_doc(const_name.node.as_ref())
|
||||||
|
.map(Cow::Owned);
|
||||||
|
self.metadata.add_variable_with_doc(
|
||||||
|
const_name.node.clone(),
|
||||||
|
Some(const_name.span),
|
||||||
|
doc_comment,
|
||||||
|
);
|
||||||
|
|
||||||
// check for a hash expression or a literal
|
// check for a hash expression or a literal
|
||||||
let value = match const_value {
|
let value = match const_value {
|
||||||
LiteralOr::Or(Spanned {
|
LiteralOr::Or(Spanned {
|
||||||
@@ -1025,6 +1215,37 @@ impl<'a> Compiler<'a> {
|
|||||||
scope.free_temp(c, None)?;
|
scope.free_temp(c, None)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expression::IndexAccess(access) => {
|
||||||
|
// Put instruction: put device address value
|
||||||
|
let IndexAccessExpression { object, index } = access.node;
|
||||||
|
|
||||||
|
let (device, dev_cleanup) = self.resolve_device(*object, scope)?;
|
||||||
|
|
||||||
|
// Check if device is "db" (not allowed)
|
||||||
|
if let Operand::Device(ref dev_str) = device {
|
||||||
|
if dev_str.as_ref() == "db" {
|
||||||
|
return Err(Error::OperationNotSupported(
|
||||||
|
"Direct stack access on 'db' is not yet supported".to_string(),
|
||||||
|
assignee.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (addr, addr_cleanup) = self.compile_operand(*index, scope)?;
|
||||||
|
let (val, val_cleanup) = self.compile_operand(*expression, scope)?;
|
||||||
|
|
||||||
|
self.write_instruction(Instruction::Put(device, addr, val), Some(assignee.span))?;
|
||||||
|
|
||||||
|
if let Some(c) = dev_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
if let Some(c) = addr_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
if let Some(c) = val_cleanup {
|
||||||
|
scope.free_temp(c, None)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Unknown(
|
return Err(Error::Unknown(
|
||||||
@@ -1180,6 +1401,27 @@ impl<'a> Compiler<'a> {
|
|||||||
Some(name.span),
|
Some(name.span),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Pop the arguments off the stack (caller cleanup convention)
|
||||||
|
// BUT: If the function returns a tuple, it saves SP in r15 and the caller
|
||||||
|
// will restore SP with "move sp r15", which automatically cleans up everything.
|
||||||
|
// So we only pop arguments for non-tuple-returning functions.
|
||||||
|
let returns_tuple = self
|
||||||
|
.function_meta
|
||||||
|
.tuple_return_sizes
|
||||||
|
.get(&name.node)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0)
|
||||||
|
> 0;
|
||||||
|
|
||||||
|
if !returns_tuple {
|
||||||
|
for _ in 0..arguments.len() {
|
||||||
|
self.write_instruction(
|
||||||
|
Instruction::Pop(Operand::Register(VariableScope::TEMP_STACK_REGISTER)),
|
||||||
|
Some(name.span),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// pop all registers back (if they were backed up)
|
// pop all registers back (if they were backed up)
|
||||||
if backup_registers {
|
if backup_registers {
|
||||||
for register in active_registers.iter().rev() {
|
for register in active_registers.iter().rev() {
|
||||||
@@ -1284,6 +1526,29 @@ impl<'a> Compiler<'a> {
|
|||||||
) -> Result<(), Error<'a>> {
|
) -> Result<(), Error<'a>> {
|
||||||
let TupleDeclarationExpression { names, value } = tuple_decl;
|
let TupleDeclarationExpression { names, value } = tuple_decl;
|
||||||
|
|
||||||
|
// Track each variable in the tuple declaration
|
||||||
|
// Get doc for the first variable
|
||||||
|
let first_var_name = names
|
||||||
|
.iter()
|
||||||
|
.find(|n| n.node.as_ref() != "_")
|
||||||
|
.map(|n| n.node.to_string());
|
||||||
|
let doc_comment = first_var_name
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|name| self.parser.get_declaration_doc(name))
|
||||||
|
.map(Cow::Owned);
|
||||||
|
|
||||||
|
for (i, name_spanned) in names.iter().enumerate() {
|
||||||
|
if name_spanned.node.as_ref() != "_" {
|
||||||
|
// Only attach doc comment to the first variable
|
||||||
|
let comment = if i == 0 { doc_comment.clone() } else { None };
|
||||||
|
self.metadata.add_variable_with_doc(
|
||||||
|
name_spanned.node.clone(),
|
||||||
|
Some(name_spanned.span),
|
||||||
|
comment,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match value.node {
|
match value.node {
|
||||||
Expression::Invocation(invoke_expr) => {
|
Expression::Invocation(invoke_expr) => {
|
||||||
// Execute the function call - tuple values will be on the stack
|
// Execute the function call - tuple values will be on the stack
|
||||||
@@ -1722,6 +1987,17 @@ impl<'a> Compiler<'a> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
expr: DeviceDeclarationExpression<'a>,
|
expr: DeviceDeclarationExpression<'a>,
|
||||||
) -> Result<(), Error<'a>> {
|
) -> Result<(), Error<'a>> {
|
||||||
|
// Track the device declaration in metadata
|
||||||
|
let doc_comment = self
|
||||||
|
.parser
|
||||||
|
.get_declaration_doc(expr.name.node.as_ref())
|
||||||
|
.map(Cow::Owned);
|
||||||
|
self.metadata.add_variable_with_doc(
|
||||||
|
expr.name.node.clone(),
|
||||||
|
Some(expr.name.span),
|
||||||
|
doc_comment,
|
||||||
|
);
|
||||||
|
|
||||||
if self.devices.contains_key(&expr.name.node) {
|
if self.devices.contains_key(&expr.name.node) {
|
||||||
self.errors.push(Error::DuplicateIdentifier(
|
self.errors.push(Error::DuplicateIdentifier(
|
||||||
expr.name.node.clone(),
|
expr.name.node.clone(),
|
||||||
@@ -2092,14 +2368,40 @@ impl<'a> Compiler<'a> {
|
|||||||
expr: Spanned<BinaryExpression<'a>>,
|
expr: Spanned<BinaryExpression<'a>>,
|
||||||
scope: &mut VariableScope<'a, '_>,
|
scope: &mut VariableScope<'a, '_>,
|
||||||
) -> Result<CompileLocation<'a>, Error<'a>> {
|
) -> Result<CompileLocation<'a>, Error<'a>> {
|
||||||
fn fold_binary_expression<'a>(expr: &BinaryExpression<'a>) -> Option<Number> {
|
fn fold_binary_expression<'a>(
|
||||||
|
expr: &BinaryExpression<'a>,
|
||||||
|
scope: &VariableScope<'a, '_>,
|
||||||
|
) -> Option<Number> {
|
||||||
|
fn number_to_i64(n: Number) -> Option<i64> {
|
||||||
|
match n {
|
||||||
|
Number::Integer(i, _) => i64::try_from(i).ok(),
|
||||||
|
Number::Decimal(d, _) => {
|
||||||
|
// Convert decimal to i64 by truncating
|
||||||
|
let int_part = d.trunc();
|
||||||
|
i64::try_from(int_part.mantissa() / 10_i128.pow(int_part.scale())).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i64_to_number(i: i64) -> Number {
|
||||||
|
Number::Integer(i as i128, Unit::None)
|
||||||
|
}
|
||||||
|
|
||||||
let (lhs, rhs) = match &expr {
|
let (lhs, rhs) = match &expr {
|
||||||
BinaryExpression::Add(l, r)
|
BinaryExpression::Add(l, r)
|
||||||
| BinaryExpression::Subtract(l, r)
|
| BinaryExpression::Subtract(l, r)
|
||||||
| BinaryExpression::Multiply(l, r)
|
| BinaryExpression::Multiply(l, r)
|
||||||
| BinaryExpression::Divide(l, r)
|
| BinaryExpression::Divide(l, r)
|
||||||
| BinaryExpression::Exponent(l, r)
|
| BinaryExpression::Exponent(l, r)
|
||||||
| BinaryExpression::Modulo(l, r) => (fold_expression(l)?, fold_expression(r)?),
|
| BinaryExpression::Modulo(l, r)
|
||||||
|
| BinaryExpression::BitwiseAnd(l, r)
|
||||||
|
| BinaryExpression::BitwiseOr(l, r)
|
||||||
|
| BinaryExpression::BitwiseXor(l, r)
|
||||||
|
| BinaryExpression::LeftShift(l, r)
|
||||||
|
| BinaryExpression::RightShiftArithmetic(l, r)
|
||||||
|
| BinaryExpression::RightShiftLogical(l, r) => {
|
||||||
|
(fold_expression(l, scope)?, fold_expression(r, scope)?)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
@@ -2108,11 +2410,44 @@ impl<'a> Compiler<'a> {
|
|||||||
BinaryExpression::Multiply(..) => Some(lhs * rhs),
|
BinaryExpression::Multiply(..) => Some(lhs * rhs),
|
||||||
BinaryExpression::Divide(..) => Some(lhs / rhs), // Watch out for div by zero panics!
|
BinaryExpression::Divide(..) => Some(lhs / rhs), // Watch out for div by zero panics!
|
||||||
BinaryExpression::Modulo(..) => Some(lhs % rhs),
|
BinaryExpression::Modulo(..) => Some(lhs % rhs),
|
||||||
_ => None, // Handle Exponent separately or implement pow
|
BinaryExpression::BitwiseAnd(..) => {
|
||||||
|
let lhs_int = number_to_i64(lhs)?;
|
||||||
|
let rhs_int = number_to_i64(rhs)?;
|
||||||
|
Some(i64_to_number(lhs_int & rhs_int))
|
||||||
|
}
|
||||||
|
BinaryExpression::BitwiseOr(..) => {
|
||||||
|
let lhs_int = number_to_i64(lhs)?;
|
||||||
|
let rhs_int = number_to_i64(rhs)?;
|
||||||
|
Some(i64_to_number(lhs_int | rhs_int))
|
||||||
|
}
|
||||||
|
BinaryExpression::BitwiseXor(..) => {
|
||||||
|
let lhs_int = number_to_i64(lhs)?;
|
||||||
|
let rhs_int = number_to_i64(rhs)?;
|
||||||
|
Some(i64_to_number(lhs_int ^ rhs_int))
|
||||||
|
}
|
||||||
|
BinaryExpression::LeftShift(..) => {
|
||||||
|
let lhs_int = number_to_i64(lhs)?;
|
||||||
|
let rhs_int = number_to_i64(rhs)?;
|
||||||
|
Some(i64_to_number(lhs_int << rhs_int))
|
||||||
|
}
|
||||||
|
BinaryExpression::RightShiftArithmetic(..) => {
|
||||||
|
let lhs_int = number_to_i64(lhs)?;
|
||||||
|
let rhs_int = number_to_i64(rhs)?;
|
||||||
|
Some(i64_to_number(lhs_int >> rhs_int))
|
||||||
|
}
|
||||||
|
BinaryExpression::RightShiftLogical(..) => {
|
||||||
|
let lhs_int = number_to_i64(lhs)?;
|
||||||
|
let rhs_int = number_to_i64(rhs)?;
|
||||||
|
Some(i64_to_number(lhs_int >> rhs_int))
|
||||||
|
}
|
||||||
|
_ => None, // Exponent not handled in compile-time folding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fold_expression<'a>(expr: &Expression<'a>) -> Option<Number> {
|
fn fold_expression<'a>(
|
||||||
|
expr: &Expression<'a>,
|
||||||
|
scope: &VariableScope<'a, '_>,
|
||||||
|
) -> Option<Number> {
|
||||||
match expr {
|
match expr {
|
||||||
// 1. Base Case: It's already a number
|
// 1. Base Case: It's already a number
|
||||||
Expression::Literal(lit) => match lit.node {
|
Expression::Literal(lit) => match lit.node {
|
||||||
@@ -2121,23 +2456,60 @@ impl<'a> Compiler<'a> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 2. Handle Parentheses: Just recurse deeper
|
// 2. Handle Parentheses: Just recurse deeper
|
||||||
Expression::Priority(inner) => fold_expression(&inner.node),
|
Expression::Priority(inner) => fold_expression(&inner.node, scope),
|
||||||
|
|
||||||
// 3. Handle Negation: Recurse, then negate
|
// 3. Handle Negation: Recurse, then negate
|
||||||
Expression::Negation(inner) => {
|
Expression::Negation(inner) => {
|
||||||
let val = fold_expression(&inner.node)?;
|
let val = fold_expression(&inner.node, scope)?;
|
||||||
Some(-val) // Requires impl Neg for Number
|
Some(-val) // Requires impl Neg for Number
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Handle Binary Ops: Recurse BOTH sides, then combine
|
// 4. Handle Binary Ops: Recurse BOTH sides, then combine
|
||||||
Expression::Binary(bin) => fold_binary_expression(&bin.node),
|
Expression::Binary(bin) => fold_binary_expression(&bin.node, scope),
|
||||||
|
|
||||||
// 5. Anything else (Variables, Function Calls) cannot be compile-time folded
|
// 5. Handle Variable Reference: Check if it's a const
|
||||||
|
Expression::Variable(var_id) => {
|
||||||
|
if let Ok(var_loc) = scope.get_location_of(var_id, None) {
|
||||||
|
if let VariableLocation::Constant(Literal::Number(num)) = var_loc {
|
||||||
|
return Some(num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Handle hash() syscall - evaluates to a constant at compile time
|
||||||
|
Expression::Syscall(Spanned {
|
||||||
|
node:
|
||||||
|
SysCall::System(System::Hash(Spanned {
|
||||||
|
node: Literal::String(str_to_hash),
|
||||||
|
..
|
||||||
|
})),
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
return Some(Number::Integer(crc_hash_signed(str_to_hash), Unit::None));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Handle hash() macro as invocation - evaluates to a constant at compile time
|
||||||
|
Expression::Invocation(inv) => {
|
||||||
|
if inv.node.name.node == "hash" && inv.node.arguments.len() == 1 {
|
||||||
|
if let Expression::Literal(Spanned {
|
||||||
|
node: Literal::String(str_to_hash),
|
||||||
|
..
|
||||||
|
}) = &inv.node.arguments[0].node
|
||||||
|
{
|
||||||
|
// hash() takes a string literal and returns a signed integer
|
||||||
|
return Some(Number::Integer(crc_hash_signed(str_to_hash), Unit::None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Anything else cannot be compile-time folded
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(const_lit) = fold_binary_expression(&expr.node) {
|
if let Some(const_lit) = fold_binary_expression(&expr.node, scope) {
|
||||||
return Ok(CompileLocation {
|
return Ok(CompileLocation {
|
||||||
location: VariableLocation::Constant(Literal::Number(const_lit)),
|
location: VariableLocation::Constant(Literal::Number(const_lit)),
|
||||||
temp_name: None,
|
temp_name: None,
|
||||||
@@ -2168,6 +2540,24 @@ impl<'a> Compiler<'a> {
|
|||||||
BinaryExpression::Modulo(l, r) => {
|
BinaryExpression::Modulo(l, r) => {
|
||||||
(|into, lhs, rhs| Instruction::Mod(into, lhs, rhs), l, r)
|
(|into, lhs, rhs| Instruction::Mod(into, lhs, rhs), l, r)
|
||||||
}
|
}
|
||||||
|
BinaryExpression::BitwiseAnd(l, r) => {
|
||||||
|
(|into, lhs, rhs| Instruction::And(into, lhs, rhs), l, r)
|
||||||
|
}
|
||||||
|
BinaryExpression::BitwiseOr(l, r) => {
|
||||||
|
(|into, lhs, rhs| Instruction::Or(into, lhs, rhs), l, r)
|
||||||
|
}
|
||||||
|
BinaryExpression::BitwiseXor(l, r) => {
|
||||||
|
(|into, lhs, rhs| Instruction::Xor(into, lhs, rhs), l, r)
|
||||||
|
}
|
||||||
|
BinaryExpression::LeftShift(l, r) => {
|
||||||
|
(|into, lhs, rhs| Instruction::Sll(into, lhs, rhs), l, r)
|
||||||
|
}
|
||||||
|
BinaryExpression::RightShiftArithmetic(l, r) => {
|
||||||
|
(|into, lhs, rhs| Instruction::Sra(into, lhs, rhs), l, r)
|
||||||
|
}
|
||||||
|
BinaryExpression::RightShiftLogical(l, r) => {
|
||||||
|
(|into, lhs, rhs| Instruction::Srl(into, lhs, rhs), l, r)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let span = Self::merge_spans(left_expr.span, right_expr.span);
|
let span = Self::merge_spans(left_expr.span, right_expr.span);
|
||||||
@@ -2612,6 +3002,17 @@ impl<'a> Compiler<'a> {
|
|||||||
span: Span,
|
span: Span,
|
||||||
scope: &mut VariableScope<'a, '_>,
|
scope: &mut VariableScope<'a, '_>,
|
||||||
) -> Result<Option<CompileLocation<'a>>, Error<'a>> {
|
) -> Result<Option<CompileLocation<'a>>, Error<'a>> {
|
||||||
|
// Track the syscall in metadata
|
||||||
|
let syscall_name = expr.name();
|
||||||
|
let doc = expr.docs().into();
|
||||||
|
self.metadata.add_syscall_with_doc(
|
||||||
|
Cow::Borrowed(syscall_name),
|
||||||
|
crate::SyscallType::System,
|
||||||
|
expr.arg_count(),
|
||||||
|
Some(span),
|
||||||
|
Some(doc),
|
||||||
|
);
|
||||||
|
|
||||||
macro_rules! cleanup {
|
macro_rules! cleanup {
|
||||||
($($to_clean:expr),*) => {
|
($($to_clean:expr),*) => {
|
||||||
$(
|
$(
|
||||||
@@ -2633,6 +3034,13 @@ impl<'a> Compiler<'a> {
|
|||||||
cleanup!(var_cleanup);
|
cleanup!(var_cleanup);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
System::Clr(device) => {
|
||||||
|
let (op, var_cleanup) = self.compile_operand(*device, scope)?;
|
||||||
|
self.write_instruction(Instruction::Clr(op), Some(span))?;
|
||||||
|
|
||||||
|
cleanup!(var_cleanup);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
System::Hash(hash_arg) => {
|
System::Hash(hash_arg) => {
|
||||||
let Spanned {
|
let Spanned {
|
||||||
node: Literal::String(str_lit),
|
node: Literal::String(str_lit),
|
||||||
@@ -2952,6 +3360,42 @@ impl<'a> Compiler<'a> {
|
|||||||
|
|
||||||
cleanup!(reagent_cleanup, reagent_hash_cleanup, device_cleanup);
|
cleanup!(reagent_cleanup, reagent_hash_cleanup, device_cleanup);
|
||||||
|
|
||||||
|
Ok(Some(CompileLocation {
|
||||||
|
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
|
||||||
|
temp_name: None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
System::Rmap(device, reagent_hash) => {
|
||||||
|
let Spanned {
|
||||||
|
node: LiteralOrVariable::Variable(device_spanned),
|
||||||
|
..
|
||||||
|
} = device
|
||||||
|
else {
|
||||||
|
return Err(Error::AgrumentMismatch(
|
||||||
|
"Arg1 expected to be a variable".into(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (device, device_cleanup) = self.compile_literal_or_variable(
|
||||||
|
LiteralOrVariable::Variable(device_spanned),
|
||||||
|
scope,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (reagent_hash, reagent_hash_cleanup) =
|
||||||
|
self.compile_operand(*reagent_hash, scope)?;
|
||||||
|
|
||||||
|
self.write_instruction(
|
||||||
|
Instruction::Rmap(
|
||||||
|
Operand::Register(VariableScope::RETURN_REGISTER),
|
||||||
|
device,
|
||||||
|
reagent_hash,
|
||||||
|
),
|
||||||
|
Some(span),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
cleanup!(reagent_hash_cleanup, device_cleanup);
|
||||||
|
|
||||||
Ok(Some(CompileLocation {
|
Ok(Some(CompileLocation {
|
||||||
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
|
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
|
||||||
temp_name: None,
|
temp_name: None,
|
||||||
@@ -2966,6 +3410,17 @@ impl<'a> Compiler<'a> {
|
|||||||
span: Span,
|
span: Span,
|
||||||
scope: &mut VariableScope<'a, '_>,
|
scope: &mut VariableScope<'a, '_>,
|
||||||
) -> Result<Option<CompileLocation<'a>>, Error<'a>> {
|
) -> Result<Option<CompileLocation<'a>>, Error<'a>> {
|
||||||
|
// Track the syscall in metadata
|
||||||
|
let syscall_name = expr.name();
|
||||||
|
let doc = expr.docs().into();
|
||||||
|
self.metadata.add_syscall_with_doc(
|
||||||
|
Cow::Borrowed(syscall_name),
|
||||||
|
crate::SyscallType::Math,
|
||||||
|
expr.arg_count(),
|
||||||
|
Some(span),
|
||||||
|
Some(doc),
|
||||||
|
);
|
||||||
|
|
||||||
macro_rules! cleanup {
|
macro_rules! cleanup {
|
||||||
($($to_clean:expr),*) => {
|
($($to_clean:expr),*) => {
|
||||||
$(
|
$(
|
||||||
@@ -3226,6 +3681,19 @@ impl<'a> Compiler<'a> {
|
|||||||
|
|
||||||
let span = expr.span;
|
let span = expr.span;
|
||||||
|
|
||||||
|
// Track the function definition in metadata
|
||||||
|
let param_names: Vec<Cow<'a, str>> = arguments.iter().map(|a| a.node.clone()).collect();
|
||||||
|
let doc_comment = self
|
||||||
|
.parser
|
||||||
|
.get_declaration_doc(name.node.as_ref())
|
||||||
|
.map(Cow::Owned);
|
||||||
|
self.metadata.add_function_with_doc(
|
||||||
|
name.node.clone(),
|
||||||
|
param_names,
|
||||||
|
Some(name.span),
|
||||||
|
doc_comment,
|
||||||
|
);
|
||||||
|
|
||||||
if self.function_meta.locations.contains_key(&name.node) {
|
if self.function_meta.locations.contains_key(&name.node) {
|
||||||
self.errors
|
self.errors
|
||||||
.push(Error::DuplicateIdentifier(name.node.clone(), name.span));
|
.push(Error::DuplicateIdentifier(name.node.clone(), name.span));
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ macro_rules! with_syscalls {
|
|||||||
// Big names
|
// Big names
|
||||||
"yield",
|
"yield",
|
||||||
"sleep",
|
"sleep",
|
||||||
|
"clr",
|
||||||
"hash",
|
"hash",
|
||||||
"load",
|
"load",
|
||||||
"loadBatched",
|
"loadBatched",
|
||||||
"loadBatchedNamed",
|
"loadBatchedNamed",
|
||||||
"loadSlot",
|
"loadSlot",
|
||||||
"loadReagent",
|
"loadReagent",
|
||||||
|
"rmap",
|
||||||
"set",
|
"set",
|
||||||
"setBatched",
|
"setBatched",
|
||||||
"setBatchedNamed",
|
"setBatchedNamed",
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ impl<'a> std::fmt::Display for Instructions<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct InstructionNode<'a> {
|
pub struct InstructionNode<'a> {
|
||||||
pub instruction: Instruction<'a>,
|
pub instruction: Instruction<'a>,
|
||||||
pub span: Option<Span>,
|
pub span: Option<Span>,
|
||||||
@@ -194,6 +195,9 @@ pub enum Instruction<'a> {
|
|||||||
/// `lr register device reagentMode int`
|
/// `lr register device reagentMode int`
|
||||||
LoadReagent(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>),
|
LoadReagent(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
|
|
||||||
|
/// `rmap register device reagentHash` - Resolve Reagent to Item Hash
|
||||||
|
Rmap(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
|
|
||||||
/// `j label` - Unconditional Jump
|
/// `j label` - Unconditional Jump
|
||||||
Jump(Operand<'a>),
|
Jump(Operand<'a>),
|
||||||
/// `jal label` - Jump and Link (Function Call)
|
/// `jal label` - Jump and Link (Function Call)
|
||||||
@@ -231,12 +235,22 @@ pub enum Instruction<'a> {
|
|||||||
/// `sle dst a b` - Set if Less or Equal
|
/// `sle dst a b` - Set if Less or Equal
|
||||||
SetLe(Operand<'a>, Operand<'a>, Operand<'a>),
|
SetLe(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
|
|
||||||
/// `and dst a b` - Logical AND
|
/// `and dst a b` - Bitwise AND
|
||||||
And(Operand<'a>, Operand<'a>, Operand<'a>),
|
And(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
/// `or dst a b` - Logical OR
|
/// `or dst a b` - Bitwise OR
|
||||||
Or(Operand<'a>, Operand<'a>, Operand<'a>),
|
Or(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
/// `xor dst a b` - Logical XOR
|
/// `xor dst a b` - Bitwise XOR
|
||||||
Xor(Operand<'a>, Operand<'a>, Operand<'a>),
|
Xor(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
|
/// `nor dst a b` - Bitwise NOR
|
||||||
|
Nor(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
|
/// `not dst a` - Bitwise NOT
|
||||||
|
Not(Operand<'a>, Operand<'a>),
|
||||||
|
/// `sll dst a b` - Logical Left Shift
|
||||||
|
Sll(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
|
/// `sra dst a b` - Arithmetic Right Shift
|
||||||
|
Sra(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
|
/// `srl dst a b` - Logical Right Shift
|
||||||
|
Srl(Operand<'a>, Operand<'a>, Operand<'a>),
|
||||||
|
|
||||||
/// `push val` - Push to Stack
|
/// `push val` - Push to Stack
|
||||||
Push(Operand<'a>),
|
Push(Operand<'a>),
|
||||||
@@ -256,6 +270,8 @@ pub enum Instruction<'a> {
|
|||||||
Yield,
|
Yield,
|
||||||
/// `sleep val` - Sleep for seconds
|
/// `sleep val` - Sleep for seconds
|
||||||
Sleep(Operand<'a>),
|
Sleep(Operand<'a>),
|
||||||
|
/// `clr val` - Clear stack memory on device
|
||||||
|
Clr(Operand<'a>),
|
||||||
|
|
||||||
/// `alias name target` - Define Alias (Usually handled by compiler, but good for IR)
|
/// `alias name target` - Define Alias (Usually handled by compiler, but good for IR)
|
||||||
Alias(Cow<'a, str>, Operand<'a>),
|
Alias(Cow<'a, str>, Operand<'a>),
|
||||||
@@ -317,6 +333,9 @@ impl<'a> fmt::Display for Instruction<'a> {
|
|||||||
Instruction::LoadReagent(reg, device, reagent_mode, reagent_hash) => {
|
Instruction::LoadReagent(reg, device, reagent_mode, reagent_hash) => {
|
||||||
write!(f, "lr {} {} {} {}", reg, device, reagent_mode, reagent_hash)
|
write!(f, "lr {} {} {} {}", reg, device, reagent_mode, reagent_hash)
|
||||||
}
|
}
|
||||||
|
Instruction::Rmap(reg, device, reagent_hash) => {
|
||||||
|
write!(f, "rmap {} {} {}", reg, device, reagent_hash)
|
||||||
|
}
|
||||||
Instruction::Jump(lbl) => write!(f, "j {}", lbl),
|
Instruction::Jump(lbl) => write!(f, "j {}", lbl),
|
||||||
Instruction::JumpAndLink(lbl) => write!(f, "jal {}", lbl),
|
Instruction::JumpAndLink(lbl) => write!(f, "jal {}", lbl),
|
||||||
Instruction::JumpRelative(off) => write!(f, "jr {}", off),
|
Instruction::JumpRelative(off) => write!(f, "jr {}", off),
|
||||||
@@ -337,6 +356,11 @@ impl<'a> fmt::Display for Instruction<'a> {
|
|||||||
Instruction::And(dst, a, b) => write!(f, "and {} {} {}", dst, a, b),
|
Instruction::And(dst, a, b) => write!(f, "and {} {} {}", dst, a, b),
|
||||||
Instruction::Or(dst, a, b) => write!(f, "or {} {} {}", dst, a, b),
|
Instruction::Or(dst, a, b) => write!(f, "or {} {} {}", dst, a, b),
|
||||||
Instruction::Xor(dst, a, b) => write!(f, "xor {} {} {}", dst, a, b),
|
Instruction::Xor(dst, a, b) => write!(f, "xor {} {} {}", dst, a, b),
|
||||||
|
Instruction::Nor(dst, a, b) => write!(f, "nor {} {} {}", dst, a, b),
|
||||||
|
Instruction::Not(dst, a) => write!(f, "not {} {}", dst, a),
|
||||||
|
Instruction::Sll(dst, a, b) => write!(f, "sll {} {} {}", dst, a, b),
|
||||||
|
Instruction::Sra(dst, a, b) => write!(f, "sra {} {} {}", dst, a, b),
|
||||||
|
Instruction::Srl(dst, a, b) => write!(f, "srl {} {} {}", dst, a, b),
|
||||||
Instruction::Push(val) => write!(f, "push {}", val),
|
Instruction::Push(val) => write!(f, "push {}", val),
|
||||||
Instruction::Pop(dst) => write!(f, "pop {}", dst),
|
Instruction::Pop(dst) => write!(f, "pop {}", dst),
|
||||||
Instruction::Peek(dst) => write!(f, "peek {}", dst),
|
Instruction::Peek(dst) => write!(f, "peek {}", dst),
|
||||||
@@ -347,6 +371,7 @@ impl<'a> fmt::Display for Instruction<'a> {
|
|||||||
}
|
}
|
||||||
Instruction::Yield => write!(f, "yield"),
|
Instruction::Yield => write!(f, "yield"),
|
||||||
Instruction::Sleep(val) => write!(f, "sleep {}", val),
|
Instruction::Sleep(val) => write!(f, "sleep {}", val),
|
||||||
|
Instruction::Clr(val) => write!(f, "clr {}", val),
|
||||||
Instruction::Alias(name, target) => write!(f, "alias {} {}", name, target),
|
Instruction::Alias(name, target) => write!(f, "alias {} {}", name, target),
|
||||||
Instruction::Define(name, val) => write!(f, "define {} {}", name, val),
|
Instruction::Define(name, val) => write!(f, "define {} {}", name, val),
|
||||||
Instruction::LabelDef(lbl) => write!(f, "{}:", lbl),
|
Instruction::LabelDef(lbl) => write!(f, "{}:", lbl),
|
||||||
|
|||||||
2
rust_compiler/libs/integration_tests/.gitattributes
vendored
Normal file
2
rust_compiler/libs/integration_tests/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Treat snapshot files as text
|
||||||
|
*.snap text
|
||||||
19
rust_compiler/libs/integration_tests/Cargo.toml
Normal file
19
rust_compiler/libs/integration_tests/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "integration_tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
compiler = { path = "../compiler" }
|
||||||
|
parser = { path = "../parser" }
|
||||||
|
tokenizer = { path = "../tokenizer" }
|
||||||
|
optimizer = { path = "../optimizer" }
|
||||||
|
il = { path = "../il" }
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
indoc = "2"
|
||||||
|
insta = "1.40"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# This is a test-only crate
|
||||||
|
path = "src/lib.rs"
|
||||||
92
rust_compiler/libs/integration_tests/README.md
Normal file
92
rust_compiler/libs/integration_tests/README.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Integration Tests for Slang Compiler with Optimizer
|
||||||
|
|
||||||
|
This crate contains end-to-end integration tests for the Slang compiler that verify the complete compilation pipeline including all optimization passes.
|
||||||
|
|
||||||
|
## Snapshot Testing with Insta
|
||||||
|
|
||||||
|
These tests use [insta](https://insta.rs/) for snapshot testing, which captures the entire compiled output and stores it in snapshot files for comparison.
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all integration tests
|
||||||
|
cargo test --package integration_tests
|
||||||
|
|
||||||
|
# Run a specific test
|
||||||
|
cargo test --package integration_tests test_simple_leaf_function
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updating Snapshots
|
||||||
|
|
||||||
|
When you make changes to the compiler or optimizer that affect the output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update all snapshots automatically
|
||||||
|
INSTA_UPDATE=always cargo test --package integration_tests
|
||||||
|
|
||||||
|
# Or use cargo-insta for interactive review (install first: cargo install cargo-insta)
|
||||||
|
cargo insta test --package integration_tests
|
||||||
|
cargo insta review --package integration_tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Understanding Snapshots
|
||||||
|
|
||||||
|
Snapshot files are stored in `src/snapshots/` and contain:
|
||||||
|
|
||||||
|
- The full IC10 assembly output from compiling Slang source code
|
||||||
|
- Metadata about which test generated them
|
||||||
|
- The expression that produced the output
|
||||||
|
|
||||||
|
Example snapshot structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
j main
|
||||||
|
move r8 10
|
||||||
|
j ra
|
||||||
|
```
|
||||||
|
|
||||||
|
### What We Test
|
||||||
|
|
||||||
|
1. **Leaf Function Optimization** - Removal of unnecessary `push sp/ra` and `pop ra/sp`
|
||||||
|
2. **Function Calls** - Preservation of stack frame when calling functions
|
||||||
|
3. **Constant Folding** - Compile-time evaluation of constant expressions
|
||||||
|
4. **Algebraic Simplification** - Identity operations like `x * 1` → `x`
|
||||||
|
5. **Strength Reduction** - Converting expensive operations like `x * 2` → `x + x`
|
||||||
|
6. **Dead Code Elimination** - Removal of unused variables
|
||||||
|
7. **Peephole Comparison Fusion** - Combining comparison + branch instructions
|
||||||
|
8. **Select Optimization** - Converting if/else to single `select` instruction
|
||||||
|
9. **Complex Arithmetic** - Multiple optimizations working together
|
||||||
|
10. **Nested Function Calls** - Full program optimization
|
||||||
|
|
||||||
|
### Adding New Tests
|
||||||
|
|
||||||
|
To add a new integration test:
|
||||||
|
|
||||||
|
1. Add a new `#[test]` function in `src/lib.rs`
|
||||||
|
2. Call `compile_optimized()` with your Slang source code
|
||||||
|
3. Use `insta::assert_snapshot!(output)` to capture the output
|
||||||
|
4. Run with `INSTA_UPDATE=always` to create the initial snapshot
|
||||||
|
5. Review the snapshot file to ensure it looks correct
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn test_my_optimization() {
|
||||||
|
let source = "fn foo(x) { return x + 1; }";
|
||||||
|
let output = compile_optimized(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benefits of Snapshot Testing
|
||||||
|
|
||||||
|
- **Full Output Verification**: Tests the entire compiled output, not just snippets
|
||||||
|
- **Easy to Review**: Visual diffs show exactly what changed in the output
|
||||||
|
- **Regression Detection**: Any change to output is immediately visible
|
||||||
|
- **Living Documentation**: Snapshots serve as examples of compiler output
|
||||||
|
- **Less Brittle**: No need to manually update expected strings when making intentional changes
|
||||||
112
rust_compiler/libs/integration_tests/src/bitwise_tests.rs
Normal file
112
rust_compiler/libs/integration_tests/src/bitwise_tests.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod bitwise_tests {
|
||||||
|
use crate::common::compile_with_and_without_optimization;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bitwise_operations() {
|
||||||
|
let source = indoc! {"
|
||||||
|
let a = 5;
|
||||||
|
let b = 3;
|
||||||
|
let and_result = a & b;
|
||||||
|
let or_result = a | b;
|
||||||
|
let xor_result = a ^ b;
|
||||||
|
let not_result = ~a;
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bitwise_shifts() {
|
||||||
|
let source = indoc! {"
|
||||||
|
let x = 8;
|
||||||
|
let left_shift = x << 2;
|
||||||
|
let arithmetic_shift = x >> 1;
|
||||||
|
let logical_shift = x >>> 1;
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bitwise_constant_folding() {
|
||||||
|
let source = indoc! {"
|
||||||
|
let packed = (1 << 16) & (255 << 8) & 2;
|
||||||
|
let mask = 0xFF & 0x0F;
|
||||||
|
let combined = (15 | 4096);
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bitwise_with_variables() {
|
||||||
|
let source = indoc! {"
|
||||||
|
fn pack_bits(high, low) {
|
||||||
|
let packed = (high << 8) | low;
|
||||||
|
return packed;
|
||||||
|
}
|
||||||
|
fn extract_bits(value) {
|
||||||
|
let high = (value >> 8) & 0xFF;
|
||||||
|
let low = value & 0xFF;
|
||||||
|
return (high, low);
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complex_bit_manipulation() {
|
||||||
|
let source = indoc! {"
|
||||||
|
fn encode_flags(enabled, mode, priority) {
|
||||||
|
let flag_byte = (enabled << 7) | (mode << 4) | priority;
|
||||||
|
return flag_byte;
|
||||||
|
}
|
||||||
|
fn decode_flags(byte) {
|
||||||
|
let enabled = (byte >> 7) & 1;
|
||||||
|
let mode = (byte >> 4) & 0x7;
|
||||||
|
let priority = byte & 0xF;
|
||||||
|
return (enabled, mode, priority);
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sorter_bitwise_operations() {
|
||||||
|
let source = indoc! {r#"
|
||||||
|
device self = "db";
|
||||||
|
device sorter = "d0";
|
||||||
|
|
||||||
|
loop {
|
||||||
|
yield();
|
||||||
|
// allow Hay with an op_code of `1`
|
||||||
|
sorter[0] = (hash("ItemCropHay") << 8) | 1;
|
||||||
|
}
|
||||||
|
"#};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bitwise_with_const() {
|
||||||
|
let source = indoc! {r#"
|
||||||
|
device sorterOutput = "d0";
|
||||||
|
|
||||||
|
const ingotSortClass = 19;
|
||||||
|
const equals = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
yield();
|
||||||
|
clr(sorterOutput);
|
||||||
|
sorterOutput[0] = (ingotSortClass << 16) | (equals << 8) | 3;
|
||||||
|
}
|
||||||
|
"#};
|
||||||
|
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
rust_compiler/libs/integration_tests/src/common.rs
Normal file
46
rust_compiler/libs/integration_tests/src/common.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use compiler::Compiler;
|
||||||
|
use parser::Parser;
|
||||||
|
use tokenizer::Tokenizer;
|
||||||
|
|
||||||
|
/// Compile Slang source code and return both unoptimized and optimized output
|
||||||
|
pub fn compile_with_and_without_optimization(source: &str) -> String {
|
||||||
|
// Compile for unoptimized output
|
||||||
|
let tokenizer = Tokenizer::from(source);
|
||||||
|
let parser = Parser::new(tokenizer);
|
||||||
|
let compiler = Compiler::new(parser, None);
|
||||||
|
let result = compiler.compile();
|
||||||
|
|
||||||
|
// Get unoptimized output
|
||||||
|
let mut unoptimized_writer = std::io::BufWriter::new(Vec::new());
|
||||||
|
result
|
||||||
|
.instructions
|
||||||
|
.write(&mut unoptimized_writer)
|
||||||
|
.expect("Failed to write unoptimized output");
|
||||||
|
let unoptimized_bytes = unoptimized_writer
|
||||||
|
.into_inner()
|
||||||
|
.expect("Failed to get bytes");
|
||||||
|
let unoptimized = String::from_utf8(unoptimized_bytes).expect("Invalid UTF-8");
|
||||||
|
|
||||||
|
// Compile again for optimized output
|
||||||
|
let tokenizer2 = Tokenizer::from(source);
|
||||||
|
let parser2 = Parser::new(tokenizer2);
|
||||||
|
let compiler2 = Compiler::new(parser2, None);
|
||||||
|
let result2 = compiler2.compile();
|
||||||
|
|
||||||
|
// Apply optimizations
|
||||||
|
let optimized_instructions = optimizer::optimize(result2.instructions);
|
||||||
|
|
||||||
|
// Get optimized output
|
||||||
|
let mut optimized_writer = std::io::BufWriter::new(Vec::new());
|
||||||
|
optimized_instructions
|
||||||
|
.write(&mut optimized_writer)
|
||||||
|
.expect("Failed to write optimized output");
|
||||||
|
let optimized_bytes = optimized_writer.into_inner().expect("Failed to get bytes");
|
||||||
|
let optimized = String::from_utf8(optimized_bytes).expect("Invalid UTF-8");
|
||||||
|
|
||||||
|
// Combine both outputs with clear separators
|
||||||
|
format!(
|
||||||
|
"## Unoptimized Output\n\n{}\n## Optimized Output\n\n{}",
|
||||||
|
unoptimized, optimized
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod device_indexing_tests {
|
||||||
|
use crate::common::compile_with_and_without_optimization;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_indexing_with_hash_and_binary_literals() {
|
||||||
|
let source = indoc! {"
|
||||||
|
device printer = \"d0\";
|
||||||
|
|
||||||
|
let item_type = hash(\"ItemIronIngot\");
|
||||||
|
let quality = 16;
|
||||||
|
let quantity = 7;
|
||||||
|
|
||||||
|
// Pack into a single value using bitwise operations
|
||||||
|
// Format: (itemHash << 16) | (quality << 8) | quantity
|
||||||
|
let packed = (item_type << 16) | (quality << 8) | quantity;
|
||||||
|
|
||||||
|
// Write to device stack at address 255
|
||||||
|
let addr = 255;
|
||||||
|
printer[addr] = packed;
|
||||||
|
|
||||||
|
// Read it back
|
||||||
|
let result = printer[addr];
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_indexing_with_computed_index() {
|
||||||
|
let source = indoc! {"
|
||||||
|
device storage = \"d1\";
|
||||||
|
|
||||||
|
let base_addr = 10;
|
||||||
|
let offset = 5;
|
||||||
|
let index = base_addr + offset;
|
||||||
|
|
||||||
|
let value = 42;
|
||||||
|
storage[index] = value;
|
||||||
|
|
||||||
|
let retrieved = storage[index];
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_indexing_with_binary_literals() {
|
||||||
|
let source = indoc! {"
|
||||||
|
device mem = \"d0\";
|
||||||
|
|
||||||
|
// Binary literals for bitwise operations
|
||||||
|
let flags = 0b1010_0101;
|
||||||
|
let mask = 0b1111_0000;
|
||||||
|
let masked = flags & mask;
|
||||||
|
|
||||||
|
// Write to device
|
||||||
|
mem[0] = masked;
|
||||||
|
|
||||||
|
// Read back
|
||||||
|
let read_value = mem[0];
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_indexing_complex_expression() {
|
||||||
|
let source = indoc! {"
|
||||||
|
device db = \"d0\";
|
||||||
|
|
||||||
|
let item = hash(\"ItemCopper\");
|
||||||
|
let quality = 5;
|
||||||
|
let quantity = 100;
|
||||||
|
|
||||||
|
// Complex bitwise expression
|
||||||
|
let packed = (item << 16) | ((quality & 0xFF) << 8) | (quantity & 0xFF);
|
||||||
|
|
||||||
|
// Index with computed address
|
||||||
|
let slot = 1;
|
||||||
|
let address = slot * 256 + 100;
|
||||||
|
db[address] = packed;
|
||||||
|
|
||||||
|
// Read back with different computation
|
||||||
|
let read_addr = (slot + 0) * 256 + 100;
|
||||||
|
let stored_value = db[read_addr];
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_device_indexing_optimization_folds_constants() {
|
||||||
|
let source = indoc! {"
|
||||||
|
device cache = \"d0\";
|
||||||
|
|
||||||
|
// This should optimize to a single constant
|
||||||
|
let packed_constant = (hash(\"ItemSilver\") << 16) | (10 << 8) | 50;
|
||||||
|
|
||||||
|
cache[255] = packed_constant;
|
||||||
|
let result = cache[255];
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
rust_compiler/libs/integration_tests/src/function_tests.rs
Normal file
48
rust_compiler/libs/integration_tests/src/function_tests.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod function_tests {
|
||||||
|
use crate::common::compile_with_and_without_optimization;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_leaf_function() {
|
||||||
|
let source = "fn test() { let x = 10; }";
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_function_with_call() {
|
||||||
|
let source = indoc! {"
|
||||||
|
fn add(a, b) { return a + b; }
|
||||||
|
fn main() { let x = add(5, 10); }
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_leaf_function_no_stack_frame() {
|
||||||
|
let source = indoc! {"
|
||||||
|
fn increment(x) {
|
||||||
|
x = x + 1;
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_function_calls() {
|
||||||
|
let source = indoc! {"
|
||||||
|
fn add(a, b) { return a + b; }
|
||||||
|
fn multiply(x, y) { return x * 2; }
|
||||||
|
fn complex(a, b) {
|
||||||
|
let sum = add(a, b);
|
||||||
|
let doubled = multiply(sum, 2);
|
||||||
|
return doubled;
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
86
rust_compiler/libs/integration_tests/src/lib.rs
Normal file
86
rust_compiler/libs/integration_tests/src/lib.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
//! Integration tests for the Slang compiler with optimizer
|
||||||
|
//!
|
||||||
|
//! These tests compile Slang source code and verify both the compilation
|
||||||
|
//! and optimization passes work correctly together using snapshot testing.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod bitwise_tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod common;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod device_indexing_tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod function_tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod number_literal_tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod optimization_tests;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod integration_tests {
|
||||||
|
use crate::common::compile_with_and_without_optimization;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod number_literal_tests {
|
||||||
|
use crate::common::compile_with_and_without_optimization;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binary_literals() {
|
||||||
|
let source = indoc! {"
|
||||||
|
let binary = 0b1010_1100;
|
||||||
|
let octal = 0o755;
|
||||||
|
let hex_upper = 0xDEAD_BEEF;
|
||||||
|
let hex_lower = 0xcafe;
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_number_literal_optimization() {
|
||||||
|
let source = indoc! {"
|
||||||
|
let decimal = 42_000;
|
||||||
|
let negative_hex = -0xFF;
|
||||||
|
let negative_binary = -0b1111_0000;
|
||||||
|
let temp_c = 100c;
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod optimization_tests {
|
||||||
|
use crate::common::compile_with_and_without_optimization;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_constant_folding() {
|
||||||
|
let source = "let x = 5 + 10;";
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_algebraic_simplification() {
|
||||||
|
let source = "let x = 5; let y = x * 1;";
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strength_reduction() {
|
||||||
|
let source = "fn double(x) { return x * 2; }";
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dead_code_elimination() {
|
||||||
|
let source = indoc! {"
|
||||||
|
fn compute(x) {
|
||||||
|
let unused = 20;
|
||||||
|
return x + 1;
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_peephole_comparison_fusion() {
|
||||||
|
let source = indoc! {"
|
||||||
|
fn compare(x, y) {
|
||||||
|
if (x > y) {
|
||||||
|
let z = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_select_optimization() {
|
||||||
|
let source = indoc! {"
|
||||||
|
fn ternary(cond) {
|
||||||
|
let result = 0;
|
||||||
|
if (cond) {
|
||||||
|
result = 10;
|
||||||
|
} else {
|
||||||
|
result = 20;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complex_arithmetic() {
|
||||||
|
let source = indoc! {"
|
||||||
|
fn compute(a, b, c) {
|
||||||
|
let x = a * 2;
|
||||||
|
let y = b + 0;
|
||||||
|
let z = c * 1;
|
||||||
|
return x + y + z;
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
let output = compile_with_and_without_optimization(source);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/bitwise_tests.rs
|
||||||
|
assertion_line: 40
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 0
|
||||||
|
move r9 15
|
||||||
|
move r10 4111
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 0
|
||||||
|
move r9 15
|
||||||
|
move r10 4111
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/bitwise_tests.rs
|
||||||
|
assertion_line: 17
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 5
|
||||||
|
move r9 3
|
||||||
|
and r1 r8 r9
|
||||||
|
move r10 r1
|
||||||
|
or r2 r8 r9
|
||||||
|
move r11 r2
|
||||||
|
xor r3 r8 r9
|
||||||
|
move r12 r3
|
||||||
|
not r4 r8
|
||||||
|
move r13 r4
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 5
|
||||||
|
move r9 3
|
||||||
|
move r1 1
|
||||||
|
move r10 1
|
||||||
|
move r2 7
|
||||||
|
move r11 7
|
||||||
|
move r3 6
|
||||||
|
move r12 6
|
||||||
|
not r4 r8
|
||||||
|
move r13 r4
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/bitwise_tests.rs
|
||||||
|
assertion_line: 29
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 8
|
||||||
|
sll r1 r8 2
|
||||||
|
move r9 r1
|
||||||
|
sra r2 r8 1
|
||||||
|
move r10 r2
|
||||||
|
srl r3 r8 1
|
||||||
|
move r11 r3
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 8
|
||||||
|
move r1 32
|
||||||
|
move r9 32
|
||||||
|
move r2 4
|
||||||
|
move r10 4
|
||||||
|
move r3 4
|
||||||
|
move r11 4
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/bitwise_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
__internal_L1:
|
||||||
|
yield
|
||||||
|
clr d0
|
||||||
|
put d0 0 1245187
|
||||||
|
j __internal_L1
|
||||||
|
__internal_L2:
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
yield
|
||||||
|
clr d0
|
||||||
|
put d0 0 1245187
|
||||||
|
j 0
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/bitwise_tests.rs
|
||||||
|
assertion_line: 57
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
pack_bits:
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
sll r1 r9 8
|
||||||
|
or r2 r1 r8
|
||||||
|
move r10 r2
|
||||||
|
move r15 r10
|
||||||
|
j __internal_L1
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
extract_bits:
|
||||||
|
pop r8
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
sra r1 r8 8
|
||||||
|
and r2 r1 255
|
||||||
|
move r9 r2
|
||||||
|
and r3 r8 255
|
||||||
|
move r10 r3
|
||||||
|
push r9
|
||||||
|
push r10
|
||||||
|
sub r0 sp 4
|
||||||
|
get r0 db r0
|
||||||
|
move r15 r0
|
||||||
|
j __internal_L2
|
||||||
|
__internal_L2:
|
||||||
|
sub r0 sp 3
|
||||||
|
get ra db r0
|
||||||
|
j ra
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
sll r1 r9 8
|
||||||
|
or r15 r1 r8
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
pop r8
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
sra r1 r8 8
|
||||||
|
and r9 r1 255
|
||||||
|
and r10 r8 255
|
||||||
|
push r9
|
||||||
|
push r10
|
||||||
|
sub r0 sp 4
|
||||||
|
get r15 db r0
|
||||||
|
sub r0 sp 3
|
||||||
|
get ra db r0
|
||||||
|
j ra
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/bitwise_tests.rs
|
||||||
|
assertion_line: 75
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
encode_flags:
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
pop r10
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
sll r1 r10 7
|
||||||
|
sll r2 r9 4
|
||||||
|
or r3 r1 r2
|
||||||
|
or r4 r3 r8
|
||||||
|
move r11 r4
|
||||||
|
move r15 r11
|
||||||
|
j __internal_L1
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
decode_flags:
|
||||||
|
pop r8
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
sra r1 r8 7
|
||||||
|
and r2 r1 1
|
||||||
|
move r9 r2
|
||||||
|
sra r3 r8 4
|
||||||
|
and r4 r3 7
|
||||||
|
move r10 r4
|
||||||
|
and r5 r8 15
|
||||||
|
move r11 r5
|
||||||
|
push r9
|
||||||
|
push r10
|
||||||
|
push r11
|
||||||
|
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
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
pop r10
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
sll r1 r10 7
|
||||||
|
sll r2 r9 4
|
||||||
|
or r3 r1 r2
|
||||||
|
or r15 r3 r8
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
pop r8
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
sra r1 r8 7
|
||||||
|
and r9 r1 1
|
||||||
|
sra r3 r8 4
|
||||||
|
and r10 r3 7
|
||||||
|
and r11 r8 15
|
||||||
|
push r9
|
||||||
|
push r10
|
||||||
|
push r11
|
||||||
|
sub r0 sp 5
|
||||||
|
get r15 db r0
|
||||||
|
sub r0 sp 4
|
||||||
|
get ra db r0
|
||||||
|
j ra
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/bitwise_tests.rs
|
||||||
|
assertion_line: 91
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
__internal_L1:
|
||||||
|
yield
|
||||||
|
put d0 0 55164456193
|
||||||
|
j __internal_L1
|
||||||
|
__internal_L2:
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
yield
|
||||||
|
put d0 0 55164456193
|
||||||
|
j 0
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||||
|
assertion_line: 90
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 r15
|
||||||
|
move r9 5
|
||||||
|
move r10 100
|
||||||
|
sll r1 r8 16
|
||||||
|
and r2 r9 255
|
||||||
|
sll r3 r2 8
|
||||||
|
or r4 r1 r3
|
||||||
|
and r5 r10 255
|
||||||
|
or r6 r4 r5
|
||||||
|
move r11 r6
|
||||||
|
move r12 1
|
||||||
|
mul r7 r12 256
|
||||||
|
add r2 r7 100
|
||||||
|
move r13 r2
|
||||||
|
put d0 r13 r11
|
||||||
|
add r1 r12 0
|
||||||
|
mul r3 r1 256
|
||||||
|
add r4 r3 100
|
||||||
|
move r14 r4
|
||||||
|
get r5 d0 r14
|
||||||
|
push r5
|
||||||
|
sub sp sp 1
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 r15
|
||||||
|
move r9 5
|
||||||
|
move r10 100
|
||||||
|
sll r1 r8 16
|
||||||
|
move r3 1280
|
||||||
|
or r4 r1 r3
|
||||||
|
move r5 100
|
||||||
|
or r11 r4 r5
|
||||||
|
move r12 1
|
||||||
|
move r7 256
|
||||||
|
move r2 356
|
||||||
|
move r13 356
|
||||||
|
put d0 r13 r11
|
||||||
|
move r1 1
|
||||||
|
move r3 256
|
||||||
|
move r4 356
|
||||||
|
move r14 356
|
||||||
|
get r5 d0 r14
|
||||||
|
push r5
|
||||||
|
sub sp sp 1
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||||
|
assertion_line: 105
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 -62440604628430
|
||||||
|
put d0 255 r8
|
||||||
|
get r1 d0 255
|
||||||
|
move r9 r1
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 -62440604628430
|
||||||
|
put d0 255 r8
|
||||||
|
get r9 d0 255
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||||
|
assertion_line: 65
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 165
|
||||||
|
move r9 240
|
||||||
|
and r1 r8 r9
|
||||||
|
move r10 r1
|
||||||
|
put d0 0 r10
|
||||||
|
get r2 d0 0
|
||||||
|
move r11 r2
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 165
|
||||||
|
move r9 240
|
||||||
|
move r1 160
|
||||||
|
move r10 160
|
||||||
|
put d0 0 r10
|
||||||
|
get r11 d0 0
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||||
|
assertion_line: 45
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 10
|
||||||
|
move r9 5
|
||||||
|
add r1 r8 r9
|
||||||
|
move r10 r1
|
||||||
|
move r11 42
|
||||||
|
put d1 r10 r11
|
||||||
|
get r2 d1 r10
|
||||||
|
move r12 r2
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 10
|
||||||
|
move r9 5
|
||||||
|
move r1 15
|
||||||
|
move r10 15
|
||||||
|
move r11 42
|
||||||
|
put d1 r10 r11
|
||||||
|
get r12 d1 r10
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/device_indexing_tests.rs
|
||||||
|
assertion_line: 27
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 r15
|
||||||
|
move r9 16
|
||||||
|
move r10 7
|
||||||
|
sll r1 r8 16
|
||||||
|
sll r2 r9 8
|
||||||
|
or r3 r1 r2
|
||||||
|
or r4 r3 r10
|
||||||
|
move r11 r4
|
||||||
|
move r12 255
|
||||||
|
put d0 r12 r11
|
||||||
|
get r5 d0 r12
|
||||||
|
move r13 r5
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 r15
|
||||||
|
move r9 16
|
||||||
|
move r10 7
|
||||||
|
sll r1 r8 16
|
||||||
|
move r2 4096
|
||||||
|
or r3 r1 r2
|
||||||
|
or r11 r3 r10
|
||||||
|
move r12 255
|
||||||
|
put d0 r12 r11
|
||||||
|
get r13 d0 r12
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/function_tests.rs
|
||||||
|
assertion_line: 20
|
||||||
|
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
|
||||||
|
main:
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
push 5
|
||||||
|
push 10
|
||||||
|
jal add
|
||||||
|
move r8 r15
|
||||||
|
__internal_L2:
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
j 9
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
add r15 r9 r8
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
push 5
|
||||||
|
push 10
|
||||||
|
jal 1
|
||||||
|
move r8 r15
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/function_tests.rs
|
||||||
|
assertion_line: 31
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
increment:
|
||||||
|
pop r8
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
add r1 r8 1
|
||||||
|
move r8 r1
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
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
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/function_tests.rs
|
||||||
|
assertion_line: 46
|
||||||
|
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 r15 r9 r8
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
add r15 r9 r9
|
||||||
|
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 9
|
||||||
|
pop r10
|
||||||
|
pop r9
|
||||||
|
pop r8
|
||||||
|
move r11 r15
|
||||||
|
move r15 r11
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/function_tests.rs
|
||||||
|
assertion_line: 10
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
test:
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
move r8 10
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
move r8 10
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 54
|
||||||
|
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
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 61
|
||||||
|
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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/lib.rs
|
||||||
|
assertion_line: 47
|
||||||
|
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
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/number_literal_tests.rs
|
||||||
|
assertion_line: 15
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 172
|
||||||
|
move r9 493
|
||||||
|
move r10 3735928559
|
||||||
|
move r11 51966
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 172
|
||||||
|
move r9 493
|
||||||
|
move r10 3735928559
|
||||||
|
move r11 51966
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/number_literal_tests.rs
|
||||||
|
assertion_line: 27
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 42000
|
||||||
|
move r9 -255
|
||||||
|
move r10 -240
|
||||||
|
move r11 373.15
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 42000
|
||||||
|
move r9 -255
|
||||||
|
move r10 -240
|
||||||
|
move r11 373.15
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/optimization_tests.rs
|
||||||
|
assertion_line: 17
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 5
|
||||||
|
mul r1 r8 1
|
||||||
|
move r9 r1
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 5
|
||||||
|
move r1 5
|
||||||
|
move r9 5
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/optimization_tests.rs
|
||||||
|
assertion_line: 80
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
compute:
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
pop r10
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
mul r1 r10 2
|
||||||
|
move r11 r1
|
||||||
|
add r2 r9 0
|
||||||
|
move r12 r2
|
||||||
|
mul r3 r8 1
|
||||||
|
move r13 r3
|
||||||
|
add r4 r11 r12
|
||||||
|
add r5 r4 r13
|
||||||
|
move r15 r5
|
||||||
|
j __internal_L1
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
pop r10
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
add r11 r10 r10
|
||||||
|
move r12 r9
|
||||||
|
move r13 r8
|
||||||
|
add r4 r11 r12
|
||||||
|
add r15 r4 r13
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/optimization_tests.rs
|
||||||
|
assertion_line: 10
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
main:
|
||||||
|
move r8 15
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
move r8 15
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/optimization_tests.rs
|
||||||
|
assertion_line: 36
|
||||||
|
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
|
||||||
|
move r9 20
|
||||||
|
add r15 r8 1
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/optimization_tests.rs
|
||||||
|
assertion_line: 49
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
compare:
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
sgt r1 r9 r8
|
||||||
|
beqz r1 __internal_L2
|
||||||
|
move r10 1
|
||||||
|
__internal_L2:
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
pop r8
|
||||||
|
pop r9
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
ble r9 r8 7
|
||||||
|
move r10 1
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/optimization_tests.rs
|
||||||
|
assertion_line: 66
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
ternary:
|
||||||
|
pop r8
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
move r9 0
|
||||||
|
beqz r8 __internal_L3
|
||||||
|
move r9 10
|
||||||
|
j __internal_L2
|
||||||
|
__internal_L3:
|
||||||
|
move r9 20
|
||||||
|
__internal_L2:
|
||||||
|
move r15 r9
|
||||||
|
j __internal_L1
|
||||||
|
__internal_L1:
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
|
|
||||||
|
## Optimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
pop r8
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
select r15 r8 10 20
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
source: libs/integration_tests/src/optimization_tests.rs
|
||||||
|
assertion_line: 24
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
## Unoptimized Output
|
||||||
|
|
||||||
|
j main
|
||||||
|
double:
|
||||||
|
pop r8
|
||||||
|
push sp
|
||||||
|
push ra
|
||||||
|
mul r1 r8 2
|
||||||
|
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 r15 r8 r8
|
||||||
|
pop ra
|
||||||
|
pop sp
|
||||||
|
j ra
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
161
rust_compiler/libs/optimizer/src/algebraic_simplification.rs
Normal file
161
rust_compiler/libs/optimizer/src/algebraic_simplification.rs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
|
/// Pass: Algebraic Simplification
|
||||||
|
/// Simplifies mathematical identities like x+0, x*1, x*0, etc.
|
||||||
|
pub fn algebraic_simplification<'a>(
|
||||||
|
input: Vec<InstructionNode<'a>>,
|
||||||
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
let mut changed = false;
|
||||||
|
|
||||||
|
for mut node in input {
|
||||||
|
let simplified = match &node.instruction {
|
||||||
|
// x + 0 = x
|
||||||
|
Instruction::Add(dst, a, Operand::Number(n)) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), a.clone()))
|
||||||
|
}
|
||||||
|
Instruction::Add(dst, Operand::Number(n), b) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), b.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// x - 0 = x
|
||||||
|
Instruction::Sub(dst, a, Operand::Number(n)) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), a.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// x * 1 = x
|
||||||
|
Instruction::Mul(dst, a, Operand::Number(n)) if *n == Decimal::from(1) => {
|
||||||
|
Some(Instruction::Move(dst.clone(), a.clone()))
|
||||||
|
}
|
||||||
|
Instruction::Mul(dst, Operand::Number(n), b) if *n == Decimal::from(1) => {
|
||||||
|
Some(Instruction::Move(dst.clone(), b.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// x * 0 = 0
|
||||||
|
Instruction::Mul(dst, _, Operand::Number(n)) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), Operand::Number(Decimal::ZERO)))
|
||||||
|
}
|
||||||
|
Instruction::Mul(dst, Operand::Number(n), _) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), Operand::Number(Decimal::ZERO)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// x / 1 = x
|
||||||
|
Instruction::Div(dst, a, Operand::Number(n)) if *n == Decimal::from(1) => {
|
||||||
|
Some(Instruction::Move(dst.clone(), a.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 / x = 0 (if x != 0, but we can't check at compile time for non-literals)
|
||||||
|
Instruction::Div(dst, Operand::Number(n), _) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), Operand::Number(Decimal::ZERO)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// x % 1 = 0
|
||||||
|
Instruction::Mod(dst, _, Operand::Number(n)) if *n == Decimal::from(1) => {
|
||||||
|
Some(Instruction::Move(dst.clone(), Operand::Number(Decimal::ZERO)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 % x = 0
|
||||||
|
Instruction::Mod(dst, Operand::Number(n), _) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), Operand::Number(Decimal::ZERO)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// x AND 0 = 0
|
||||||
|
Instruction::And(dst, _, Operand::Number(n)) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), Operand::Number(Decimal::ZERO)))
|
||||||
|
}
|
||||||
|
Instruction::And(dst, Operand::Number(n), _) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), Operand::Number(Decimal::ZERO)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// x OR 0 = x
|
||||||
|
Instruction::Or(dst, a, Operand::Number(n)) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), a.clone()))
|
||||||
|
}
|
||||||
|
Instruction::Or(dst, Operand::Number(n), b) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), b.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// x XOR 0 = x
|
||||||
|
Instruction::Xor(dst, a, Operand::Number(n)) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), a.clone()))
|
||||||
|
}
|
||||||
|
Instruction::Xor(dst, Operand::Number(n), b) if n.is_zero() => {
|
||||||
|
Some(Instruction::Move(dst.clone(), b.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(new) = simplified {
|
||||||
|
node.instruction = new;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_zero() {
|
||||||
|
let input = vec![InstructionNode::new(
|
||||||
|
Instruction::Add(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Register(2),
|
||||||
|
Operand::Number(Decimal::ZERO),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)];
|
||||||
|
|
||||||
|
let (output, changed) = algebraic_simplification(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::Move(Operand::Register(1), Operand::Register(2))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mul_one() {
|
||||||
|
let input = vec![InstructionNode::new(
|
||||||
|
Instruction::Mul(
|
||||||
|
Operand::Register(3),
|
||||||
|
Operand::Register(4),
|
||||||
|
Operand::Number(Decimal::ONE),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)];
|
||||||
|
|
||||||
|
let (output, changed) = algebraic_simplification(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::Move(Operand::Register(3), Operand::Register(4))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mul_zero() {
|
||||||
|
let input = vec![InstructionNode::new(
|
||||||
|
Instruction::Mul(
|
||||||
|
Operand::Register(5),
|
||||||
|
Operand::Register(6),
|
||||||
|
Operand::Number(Decimal::ZERO),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)];
|
||||||
|
|
||||||
|
let (output, changed) = algebraic_simplification(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::Move(Operand::Register(5), Operand::Number(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
213
rust_compiler/libs/optimizer/src/constant_propagation.rs
Normal file
213
rust_compiler/libs/optimizer/src/constant_propagation.rs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
use crate::helpers::get_destination_reg;
|
||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
|
/// Pass: Constant Propagation
|
||||||
|
/// Folds arithmetic operations when both operands are constant.
|
||||||
|
/// Also tracks register values and propagates them forward.
|
||||||
|
pub fn constant_propagation<'a>(
|
||||||
|
input: Vec<InstructionNode<'a>>,
|
||||||
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
let mut changed = false;
|
||||||
|
let mut registers: [Option<Decimal>; 16] = [None; 16];
|
||||||
|
|
||||||
|
for mut node in input {
|
||||||
|
// Invalidate register tracking on label/call boundaries
|
||||||
|
match &node.instruction {
|
||||||
|
Instruction::LabelDef(_) | Instruction::JumpAndLink(_) => registers = [None; 16],
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let simplified = match &node.instruction {
|
||||||
|
Instruction::Move(dst, src) => resolve_value(src, ®isters)
|
||||||
|
.map(|val| Instruction::Move(dst.clone(), Operand::Number(val))),
|
||||||
|
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::Mul(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x * y),
|
||||||
|
Instruction::Div(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| {
|
||||||
|
if y.is_zero() { Decimal::ZERO } else { x / y }
|
||||||
|
}),
|
||||||
|
Instruction::Mod(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| {
|
||||||
|
if y.is_zero() { Decimal::ZERO } else { x % y }
|
||||||
|
}),
|
||||||
|
Instruction::And(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| x & y),
|
||||||
|
Instruction::Or(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| x | y),
|
||||||
|
Instruction::Xor(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| x ^ y),
|
||||||
|
Instruction::Sll(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| {
|
||||||
|
if y >= 64 { 0 } else { x << y as u32 }
|
||||||
|
}),
|
||||||
|
Instruction::Sra(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| {
|
||||||
|
if y >= 64 {
|
||||||
|
if x < 0 { -1 } else { 0 }
|
||||||
|
} else {
|
||||||
|
x >> y as u32
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Instruction::Srl(dst, a, b) => try_fold_bitwise(dst, a, b, ®isters, |x, y| {
|
||||||
|
if y >= 64 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(x as u64 >> y as u32) as i64
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Instruction::BranchEq(a, b, l) => {
|
||||||
|
try_resolve_branch(a, b, l, ®isters, |x, y| x == y)
|
||||||
|
}
|
||||||
|
Instruction::BranchNe(a, b, l) => {
|
||||||
|
try_resolve_branch(a, b, l, ®isters, |x, y| x != y)
|
||||||
|
}
|
||||||
|
Instruction::BranchGt(a, b, l) => try_resolve_branch(a, b, l, ®isters, |x, y| x > y),
|
||||||
|
Instruction::BranchLt(a, b, l) => try_resolve_branch(a, b, l, ®isters, |x, y| x < y),
|
||||||
|
Instruction::BranchGe(a, b, l) => {
|
||||||
|
try_resolve_branch(a, b, l, ®isters, |x, y| x >= y)
|
||||||
|
}
|
||||||
|
Instruction::BranchLe(a, b, l) => {
|
||||||
|
try_resolve_branch(a, b, l, ®isters, |x, y| x <= y)
|
||||||
|
}
|
||||||
|
Instruction::BranchEqZero(a, l) => {
|
||||||
|
try_resolve_branch(a, &Operand::Number(0.into()), l, ®isters, |x, y| x == y)
|
||||||
|
}
|
||||||
|
Instruction::BranchNeZero(a, l) => {
|
||||||
|
try_resolve_branch(a, &Operand::Number(0.into()), l, ®isters, |x, y| x != y)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(new) = simplified {
|
||||||
|
node.instruction = new;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update register tracking
|
||||||
|
match &node.instruction {
|
||||||
|
Instruction::Move(Operand::Register(r), src) => {
|
||||||
|
registers[*r as usize] = resolve_value(src, ®isters)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(r) = get_destination_reg(&node.instruction) {
|
||||||
|
registers[r as usize] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out NOPs (empty labels from branch resolution)
|
||||||
|
if let Instruction::LabelDef(l) = &node.instruction
|
||||||
|
&& l.is_empty()
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(node);
|
||||||
|
}
|
||||||
|
(output, changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_value(op: &Operand, regs: &[Option<Decimal>; 16]) -> Option<Decimal> {
|
||||||
|
match op {
|
||||||
|
Operand::Number(n) => Some(*n),
|
||||||
|
Operand::Register(r) => regs[*r as usize],
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_fold_math<'a, F>(
|
||||||
|
dst: &Operand<'a>,
|
||||||
|
a: &Operand<'a>,
|
||||||
|
b: &Operand<'a>,
|
||||||
|
regs: &[Option<Decimal>; 16],
|
||||||
|
op: F,
|
||||||
|
) -> Option<Instruction<'a>>
|
||||||
|
where
|
||||||
|
F: Fn(Decimal, Decimal) -> Decimal,
|
||||||
|
{
|
||||||
|
let val_a = resolve_value(a, regs)?;
|
||||||
|
let val_b = resolve_value(b, regs)?;
|
||||||
|
Some(Instruction::Move(
|
||||||
|
dst.clone(),
|
||||||
|
Operand::Number(op(val_a, val_b)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decimal_to_i64(d: Decimal) -> i64 {
|
||||||
|
// Convert decimal to i64, truncating if needed
|
||||||
|
if let Ok(int_val) = d.try_into() {
|
||||||
|
int_val
|
||||||
|
} else {
|
||||||
|
// For very large or very small values, use a default
|
||||||
|
if d.is_sign_negative() {
|
||||||
|
i64::MIN
|
||||||
|
} else {
|
||||||
|
i64::MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i64_to_decimal(i: i64) -> Decimal {
|
||||||
|
Decimal::from(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_fold_bitwise<'a, F>(
|
||||||
|
dst: &Operand<'a>,
|
||||||
|
a: &Operand<'a>,
|
||||||
|
b: &Operand<'a>,
|
||||||
|
regs: &[Option<Decimal>; 16],
|
||||||
|
op: F,
|
||||||
|
) -> Option<Instruction<'a>>
|
||||||
|
where
|
||||||
|
F: Fn(i64, i64) -> i64,
|
||||||
|
{
|
||||||
|
let val_a = resolve_value(a, regs)?;
|
||||||
|
let val_b = resolve_value(b, regs)?;
|
||||||
|
let result = op(decimal_to_i64(val_a), decimal_to_i64(val_b));
|
||||||
|
Some(Instruction::Move(
|
||||||
|
dst.clone(),
|
||||||
|
Operand::Number(i64_to_decimal(result)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_resolve_branch<'a, F>(
|
||||||
|
a: &Operand<'a>,
|
||||||
|
b: &Operand<'a>,
|
||||||
|
label: &Operand<'a>,
|
||||||
|
regs: &[Option<Decimal>; 16],
|
||||||
|
check: F,
|
||||||
|
) -> Option<Instruction<'a>>
|
||||||
|
where
|
||||||
|
F: Fn(Decimal, Decimal) -> bool,
|
||||||
|
{
|
||||||
|
let val_a = resolve_value(a, regs)?;
|
||||||
|
let val_b = resolve_value(b, regs)?;
|
||||||
|
if check(val_a, val_b) {
|
||||||
|
Some(Instruction::Jump(label.clone()))
|
||||||
|
} else {
|
||||||
|
Some(Instruction::LabelDef("".into())) // NOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use il::InstructionNode;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fold_add() {
|
||||||
|
let input = vec![InstructionNode::new(
|
||||||
|
Instruction::Add(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Number(5.into()),
|
||||||
|
Operand::Number(3.into()),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)];
|
||||||
|
|
||||||
|
let (output, changed) = constant_propagation(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::Move(Operand::Register(1), Operand::Number(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
148
rust_compiler/libs/optimizer/src/dead_code.rs
Normal file
148
rust_compiler/libs/optimizer/src/dead_code.rs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
|
||||||
|
/// Pass: Redundant Move Elimination
|
||||||
|
/// Removes moves where source and destination are the same: `move rx rx`
|
||||||
|
pub fn remove_redundant_moves<'a>(
|
||||||
|
input: Vec<InstructionNode<'a>>,
|
||||||
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
let mut changed = false;
|
||||||
|
for node in input {
|
||||||
|
if let Instruction::Move(dst, src) = &node.instruction
|
||||||
|
&& dst == src
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
output.push(node);
|
||||||
|
}
|
||||||
|
(output, changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass: Dead Code Elimination
|
||||||
|
/// Removes unreachable code after unconditional jumps.
|
||||||
|
pub fn remove_unreachable_code<'a>(
|
||||||
|
input: Vec<InstructionNode<'a>>,
|
||||||
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
let mut changed = false;
|
||||||
|
let mut dead = false;
|
||||||
|
for node in input {
|
||||||
|
if let Instruction::LabelDef(_) = node.instruction {
|
||||||
|
dead = false;
|
||||||
|
}
|
||||||
|
if dead {
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Instruction::Jump(_) = node.instruction {
|
||||||
|
dead = true
|
||||||
|
}
|
||||||
|
output.push(node);
|
||||||
|
}
|
||||||
|
(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::*;
|
||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_redundant_move() {
|
||||||
|
let input = vec![InstructionNode::new(
|
||||||
|
Instruction::Move(Operand::Register(1), Operand::Register(1)),
|
||||||
|
None,
|
||||||
|
)];
|
||||||
|
|
||||||
|
let (output, changed) = remove_redundant_moves(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_unreachable() {
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(Instruction::Jump(Operand::Label("main".into())), None),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Add(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Number(1.into()),
|
||||||
|
Operand::Number(2.into()),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::LabelDef("main".into()), None),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = remove_unreachable_code(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
126
rust_compiler/libs/optimizer/src/dead_store_elimination.rs
Normal file
126
rust_compiler/libs/optimizer/src/dead_store_elimination.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
use crate::helpers::get_destination_reg;
|
||||||
|
use il::{Instruction, InstructionNode};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Pass: Dead Store Elimination
|
||||||
|
/// Removes writes to registers that are never read before being overwritten.
|
||||||
|
pub fn dead_store_elimination<'a>(
|
||||||
|
input: Vec<InstructionNode<'a>>,
|
||||||
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
|
// 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))
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last write location
|
||||||
|
last_write.insert(dest_reg, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
// 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 !to_remove.is_empty() {
|
||||||
|
let output = input
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, node)| {
|
||||||
|
if to_remove.contains(&i) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
(output, true)
|
||||||
|
} else {
|
||||||
|
(input, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simplified check: Does this instruction read the register?
|
||||||
|
fn reg_is_read_or_affects_control(instr: &Instruction, reg: u8) -> bool {
|
||||||
|
use crate::helpers::reg_is_read;
|
||||||
|
|
||||||
|
// If it reads the register, it's used
|
||||||
|
reg_is_read(instr, reg)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use il::Operand;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dead_store() {
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Move(Operand::Register(1), Operand::Number(5.into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Move(Operand::Register(1), Operand::Number(10.into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = dead_store_elimination(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
160
rust_compiler/libs/optimizer/src/function_call_optimization.rs
Normal file
160
rust_compiler/libs/optimizer/src/function_call_optimization.rs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
use crate::helpers::get_destination_reg;
|
||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
/// Analyzes which registers are written to by each function label.
|
||||||
|
fn analyze_clobbers(instructions: &[InstructionNode]) -> HashMap<String, HashSet<u8>> {
|
||||||
|
let mut clobbers = HashMap::new();
|
||||||
|
let mut current_label = None;
|
||||||
|
|
||||||
|
for node in instructions {
|
||||||
|
if let Instruction::LabelDef(label) = &node.instruction {
|
||||||
|
current_label = Some(label.to_string());
|
||||||
|
clobbers.insert(label.to_string(), HashSet::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(label) = ¤t_label
|
||||||
|
&& let Some(reg) = get_destination_reg(&node.instruction)
|
||||||
|
&& let Some(set) = clobbers.get_mut(label)
|
||||||
|
{
|
||||||
|
set.insert(reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clobbers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass: Function Call Optimization
|
||||||
|
/// Removes Push/Restore pairs surrounding a JAL if the target function does not clobber that register.
|
||||||
|
pub fn optimize_function_calls<'a>(
|
||||||
|
input: Vec<InstructionNode<'a>>,
|
||||||
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
|
let clobbers = analyze_clobbers(&input);
|
||||||
|
let mut changed = false;
|
||||||
|
let mut to_remove = HashSet::new();
|
||||||
|
let mut stack_adjustments = HashMap::new();
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < input.len() {
|
||||||
|
if let Instruction::JumpAndLink(Operand::Label(target)) = &input[i].instruction {
|
||||||
|
let target_key = target.to_string();
|
||||||
|
|
||||||
|
if let Some(func_clobbers) = clobbers.get(&target_key) {
|
||||||
|
// 1. Identify Pushes immediately preceding the JAL
|
||||||
|
let mut pushes = Vec::new(); // (index, register)
|
||||||
|
let mut scan_back = i.saturating_sub(1);
|
||||||
|
while scan_back > 0 {
|
||||||
|
if to_remove.contains(&scan_back) {
|
||||||
|
scan_back -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Instruction::Push(Operand::Register(r)) = &input[scan_back].instruction {
|
||||||
|
pushes.push((scan_back, *r));
|
||||||
|
scan_back -= 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Identify Restores immediately following the JAL
|
||||||
|
let mut restores = Vec::new(); // (index_of_get, register, index_of_sub)
|
||||||
|
let mut scan_fwd = i + 1;
|
||||||
|
while scan_fwd < input.len() {
|
||||||
|
// Skip 'sub r0 sp X'
|
||||||
|
if let Instruction::Sub(Operand::Register(0), Operand::StackPointer, _) =
|
||||||
|
&input[scan_fwd].instruction
|
||||||
|
{
|
||||||
|
// Check next instruction for the Get
|
||||||
|
if scan_fwd + 1 < input.len()
|
||||||
|
&& let Instruction::Get(Operand::Register(r), _, Operand::Register(0)) =
|
||||||
|
&input[scan_fwd + 1].instruction
|
||||||
|
{
|
||||||
|
restores.push((scan_fwd + 1, *r, scan_fwd));
|
||||||
|
scan_fwd += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Stack Cleanup
|
||||||
|
let cleanup_idx = scan_fwd;
|
||||||
|
let has_cleanup = if cleanup_idx < input.len() {
|
||||||
|
matches!(
|
||||||
|
input[cleanup_idx].instruction,
|
||||||
|
Instruction::Sub(
|
||||||
|
Operand::StackPointer,
|
||||||
|
Operand::StackPointer,
|
||||||
|
Operand::Number(_)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFEGUARD: Check Counts!
|
||||||
|
let mut push_counts = HashMap::new();
|
||||||
|
for (_, r) in &pushes {
|
||||||
|
*push_counts.entry(*r).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut restore_counts = HashMap::new();
|
||||||
|
for (_, r, _) in &restores {
|
||||||
|
*restore_counts.entry(*r).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let counts_match = push_counts
|
||||||
|
.iter()
|
||||||
|
.all(|(reg, count)| restore_counts.get(reg).unwrap_or(&0) == count);
|
||||||
|
let counts_match_reverse = restore_counts
|
||||||
|
.iter()
|
||||||
|
.all(|(reg, count)| push_counts.get(reg).unwrap_or(&0) == count);
|
||||||
|
|
||||||
|
// Clobber Check
|
||||||
|
let all_pushes_safe = pushes.iter().all(|(_, r)| !func_clobbers.contains(r));
|
||||||
|
|
||||||
|
if all_pushes_safe && has_cleanup && counts_match && counts_match_reverse {
|
||||||
|
// Remove all pushes/restores
|
||||||
|
for (p_idx, _) in pushes {
|
||||||
|
to_remove.insert(p_idx);
|
||||||
|
}
|
||||||
|
for (g_idx, _, s_idx) in restores {
|
||||||
|
to_remove.insert(g_idx);
|
||||||
|
to_remove.insert(s_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce stack cleanup amount
|
||||||
|
let num_removed = push_counts.values().sum::<i32>() as i64;
|
||||||
|
stack_adjustments.insert(cleanup_idx, num_removed);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
let mut clean = Vec::with_capacity(input.len());
|
||||||
|
for (idx, mut node) in input.into_iter().enumerate() {
|
||||||
|
if to_remove.contains(&idx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply stack adjustment
|
||||||
|
if let Some(reduction) = stack_adjustments.get(&idx)
|
||||||
|
&& let Instruction::Sub(dst, a, Operand::Number(n)) = &node.instruction
|
||||||
|
{
|
||||||
|
let new_n = n - Decimal::from(*reduction);
|
||||||
|
if new_n.is_zero() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
node.instruction = Instruction::Sub(dst.clone(), a.clone(), Operand::Number(new_n));
|
||||||
|
}
|
||||||
|
|
||||||
|
clean.push(node);
|
||||||
|
}
|
||||||
|
return (clean, changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
(input, false)
|
||||||
|
}
|
||||||
180
rust_compiler/libs/optimizer/src/helpers.rs
Normal file
180
rust_compiler/libs/optimizer/src/helpers.rs
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
use il::{Instruction, Operand};
|
||||||
|
|
||||||
|
/// Returns the register number written to by an instruction, if any.
|
||||||
|
pub fn get_destination_reg(instr: &Instruction) -> Option<u8> {
|
||||||
|
match instr {
|
||||||
|
Instruction::Move(Operand::Register(r), _)
|
||||||
|
| Instruction::Add(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Sub(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Mul(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Div(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Mod(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Pow(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Load(Operand::Register(r), _, _)
|
||||||
|
| Instruction::LoadSlot(Operand::Register(r), _, _, _)
|
||||||
|
| Instruction::LoadBatch(Operand::Register(r), _, _, _)
|
||||||
|
| Instruction::LoadBatchNamed(Operand::Register(r), _, _, _, _)
|
||||||
|
| Instruction::SetEq(Operand::Register(r), _, _)
|
||||||
|
| Instruction::SetNe(Operand::Register(r), _, _)
|
||||||
|
| Instruction::SetGt(Operand::Register(r), _, _)
|
||||||
|
| Instruction::SetLt(Operand::Register(r), _, _)
|
||||||
|
| Instruction::SetGe(Operand::Register(r), _, _)
|
||||||
|
| Instruction::SetLe(Operand::Register(r), _, _)
|
||||||
|
| Instruction::And(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Or(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Xor(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Peek(Operand::Register(r))
|
||||||
|
| Instruction::Get(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Select(Operand::Register(r), _, _, _)
|
||||||
|
| Instruction::Rand(Operand::Register(r))
|
||||||
|
| Instruction::Acos(Operand::Register(r), _)
|
||||||
|
| Instruction::Asin(Operand::Register(r), _)
|
||||||
|
| Instruction::Atan(Operand::Register(r), _)
|
||||||
|
| Instruction::Atan2(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Abs(Operand::Register(r), _)
|
||||||
|
| Instruction::Ceil(Operand::Register(r), _)
|
||||||
|
| Instruction::Cos(Operand::Register(r), _)
|
||||||
|
| Instruction::Floor(Operand::Register(r), _)
|
||||||
|
| Instruction::Log(Operand::Register(r), _)
|
||||||
|
| Instruction::Max(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Min(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Sin(Operand::Register(r), _)
|
||||||
|
| Instruction::Sqrt(Operand::Register(r), _)
|
||||||
|
| Instruction::Tan(Operand::Register(r), _)
|
||||||
|
| Instruction::Trunc(Operand::Register(r), _)
|
||||||
|
| Instruction::LoadReagent(Operand::Register(r), _, _, _)
|
||||||
|
| Instruction::Rmap(Operand::Register(r), _, _)
|
||||||
|
| Instruction::Pop(Operand::Register(r)) => Some(*r),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new instruction with the destination register changed.
|
||||||
|
pub fn set_destination_reg<'a>(instr: &Instruction<'a>, new_reg: u8) -> Option<Instruction<'a>> {
|
||||||
|
let r = Operand::Register(new_reg);
|
||||||
|
match instr {
|
||||||
|
Instruction::Move(_, b) => Some(Instruction::Move(r, b.clone())),
|
||||||
|
Instruction::Add(_, a, b) => Some(Instruction::Add(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Sub(_, a, b) => Some(Instruction::Sub(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Mul(_, a, b) => Some(Instruction::Mul(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Div(_, a, b) => Some(Instruction::Div(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Mod(_, a, b) => Some(Instruction::Mod(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Pow(_, a, b) => Some(Instruction::Pow(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Load(_, a, b) => Some(Instruction::Load(r, a.clone(), b.clone())),
|
||||||
|
Instruction::LoadSlot(_, a, b, c) => {
|
||||||
|
Some(Instruction::LoadSlot(r, a.clone(), b.clone(), c.clone()))
|
||||||
|
}
|
||||||
|
Instruction::LoadBatch(_, a, b, c) => {
|
||||||
|
Some(Instruction::LoadBatch(r, a.clone(), b.clone(), c.clone()))
|
||||||
|
}
|
||||||
|
Instruction::LoadBatchNamed(_, a, b, c, d) => Some(Instruction::LoadBatchNamed(
|
||||||
|
r,
|
||||||
|
a.clone(),
|
||||||
|
b.clone(),
|
||||||
|
c.clone(),
|
||||||
|
d.clone(),
|
||||||
|
)),
|
||||||
|
Instruction::LoadReagent(_, b, c, d) => {
|
||||||
|
Some(Instruction::LoadReagent(r, b.clone(), c.clone(), d.clone()))
|
||||||
|
}
|
||||||
|
Instruction::SetEq(_, a, b) => Some(Instruction::SetEq(r, a.clone(), b.clone())),
|
||||||
|
Instruction::SetNe(_, a, b) => Some(Instruction::SetNe(r, a.clone(), b.clone())),
|
||||||
|
Instruction::SetGt(_, a, b) => Some(Instruction::SetGt(r, a.clone(), b.clone())),
|
||||||
|
Instruction::SetLt(_, a, b) => Some(Instruction::SetLt(r, a.clone(), b.clone())),
|
||||||
|
Instruction::SetGe(_, a, b) => Some(Instruction::SetGe(r, a.clone(), b.clone())),
|
||||||
|
Instruction::SetLe(_, a, b) => Some(Instruction::SetLe(r, a.clone(), b.clone())),
|
||||||
|
Instruction::And(_, a, b) => Some(Instruction::And(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Or(_, a, b) => Some(Instruction::Or(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Xor(_, a, b) => Some(Instruction::Xor(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Peek(_) => Some(Instruction::Peek(r)),
|
||||||
|
Instruction::Get(_, a, b) => Some(Instruction::Get(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Select(_, a, b, c) => {
|
||||||
|
Some(Instruction::Select(r, a.clone(), b.clone(), c.clone()))
|
||||||
|
}
|
||||||
|
Instruction::Rand(_) => Some(Instruction::Rand(r)),
|
||||||
|
Instruction::Pop(_) => Some(Instruction::Pop(r)),
|
||||||
|
Instruction::Acos(_, a) => Some(Instruction::Acos(r, a.clone())),
|
||||||
|
Instruction::Asin(_, a) => Some(Instruction::Asin(r, a.clone())),
|
||||||
|
Instruction::Atan(_, a) => Some(Instruction::Atan(r, a.clone())),
|
||||||
|
Instruction::Atan2(_, a, b) => Some(Instruction::Atan2(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Abs(_, a) => Some(Instruction::Abs(r, a.clone())),
|
||||||
|
Instruction::Ceil(_, a) => Some(Instruction::Ceil(r, a.clone())),
|
||||||
|
Instruction::Cos(_, a) => Some(Instruction::Cos(r, a.clone())),
|
||||||
|
Instruction::Floor(_, a) => Some(Instruction::Floor(r, a.clone())),
|
||||||
|
Instruction::Log(_, a) => Some(Instruction::Log(r, a.clone())),
|
||||||
|
Instruction::Max(_, a, b) => Some(Instruction::Max(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Min(_, a, b) => Some(Instruction::Min(r, a.clone(), b.clone())),
|
||||||
|
Instruction::Sin(_, a) => Some(Instruction::Sin(r, a.clone())),
|
||||||
|
Instruction::Sqrt(_, a) => Some(Instruction::Sqrt(r, a.clone())),
|
||||||
|
Instruction::Tan(_, a) => Some(Instruction::Tan(r, a.clone())),
|
||||||
|
Instruction::Trunc(_, a) => Some(Instruction::Trunc(r, a.clone())),
|
||||||
|
Instruction::Rmap(_, a, b) => Some(Instruction::Rmap(r, a.clone(), b.clone())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a register is read by an instruction.
|
||||||
|
pub fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
|
||||||
|
let check = |op: &Operand| matches!(op, Operand::Register(r) if *r == reg);
|
||||||
|
|
||||||
|
match instr {
|
||||||
|
Instruction::Move(_, a) => check(a),
|
||||||
|
Instruction::Add(_, a, b)
|
||||||
|
| Instruction::Sub(_, a, b)
|
||||||
|
| Instruction::Mul(_, a, b)
|
||||||
|
| Instruction::Div(_, a, b)
|
||||||
|
| Instruction::Mod(_, a, b)
|
||||||
|
| 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, _)
|
||||||
|
| Instruction::BranchLt(a, b, _)
|
||||||
|
| Instruction::BranchGe(a, b, _)
|
||||||
|
| Instruction::BranchLe(a, b, _) => check(a) || check(b),
|
||||||
|
Instruction::BranchEqZero(a, _) | Instruction::BranchNeZero(a, _) => check(a),
|
||||||
|
Instruction::LoadReagent(_, device, _, item_hash) => check(device) || check(item_hash),
|
||||||
|
Instruction::Rmap(_, device, reagent_hash) => check(device) || check(reagent_hash),
|
||||||
|
Instruction::LoadSlot(_, dev, slot, _) => check(dev) || check(slot),
|
||||||
|
Instruction::LoadBatch(_, dev, _, mode) => check(dev) || check(mode),
|
||||||
|
Instruction::LoadBatchNamed(_, d_hash, n_hash, _, mode) => {
|
||||||
|
check(d_hash) || check(n_hash) || check(mode)
|
||||||
|
}
|
||||||
|
Instruction::SetEq(_, a, b)
|
||||||
|
| Instruction::SetNe(_, a, b)
|
||||||
|
| Instruction::SetGt(_, a, b)
|
||||||
|
| Instruction::SetLt(_, a, b)
|
||||||
|
| Instruction::SetGe(_, a, b)
|
||||||
|
| Instruction::SetLe(_, a, b)
|
||||||
|
| Instruction::And(_, a, b)
|
||||||
|
| Instruction::Or(_, a, b)
|
||||||
|
| Instruction::Xor(_, a, b) => check(a) || check(b),
|
||||||
|
Instruction::Push(a) => check(a),
|
||||||
|
Instruction::Get(_, a, b) => check(a) || check(b),
|
||||||
|
Instruction::Put(a, b, c) => check(a) || check(b) || check(c),
|
||||||
|
Instruction::Select(_, a, b, c) => check(a) || check(b) || check(c),
|
||||||
|
Instruction::Sleep(a) => check(a),
|
||||||
|
Instruction::Acos(_, a)
|
||||||
|
| Instruction::Asin(_, a)
|
||||||
|
| Instruction::Atan(_, a)
|
||||||
|
| Instruction::Abs(_, a)
|
||||||
|
| Instruction::Ceil(_, a)
|
||||||
|
| Instruction::Cos(_, a)
|
||||||
|
| Instruction::Floor(_, a)
|
||||||
|
| Instruction::Log(_, a)
|
||||||
|
| Instruction::Sin(_, a)
|
||||||
|
| Instruction::Sqrt(_, a)
|
||||||
|
| Instruction::Tan(_, a)
|
||||||
|
| Instruction::Trunc(_, a) => check(a),
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
70
rust_compiler/libs/optimizer/src/label_resolution.rs
Normal file
70
rust_compiler/libs/optimizer/src/label_resolution.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Pass: Resolve Labels
|
||||||
|
/// Converts all Jump/Branch labels to absolute line numbers and removes LabelDefs.
|
||||||
|
pub fn resolve_labels<'a>(input: Vec<InstructionNode<'a>>) -> Vec<InstructionNode<'a>> {
|
||||||
|
let mut label_map: HashMap<String, usize> = HashMap::new();
|
||||||
|
let mut line_number = 0;
|
||||||
|
|
||||||
|
// Build Label Map (filtering out LabelDefs from the count)
|
||||||
|
for node in &input {
|
||||||
|
if let Instruction::LabelDef(name) = &node.instruction {
|
||||||
|
label_map.insert(name.to_string(), line_number);
|
||||||
|
} else {
|
||||||
|
line_number += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
|
||||||
|
// Rewrite Jumps and Filter Labels
|
||||||
|
for mut node in input {
|
||||||
|
// Helper to get line number as Decimal operand
|
||||||
|
let get_line = |lbl: &Operand| -> Option<Operand<'a>> {
|
||||||
|
if let Operand::Label(name) = lbl {
|
||||||
|
label_map
|
||||||
|
.get(name.as_ref())
|
||||||
|
.map(|&l| Operand::Number(Decimal::from(l)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match &mut node.instruction {
|
||||||
|
Instruction::LabelDef(_) => continue, // Strip labels
|
||||||
|
|
||||||
|
// Jumps
|
||||||
|
Instruction::Jump(op) => {
|
||||||
|
if let Some(num) = get_line(op) {
|
||||||
|
*op = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::JumpAndLink(op) => {
|
||||||
|
if let Some(num) = get_line(op) {
|
||||||
|
*op = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::BranchEq(_, _, op)
|
||||||
|
| Instruction::BranchNe(_, _, op)
|
||||||
|
| Instruction::BranchGt(_, _, op)
|
||||||
|
| Instruction::BranchLt(_, _, op)
|
||||||
|
| Instruction::BranchGe(_, _, op)
|
||||||
|
| Instruction::BranchLe(_, _, op) => {
|
||||||
|
if let Some(num) = get_line(op) {
|
||||||
|
*op = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::BranchEqZero(_, op) | Instruction::BranchNeZero(_, op) => {
|
||||||
|
if let Some(num) = get_line(op) {
|
||||||
|
*op = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
output.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
use crate::leaf_function::find_leaf_functions;
|
||||||
|
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) {
|
||||||
|
// Optimization disabled - returns input unchanged
|
||||||
|
#[allow(unused)]
|
||||||
|
let _leaves = find_leaf_functions(&input);
|
||||||
|
(input, false)
|
||||||
|
}
|
||||||
@@ -1,9 +1,30 @@
|
|||||||
use il::{Instruction, InstructionNode, Instructions, Operand};
|
use il::Instructions;
|
||||||
use rust_decimal::Decimal;
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
|
|
||||||
|
// Optimization pass modules
|
||||||
|
mod helpers;
|
||||||
mod leaf_function;
|
mod leaf_function;
|
||||||
use leaf_function::find_leaf_functions;
|
|
||||||
|
mod algebraic_simplification;
|
||||||
|
mod constant_propagation;
|
||||||
|
mod dead_code;
|
||||||
|
mod dead_store_elimination;
|
||||||
|
mod function_call_optimization;
|
||||||
|
mod label_resolution;
|
||||||
|
mod leaf_function_optimization;
|
||||||
|
mod peephole_optimization;
|
||||||
|
mod register_forwarding;
|
||||||
|
mod strength_reduction;
|
||||||
|
|
||||||
|
use algebraic_simplification::algebraic_simplification;
|
||||||
|
use constant_propagation::constant_propagation;
|
||||||
|
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;
|
||||||
|
use leaf_function_optimization::optimize_leaf_functions;
|
||||||
|
use peephole_optimization::peephole_optimization;
|
||||||
|
use register_forwarding::register_forwarding;
|
||||||
|
use strength_reduction::strength_reduction;
|
||||||
|
|
||||||
/// Entry point for the optimizer.
|
/// Entry point for the optimizer.
|
||||||
pub fn optimize<'a>(instructions: Instructions<'a>) -> Instructions<'a> {
|
pub fn optimize<'a>(instructions: Instructions<'a>) -> Instructions<'a> {
|
||||||
@@ -38,845 +59,42 @@ pub fn optimize<'a>(instructions: Instructions<'a>) -> Instructions<'a> {
|
|||||||
instructions = new_inst;
|
instructions = new_inst;
|
||||||
changed |= c4;
|
changed |= c4;
|
||||||
|
|
||||||
// Pass 5: Redundant Move Elimination
|
// Pass 5: Algebraic Simplification (Identity operations)
|
||||||
let (new_inst, c5) = remove_redundant_moves(instructions);
|
let (new_inst, c5) = algebraic_simplification(instructions);
|
||||||
instructions = new_inst;
|
instructions = new_inst;
|
||||||
changed |= c5;
|
changed |= c5;
|
||||||
|
|
||||||
// Pass 6: Dead Code Elimination
|
// Pass 6: Strength Reduction (Replace expensive ops with cheaper ones)
|
||||||
let (new_inst, c6) = remove_unreachable_code(instructions);
|
let (new_inst, c6) = strength_reduction(instructions);
|
||||||
instructions = new_inst;
|
instructions = new_inst;
|
||||||
changed |= c6;
|
changed |= c6;
|
||||||
|
|
||||||
|
// Pass 7: Peephole Optimizations (Common patterns)
|
||||||
|
let (new_inst, c7) = peephole_optimization(instructions);
|
||||||
|
instructions = new_inst;
|
||||||
|
changed |= c7;
|
||||||
|
|
||||||
|
// Pass 8: Dead Store Elimination
|
||||||
|
let (new_inst, c8) = dead_store_elimination(instructions);
|
||||||
|
instructions = new_inst;
|
||||||
|
changed |= c8;
|
||||||
|
|
||||||
|
// Pass 9: Redundant Move Elimination
|
||||||
|
let (new_inst, c9) = remove_redundant_moves(instructions);
|
||||||
|
instructions = new_inst;
|
||||||
|
changed |= c9;
|
||||||
|
|
||||||
|
// Pass 10: Dead Code Elimination
|
||||||
|
let (new_inst, c10) = remove_unreachable_code(instructions);
|
||||||
|
instructions = new_inst;
|
||||||
|
changed |= c10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
/// Helper: Check if a function body contains unsafe stack manipulation.
|
let (instructions, _) = remove_redundant_jumps(instructions);
|
||||||
/// Returns true if the function modifies SP in a way that makes static RA offset analysis unsafe.
|
|
||||||
fn function_has_complex_stack_ops(
|
Instructions::new(instructions)
|
||||||
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,
|
|
||||||
// Check for explicit SP modification
|
|
||||||
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
|
|
||||||
/// If a function makes no calls (is a leaf), it doesn't need to save/restore `ra`.
|
|
||||||
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();
|
|
||||||
|
|
||||||
// We map function names to the INDEX of the instruction that restores RA.
|
|
||||||
// We use this to validate the function body later.
|
|
||||||
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(_)) => {
|
|
||||||
// This is the restore instruction: `get ra db r0`
|
|
||||||
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 that functions marked for optimization don't have complex stack ops.
|
|
||||||
// If they do, unmark them.
|
|
||||||
let mut safe_functions = HashSet::new();
|
|
||||||
|
|
||||||
for (func, start_idx) in &function_start_indices {
|
|
||||||
if let Some(restore_idx) = func_restore_indices.get(func) {
|
|
||||||
// Check instructions between start and restore using the helper function.
|
|
||||||
// We need to skip the `push ra` we just marked for removal, otherwise the helper
|
|
||||||
// will flag it as a complex op (Push).
|
|
||||||
// `start_idx` is the LabelDef. `start_idx + 1` is typically `push ra`.
|
|
||||||
|
|
||||||
let check_start = if to_remove.contains(&(start_idx + 1)) {
|
|
||||||
start_idx + 2
|
|
||||||
} else {
|
|
||||||
start_idx + 1
|
|
||||||
};
|
|
||||||
|
|
||||||
// `restore_idx` points to the `get ra` instruction. The helper scans up to `end_idx` exclusive,
|
|
||||||
// so we don't need to worry about the restore instruction itself.
|
|
||||||
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, but only for SAFE functions
|
|
||||||
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; // SKIP (Remove)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// 1. Stack Cleanup Adjustment
|
|
||||||
if let Instruction::Sub(
|
|
||||||
Operand::StackPointer,
|
|
||||||
Operand::StackPointer,
|
|
||||||
Operand::Number(n),
|
|
||||||
) = &mut node.instruction
|
|
||||||
{
|
|
||||||
// Decrease cleanup amount by 1 (for the removed RA)
|
|
||||||
let new_n = *n - Decimal::from(1);
|
|
||||||
if new_n.is_zero() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
*n = new_n;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Stack Variable Offset Adjustment
|
|
||||||
// Since we verified the function is "Simple" (no nested stack mods),
|
|
||||||
// we can safely assume offsets > ra_offset need shifting.
|
|
||||||
if let Instruction::Sub(_, Operand::StackPointer, Operand::Number(n)) =
|
|
||||||
&mut node.instruction
|
|
||||||
&& *n > *ra_offset
|
|
||||||
{
|
|
||||||
*n -= Decimal::from(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
(output, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Analyzes which registers are written to by each function label.
|
|
||||||
fn analyze_clobbers(instructions: &[InstructionNode]) -> HashMap<String, HashSet<u8>> {
|
|
||||||
let mut clobbers = HashMap::new();
|
|
||||||
let mut current_label = None;
|
|
||||||
|
|
||||||
for node in instructions {
|
|
||||||
if let Instruction::LabelDef(label) = &node.instruction {
|
|
||||||
current_label = Some(label.to_string());
|
|
||||||
clobbers.insert(label.to_string(), HashSet::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(label) = ¤t_label
|
|
||||||
&& let Some(reg) = get_destination_reg(&node.instruction)
|
|
||||||
&& let Some(set) = clobbers.get_mut(label)
|
|
||||||
{
|
|
||||||
set.insert(reg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clobbers
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pass: Function Call Optimization
|
|
||||||
/// Removes Push/Restore pairs surrounding a JAL if the target function does not clobber that register.
|
|
||||||
fn optimize_function_calls<'a>(
|
|
||||||
input: Vec<InstructionNode<'a>>,
|
|
||||||
) -> (Vec<InstructionNode<'a>>, bool) {
|
|
||||||
let clobbers = analyze_clobbers(&input);
|
|
||||||
let mut changed = false;
|
|
||||||
let mut to_remove = HashSet::new();
|
|
||||||
let mut stack_adjustments = HashMap::new();
|
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
while i < input.len() {
|
|
||||||
if let Instruction::JumpAndLink(Operand::Label(target)) = &input[i].instruction {
|
|
||||||
let target_key = target.to_string();
|
|
||||||
|
|
||||||
if let Some(func_clobbers) = clobbers.get(&target_key) {
|
|
||||||
// 1. Identify Pushes immediately preceding the JAL
|
|
||||||
let mut pushes = Vec::new(); // (index, register)
|
|
||||||
let mut scan_back = i.saturating_sub(1);
|
|
||||||
while scan_back > 0 {
|
|
||||||
if to_remove.contains(&scan_back) {
|
|
||||||
scan_back -= 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Instruction::Push(Operand::Register(r)) = &input[scan_back].instruction {
|
|
||||||
pushes.push((scan_back, *r));
|
|
||||||
scan_back -= 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Identify Restores immediately following the JAL
|
|
||||||
let mut restores = Vec::new(); // (index_of_get, register, index_of_sub)
|
|
||||||
let mut scan_fwd = i + 1;
|
|
||||||
while scan_fwd < input.len() {
|
|
||||||
// Skip 'sub r0 sp X'
|
|
||||||
if let Instruction::Sub(Operand::Register(0), Operand::StackPointer, _) =
|
|
||||||
&input[scan_fwd].instruction
|
|
||||||
{
|
|
||||||
// Check next instruction for the Get
|
|
||||||
if scan_fwd + 1 < input.len()
|
|
||||||
&& let Instruction::Get(Operand::Register(r), _, Operand::Register(0)) =
|
|
||||||
&input[scan_fwd + 1].instruction
|
|
||||||
{
|
|
||||||
restores.push((scan_fwd + 1, *r, scan_fwd));
|
|
||||||
scan_fwd += 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Stack Cleanup
|
|
||||||
let cleanup_idx = scan_fwd;
|
|
||||||
let has_cleanup = if cleanup_idx < input.len() {
|
|
||||||
matches!(
|
|
||||||
input[cleanup_idx].instruction,
|
|
||||||
Instruction::Sub(
|
|
||||||
Operand::StackPointer,
|
|
||||||
Operand::StackPointer,
|
|
||||||
Operand::Number(_)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
// SAFEGUARD: Check Counts!
|
|
||||||
// If we pushed r8 twice but only restored it once, we have an argument.
|
|
||||||
// We must ensure the number of pushes for each register MATCHES the number of restores.
|
|
||||||
let mut push_counts = HashMap::new();
|
|
||||||
for (_, r) in &pushes {
|
|
||||||
*push_counts.entry(*r).or_insert(0) += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut restore_counts = HashMap::new();
|
|
||||||
for (_, r, _) in &restores {
|
|
||||||
*restore_counts.entry(*r).or_insert(0) += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let counts_match = push_counts
|
|
||||||
.iter()
|
|
||||||
.all(|(reg, count)| restore_counts.get(reg).unwrap_or(&0) == count);
|
|
||||||
// Also check reverse to ensure we didn't restore something we didn't push (unlikely but possible)
|
|
||||||
let counts_match_reverse = restore_counts
|
|
||||||
.iter()
|
|
||||||
.all(|(reg, count)| push_counts.get(reg).unwrap_or(&0) == count);
|
|
||||||
|
|
||||||
// Clobber Check
|
|
||||||
let all_pushes_safe = pushes.iter().all(|(_, r)| !func_clobbers.contains(r));
|
|
||||||
|
|
||||||
if all_pushes_safe && has_cleanup && counts_match && counts_match_reverse {
|
|
||||||
// We can remove ALL found pushes/restores safely
|
|
||||||
for (p_idx, _) in pushes {
|
|
||||||
to_remove.insert(p_idx);
|
|
||||||
}
|
|
||||||
for (g_idx, _, s_idx) in restores {
|
|
||||||
to_remove.insert(g_idx);
|
|
||||||
to_remove.insert(s_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reduce stack cleanup amount
|
|
||||||
let num_removed = push_counts.values().sum::<i32>() as i64;
|
|
||||||
stack_adjustments.insert(cleanup_idx, num_removed);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
let mut clean = Vec::with_capacity(input.len());
|
|
||||||
for (idx, mut node) in input.into_iter().enumerate() {
|
|
||||||
if to_remove.contains(&idx) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply stack adjustment
|
|
||||||
if let Some(reduction) = stack_adjustments.get(&idx)
|
|
||||||
&& let Instruction::Sub(dst, a, Operand::Number(n)) = &node.instruction
|
|
||||||
{
|
|
||||||
let new_n = n - Decimal::from(*reduction);
|
|
||||||
if new_n.is_zero() {
|
|
||||||
continue; // Remove the sub entirely if 0
|
|
||||||
}
|
|
||||||
node.instruction = Instruction::Sub(dst.clone(), a.clone(), Operand::Number(new_n));
|
|
||||||
}
|
|
||||||
|
|
||||||
clean.push(node);
|
|
||||||
}
|
|
||||||
return (clean, changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
(input, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pass: Register Forwarding
|
|
||||||
/// Eliminates intermediate moves by writing directly to the final destination.
|
|
||||||
/// Example: `l r1 d0 T` + `move r9 r1` -> `l r9 d0 T`
|
|
||||||
fn register_forwarding<'a>(
|
|
||||||
mut input: Vec<InstructionNode<'a>>,
|
|
||||||
) -> (Vec<InstructionNode<'a>>, bool) {
|
|
||||||
let mut changed = false;
|
|
||||||
let mut i = 0;
|
|
||||||
|
|
||||||
// We use a while loop to manually control index so we can peek ahead
|
|
||||||
while i < input.len().saturating_sub(1) {
|
|
||||||
let next_idx = i + 1;
|
|
||||||
|
|
||||||
// Check if current instruction defines a register
|
|
||||||
// and the NEXT instruction is a move from that register.
|
|
||||||
let forward_candidate = if let Some(def_reg) = get_destination_reg(&input[i].instruction) {
|
|
||||||
if let Instruction::Move(Operand::Register(dest_reg), Operand::Register(src_reg)) =
|
|
||||||
&input[next_idx].instruction
|
|
||||||
{
|
|
||||||
if *src_reg == def_reg {
|
|
||||||
// Candidate found: Instruction `i` defines `src_reg`, Instruction `i+1` moves `src_reg` to `dest_reg`.
|
|
||||||
// We can optimize if `src_reg` (the temp) is NOT used after this move.
|
|
||||||
Some((def_reg, *dest_reg))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((temp_reg, final_reg)) = forward_candidate {
|
|
||||||
// Check liveness: Is temp_reg used after i+1?
|
|
||||||
// We scan from i+2 onwards.
|
|
||||||
let mut temp_is_dead = true;
|
|
||||||
for node in input.iter().skip(i + 2) {
|
|
||||||
if reg_is_read(&node.instruction, temp_reg) {
|
|
||||||
temp_is_dead = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// If the temp is redefined, then the old value is dead, so we are safe.
|
|
||||||
if let Some(redef) = get_destination_reg(&node.instruction)
|
|
||||||
&& redef == temp_reg
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we hit a label/jump, we assume liveness might leak (conservative safety)
|
|
||||||
if matches!(
|
|
||||||
node.instruction,
|
|
||||||
Instruction::LabelDef(_) | Instruction::Jump(_) | Instruction::JumpAndLink(_)
|
|
||||||
) {
|
|
||||||
temp_is_dead = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if temp_is_dead {
|
|
||||||
// Perform the swap
|
|
||||||
// 1. Rewrite input[i] to write to final_reg
|
|
||||||
if let Some(new_instr) = set_destination_reg(&input[i].instruction, final_reg) {
|
|
||||||
input[i].instruction = new_instr;
|
|
||||||
// 2. Remove input[i+1] (The Move)
|
|
||||||
input.remove(next_idx);
|
|
||||||
changed = true;
|
|
||||||
// Don't increment i, re-evaluate current index (which is now a new neighbor)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
(input, changed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pass: Resolve Labels
|
|
||||||
/// Converts all Jump/Branch labels to absolute line numbers and removes LabelDefs.
|
|
||||||
fn resolve_labels<'a>(input: Vec<InstructionNode<'a>>) -> Vec<InstructionNode<'a>> {
|
|
||||||
let mut label_map: HashMap<String, usize> = HashMap::new();
|
|
||||||
let mut line_number = 0;
|
|
||||||
|
|
||||||
// 1. Build Label Map (filtering out LabelDefs from the count)
|
|
||||||
for node in &input {
|
|
||||||
if let Instruction::LabelDef(name) = &node.instruction {
|
|
||||||
label_map.insert(name.to_string(), line_number);
|
|
||||||
} else {
|
|
||||||
line_number += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut output = Vec::with_capacity(input.len());
|
|
||||||
|
|
||||||
// 2. Rewrite Jumps and Filter Labels
|
|
||||||
for mut node in input {
|
|
||||||
// Helper to get line number as Decimal operand
|
|
||||||
let get_line = |lbl: &Operand| -> Option<Operand<'a>> {
|
|
||||||
if let Operand::Label(name) = lbl {
|
|
||||||
label_map
|
|
||||||
.get(name.as_ref())
|
|
||||||
.map(|&l| Operand::Number(Decimal::from(l)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match &mut node.instruction {
|
|
||||||
Instruction::LabelDef(_) => continue, // Strip labels
|
|
||||||
|
|
||||||
// Jumps
|
|
||||||
Instruction::Jump(op) => {
|
|
||||||
if let Some(num) = get_line(op) {
|
|
||||||
*op = num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::JumpAndLink(op) => {
|
|
||||||
if let Some(num) = get_line(op) {
|
|
||||||
*op = num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::BranchEq(_, _, op)
|
|
||||||
| Instruction::BranchNe(_, _, op)
|
|
||||||
| Instruction::BranchGt(_, _, op)
|
|
||||||
| Instruction::BranchLt(_, _, op)
|
|
||||||
| Instruction::BranchGe(_, _, op)
|
|
||||||
| Instruction::BranchLe(_, _, op) => {
|
|
||||||
if let Some(num) = get_line(op) {
|
|
||||||
*op = num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::BranchEqZero(_, op) | Instruction::BranchNeZero(_, op) => {
|
|
||||||
if let Some(num) = get_line(op) {
|
|
||||||
*op = num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
output.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Helpers for Register Analysis ---
|
|
||||||
|
|
||||||
fn get_destination_reg(instr: &Instruction) -> Option<u8> {
|
|
||||||
match instr {
|
|
||||||
Instruction::Move(Operand::Register(r), _)
|
|
||||||
| Instruction::Add(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Sub(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Mul(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Div(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Mod(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Pow(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Load(Operand::Register(r), _, _)
|
|
||||||
| Instruction::LoadSlot(Operand::Register(r), _, _, _)
|
|
||||||
| Instruction::LoadBatch(Operand::Register(r), _, _, _)
|
|
||||||
| Instruction::LoadBatchNamed(Operand::Register(r), _, _, _, _)
|
|
||||||
| Instruction::SetEq(Operand::Register(r), _, _)
|
|
||||||
| Instruction::SetNe(Operand::Register(r), _, _)
|
|
||||||
| Instruction::SetGt(Operand::Register(r), _, _)
|
|
||||||
| Instruction::SetLt(Operand::Register(r), _, _)
|
|
||||||
| Instruction::SetGe(Operand::Register(r), _, _)
|
|
||||||
| Instruction::SetLe(Operand::Register(r), _, _)
|
|
||||||
| Instruction::And(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Or(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Xor(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Peek(Operand::Register(r))
|
|
||||||
| Instruction::Get(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Select(Operand::Register(r), _, _, _)
|
|
||||||
| Instruction::Rand(Operand::Register(r))
|
|
||||||
| Instruction::Acos(Operand::Register(r), _)
|
|
||||||
| Instruction::Asin(Operand::Register(r), _)
|
|
||||||
| Instruction::Atan(Operand::Register(r), _)
|
|
||||||
| Instruction::Atan2(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Abs(Operand::Register(r), _)
|
|
||||||
| Instruction::Ceil(Operand::Register(r), _)
|
|
||||||
| Instruction::Cos(Operand::Register(r), _)
|
|
||||||
| Instruction::Floor(Operand::Register(r), _)
|
|
||||||
| Instruction::Log(Operand::Register(r), _)
|
|
||||||
| Instruction::Max(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Min(Operand::Register(r), _, _)
|
|
||||||
| Instruction::Sin(Operand::Register(r), _)
|
|
||||||
| Instruction::Sqrt(Operand::Register(r), _)
|
|
||||||
| Instruction::Tan(Operand::Register(r), _)
|
|
||||||
| Instruction::Trunc(Operand::Register(r), _)
|
|
||||||
| Instruction::LoadReagent(Operand::Register(r), _, _, _)
|
|
||||||
| Instruction::Pop(Operand::Register(r)) => Some(*r),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_destination_reg<'a>(instr: &Instruction<'a>, new_reg: u8) -> Option<Instruction<'a>> {
|
|
||||||
// Helper to easily recreate instruction with new dest
|
|
||||||
let r = Operand::Register(new_reg);
|
|
||||||
match instr {
|
|
||||||
Instruction::Move(_, b) => Some(Instruction::Move(r, b.clone())),
|
|
||||||
Instruction::Add(_, a, b) => Some(Instruction::Add(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Sub(_, a, b) => Some(Instruction::Sub(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Mul(_, a, b) => Some(Instruction::Mul(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Div(_, a, b) => Some(Instruction::Div(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Mod(_, a, b) => Some(Instruction::Mod(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Pow(_, a, b) => Some(Instruction::Pow(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Load(_, a, b) => Some(Instruction::Load(r, a.clone(), b.clone())),
|
|
||||||
Instruction::LoadSlot(_, a, b, c) => {
|
|
||||||
Some(Instruction::LoadSlot(r, a.clone(), b.clone(), c.clone()))
|
|
||||||
}
|
|
||||||
Instruction::LoadBatch(_, a, b, c) => {
|
|
||||||
Some(Instruction::LoadBatch(r, a.clone(), b.clone(), c.clone()))
|
|
||||||
}
|
|
||||||
Instruction::LoadBatchNamed(_, a, b, c, d) => Some(Instruction::LoadBatchNamed(
|
|
||||||
r,
|
|
||||||
a.clone(),
|
|
||||||
b.clone(),
|
|
||||||
c.clone(),
|
|
||||||
d.clone(),
|
|
||||||
)),
|
|
||||||
Instruction::LoadReagent(_, b, c, d) => {
|
|
||||||
Some(Instruction::LoadReagent(r, b.clone(), c.clone(), d.clone()))
|
|
||||||
}
|
|
||||||
Instruction::SetEq(_, a, b) => Some(Instruction::SetEq(r, a.clone(), b.clone())),
|
|
||||||
Instruction::SetNe(_, a, b) => Some(Instruction::SetNe(r, a.clone(), b.clone())),
|
|
||||||
Instruction::SetGt(_, a, b) => Some(Instruction::SetGt(r, a.clone(), b.clone())),
|
|
||||||
Instruction::SetLt(_, a, b) => Some(Instruction::SetLt(r, a.clone(), b.clone())),
|
|
||||||
Instruction::SetGe(_, a, b) => Some(Instruction::SetGe(r, a.clone(), b.clone())),
|
|
||||||
Instruction::SetLe(_, a, b) => Some(Instruction::SetLe(r, a.clone(), b.clone())),
|
|
||||||
Instruction::And(_, a, b) => Some(Instruction::And(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Or(_, a, b) => Some(Instruction::Or(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Xor(_, a, b) => Some(Instruction::Xor(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Peek(_) => Some(Instruction::Peek(r)),
|
|
||||||
Instruction::Get(_, a, b) => Some(Instruction::Get(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Select(_, a, b, c) => {
|
|
||||||
Some(Instruction::Select(r, a.clone(), b.clone(), c.clone()))
|
|
||||||
}
|
|
||||||
Instruction::Rand(_) => Some(Instruction::Rand(r)),
|
|
||||||
Instruction::Pop(_) => Some(Instruction::Pop(r)),
|
|
||||||
|
|
||||||
// Math funcs
|
|
||||||
Instruction::Acos(_, a) => Some(Instruction::Acos(r, a.clone())),
|
|
||||||
Instruction::Asin(_, a) => Some(Instruction::Asin(r, a.clone())),
|
|
||||||
Instruction::Atan(_, a) => Some(Instruction::Atan(r, a.clone())),
|
|
||||||
Instruction::Atan2(_, a, b) => Some(Instruction::Atan2(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Abs(_, a) => Some(Instruction::Abs(r, a.clone())),
|
|
||||||
Instruction::Ceil(_, a) => Some(Instruction::Ceil(r, a.clone())),
|
|
||||||
Instruction::Cos(_, a) => Some(Instruction::Cos(r, a.clone())),
|
|
||||||
Instruction::Floor(_, a) => Some(Instruction::Floor(r, a.clone())),
|
|
||||||
Instruction::Log(_, a) => Some(Instruction::Log(r, a.clone())),
|
|
||||||
Instruction::Max(_, a, b) => Some(Instruction::Max(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Min(_, a, b) => Some(Instruction::Min(r, a.clone(), b.clone())),
|
|
||||||
Instruction::Sin(_, a) => Some(Instruction::Sin(r, a.clone())),
|
|
||||||
Instruction::Sqrt(_, a) => Some(Instruction::Sqrt(r, a.clone())),
|
|
||||||
Instruction::Tan(_, a) => Some(Instruction::Tan(r, a.clone())),
|
|
||||||
Instruction::Trunc(_, a) => Some(Instruction::Trunc(r, a.clone())),
|
|
||||||
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
|
|
||||||
let check = |op: &Operand| matches!(op, Operand::Register(r) if *r == reg);
|
|
||||||
|
|
||||||
match instr {
|
|
||||||
Instruction::Move(_, a) => check(a),
|
|
||||||
Instruction::Add(_, a, b)
|
|
||||||
| Instruction::Sub(_, a, b)
|
|
||||||
| Instruction::Mul(_, a, b)
|
|
||||||
| Instruction::Div(_, a, b)
|
|
||||||
| Instruction::Mod(_, a, b)
|
|
||||||
| Instruction::Pow(_, a, b) => check(a) || check(b),
|
|
||||||
|
|
||||||
Instruction::Load(_, a, _) => check(a), // Load reads device? Device can be reg? Yes.
|
|
||||||
Instruction::Store(a, _, b) => check(a) || check(b),
|
|
||||||
|
|
||||||
Instruction::BranchEq(a, b, _)
|
|
||||||
| Instruction::BranchNe(a, b, _)
|
|
||||||
| Instruction::BranchGt(a, b, _)
|
|
||||||
| Instruction::BranchLt(a, b, _)
|
|
||||||
| Instruction::BranchGe(a, b, _)
|
|
||||||
| Instruction::BranchLe(a, b, _) => check(a) || check(b),
|
|
||||||
|
|
||||||
Instruction::BranchEqZero(a, _) | Instruction::BranchNeZero(a, _) => check(a),
|
|
||||||
|
|
||||||
Instruction::LoadReagent(_, device, _, item_hash) => check(device) || check(item_hash),
|
|
||||||
|
|
||||||
Instruction::LoadSlot(_, dev, slot, _) => check(dev) || check(slot),
|
|
||||||
Instruction::LoadBatch(_, dev, _, mode) => check(dev) || check(mode),
|
|
||||||
Instruction::LoadBatchNamed(_, d_hash, n_hash, _, mode) => {
|
|
||||||
check(d_hash) || check(n_hash) || check(mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
Instruction::SetEq(_, a, b)
|
|
||||||
| Instruction::SetNe(_, a, b)
|
|
||||||
| Instruction::SetGt(_, a, b)
|
|
||||||
| Instruction::SetLt(_, a, b)
|
|
||||||
| Instruction::SetGe(_, a, b)
|
|
||||||
| Instruction::SetLe(_, a, b)
|
|
||||||
| Instruction::And(_, a, b)
|
|
||||||
| Instruction::Or(_, a, b)
|
|
||||||
| Instruction::Xor(_, a, b) => check(a) || check(b),
|
|
||||||
|
|
||||||
Instruction::Push(a) => check(a),
|
|
||||||
Instruction::Get(_, a, b) => check(a) || check(b),
|
|
||||||
Instruction::Put(a, b, c) => check(a) || check(b) || check(c),
|
|
||||||
|
|
||||||
Instruction::Select(_, a, b, c) => check(a) || check(b) || check(c),
|
|
||||||
Instruction::Sleep(a) => check(a),
|
|
||||||
|
|
||||||
// Math single arg
|
|
||||||
Instruction::Acos(_, a)
|
|
||||||
| Instruction::Asin(_, a)
|
|
||||||
| Instruction::Atan(_, a)
|
|
||||||
| Instruction::Abs(_, a)
|
|
||||||
| Instruction::Ceil(_, a)
|
|
||||||
| Instruction::Cos(_, a)
|
|
||||||
| Instruction::Floor(_, a)
|
|
||||||
| Instruction::Log(_, a)
|
|
||||||
| Instruction::Sin(_, a)
|
|
||||||
| Instruction::Sqrt(_, a)
|
|
||||||
| Instruction::Tan(_, a)
|
|
||||||
| Instruction::Trunc(_, a) => check(a),
|
|
||||||
|
|
||||||
// Math double arg
|
|
||||||
Instruction::Atan2(_, a, b) | Instruction::Max(_, a, b) | Instruction::Min(_, a, b) => {
|
|
||||||
check(a) || check(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// --- Constant Propagation & Dead Code ---
|
|
||||||
fn constant_propagation<'a>(input: Vec<InstructionNode<'a>>) -> (Vec<InstructionNode<'a>>, bool) {
|
|
||||||
let mut output = Vec::with_capacity(input.len());
|
|
||||||
let mut changed = false;
|
|
||||||
let mut registers: [Option<Decimal>; 16] = [None; 16];
|
|
||||||
|
|
||||||
for mut node in input {
|
|
||||||
match &node.instruction {
|
|
||||||
Instruction::LabelDef(_) | Instruction::JumpAndLink(_) => registers = [None; 16],
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let simplified = match &node.instruction {
|
|
||||||
Instruction::Move(dst, src) => resolve_value(src, ®isters)
|
|
||||||
.map(|val| Instruction::Move(dst.clone(), Operand::Number(val))),
|
|
||||||
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::Mul(dst, a, b) => try_fold_math(dst, a, b, ®isters, |x, y| x * y),
|
|
||||||
Instruction::Div(dst, a, b) => {
|
|
||||||
try_fold_math(
|
|
||||||
dst,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
®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) => {
|
|
||||||
try_resolve_branch(a, b, l, ®isters, |x, y| x == y)
|
|
||||||
}
|
|
||||||
Instruction::BranchNe(a, b, l) => {
|
|
||||||
try_resolve_branch(a, b, l, ®isters, |x, y| x != y)
|
|
||||||
}
|
|
||||||
Instruction::BranchGt(a, b, l) => try_resolve_branch(a, b, l, ®isters, |x, y| x > y),
|
|
||||||
Instruction::BranchLt(a, b, l) => try_resolve_branch(a, b, l, ®isters, |x, y| x < y),
|
|
||||||
Instruction::BranchGe(a, b, l) => {
|
|
||||||
try_resolve_branch(a, b, l, ®isters, |x, y| x >= y)
|
|
||||||
}
|
|
||||||
Instruction::BranchLe(a, b, l) => {
|
|
||||||
try_resolve_branch(a, b, l, ®isters, |x, y| x <= y)
|
|
||||||
}
|
|
||||||
Instruction::BranchEqZero(a, l) => {
|
|
||||||
try_resolve_branch(a, &Operand::Number(0.into()), l, ®isters, |x, y| x == y)
|
|
||||||
}
|
|
||||||
Instruction::BranchNeZero(a, l) => {
|
|
||||||
try_resolve_branch(a, &Operand::Number(0.into()), l, ®isters, |x, y| x != y)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(new) = simplified {
|
|
||||||
node.instruction = new;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update tracking
|
|
||||||
match &node.instruction {
|
|
||||||
Instruction::Move(Operand::Register(r), src) => {
|
|
||||||
registers[*r as usize] = resolve_value(src, ®isters)
|
|
||||||
}
|
|
||||||
// Invalidate if destination is register
|
|
||||||
_ => {
|
|
||||||
if let Some(r) = get_destination_reg(&node.instruction) {
|
|
||||||
registers[r as usize] = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out NOPs (Empty LabelDefs from branch resolution)
|
|
||||||
if let Instruction::LabelDef(l) = &node.instruction
|
|
||||||
&& l.is_empty()
|
|
||||||
{
|
|
||||||
changed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(node);
|
|
||||||
}
|
|
||||||
(output, changed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_value(op: &Operand, regs: &[Option<Decimal>; 16]) -> Option<Decimal> {
|
|
||||||
match op {
|
|
||||||
Operand::Number(n) => Some(*n),
|
|
||||||
Operand::Register(r) => regs[*r as usize],
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_fold_math<'a, F>(
|
|
||||||
dst: &Operand<'a>,
|
|
||||||
a: &Operand<'a>,
|
|
||||||
b: &Operand<'a>,
|
|
||||||
regs: &[Option<Decimal>; 16],
|
|
||||||
op: F,
|
|
||||||
) -> Option<Instruction<'a>>
|
|
||||||
where
|
|
||||||
F: Fn(Decimal, Decimal) -> Decimal,
|
|
||||||
{
|
|
||||||
let val_a = resolve_value(a, regs)?;
|
|
||||||
let val_b = resolve_value(b, regs)?;
|
|
||||||
Some(Instruction::Move(
|
|
||||||
dst.clone(),
|
|
||||||
Operand::Number(op(val_a, val_b)),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_resolve_branch<'a, F>(
|
|
||||||
a: &Operand<'a>,
|
|
||||||
b: &Operand<'a>,
|
|
||||||
label: &Operand<'a>,
|
|
||||||
regs: &[Option<Decimal>; 16],
|
|
||||||
check: F,
|
|
||||||
) -> Option<Instruction<'a>>
|
|
||||||
where
|
|
||||||
F: Fn(Decimal, Decimal) -> bool,
|
|
||||||
{
|
|
||||||
let val_a = resolve_value(a, regs)?;
|
|
||||||
let val_b = resolve_value(b, regs)?;
|
|
||||||
if check(val_a, val_b) {
|
|
||||||
Some(Instruction::Jump(label.clone()))
|
|
||||||
} else {
|
|
||||||
Some(Instruction::LabelDef("".into())) // NOP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_redundant_moves<'a>(input: Vec<InstructionNode<'a>>) -> (Vec<InstructionNode<'a>>, bool) {
|
|
||||||
let mut output = Vec::with_capacity(input.len());
|
|
||||||
let mut changed = false;
|
|
||||||
for node in input {
|
|
||||||
if let Instruction::Move(dst, src) = &node.instruction
|
|
||||||
&& dst == src
|
|
||||||
{
|
|
||||||
changed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
output.push(node);
|
|
||||||
}
|
|
||||||
(output, changed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_unreachable_code<'a>(
|
|
||||||
input: Vec<InstructionNode<'a>>,
|
|
||||||
) -> (Vec<InstructionNode<'a>>, bool) {
|
|
||||||
let mut output = Vec::with_capacity(input.len());
|
|
||||||
let mut changed = false;
|
|
||||||
let mut dead = false;
|
|
||||||
for node in input {
|
|
||||||
if let Instruction::LabelDef(_) = node.instruction {
|
|
||||||
dead = false;
|
|
||||||
}
|
|
||||||
if dead {
|
|
||||||
changed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Instruction::Jump(_) = node.instruction {
|
|
||||||
dead = true
|
|
||||||
}
|
|
||||||
output.push(node);
|
|
||||||
}
|
|
||||||
(output, changed)
|
|
||||||
}
|
}
|
||||||
|
|||||||
761
rust_compiler/libs/optimizer/src/peephole_optimization.rs
Normal file
761
rust_compiler/libs/optimizer/src/peephole_optimization.rs
Normal file
@@ -0,0 +1,761 @@
|
|||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
|
||||||
|
/// Pass: Peephole Optimization
|
||||||
|
/// Recognizes and optimizes common instruction patterns.
|
||||||
|
pub fn peephole_optimization<'a>(
|
||||||
|
input: Vec<InstructionNode<'a>>,
|
||||||
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
let mut changed = false;
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
while i < input.len() {
|
||||||
|
// Pattern: push sp; push ra ... pop ra; pop sp (with no jal in between)
|
||||||
|
// If we push sp and ra and later pop them, but never call a function in between, remove all four
|
||||||
|
// and adjust any stack pointer offsets in between by -2
|
||||||
|
if i + 1 < input.len() {
|
||||||
|
if let (
|
||||||
|
Instruction::Push(Operand::StackPointer),
|
||||||
|
Instruction::Push(Operand::ReturnAddress),
|
||||||
|
) = (&input[i].instruction, &input[i + 1].instruction)
|
||||||
|
{
|
||||||
|
// Look for matching pop ra; pop sp pattern
|
||||||
|
if let Some((ra_pop_idx, instructions_between)) =
|
||||||
|
find_matching_ra_pop(&input[i + 1..])
|
||||||
|
{
|
||||||
|
let absolute_ra_pop = i + 1 + ra_pop_idx;
|
||||||
|
// Check if the next instruction is pop sp
|
||||||
|
if absolute_ra_pop + 1 < input.len() {
|
||||||
|
if let Instruction::Pop(Operand::StackPointer) =
|
||||||
|
&input[absolute_ra_pop + 1].instruction
|
||||||
|
{
|
||||||
|
// Check if there's any jal between push and pop
|
||||||
|
let has_call = instructions_between.iter().any(|node| {
|
||||||
|
matches!(node.instruction, Instruction::JumpAndLink(_))
|
||||||
|
});
|
||||||
|
|
||||||
|
if !has_call {
|
||||||
|
// Safe to remove all four: push sp, push ra, pop ra, pop sp
|
||||||
|
// Also need to adjust stack pointer offsets in between by -2
|
||||||
|
let absolute_sp_pop = absolute_ra_pop + 1;
|
||||||
|
// Clear output since we're going to reprocess the entire input
|
||||||
|
output.clear();
|
||||||
|
for (idx, node) in input.iter().enumerate() {
|
||||||
|
if idx == i
|
||||||
|
|| idx == i + 1
|
||||||
|
|| idx == absolute_ra_pop
|
||||||
|
|| idx == absolute_sp_pop
|
||||||
|
{
|
||||||
|
// Skip all four push/pop instructions
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this instruction is between the pushes and pops, adjust its stack offsets
|
||||||
|
if idx > i + 1 && idx < absolute_ra_pop {
|
||||||
|
let adjusted_instruction =
|
||||||
|
adjust_stack_offset(node.instruction.clone(), 2);
|
||||||
|
output.push(InstructionNode::new(
|
||||||
|
adjusted_instruction,
|
||||||
|
node.span,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
output.push(node.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
// We've processed the entire input, so break
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern: push ra ... pop ra (with no jal in between)
|
||||||
|
// Fallback for when there's only ra push/pop without sp
|
||||||
|
if let Instruction::Push(Operand::ReturnAddress) = &input[i].instruction {
|
||||||
|
if let Some((pop_idx, instructions_between)) = find_matching_ra_pop(&input[i..]) {
|
||||||
|
// Check if there's any jal between push and pop
|
||||||
|
let has_call = instructions_between
|
||||||
|
.iter()
|
||||||
|
.any(|node| matches!(node.instruction, Instruction::JumpAndLink(_)));
|
||||||
|
|
||||||
|
if !has_call {
|
||||||
|
// Safe to remove both push and pop
|
||||||
|
// Also need to adjust stack pointer offsets in between
|
||||||
|
let absolute_pop_idx = i + pop_idx;
|
||||||
|
// Clear output since we're going to reprocess the entire input
|
||||||
|
output.clear();
|
||||||
|
for (idx, node) in input.iter().enumerate() {
|
||||||
|
if idx == i || idx == absolute_pop_idx {
|
||||||
|
// Skip the push and pop
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this instruction is between push and pop, adjust its stack offsets
|
||||||
|
if idx > i && idx < absolute_pop_idx {
|
||||||
|
let adjusted_instruction =
|
||||||
|
adjust_stack_offset(node.instruction.clone(), 1);
|
||||||
|
output.push(InstructionNode::new(adjusted_instruction, node.span));
|
||||||
|
} else {
|
||||||
|
output.push(node.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
// We've processed the entire input, so break
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern: Branch-Move-Jump-Label-Move-Label -> Select
|
||||||
|
// beqz r1 else_label
|
||||||
|
// move r2 val1
|
||||||
|
// j end_label
|
||||||
|
// else_label:
|
||||||
|
// move r2 val2
|
||||||
|
// end_label:
|
||||||
|
// Converts to: select r2 r1 val1 val2
|
||||||
|
if i + 5 < input.len() {
|
||||||
|
let select_pattern = try_match_select_pattern(&input[i..i + 6]);
|
||||||
|
if let Some((dst, cond, true_val, false_val, skip_count)) = select_pattern {
|
||||||
|
output.push(InstructionNode::new(
|
||||||
|
Instruction::Select(dst, cond, true_val, false_val),
|
||||||
|
input[i].span,
|
||||||
|
));
|
||||||
|
changed = true;
|
||||||
|
i += skip_count;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern: seq + beqz -> beq
|
||||||
|
if i + 1 < input.len() {
|
||||||
|
let pattern = match (&input[i].instruction, &input[i + 1].instruction) {
|
||||||
|
(
|
||||||
|
Instruction::SetEq(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchEqZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Eq, true)), // invert: beqz means "if NOT equal"
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetNe(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchEqZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Ne, true)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetGt(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchEqZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Gt, true)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetLt(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchEqZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Lt, true)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetGe(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchEqZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Ge, true)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetLe(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchEqZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Le, true)),
|
||||||
|
|
||||||
|
// Pattern: seq + bnez -> bne
|
||||||
|
(
|
||||||
|
Instruction::SetEq(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchNeZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Eq, false)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetNe(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchNeZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Ne, false)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetGt(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchNeZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Gt, false)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetLt(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchNeZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Lt, false)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetGe(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchNeZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Ge, false)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Instruction::SetLe(Operand::Register(temp), a, b),
|
||||||
|
Instruction::BranchNeZero(Operand::Register(cond), label),
|
||||||
|
) if temp == cond => Some((a, b, label, BranchType::Le, false)),
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((a, b, label, branch_type, invert)) = pattern {
|
||||||
|
// Create optimized branch instruction
|
||||||
|
let new_instr = if invert {
|
||||||
|
// beqz after seq means "branch if NOT equal" -> bne
|
||||||
|
match branch_type {
|
||||||
|
BranchType::Eq => {
|
||||||
|
Instruction::BranchNe(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Ne => {
|
||||||
|
Instruction::BranchEq(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Gt => {
|
||||||
|
Instruction::BranchLe(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Lt => {
|
||||||
|
Instruction::BranchGe(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Ge => {
|
||||||
|
Instruction::BranchLt(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Le => {
|
||||||
|
Instruction::BranchGt(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// bnez after seq means "branch if equal" -> beq
|
||||||
|
match branch_type {
|
||||||
|
BranchType::Eq => {
|
||||||
|
Instruction::BranchEq(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Ne => {
|
||||||
|
Instruction::BranchNe(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Gt => {
|
||||||
|
Instruction::BranchGt(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Lt => {
|
||||||
|
Instruction::BranchLt(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Ge => {
|
||||||
|
Instruction::BranchGe(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
BranchType::Le => {
|
||||||
|
Instruction::BranchLe(a.clone(), b.clone(), label.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
output.push(InstructionNode::new(new_instr, input[i].span));
|
||||||
|
changed = true;
|
||||||
|
i += 2; // Skip both instructions
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(input[i].clone());
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to match a select pattern in the instruction sequence.
|
||||||
|
/// Pattern (6 instructions):
|
||||||
|
/// beqz/bnez cond else_label (i+0)
|
||||||
|
/// move dst val1 (i+1)
|
||||||
|
/// j end_label (i+2)
|
||||||
|
/// else_label: (i+3)
|
||||||
|
/// move dst val2 (i+4)
|
||||||
|
/// end_label: (i+5)
|
||||||
|
/// Returns: (dst, cond, true_val, false_val, instruction_count)
|
||||||
|
fn try_match_select_pattern<'a>(
|
||||||
|
instructions: &[InstructionNode<'a>],
|
||||||
|
) -> Option<(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>, usize)> {
|
||||||
|
if instructions.len() < 6 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for beqz pattern
|
||||||
|
if let Instruction::BranchEqZero(cond, Operand::Label(else_label)) =
|
||||||
|
&instructions[0].instruction
|
||||||
|
{
|
||||||
|
if let Instruction::Move(dst1, val1) = &instructions[1].instruction {
|
||||||
|
if let Instruction::Jump(Operand::Label(end_label)) = &instructions[2].instruction {
|
||||||
|
if let Instruction::LabelDef(label3) = &instructions[3].instruction {
|
||||||
|
if label3 == else_label {
|
||||||
|
if let Instruction::Move(dst2, val2) = &instructions[4].instruction {
|
||||||
|
if dst1 == dst2 {
|
||||||
|
if let Instruction::LabelDef(label5) = &instructions[5].instruction
|
||||||
|
{
|
||||||
|
if label5 == end_label {
|
||||||
|
// beqz means: if cond==0, goto else, so val1 is for true, val2 for false
|
||||||
|
// select dst cond true_val false_val
|
||||||
|
// When cond is non-zero (true), use val1, otherwise val2
|
||||||
|
return Some((
|
||||||
|
dst1.clone(),
|
||||||
|
cond.clone(),
|
||||||
|
val1.clone(),
|
||||||
|
val2.clone(),
|
||||||
|
6,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for bnez pattern
|
||||||
|
if let Instruction::BranchNeZero(cond, Operand::Label(then_label)) =
|
||||||
|
&instructions[0].instruction
|
||||||
|
{
|
||||||
|
if let Instruction::Move(dst1, val_false) = &instructions[1].instruction {
|
||||||
|
if let Instruction::Jump(Operand::Label(end_label)) = &instructions[2].instruction {
|
||||||
|
if let Instruction::LabelDef(label3) = &instructions[3].instruction {
|
||||||
|
if label3 == then_label {
|
||||||
|
if let Instruction::Move(dst2, val_true) = &instructions[4].instruction {
|
||||||
|
if dst1 == dst2 {
|
||||||
|
if let Instruction::LabelDef(label5) = &instructions[5].instruction
|
||||||
|
{
|
||||||
|
if label5 == end_label {
|
||||||
|
// bnez means: if cond!=0, goto then, so val_true for true, val_false for false
|
||||||
|
return Some((
|
||||||
|
dst1.clone(),
|
||||||
|
cond.clone(),
|
||||||
|
val_true.clone(),
|
||||||
|
val_false.clone(),
|
||||||
|
6,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds a matching `pop ra` for a `push ra` at the start of the slice.
|
||||||
|
/// Returns the index of the pop and the instructions in between.
|
||||||
|
fn find_matching_ra_pop<'a>(
|
||||||
|
instructions: &'a [InstructionNode<'a>],
|
||||||
|
) -> Option<(usize, &'a [InstructionNode<'a>])> {
|
||||||
|
if instructions.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the push itself
|
||||||
|
for (idx, node) in instructions.iter().enumerate().skip(1) {
|
||||||
|
if let Instruction::Pop(Operand::ReturnAddress) = &node.instruction {
|
||||||
|
// Found matching pop
|
||||||
|
return Some((idx, &instructions[1..idx]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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::LabelDef(_)
|
||||||
|
) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if an instruction uses or modifies the stack pointer.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn uses_stack_pointer(instruction: &Instruction) -> bool {
|
||||||
|
match instruction {
|
||||||
|
Instruction::Push(_) | Instruction::Pop(_) | Instruction::Peek(_) => true,
|
||||||
|
Instruction::Add(Operand::StackPointer, _, _)
|
||||||
|
| Instruction::Sub(Operand::StackPointer, _, _)
|
||||||
|
| Instruction::Mul(Operand::StackPointer, _, _)
|
||||||
|
| Instruction::Div(Operand::StackPointer, _, _)
|
||||||
|
| Instruction::Mod(Operand::StackPointer, _, _) => true,
|
||||||
|
Instruction::Add(_, Operand::StackPointer, _)
|
||||||
|
| Instruction::Sub(_, Operand::StackPointer, _)
|
||||||
|
| Instruction::Mul(_, Operand::StackPointer, _)
|
||||||
|
| Instruction::Div(_, Operand::StackPointer, _)
|
||||||
|
| Instruction::Mod(_, Operand::StackPointer, _) => true,
|
||||||
|
Instruction::Add(_, _, Operand::StackPointer)
|
||||||
|
| Instruction::Sub(_, _, Operand::StackPointer)
|
||||||
|
| Instruction::Mul(_, _, Operand::StackPointer)
|
||||||
|
| Instruction::Div(_, _, Operand::StackPointer)
|
||||||
|
| Instruction::Mod(_, _, Operand::StackPointer) => true,
|
||||||
|
Instruction::Move(Operand::StackPointer, _)
|
||||||
|
| Instruction::Move(_, Operand::StackPointer) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adjusts stack pointer offsets in an instruction by decrementing them by a given amount.
|
||||||
|
/// This is necessary when removing push operations that would have increased the stack size.
|
||||||
|
fn adjust_stack_offset<'a>(instruction: Instruction<'a>, decrement: i64) -> Instruction<'a> {
|
||||||
|
use rust_decimal::prelude::*;
|
||||||
|
|
||||||
|
match instruction {
|
||||||
|
// Adjust arithmetic operations on sp that use literal offsets
|
||||||
|
Instruction::Sub(dst, Operand::StackPointer, Operand::Number(n)) => {
|
||||||
|
let new_n = n - Decimal::from(decrement);
|
||||||
|
// If the result is 0 or negative, we may want to skip this entirely
|
||||||
|
// but for now, just adjust the value
|
||||||
|
Instruction::Sub(dst, Operand::StackPointer, Operand::Number(new_n))
|
||||||
|
}
|
||||||
|
Instruction::Add(dst, Operand::StackPointer, Operand::Number(n)) => {
|
||||||
|
let new_n = n - Decimal::from(decrement);
|
||||||
|
Instruction::Add(dst, Operand::StackPointer, Operand::Number(new_n))
|
||||||
|
}
|
||||||
|
// Return the instruction unchanged if it doesn't need adjustment
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum BranchType {
|
||||||
|
Eq,
|
||||||
|
Ne,
|
||||||
|
Gt,
|
||||||
|
Lt,
|
||||||
|
Ge,
|
||||||
|
Le,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seq_beqz_to_bne() {
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::SetEq(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Register(2),
|
||||||
|
Operand::Register(3),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::BranchEqZero(Operand::Register(1), Operand::Label("target".into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::BranchNe(_, _, _)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sne_beqz_to_beq() {
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::SetNe(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Register(2),
|
||||||
|
Operand::Register(3),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::BranchEqZero(Operand::Register(1), Operand::Label("target".into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::BranchEq(_, _, _)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seq_bnez_to_beq() {
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::SetEq(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Register(2),
|
||||||
|
Operand::Register(3),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::BranchNeZero(Operand::Register(1), Operand::Label("target".into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::BranchEq(_, _, _)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sgt_beqz_to_ble() {
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::SetGt(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Register(2),
|
||||||
|
Operand::Register(3),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::BranchEqZero(Operand::Register(1), Operand::Label("target".into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::BranchLe(_, _, _)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_branch_move_jump_to_select_beqz() {
|
||||||
|
// Pattern: beqz r1 else / move r2 10 / j end / else: / move r2 20 / end:
|
||||||
|
// Should convert to: select r2 r1 10 20
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::BranchEqZero(Operand::Register(1), Operand::Label("else".into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Move(Operand::Register(2), Operand::Number(10.into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::Jump(Operand::Label("end".into())), None),
|
||||||
|
InstructionNode::new(Instruction::LabelDef("else".into()), None),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Move(Operand::Register(2), Operand::Number(20.into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::LabelDef("end".into()), None),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
if let Instruction::Select(dst, cond, true_val, false_val) = &output[0].instruction {
|
||||||
|
assert!(matches!(dst, Operand::Register(2)));
|
||||||
|
assert!(matches!(cond, Operand::Register(1)));
|
||||||
|
assert!(matches!(true_val, Operand::Number(_)));
|
||||||
|
assert!(matches!(false_val, Operand::Number(_)));
|
||||||
|
} else {
|
||||||
|
panic!("Expected Select instruction");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_branch_move_jump_to_select_bnez() {
|
||||||
|
// Pattern: bnez r1 then / move r2 20 / j end / then: / move r2 10 / end:
|
||||||
|
// Should convert to: select r2 r1 10 20
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::BranchNeZero(Operand::Register(1), Operand::Label("then".into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Move(Operand::Register(2), Operand::Number(20.into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::Jump(Operand::Label("end".into())), None),
|
||||||
|
InstructionNode::new(Instruction::LabelDef("then".into()), None),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Move(Operand::Register(2), Operand::Number(10.into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::LabelDef("end".into()), None),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
if let Instruction::Select(dst, cond, true_val, false_val) = &output[0].instruction {
|
||||||
|
assert!(matches!(dst, Operand::Register(2)));
|
||||||
|
assert!(matches!(cond, Operand::Register(1)));
|
||||||
|
assert!(matches!(true_val, Operand::Number(_)));
|
||||||
|
assert!(matches!(false_val, Operand::Number(_)));
|
||||||
|
} else {
|
||||||
|
panic!("Expected Select instruction");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_useless_ra_push_pop() {
|
||||||
|
// Pattern: push ra / add r1 r2 r3 / pop ra
|
||||||
|
// Should remove both push and pop since no jal in between
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(Instruction::Push(Operand::ReturnAddress), None),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Add(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Register(2),
|
||||||
|
Operand::Register(3),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::Pop(Operand::ReturnAddress), None),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
assert!(matches!(output[0].instruction, Instruction::Add(_, _, _)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keep_ra_push_pop_with_jal() {
|
||||||
|
// Pattern: push ra / jal func / pop ra
|
||||||
|
// Should keep both since there's a jal in between
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(Instruction::Push(Operand::ReturnAddress), None),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::JumpAndLink(Operand::Label("func".into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::Pop(Operand::ReturnAddress), None),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(!changed);
|
||||||
|
assert_eq!(output.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ra_push_pop_with_stack_offset_adjustment() {
|
||||||
|
// Pattern: push ra / sub r1 sp 2 / pop ra
|
||||||
|
// Should remove push/pop AND adjust the stack offset from 2 to 1
|
||||||
|
use rust_decimal::prelude::*;
|
||||||
|
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(Instruction::Push(Operand::ReturnAddress), None),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Sub(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::StackPointer,
|
||||||
|
Operand::Number(Decimal::from(2)),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::Pop(Operand::ReturnAddress), None),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
|
||||||
|
if let Instruction::Sub(dst, src, Operand::Number(offset)) = &output[0].instruction {
|
||||||
|
assert!(matches!(dst, Operand::Register(1)));
|
||||||
|
assert!(matches!(src, Operand::StackPointer));
|
||||||
|
assert_eq!(*offset, Decimal::from(1)); // Should be decremented from 2 to 1
|
||||||
|
} else {
|
||||||
|
panic!("Expected Sub instruction with adjusted offset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_sp_and_ra_push_pop() {
|
||||||
|
// Pattern: push sp / push ra / move r8 10 / pop ra / pop sp
|
||||||
|
// Should remove all four push/pop instructions since no jal in between
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(Instruction::Push(Operand::StackPointer), None),
|
||||||
|
InstructionNode::new(Instruction::Push(Operand::ReturnAddress), None),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Move(Operand::Register(8), Operand::Number(10.into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::Pop(Operand::ReturnAddress), None),
|
||||||
|
InstructionNode::new(Instruction::Pop(Operand::StackPointer), None),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::Move(Operand::Register(8), _)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keep_sp_and_ra_push_pop_with_jal() {
|
||||||
|
// Pattern: push sp / push ra / jal func / pop ra / pop sp
|
||||||
|
// Should keep all since there's a jal in between
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(Instruction::Push(Operand::StackPointer), None),
|
||||||
|
InstructionNode::new(Instruction::Push(Operand::ReturnAddress), None),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::JumpAndLink(Operand::Label("func".into())),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::Pop(Operand::ReturnAddress), None),
|
||||||
|
InstructionNode::new(Instruction::Pop(Operand::StackPointer), None),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(!changed);
|
||||||
|
assert_eq!(output.len(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sp_and_ra_with_stack_offset_adjustment() {
|
||||||
|
// Pattern: push sp / push ra / sub r1 sp 3 / pop ra / pop sp
|
||||||
|
// Should remove all push/pop AND adjust the stack offset from 3 to 1 (decrement by 2)
|
||||||
|
use rust_decimal::prelude::*;
|
||||||
|
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(Instruction::Push(Operand::StackPointer), None),
|
||||||
|
InstructionNode::new(Instruction::Push(Operand::ReturnAddress), None),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Sub(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::StackPointer,
|
||||||
|
Operand::Number(Decimal::from(3)),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(Instruction::Pop(Operand::ReturnAddress), None),
|
||||||
|
InstructionNode::new(Instruction::Pop(Operand::StackPointer), None),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = peephole_optimization(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
|
||||||
|
if let Instruction::Sub(dst, src, Operand::Number(offset)) = &output[0].instruction {
|
||||||
|
assert!(matches!(dst, Operand::Register(1)));
|
||||||
|
assert!(matches!(src, Operand::StackPointer));
|
||||||
|
assert_eq!(*offset, Decimal::from(1)); // Should be decremented from 3 to 1
|
||||||
|
} else {
|
||||||
|
panic!("Expected Sub instruction with adjusted offset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
151
rust_compiler/libs/optimizer/src/register_forwarding.rs
Normal file
151
rust_compiler/libs/optimizer/src/register_forwarding.rs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
use crate::helpers::{get_destination_reg, reg_is_read, set_destination_reg};
|
||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Pass: Register Forwarding
|
||||||
|
/// Eliminates intermediate moves by writing directly to the final destination.
|
||||||
|
/// Example: `l r1 d0 Temperature` + `move r9 r1` -> `l r9 d0 Temperature`
|
||||||
|
pub fn register_forwarding<'a>(
|
||||||
|
mut input: Vec<InstructionNode<'a>>,
|
||||||
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Check if current instruction defines a register
|
||||||
|
// and the NEXT instruction is a move from that register.
|
||||||
|
let forward_candidate = if let Some(def_reg) = get_destination_reg(&input[i].instruction) {
|
||||||
|
if let Instruction::Move(
|
||||||
|
il::Operand::Register(dest_reg),
|
||||||
|
il::Operand::Register(src_reg),
|
||||||
|
) = &input[next_idx].instruction
|
||||||
|
{
|
||||||
|
if *src_reg == def_reg {
|
||||||
|
Some((def_reg, *dest_reg))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((temp_reg, final_reg)) = forward_candidate {
|
||||||
|
// Check liveness: Is temp_reg used after i+1?
|
||||||
|
let mut temp_is_dead = true;
|
||||||
|
for node in input.iter().skip(i + 2) {
|
||||||
|
if reg_is_read(&node.instruction, temp_reg) {
|
||||||
|
temp_is_dead = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If the temp is redefined, then the old value is dead
|
||||||
|
if let Some(redef) = get_destination_reg(&node.instruction)
|
||||||
|
&& redef == temp_reg
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
input.remove(next_idx);
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(input, changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_forward_simple_move() {
|
||||||
|
let input = vec![
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Add(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Register(2),
|
||||||
|
Operand::Register(3),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
InstructionNode::new(
|
||||||
|
Instruction::Move(Operand::Register(5), Operand::Register(1)),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (output, changed) = register_forwarding(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(output.len(), 1);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::Add(Operand::Register(5), _, _)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
63
rust_compiler/libs/optimizer/src/strength_reduction.rs
Normal file
63
rust_compiler/libs/optimizer/src/strength_reduction.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use il::{Instruction, InstructionNode, Operand};
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
|
/// Pass: Strength Reduction
|
||||||
|
/// Replaces expensive operations with cheaper equivalents.
|
||||||
|
/// Example: x * 2 -> add x x x (addition is typically faster than multiplication)
|
||||||
|
pub fn strength_reduction<'a>(
|
||||||
|
input: Vec<InstructionNode<'a>>,
|
||||||
|
) -> (Vec<InstructionNode<'a>>, bool) {
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
let mut changed = false;
|
||||||
|
|
||||||
|
for mut node in input {
|
||||||
|
let reduced = match &node.instruction {
|
||||||
|
// x * 2 = x + x
|
||||||
|
Instruction::Mul(dst, a, Operand::Number(n)) if *n == Decimal::from(2) => {
|
||||||
|
Some(Instruction::Add(dst.clone(), a.clone(), a.clone()))
|
||||||
|
}
|
||||||
|
Instruction::Mul(dst, Operand::Number(n), b) if *n == Decimal::from(2) => {
|
||||||
|
Some(Instruction::Add(dst.clone(), b.clone(), b.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future: Could add power-of-2 optimizations using bit shifts if IC10 supports them
|
||||||
|
// x * 4 = (x + x) + (x + x) or x << 2
|
||||||
|
// x / 2 = x >> 1
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(new) = reduced {
|
||||||
|
node.instruction = new;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mul_two_to_add() {
|
||||||
|
let input = vec![InstructionNode::new(
|
||||||
|
Instruction::Mul(
|
||||||
|
Operand::Register(1),
|
||||||
|
Operand::Register(2),
|
||||||
|
Operand::Number(Decimal::from(2)),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)];
|
||||||
|
|
||||||
|
let (output, changed) = strength_reduction(input);
|
||||||
|
assert!(changed);
|
||||||
|
assert!(matches!(
|
||||||
|
output[0].instruction,
|
||||||
|
Instruction::Add(Operand::Register(1), Operand::Register(2), Operand::Register(2))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -127,6 +127,52 @@ impl<'a> std::fmt::Display for Math<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Math<'a> {
|
||||||
|
/// Returns the name of this math function (e.g., "acos", "sin", "sqrt", etc.)
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Math::Acos(_) => "acos",
|
||||||
|
Math::Asin(_) => "asin",
|
||||||
|
Math::Atan(_) => "atan",
|
||||||
|
Math::Atan2(_, _) => "atan2",
|
||||||
|
Math::Abs(_) => "abs",
|
||||||
|
Math::Ceil(_) => "ceil",
|
||||||
|
Math::Cos(_) => "cos",
|
||||||
|
Math::Floor(_) => "floor",
|
||||||
|
Math::Log(_) => "log",
|
||||||
|
Math::Max(_, _) => "max",
|
||||||
|
Math::Min(_, _) => "min",
|
||||||
|
Math::Rand => "rand",
|
||||||
|
Math::Sin(_) => "sin",
|
||||||
|
Math::Sqrt(_) => "sqrt",
|
||||||
|
Math::Tan(_) => "tan",
|
||||||
|
Math::Trunc(_) => "trunc",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of arguments this math function expects
|
||||||
|
pub fn arg_count(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Math::Acos(_) => 1,
|
||||||
|
Math::Asin(_) => 1,
|
||||||
|
Math::Atan(_) => 1,
|
||||||
|
Math::Atan2(_, _) => 2,
|
||||||
|
Math::Abs(_) => 1,
|
||||||
|
Math::Ceil(_) => 1,
|
||||||
|
Math::Cos(_) => 1,
|
||||||
|
Math::Floor(_) => 1,
|
||||||
|
Math::Log(_) => 1,
|
||||||
|
Math::Max(_, _) => 2,
|
||||||
|
Math::Min(_, _) => 2,
|
||||||
|
Math::Rand => 0,
|
||||||
|
Math::Sin(_) => 1,
|
||||||
|
Math::Sqrt(_) => 1,
|
||||||
|
Math::Tan(_) => 1,
|
||||||
|
Math::Trunc(_) => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
documented! {
|
documented! {
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum System<'a> {
|
pub enum System<'a> {
|
||||||
@@ -142,6 +188,12 @@ documented! {
|
|||||||
/// ## Slang
|
/// ## Slang
|
||||||
/// `sleep(number|var);`
|
/// `sleep(number|var);`
|
||||||
Sleep(Box<Spanned<Expression<'a>>>),
|
Sleep(Box<Spanned<Expression<'a>>>),
|
||||||
|
/// Clears stack memory on the provided device.
|
||||||
|
/// ## IC10
|
||||||
|
/// `clr d?`
|
||||||
|
/// ## Slang
|
||||||
|
/// `clr(device);`
|
||||||
|
Clr(Box<Spanned<Expression<'a>>>),
|
||||||
/// Gets the in-game hash for a specific prefab name. NOTE! This call is COMPLETELY
|
/// Gets the in-game hash for a specific prefab name. NOTE! This call is COMPLETELY
|
||||||
/// optimized away unless you bind it to a `let` variable. If you use a `const` variable
|
/// optimized away unless you bind it to a `let` variable. If you use a `const` variable
|
||||||
/// however, the hash is correctly computed at compile time and substitued automatically.
|
/// however, the hash is correctly computed at compile time and substitued automatically.
|
||||||
@@ -249,6 +301,17 @@ documented! {
|
|||||||
Spanned<LiteralOrVariable<'a>>,
|
Spanned<LiteralOrVariable<'a>>,
|
||||||
Spanned<Literal<'a>>,
|
Spanned<Literal<'a>>,
|
||||||
Box<Spanned<Expression<'a>>>
|
Box<Spanned<Expression<'a>>>
|
||||||
|
),
|
||||||
|
/// Maps a reagent hash to the item hash that fulfills it on a device
|
||||||
|
///
|
||||||
|
/// ## IC10
|
||||||
|
/// `rmap r? d? reagentHash(r?|num)`
|
||||||
|
/// ## Slang
|
||||||
|
/// `let itemHash = rmap(device, reagentHash);`
|
||||||
|
/// `let itemHash = rmap(device, reagentHashValue);`
|
||||||
|
Rmap(
|
||||||
|
Spanned<LiteralOrVariable<'a>>,
|
||||||
|
Box<Spanned<Expression<'a>>>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,6 +321,7 @@ impl<'a> std::fmt::Display for System<'a> {
|
|||||||
match self {
|
match self {
|
||||||
System::Yield => write!(f, "yield()"),
|
System::Yield => write!(f, "yield()"),
|
||||||
System::Sleep(a) => write!(f, "sleep({})", a),
|
System::Sleep(a) => write!(f, "sleep({})", a),
|
||||||
|
System::Clr(a) => write!(f, "clr({})", a),
|
||||||
System::Hash(a) => write!(f, "hash({})", a),
|
System::Hash(a) => write!(f, "hash({})", a),
|
||||||
System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b),
|
System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b),
|
||||||
System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c),
|
System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c),
|
||||||
@@ -274,6 +338,49 @@ impl<'a> std::fmt::Display for System<'a> {
|
|||||||
System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c),
|
System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c),
|
||||||
System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d),
|
System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d),
|
||||||
System::LoadReagent(a, b, c) => write!(f, "loadReagent({}, {}, {})", a, b, c),
|
System::LoadReagent(a, b, c) => write!(f, "loadReagent({}, {}, {})", a, b, c),
|
||||||
|
System::Rmap(a, b) => write!(f, "rmap({}, {})", a, b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> System<'a> {
|
||||||
|
/// Returns the name of this syscall (e.g., "yield", "sleep", "hash", etc.)
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
System::Yield => "yield",
|
||||||
|
System::Sleep(_) => "sleep",
|
||||||
|
System::Clr(_) => "clr",
|
||||||
|
System::Hash(_) => "hash",
|
||||||
|
System::LoadFromDevice(_, _) => "loadFromDevice",
|
||||||
|
System::LoadBatch(_, _, _) => "loadBatch",
|
||||||
|
System::LoadBatchNamed(_, _, _, _) => "loadBatchNamed",
|
||||||
|
System::SetOnDevice(_, _, _) => "setOnDevice",
|
||||||
|
System::SetOnDeviceBatched(_, _, _) => "setOnDeviceBatched",
|
||||||
|
System::SetOnDeviceBatchedNamed(_, _, _, _) => "setOnDeviceBatchedNamed",
|
||||||
|
System::LoadSlot(_, _, _) => "loadSlot",
|
||||||
|
System::SetSlot(_, _, _, _) => "setSlot",
|
||||||
|
System::LoadReagent(_, _, _) => "loadReagent",
|
||||||
|
System::Rmap(_, _) => "rmap",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of arguments this syscall expects
|
||||||
|
pub fn arg_count(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
System::Yield => 0,
|
||||||
|
System::Sleep(_) => 1,
|
||||||
|
System::Clr(_) => 1,
|
||||||
|
System::Hash(_) => 1,
|
||||||
|
System::LoadFromDevice(_, _) => 2,
|
||||||
|
System::LoadBatch(_, _, _) => 3,
|
||||||
|
System::LoadBatchNamed(_, _, _, _) => 4,
|
||||||
|
System::SetOnDevice(_, _, _) => 3,
|
||||||
|
System::SetOnDeviceBatched(_, _, _) => 3,
|
||||||
|
System::SetOnDeviceBatchedNamed(_, _, _, _) => 4,
|
||||||
|
System::LoadSlot(_, _, _) => 3,
|
||||||
|
System::SetSlot(_, _, _, _) => 4,
|
||||||
|
System::LoadReagent(_, _, _) => 3,
|
||||||
|
System::Rmap(_, _) => 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,23 @@ fn test_const_hash_expression() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_const_hash() -> Result<()> {
|
||||||
|
// This test explicitly validates the tokenizer rewind logic.
|
||||||
|
// When parsing "const h = hash(...)", the parser:
|
||||||
|
// 1. Consumes "const", identifier, "="
|
||||||
|
// 2. Attempts to parse "hash(...)" as a literal - this fails
|
||||||
|
// 3. Must rewind the tokenizer to before "hash"
|
||||||
|
// 4. Then parse it as a syscall
|
||||||
|
// If the rewind offset is wrong (e.g., positive instead of negative),
|
||||||
|
// the tokenizer will be at the wrong position and parsing will fail.
|
||||||
|
let expr = parser!(r#"const h = hash("ComponentComputer")"#)
|
||||||
|
.parse()?
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(r#"(const h = hash("ComponentComputer"))"#, expr.to_string());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_negative_literal_const() -> Result<()> {
|
fn test_negative_literal_const() -> Result<()> {
|
||||||
let expr = parser!(r#"const i = -123"#).parse()?.unwrap();
|
let expr = parser!(r#"const i = -123"#).parse()?.unwrap();
|
||||||
@@ -287,3 +304,36 @@ fn test_tuple_declaration_all_complex_expressions() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_eof_error_has_span() -> Result<()> {
|
||||||
|
// Test that UnexpectedEOF errors capture the span of the last token
|
||||||
|
let mut parser = parser!("let x = 5");
|
||||||
|
let result = parser.parse();
|
||||||
|
|
||||||
|
// Should have an error
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
|
||||||
|
// Check that it's an UnexpectedEOF error
|
||||||
|
match err {
|
||||||
|
super::Error::UnexpectedEOF(Some(span)) => {
|
||||||
|
// Verify the span points to somewhere in the code (not zero defaults)
|
||||||
|
assert!(
|
||||||
|
span.start_line > 0 || span.start_col > 0 || span.end_line > 0 || span.end_col > 0,
|
||||||
|
"Span should not be all zeros: {:?}",
|
||||||
|
span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
super::Error::UnexpectedEOF(None) => {
|
||||||
|
eprintln!("ERROR: UnexpectedEOF captured None span instead of previous token span");
|
||||||
|
eprintln!("This means unexpected_eof() is being called when current_token is None");
|
||||||
|
panic!("UnexpectedEOF should have captured the previous token's span");
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
panic!("Expected UnexpectedEOF error, got: {:?}", other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ pub enum BinaryExpression<'a> {
|
|||||||
Subtract(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
Subtract(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
||||||
Exponent(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
Exponent(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
||||||
Modulo(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
Modulo(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
||||||
|
BitwiseAnd(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
||||||
|
BitwiseOr(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
||||||
|
BitwiseXor(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
||||||
|
LeftShift(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
||||||
|
RightShiftArithmetic(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
||||||
|
RightShiftLogical(Box<Spanned<Expression<'a>>>, Box<Spanned<Expression<'a>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Display for BinaryExpression<'a> {
|
impl<'a> std::fmt::Display for BinaryExpression<'a> {
|
||||||
@@ -56,6 +62,12 @@ impl<'a> std::fmt::Display for BinaryExpression<'a> {
|
|||||||
BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r),
|
BinaryExpression::Subtract(l, r) => write!(f, "({} - {})", l, r),
|
||||||
BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r),
|
BinaryExpression::Exponent(l, r) => write!(f, "({} ** {})", l, r),
|
||||||
BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r),
|
BinaryExpression::Modulo(l, r) => write!(f, "({} % {})", l, r),
|
||||||
|
BinaryExpression::BitwiseAnd(l, r) => write!(f, "({} & {})", l, r),
|
||||||
|
BinaryExpression::BitwiseOr(l, r) => write!(f, "({} | {})", l, r),
|
||||||
|
BinaryExpression::BitwiseXor(l, r) => write!(f, "({} ^ {})", l, r),
|
||||||
|
BinaryExpression::LeftShift(l, r) => write!(f, "({} << {})", l, r),
|
||||||
|
BinaryExpression::RightShiftArithmetic(l, r) => write!(f, "({} >> {})", l, r),
|
||||||
|
BinaryExpression::RightShiftLogical(l, r) => write!(f, "({} >>> {})", l, r),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,6 +209,18 @@ impl<'a> std::fmt::Display for MethodCallExpression<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct IndexAccessExpression<'a> {
|
||||||
|
pub object: Box<Spanned<Expression<'a>>>,
|
||||||
|
pub index: Box<Spanned<Expression<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Display for IndexAccessExpression<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}[{}]", self.object, self.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum LiteralOrVariable<'a> {
|
pub enum LiteralOrVariable<'a> {
|
||||||
Literal(Literal<'a>),
|
Literal(Literal<'a>),
|
||||||
@@ -367,6 +391,7 @@ pub enum Expression<'a> {
|
|||||||
Binary(Spanned<BinaryExpression<'a>>),
|
Binary(Spanned<BinaryExpression<'a>>),
|
||||||
Block(Spanned<BlockExpression<'a>>),
|
Block(Spanned<BlockExpression<'a>>),
|
||||||
Break(Span),
|
Break(Span),
|
||||||
|
BitwiseNot(Box<Spanned<Expression<'a>>>),
|
||||||
ConstDeclaration(Spanned<ConstDeclarationExpression<'a>>),
|
ConstDeclaration(Spanned<ConstDeclarationExpression<'a>>),
|
||||||
Continue(Span),
|
Continue(Span),
|
||||||
Declaration(Spanned<Cow<'a, str>>, Box<Spanned<Expression<'a>>>),
|
Declaration(Spanned<Cow<'a, str>>, Box<Spanned<Expression<'a>>>),
|
||||||
@@ -389,6 +414,7 @@ pub enum Expression<'a> {
|
|||||||
TupleDeclaration(Spanned<TupleDeclarationExpression<'a>>),
|
TupleDeclaration(Spanned<TupleDeclarationExpression<'a>>),
|
||||||
Variable(Spanned<Cow<'a, str>>),
|
Variable(Spanned<Cow<'a, str>>),
|
||||||
While(Spanned<WhileExpression<'a>>),
|
While(Spanned<WhileExpression<'a>>),
|
||||||
|
IndexAccess(Spanned<IndexAccessExpression<'a>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Display for Expression<'a> {
|
impl<'a> std::fmt::Display for Expression<'a> {
|
||||||
@@ -398,6 +424,7 @@ impl<'a> std::fmt::Display for Expression<'a> {
|
|||||||
Expression::Binary(e) => write!(f, "{}", e),
|
Expression::Binary(e) => write!(f, "{}", e),
|
||||||
Expression::Block(e) => write!(f, "{}", e),
|
Expression::Block(e) => write!(f, "{}", e),
|
||||||
Expression::Break(_) => write!(f, "break"),
|
Expression::Break(_) => write!(f, "break"),
|
||||||
|
Expression::BitwiseNot(e) => write!(f, "(~{})", e),
|
||||||
Expression::ConstDeclaration(e) => write!(f, "{}", e),
|
Expression::ConstDeclaration(e) => write!(f, "{}", e),
|
||||||
Expression::Continue(_) => write!(f, "continue"),
|
Expression::Continue(_) => write!(f, "continue"),
|
||||||
Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e),
|
Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e),
|
||||||
@@ -436,7 +463,7 @@ impl<'a> std::fmt::Display for Expression<'a> {
|
|||||||
Expression::TupleDeclaration(e) => write!(f, "{}", e),
|
Expression::TupleDeclaration(e) => write!(f, "{}", e),
|
||||||
Expression::Variable(id) => write!(f, "{}", id),
|
Expression::Variable(id) => write!(f, "{}", id),
|
||||||
Expression::While(e) => write!(f, "{}", e),
|
Expression::While(e) => write!(f, "{}", e),
|
||||||
|
Expression::IndexAccess(e) => write!(f, "{}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ impl<'a> Tokenizer<'a> {
|
|||||||
|
|
||||||
Ok(current.map(|t| t.map(|t| self.get_token(t)))?)
|
Ok(current.map(|t| t.map(|t| self.get_token(t)))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the next token, including comments. Used to preserve doc comments.
|
||||||
|
pub fn next_token_with_comments(&mut self) -> Result<Option<Token<'a>>, Error> {
|
||||||
|
let current = self.lexer.next().transpose();
|
||||||
|
Ok(current.map(|t| t.map(|t| self.get_token(t)))?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... Iterator and TokenizerBuffer implementations remain unchanged ...
|
// ... Iterator and TokenizerBuffer implementations remain unchanged ...
|
||||||
@@ -127,12 +133,28 @@ impl<'a> TokenizerBuffer<'a> {
|
|||||||
self.index += 1;
|
self.index += 1;
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn next_token_with_comments(&mut self) -> Result<Option<Token<'a>>, Error> {
|
||||||
|
if let Some(token) = self.buffer.pop_front() {
|
||||||
|
self.history.push_back(token.clone());
|
||||||
|
self.index += 1;
|
||||||
|
return Ok(Some(token));
|
||||||
|
}
|
||||||
|
let token = self.tokenizer.next_token_with_comments()?;
|
||||||
|
|
||||||
|
if let Some(ref token) = token {
|
||||||
|
self.history.push_back(token.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.index += 1;
|
||||||
|
Ok(token)
|
||||||
|
}
|
||||||
pub fn peek(&mut self) -> Result<Option<Token<'a>>, Error> {
|
pub fn peek(&mut self) -> Result<Option<Token<'a>>, Error> {
|
||||||
if let Some(token) = self.buffer.front() {
|
if let Some(token) = self.buffer.front() {
|
||||||
return Ok(Some(token.clone()));
|
return Ok(Some(token.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(new_token) = self.tokenizer.next_token()? else {
|
let Some(new_token) = self.tokenizer.next_token_with_comments()? else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
self.buffer.push_front(new_token.clone());
|
self.buffer.push_front(new_token.clone());
|
||||||
@@ -145,8 +167,20 @@ impl<'a> TokenizerBuffer<'a> {
|
|||||||
use Ordering::*;
|
use Ordering::*;
|
||||||
match seek_to_int.cmp(&0) {
|
match seek_to_int.cmp(&0) {
|
||||||
Greater => {
|
Greater => {
|
||||||
let mut tokens = Vec::with_capacity(seek_to_int as usize);
|
let mut seek_remaining = seek_to_int as usize;
|
||||||
for _ in 0..seek_to_int {
|
|
||||||
|
// First, consume tokens from the buffer (peeked but not yet consumed)
|
||||||
|
while seek_remaining > 0 && !self.buffer.is_empty() {
|
||||||
|
if let Some(token) = self.buffer.pop_front() {
|
||||||
|
self.history.push_back(token);
|
||||||
|
seek_remaining -= 1;
|
||||||
|
self.index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then get tokens from tokenizer if needed
|
||||||
|
let mut tokens = Vec::with_capacity(seek_remaining);
|
||||||
|
for _ in 0..seek_remaining {
|
||||||
if let Some(token) = self.tokenizer.next_token()? {
|
if let Some(token) = self.tokenizer.next_token()? {
|
||||||
tokens.push(token);
|
tokens.push(token);
|
||||||
} else {
|
} else {
|
||||||
@@ -157,6 +191,7 @@ impl<'a> TokenizerBuffer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.history.extend(tokens);
|
self.history.extend(tokens);
|
||||||
|
self.index += seek_remaining as i64;
|
||||||
}
|
}
|
||||||
Less => {
|
Less => {
|
||||||
let seek_to = seek_to_int.unsigned_abs() as usize;
|
let seek_to = seek_to_int.unsigned_abs() as usize;
|
||||||
|
|||||||
@@ -135,6 +135,9 @@ pub enum TokenType<'a> {
|
|||||||
/// Represents a string token
|
/// Represents a string token
|
||||||
String(Cow<'a, str>),
|
String(Cow<'a, str>),
|
||||||
|
|
||||||
|
#[regex(r"0[xX][0-9a-fA-F][0-9a-fA-F_]*", parse_number)]
|
||||||
|
#[regex(r"0[oO][0-7][0-7_]*", parse_number)]
|
||||||
|
#[regex(r"0[bB][01][01_]*", parse_number)]
|
||||||
#[regex(r"[0-9][0-9_]*(\.[0-9][0-9_]*)?([cfk])?", parse_number)]
|
#[regex(r"[0-9][0-9_]*(\.[0-9][0-9_]*)?([cfk])?", parse_number)]
|
||||||
/// Represents a number token
|
/// Represents a number token
|
||||||
Number(Number),
|
Number(Number),
|
||||||
@@ -172,6 +175,23 @@ pub enum TokenType<'a> {
|
|||||||
#[token(";", symbol!(Semicolon))]
|
#[token(";", symbol!(Semicolon))]
|
||||||
#[token(":", symbol!(Colon))]
|
#[token(":", symbol!(Colon))]
|
||||||
#[token(",", symbol!(Comma))]
|
#[token(",", symbol!(Comma))]
|
||||||
|
#[token("?", symbol!(Question))]
|
||||||
|
#[token(".", symbol!(Dot))]
|
||||||
|
#[token("%", symbol!(Percent))]
|
||||||
|
#[token("~", symbol!(BitwiseNot))]
|
||||||
|
// Multi-character tokens must be defined before their single-character prefixes
|
||||||
|
// For tokens like >> and >>>, define >>> before >> to ensure correct matching
|
||||||
|
#[token(">>>", symbol!(RightShiftLogical))]
|
||||||
|
#[token(">>", symbol!(RightShiftArithmetic))]
|
||||||
|
#[token("<<", symbol!(LeftShift))]
|
||||||
|
#[token("==", symbol!(Equal))]
|
||||||
|
#[token("!=", symbol!(NotEqual))]
|
||||||
|
#[token("&&", symbol!(LogicalAnd))]
|
||||||
|
#[token("||", symbol!(LogicalOr))]
|
||||||
|
#[token("<=", symbol!(LessThanOrEqual))]
|
||||||
|
#[token(">=", symbol!(GreaterThanOrEqual))]
|
||||||
|
#[token("**", symbol!(Exp))]
|
||||||
|
// Single-character tokens
|
||||||
#[token("+", symbol!(Plus))]
|
#[token("+", symbol!(Plus))]
|
||||||
#[token("-", symbol!(Minus))]
|
#[token("-", symbol!(Minus))]
|
||||||
#[token("*", symbol!(Asterisk))]
|
#[token("*", symbol!(Asterisk))]
|
||||||
@@ -180,17 +200,9 @@ pub enum TokenType<'a> {
|
|||||||
#[token(">", symbol!(GreaterThan))]
|
#[token(">", symbol!(GreaterThan))]
|
||||||
#[token("=", symbol!(Assign))]
|
#[token("=", symbol!(Assign))]
|
||||||
#[token("!", symbol!(LogicalNot))]
|
#[token("!", symbol!(LogicalNot))]
|
||||||
#[token(".", symbol!(Dot))]
|
|
||||||
#[token("^", symbol!(Caret))]
|
#[token("^", symbol!(Caret))]
|
||||||
#[token("%", symbol!(Percent))]
|
#[token("&", symbol!(BitwiseAnd))]
|
||||||
#[token("?", symbol!(Question))]
|
#[token("|", symbol!(BitwiseOr))]
|
||||||
#[token("==", symbol!(Equal))]
|
|
||||||
#[token("!=", symbol!(NotEqual))]
|
|
||||||
#[token("&&", symbol!(LogicalAnd))]
|
|
||||||
#[token("||", symbol!(LogicalOr))]
|
|
||||||
#[token("<=", symbol!(LessThanOrEqual))]
|
|
||||||
#[token(">=", symbol!(GreaterThanOrEqual))]
|
|
||||||
#[token("**", symbol!(Exp))]
|
|
||||||
/// Represents a symbol token
|
/// Represents a symbol token
|
||||||
Symbol(Symbol),
|
Symbol(Symbol),
|
||||||
|
|
||||||
@@ -221,44 +233,75 @@ pub enum Comment<'a> {
|
|||||||
|
|
||||||
fn parse_number<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Result<Number, LexError> {
|
fn parse_number<'a>(lexer: &mut Lexer<'a, TokenType<'a>>) -> Result<Number, LexError> {
|
||||||
let slice = lexer.slice();
|
let slice = lexer.slice();
|
||||||
let last_char = slice.chars().last().unwrap_or_default();
|
|
||||||
let (num_str, suffix) = match last_char {
|
|
||||||
'c' | 'k' | 'f' => (&slice[..slice.len() - 1], Some(last_char)),
|
|
||||||
_ => (slice, None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let clean_str = if num_str.contains('_') {
|
|
||||||
num_str.replace('_', "")
|
|
||||||
} else {
|
|
||||||
num_str.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let line = lexer.extras.line_count;
|
let line = lexer.extras.line_count;
|
||||||
let mut span = lexer.span();
|
let mut span = lexer.span();
|
||||||
span.end -= lexer.extras.line_start_index;
|
span.end -= lexer.extras.line_start_index;
|
||||||
span.start -= lexer.extras.line_start_index;
|
span.start -= lexer.extras.line_start_index;
|
||||||
|
|
||||||
let unit = match suffix {
|
// Determine the base and parse accordingly
|
||||||
Some('c') => Unit::Celsius,
|
if slice.starts_with("0x") || slice.starts_with("0X") {
|
||||||
Some('f') => Unit::Fahrenheit,
|
// Hexadecimal - no temperature suffix allowed
|
||||||
Some('k') => Unit::Kelvin,
|
let clean_str = slice[2..].replace('_', "");
|
||||||
_ => Unit::None,
|
Ok(Number::Integer(
|
||||||
};
|
i128::from_str_radix(&clean_str, 16)
|
||||||
|
|
||||||
if clean_str.contains('.') {
|
|
||||||
Ok(Number::Decimal(
|
|
||||||
clean_str
|
|
||||||
.parse::<Decimal>()
|
|
||||||
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
||||||
unit,
|
Unit::None,
|
||||||
|
))
|
||||||
|
} else if slice.starts_with("0o") || slice.starts_with("0O") {
|
||||||
|
// Octal - no temperature suffix allowed
|
||||||
|
let clean_str = slice[2..].replace('_', "");
|
||||||
|
Ok(Number::Integer(
|
||||||
|
i128::from_str_radix(&clean_str, 8)
|
||||||
|
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
||||||
|
Unit::None,
|
||||||
|
))
|
||||||
|
} else if slice.starts_with("0b") || slice.starts_with("0B") {
|
||||||
|
// Binary - no temperature suffix allowed
|
||||||
|
let clean_str = slice[2..].replace('_', "");
|
||||||
|
Ok(Number::Integer(
|
||||||
|
i128::from_str_radix(&clean_str, 2)
|
||||||
|
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
||||||
|
Unit::None,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(Number::Integer(
|
// Decimal (with optional temperature suffix)
|
||||||
clean_str
|
let last_char = slice.chars().last().unwrap_or_default();
|
||||||
.parse::<i128>()
|
let (num_str, suffix) = match last_char {
|
||||||
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
'c' | 'k' | 'f' => (&slice[..slice.len() - 1], Some(last_char)),
|
||||||
unit,
|
_ => (slice, None),
|
||||||
))
|
};
|
||||||
|
|
||||||
|
let clean_str = if num_str.contains('_') {
|
||||||
|
num_str.replace('_', "")
|
||||||
|
} else {
|
||||||
|
num_str.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let unit = match suffix {
|
||||||
|
Some('c') => Unit::Celsius,
|
||||||
|
Some('f') => Unit::Fahrenheit,
|
||||||
|
Some('k') => Unit::Kelvin,
|
||||||
|
_ => Unit::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if clean_str.contains('.') {
|
||||||
|
// Decimal floating point
|
||||||
|
Ok(Number::Decimal(
|
||||||
|
clean_str
|
||||||
|
.parse::<Decimal>()
|
||||||
|
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
||||||
|
unit,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
// Decimal integer
|
||||||
|
Ok(Number::Integer(
|
||||||
|
clean_str
|
||||||
|
.parse::<i128>()
|
||||||
|
.map_err(|_| LexError::NumberParse(line, span, slice.to_string()))?,
|
||||||
|
unit,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,6 +658,12 @@ pub enum Symbol {
|
|||||||
Percent,
|
Percent,
|
||||||
/// Represents the `?` symbol
|
/// Represents the `?` symbol
|
||||||
Question,
|
Question,
|
||||||
|
/// Represents the `&` symbol (bitwise AND)
|
||||||
|
BitwiseAnd,
|
||||||
|
/// Represents the `|` symbol (bitwise OR)
|
||||||
|
BitwiseOr,
|
||||||
|
/// Represents the `~` symbol (bitwise NOT)
|
||||||
|
BitwiseNot,
|
||||||
|
|
||||||
// Double Character Symbols
|
// Double Character Symbols
|
||||||
/// Represents the `==` symbol
|
/// Represents the `==` symbol
|
||||||
@@ -629,6 +678,12 @@ pub enum Symbol {
|
|||||||
LessThanOrEqual,
|
LessThanOrEqual,
|
||||||
/// Represents the `>=` symbol
|
/// Represents the `>=` symbol
|
||||||
GreaterThanOrEqual,
|
GreaterThanOrEqual,
|
||||||
|
/// Represents the `<<` symbol (left shift)
|
||||||
|
LeftShift,
|
||||||
|
/// Represents the `>>` symbol (arithmetic right shift)
|
||||||
|
RightShiftArithmetic,
|
||||||
|
/// Represents the `>>>` symbol (logical right shift)
|
||||||
|
RightShiftLogical,
|
||||||
/// Represents the `**` symbol
|
/// Represents the `**` symbol
|
||||||
Exp,
|
Exp,
|
||||||
}
|
}
|
||||||
@@ -643,6 +698,19 @@ impl Symbol {
|
|||||||
| Symbol::Slash
|
| Symbol::Slash
|
||||||
| Symbol::Exp
|
| Symbol::Exp
|
||||||
| Symbol::Percent
|
| Symbol::Percent
|
||||||
|
| Symbol::Caret
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_bitwise(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Symbol::BitwiseAnd
|
||||||
|
| Symbol::BitwiseOr
|
||||||
|
| Symbol::BitwiseNot
|
||||||
|
| Symbol::LeftShift
|
||||||
|
| Symbol::RightShiftArithmetic
|
||||||
|
| Symbol::RightShiftLogical
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,6 +761,12 @@ impl std::fmt::Display for Symbol {
|
|||||||
Self::NotEqual => write!(f, "!="),
|
Self::NotEqual => write!(f, "!="),
|
||||||
Self::Dot => write!(f, "."),
|
Self::Dot => write!(f, "."),
|
||||||
Self::Caret => write!(f, "^"),
|
Self::Caret => write!(f, "^"),
|
||||||
|
Self::BitwiseAnd => write!(f, "&"),
|
||||||
|
Self::BitwiseOr => write!(f, "|"),
|
||||||
|
Self::BitwiseNot => write!(f, "~"),
|
||||||
|
Self::LeftShift => write!(f, "<<"),
|
||||||
|
Self::RightShiftArithmetic => write!(f, ">>"),
|
||||||
|
Self::RightShiftLogical => write!(f, ">>>"),
|
||||||
Self::Exp => write!(f, "**"),
|
Self::Exp => write!(f, "**"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -715,7 +789,7 @@ documented! {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
Continue,
|
Continue,
|
||||||
/// Prepresents the `const` keyword. This allows you to define a variable that will never
|
/// Represents the `const` keyword. This allows you to define a variable that will never
|
||||||
/// change throughout the lifetime of the program, similar to `define` in IC10. If you are
|
/// change throughout the lifetime of the program, similar to `define` in IC10. If you are
|
||||||
/// not planning on mutating the variable (changing it), it is recommend you store it as a
|
/// not planning on mutating the variable (changing it), it is recommend you store it as a
|
||||||
/// const, as the compiler will not assign it to a register or stack variable.
|
/// const, as the compiler will not assign it to a register or stack variable.
|
||||||
@@ -846,6 +920,7 @@ documented! {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::TokenType;
|
use super::TokenType;
|
||||||
|
use super::{Number, Unit};
|
||||||
use logos::Logos;
|
use logos::Logos;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -856,7 +931,141 @@ mod tests {
|
|||||||
|
|
||||||
let tokens = lexer.collect::<Vec<_>>();
|
let tokens = lexer.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert!(!tokens.iter().any(|res| res.is_err()));
|
assert!(
|
||||||
|
!tokens.iter().any(|res| res.is_err()),
|
||||||
|
"Expected no lexing errors for CRLF endings"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binary_literals() -> anyhow::Result<()> {
|
||||||
|
let src = "0b1010 0b0 0b1111_0000";
|
||||||
|
let lexer = TokenType::lexer(src);
|
||||||
|
let tokens: Vec<_> = lexer.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 3);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[0],
|
||||||
|
TokenType::Number(Number::Integer(10, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected binary 0b1010 = 10"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[1],
|
||||||
|
TokenType::Number(Number::Integer(0, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected binary 0b0 = 0"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[2],
|
||||||
|
TokenType::Number(Number::Integer(240, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected binary 0b1111_0000 = 240"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_octal_literals() -> anyhow::Result<()> {
|
||||||
|
let src = "0o77 0o0 0o7_777";
|
||||||
|
let lexer = TokenType::lexer(src);
|
||||||
|
let tokens: Vec<_> = lexer.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 3);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[0],
|
||||||
|
TokenType::Number(Number::Integer(63, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected octal 0o77 = 63"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[1],
|
||||||
|
TokenType::Number(Number::Integer(0, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected octal 0o0 = 0"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[2],
|
||||||
|
TokenType::Number(Number::Integer(4095, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected octal 0o7_777 = 4095"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hex_literals() -> anyhow::Result<()> {
|
||||||
|
let src = "0xFF 0x0 0xFF_FF 0xFF_FF_FF";
|
||||||
|
let lexer = TokenType::lexer(src);
|
||||||
|
let tokens: Vec<_> = lexer.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 4);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[0],
|
||||||
|
TokenType::Number(Number::Integer(255, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected hex 0xFF = 255"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[1],
|
||||||
|
TokenType::Number(Number::Integer(0, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected hex 0x0 = 0"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[2],
|
||||||
|
TokenType::Number(Number::Integer(65535, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected hex 0xFF_FF = 65535"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[3],
|
||||||
|
TokenType::Number(Number::Integer(16777215, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected hex 0xFF_FF_FF = 16777215"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hex_literals_lowercase() -> anyhow::Result<()> {
|
||||||
|
let src = "0xff 0xab 0xcd_ef";
|
||||||
|
let lexer = TokenType::lexer(src);
|
||||||
|
let tokens: Vec<_> = lexer.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), 3);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[0],
|
||||||
|
TokenType::Number(Number::Integer(255, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected hex 0xff = 255"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[1],
|
||||||
|
TokenType::Number(Number::Integer(171, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected hex 0xab = 171"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&tokens[2],
|
||||||
|
TokenType::Number(Number::Integer(52719, Unit::None))
|
||||||
|
),
|
||||||
|
"Expected hex 0xcd_ef = 52719"
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,30 @@ impl From<lsp_types::Diagnostic> for FfiDiagnostic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive_ReprC]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FfiSymbolKindData {
|
||||||
|
pub kind: u32, // 0=Function, 1=Syscall, 2=Variable
|
||||||
|
pub arg_count: u32,
|
||||||
|
pub syscall_type: u32, // 0=System, 1=Math (only for Syscall kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive_ReprC]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FfiSymbolInfo {
|
||||||
|
pub name: safer_ffi::String,
|
||||||
|
pub kind_data: FfiSymbolKindData,
|
||||||
|
pub span: FfiRange,
|
||||||
|
pub description: safer_ffi::String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive_ReprC]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FfiDiagnosticsAndSymbols {
|
||||||
|
pub diagnostics: safer_ffi::Vec<FfiDiagnostic>,
|
||||||
|
pub symbols: safer_ffi::Vec<FfiSymbolInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
#[ffi_export]
|
||||||
pub fn free_ffi_compilation_result(input: FfiCompilationResult) {
|
pub fn free_ffi_compilation_result(input: FfiCompilationResult) {
|
||||||
drop(input)
|
drop(input)
|
||||||
@@ -109,6 +133,11 @@ pub fn free_ffi_diagnostic_vec(v: safer_ffi::Vec<FfiDiagnostic>) {
|
|||||||
drop(v)
|
drop(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
pub fn free_ffi_diagnostics_and_symbols(v: FfiDiagnosticsAndSymbols) {
|
||||||
|
drop(v)
|
||||||
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
#[ffi_export]
|
||||||
pub fn free_string(s: safer_ffi::String) {
|
pub fn free_string(s: safer_ffi::String) {
|
||||||
drop(s)
|
drop(s)
|
||||||
@@ -182,6 +211,10 @@ pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<Ff
|
|||||||
let input = String::from_utf16_lossy(input.as_slice());
|
let input = String::from_utf16_lossy(input.as_slice());
|
||||||
let tokenizer = Tokenizer::from(input.as_str());
|
let tokenizer = Tokenizer::from(input.as_str());
|
||||||
|
|
||||||
|
// Build a lookup table for syscall documentation
|
||||||
|
let syscall_docs: std::collections::HashMap<&'static str, String> =
|
||||||
|
SysCall::get_all_documentation().into_iter().collect();
|
||||||
|
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
|
|
||||||
for token in tokenizer {
|
for token in tokenizer {
|
||||||
@@ -217,13 +250,26 @@ pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<Ff
|
|||||||
}
|
}
|
||||||
Ok(Token {
|
Ok(Token {
|
||||||
span, token_type, ..
|
span, token_type, ..
|
||||||
}) => tokens.push(FfiToken {
|
}) => {
|
||||||
column: span.start as i32,
|
let mut tooltip = token_type.docs();
|
||||||
error: "".into(),
|
|
||||||
length: (span.end - span.start) as i32,
|
// If no docs from token type, check if it's a syscall
|
||||||
tooltip: token_type.docs().into(),
|
if tooltip.is_empty() {
|
||||||
token_kind: token_type.into(),
|
if let TokenType::Identifier(id) = &token_type {
|
||||||
}),
|
if let Some(doc) = syscall_docs.get(id.as_ref()) {
|
||||||
|
tooltip = doc.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.push(FfiToken {
|
||||||
|
column: span.start as i32,
|
||||||
|
error: "".into(),
|
||||||
|
length: (span.end - span.start) as i32,
|
||||||
|
tooltip: tooltip.into(),
|
||||||
|
token_kind: token_type.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +303,88 @@ pub fn diagnose_source(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<
|
|||||||
res.unwrap_or(vec![].into())
|
res.unwrap_or(vec![].into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
pub fn diagnose_source_with_symbols(
|
||||||
|
input: safer_ffi::slice::Ref<'_, u16>,
|
||||||
|
) -> FfiDiagnosticsAndSymbols {
|
||||||
|
let res = std::panic::catch_unwind(|| {
|
||||||
|
let input = String::from_utf16_lossy(input.as_slice());
|
||||||
|
|
||||||
|
let tokenizer = Tokenizer::from(input.as_str());
|
||||||
|
let compiler = Compiler::new(Parser::new(tokenizer), None);
|
||||||
|
|
||||||
|
let CompilationResult {
|
||||||
|
errors: diagnosis,
|
||||||
|
metadata,
|
||||||
|
..
|
||||||
|
} = compiler.compile();
|
||||||
|
|
||||||
|
// Convert diagnostics
|
||||||
|
let mut diagnostics_vec: Vec<FfiDiagnostic> = Vec::with_capacity(diagnosis.len());
|
||||||
|
for err in diagnosis {
|
||||||
|
diagnostics_vec.push(lsp_types::Diagnostic::from(err).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert symbols
|
||||||
|
let mut symbols_vec: Vec<FfiSymbolInfo> = Vec::with_capacity(metadata.symbols.len());
|
||||||
|
for symbol in &metadata.symbols {
|
||||||
|
let (kind, arg_count, syscall_type) = match &symbol.kind {
|
||||||
|
compiler::SymbolKind::Function { parameters, .. } => {
|
||||||
|
(0, parameters.len() as u32, 0)
|
||||||
|
}
|
||||||
|
compiler::SymbolKind::Syscall {
|
||||||
|
syscall_type,
|
||||||
|
argument_count,
|
||||||
|
} => {
|
||||||
|
let sc_type = match syscall_type {
|
||||||
|
compiler::SyscallType::System => 0,
|
||||||
|
compiler::SyscallType::Math => 1,
|
||||||
|
};
|
||||||
|
(1, *argument_count as u32, sc_type)
|
||||||
|
}
|
||||||
|
compiler::SymbolKind::Variable { .. } => (2, 0, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = symbol
|
||||||
|
.span
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| (*s).into())
|
||||||
|
.unwrap_or(FfiRange {
|
||||||
|
start_line: 0,
|
||||||
|
end_line: 0,
|
||||||
|
start_col: 0,
|
||||||
|
end_col: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
symbols_vec.push(FfiSymbolInfo {
|
||||||
|
name: symbol.name.to_string().into(),
|
||||||
|
kind_data: FfiSymbolKindData {
|
||||||
|
kind,
|
||||||
|
arg_count,
|
||||||
|
syscall_type,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
description: symbol
|
||||||
|
.description
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| d.to_string())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
FfiDiagnosticsAndSymbols {
|
||||||
|
diagnostics: diagnostics_vec.into(),
|
||||||
|
symbols: symbols_vec.into(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.unwrap_or(FfiDiagnosticsAndSymbols {
|
||||||
|
diagnostics: vec![].into(),
|
||||||
|
symbols: vec![].into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
#[ffi_export]
|
||||||
pub fn get_docs() -> safer_ffi::Vec<FfiDocumentedItem> {
|
pub fn get_docs() -> safer_ffi::Vec<FfiDocumentedItem> {
|
||||||
let res = std::panic::catch_unwind(|| {
|
let res = std::panic::catch_unwind(|| {
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ fn run_logic<'a>() -> Result<(), Error<'a>> {
|
|||||||
let input_string = match input_file {
|
let input_string = match input_file {
|
||||||
Some(input_path) => {
|
Some(input_path) => {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
let mut file = std::fs::File::open(input_path).unwrap();
|
let mut file = std::fs::File::open(input_path)?;
|
||||||
file.read_to_string(&mut buf).unwrap();
|
file.read_to_string(&mut buf)?;
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
Reference in New Issue
Block a user