From 3fb04aef3b235baa4b27543f821fe798e348eeea Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 12 Dec 2025 15:51:36 -0700 Subject: [PATCH 1/5] Emit IL alongside raw IC10 for use in future optimization passes --- rust_compiler/Cargo.lock | 14 + rust_compiler/libs/compiler/Cargo.toml | 1 + .../compiler/src/test/binary_expression.rs | 22 +- .../libs/compiler/src/test/branching.rs | 46 +- .../test/declaration_function_invocation.rs | 38 +- .../compiler/src/test/declaration_literal.rs | 32 +- .../compiler/src/test/function_declaration.rs | 24 +- .../compiler/src/test/logic_expression.rs | 30 +- rust_compiler/libs/compiler/src/test/loops.rs | 22 +- .../libs/compiler/src/test/math_syscall.rs | 36 +- .../libs/compiler/src/test/syscall.rs | 8 +- rust_compiler/libs/compiler/src/v1.rs | 834 ++++++++++-------- .../libs/compiler/src/variable_manager.rs | 3 +- rust_compiler/libs/helpers/Cargo.toml | 1 + rust_compiler/libs/helpers/src/lib.rs | 38 + rust_compiler/libs/il/Cargo.toml | 8 + rust_compiler/libs/il/src/lib.rs | 286 ++++++ rust_compiler/libs/optimizer/Cargo.toml | 6 + rust_compiler/libs/optimizer/src/lib.rs | 14 + rust_compiler/libs/parser/src/lib.rs | 1 + rust_compiler/libs/parser/src/tree_node.rs | 39 +- rust_compiler/libs/tokenizer/src/token.rs | 6 + rust_compiler/src/ffi/mod.rs | 4 +- 23 files changed, 990 insertions(+), 523 deletions(-) create mode 100644 rust_compiler/libs/il/Cargo.toml create mode 100644 rust_compiler/libs/il/src/lib.rs create mode 100644 rust_compiler/libs/optimizer/Cargo.toml create mode 100644 rust_compiler/libs/optimizer/src/lib.rs diff --git a/rust_compiler/Cargo.lock b/rust_compiler/Cargo.lock index ca53d0a..30b4270 100644 --- a/rust_compiler/Cargo.lock +++ b/rust_compiler/Cargo.lock @@ -268,6 +268,7 @@ version = "0.1.0" dependencies = [ "anyhow", "helpers", + "il", "indoc", "lsp-types", "parser", @@ -397,6 +398,15 @@ name = "helpers" version = "0.1.0" dependencies = [ "crc32fast", + "lsp-types", +] + +[[package]] +name = "il" +version = "0.1.0" +dependencies = [ + "helpers", + "rust_decimal", ] [[package]] @@ -563,6 +573,10 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "optimizer" +version = "0.1.0" + [[package]] name = "parser" version = "0.1.0" diff --git a/rust_compiler/libs/compiler/Cargo.toml b/rust_compiler/libs/compiler/Cargo.toml index 829e4f9..721bfcf 100644 --- a/rust_compiler/libs/compiler/Cargo.toml +++ b/rust_compiler/libs/compiler/Cargo.toml @@ -8,6 +8,7 @@ thiserror = { workspace = true } parser = { path = "../parser" } tokenizer = { path = "../tokenizer" } helpers = { path = "../helpers" } +il = { path = "../il" } lsp-types = { workspace = true } rust_decimal = { workspace = true } diff --git a/rust_compiler/libs/compiler/src/test/binary_expression.rs b/rust_compiler/libs/compiler/src/test/binary_expression.rs index 558b04f..4ac0754 100644 --- a/rust_compiler/libs/compiler/src/test/binary_expression.rs +++ b/rust_compiler/libs/compiler/src/test/binary_expression.rs @@ -18,7 +18,7 @@ fn simple_binary_expression() -> Result<()> { " j main main: - move r8 3 #i + move r8 3 " } ); @@ -45,9 +45,9 @@ fn nested_binary_expressions() -> Result<()> { " j main calculateArgs: - pop r8 #arg3 - pop r9 #arg2 - pop r10 #arg1 + pop r8 + pop r9 + pop r10 push ra add r1 r10 r9 mul r2 r1 r8 @@ -63,9 +63,9 @@ fn nested_binary_expressions() -> Result<()> { push 20 push 30 jal calculateArgs - move r1 r15 #__binary_temp_3 + move r1 r15 add r2 r1 100 - move r8 r2 #returned + move r8 r2 " } ); @@ -88,7 +88,7 @@ fn stress_test_constant_folding() -> Result<()> { " j main main: - move r8 -123 #negationHell + move r8 -123 " } ); @@ -116,7 +116,7 @@ fn test_constant_folding_with_variables_mixed_in() -> Result<()> { mul r2 373.2 r1 sub r3 1 r2 add r4 r3 518.15 - move r8 r4 #i + move r8 r4 " } ); @@ -141,7 +141,7 @@ fn test_ternary_expression() -> Result<()> { main: sgt r1 1 2 select r2 r1 15 20 - move r8 r2 #i + move r8 r2 " } ); @@ -165,10 +165,10 @@ fn test_ternary_expression_assignment() -> Result<()> { " j main main: - move r8 0 #i + move r8 0 sgt r1 1 2 select r2 r1 15 20 - move r8 r2 #i + move r8 r2 " } ); diff --git a/rust_compiler/libs/compiler/src/test/branching.rs b/rust_compiler/libs/compiler/src/test/branching.rs index 1a852da..155c535 100644 --- a/rust_compiler/libs/compiler/src/test/branching.rs +++ b/rust_compiler/libs/compiler/src/test/branching.rs @@ -20,10 +20,10 @@ fn test_if_statement() -> anyhow::Result<()> { " j main main: - move r8 10 #a + move r8 10 sgt r1 r8 5 - beq r1 0 L1 - move r8 20 #a + beqz r1 L1 + move r8 20 L1: " } @@ -52,13 +52,13 @@ fn test_if_else_statement() -> anyhow::Result<()> { " j main main: - move r8 0 #a + move r8 0 sgt r1 10 5 - beq r1 0 L2 - move r8 1 #a + beqz r1 L2 + move r8 1 j L1 L2: - move r8 2 #a + move r8 2 L1: " } @@ -89,18 +89,18 @@ fn test_if_else_if_statement() -> anyhow::Result<()> { " j main main: - move r8 0 #a + move r8 0 seq r1 r8 1 - beq r1 0 L2 - move r8 10 #a + beqz r1 L2 + move r8 10 j L1 L2: seq r2 r8 2 - beq r2 0 L4 - move r8 20 #a + beqz r2 L4 + move r8 20 j L3 L4: - move r8 30 #a + move r8 30 L3: L1: " @@ -136,18 +136,18 @@ fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> { " j main main: - move r8 1 #a - move r9 2 #b - move r10 3 #c - move r11 4 #d - move r12 5 #e - move r13 6 #f - move r14 7 #g - push 8 #h + move r8 1 + move r9 2 + move r10 3 + move r11 4 + move r12 5 + move r13 6 + move r14 7 + push 8 seq r1 r8 1 - beq r1 0 L1 + beqz r1 L1 sub r0 sp 1 - put db r0 99 #h + put db r0 99 L1: sub sp sp 1 " diff --git a/rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs b/rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs index 5f3f499..dbdfaca 100644 --- a/rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs +++ b/rust_compiler/libs/compiler/src/test/declaration_function_invocation.rs @@ -24,7 +24,7 @@ fn no_arguments() -> anyhow::Result<()> { j ra main: jal doSomething - move r8 r15 #i + move r8 r15 " }; @@ -55,7 +55,7 @@ fn let_var_args() -> anyhow::Result<()> { " j main mul2: - pop r8 #arg1 + pop r8 push ra mul r1 r8 2 move r15 r1 @@ -67,16 +67,16 @@ fn let_var_args() -> anyhow::Result<()> { j ra main: L2: - move r8 123 #arg1 + move r8 123 push r8 push r8 jal mul2 sub r0 sp 1 get r8 db r0 sub sp sp 1 - move r9 r15 #i + move r9 r15 pow r1 r9 2 - move r9 r1 #i + move r9 r1 j L2 L3: " @@ -123,10 +123,10 @@ fn inline_literal_args() -> anyhow::Result<()> { " j main doSomething: - pop r8 #arg2 - pop r9 #arg1 + pop r8 + pop r9 push ra - move r15 5 #returnValue + move r15 5 j L1 L1: sub r0 sp 1 @@ -134,7 +134,7 @@ fn inline_literal_args() -> anyhow::Result<()> { sub sp sp 1 j ra main: - move r8 123 #thisVariableShouldStayInPlace + move r8 123 push r8 push 12 push 34 @@ -142,7 +142,7 @@ fn inline_literal_args() -> anyhow::Result<()> { sub r0 sp 1 get r8 db r0 sub sp sp 1 - move r9 r15 #returnedValue + move r9 r15 " } ); @@ -167,8 +167,8 @@ fn mixed_args() -> anyhow::Result<()> { " j main doSomething: - pop r8 #arg2 - pop r9 #arg1 + pop r8 + pop r9 push ra L1: sub r0 sp 1 @@ -176,7 +176,7 @@ fn mixed_args() -> anyhow::Result<()> { sub sp sp 1 j ra main: - move r8 123 #arg1 + move r8 123 push r8 push r8 push 456 @@ -184,7 +184,7 @@ fn mixed_args() -> anyhow::Result<()> { sub r0 sp 1 get r8 db r0 sub sp sp 1 - move r9 r15 #returnValue + move r9 r15 " } ); @@ -211,9 +211,9 @@ fn with_return_statement() -> anyhow::Result<()> { " j main doSomething: - pop r8 #arg1 + pop r8 push ra - move r15 456 #returnValue + move r15 456 j L1 L1: sub r0 sp 1 @@ -223,7 +223,7 @@ fn with_return_statement() -> anyhow::Result<()> { main: push 123 jal doSomething - move r8 r15 #returned + move r8 r15 " } ); @@ -250,7 +250,7 @@ fn with_negative_return_literal() -> anyhow::Result<()> { j main doSomething: push ra - move r15 -1 #returnValue + move r15 -1 L1: sub r0 sp 1 get ra db r0 @@ -258,7 +258,7 @@ fn with_negative_return_literal() -> anyhow::Result<()> { j ra main: jal doSomething - move r8 r15 #i + move r8 r15 " } ); diff --git a/rust_compiler/libs/compiler/src/test/declaration_literal.rs b/rust_compiler/libs/compiler/src/test/declaration_literal.rs index 171f83e..550e332 100644 --- a/rust_compiler/libs/compiler/src/test/declaration_literal.rs +++ b/rust_compiler/libs/compiler/src/test/declaration_literal.rs @@ -15,7 +15,7 @@ fn variable_declaration_numeric_literal() -> anyhow::Result<()> { " j main main: - move r8 293.15 #i + move r8 293.15 " } ); @@ -46,16 +46,16 @@ fn variable_declaration_numeric_literal_stack_spillover() -> anyhow::Result<()> " j main main: - move r8 0 #a - move r9 1 #b - move r10 2 #c - move r11 3 #d - move r12 4 #e - move r13 5 #f - move r14 6 #g - push 7 #h - push 8 #i - push 9 #j + move r8 0 + move r9 1 + move r10 2 + move r11 3 + move r12 4 + move r13 5 + move r14 6 + push 7 + push 8 + push 9 sub sp sp 3 " } @@ -79,7 +79,7 @@ fn variable_declaration_negative() -> anyhow::Result<()> { " j main main: - move r8 -1 #i + move r8 -1 " } ); @@ -103,8 +103,8 @@ fn test_boolean_declaration() -> anyhow::Result<()> { " j main main: - move r8 1 #t - move r9 0 #f + move r8 1 + move r9 0 " } ); @@ -132,7 +132,7 @@ fn test_boolean_return() -> anyhow::Result<()> { j main getTrue: push ra - move r15 1 #returnValue + move r15 1 j L1 L1: sub r0 sp 1 @@ -141,7 +141,7 @@ fn test_boolean_return() -> anyhow::Result<()> { j ra main: jal getTrue - move r8 r15 #val + move r8 r15 " } ); diff --git a/rust_compiler/libs/compiler/src/test/function_declaration.rs b/rust_compiler/libs/compiler/src/test/function_declaration.rs index 374902e..18f5b84 100644 --- a/rust_compiler/libs/compiler/src/test/function_declaration.rs +++ b/rust_compiler/libs/compiler/src/test/function_declaration.rs @@ -13,13 +13,13 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> { indoc! {" j main doSomething: - pop r8 #arg9 - pop r9 #arg8 - pop r10 #arg7 - pop r11 #arg6 - pop r12 #arg5 - pop r13 #arg4 - pop r14 #arg3 + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 push ra L1: sub r0 sp 1 @@ -54,10 +54,10 @@ fn test_early_return() -> anyhow::Result<()> { doSomething: push ra seq r1 1 1 - beq r1 0 L2 + beqz r1 L2 j L1 L2: - move r8 3 #i + move r8 3 j L1 L1: sub r0 sp 1 @@ -66,7 +66,7 @@ fn test_early_return() -> anyhow::Result<()> { j ra main: jal doSomething - move r1 r15 #__binary_temp_2 + move r1 r15 " } ); @@ -87,8 +87,8 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> { indoc! {" j main doSomething: - pop r8 #arg2 - pop r9 #arg1 + pop r8 + pop r9 push ra L1: sub r0 sp 1 diff --git a/rust_compiler/libs/compiler/src/test/logic_expression.rs b/rust_compiler/libs/compiler/src/test/logic_expression.rs index 1f65d4b..df77bd5 100644 --- a/rust_compiler/libs/compiler/src/test/logic_expression.rs +++ b/rust_compiler/libs/compiler/src/test/logic_expression.rs @@ -23,17 +23,17 @@ fn test_comparison_expressions() -> anyhow::Result<()> { j main main: sgt r1 10 5 - move r8 r1 #isGreater + move r8 r1 slt r2 5 10 - move r9 r2 #isLess + move r9 r2 seq r3 5 5 - move r10 r3 #isEqual + move r10 r3 sne r4 5 10 - move r11 r4 #isNotEqual + move r11 r4 sge r5 10 10 - move r12 r5 #isGreaterOrEqual + move r12 r5 sle r6 5 5 - move r13 r6 #isLessOrEqual + move r13 r6 " } ); @@ -59,11 +59,11 @@ fn test_logical_and_or_not() -> anyhow::Result<()> { j main main: and r1 1 1 - move r8 r1 #logic1 + move r8 r1 or r2 1 0 - move r9 r2 #logic2 + move r9 r2 seq r3 1 0 - move r10 r3 #logic3 + move r10 r3 " } ); @@ -89,7 +89,7 @@ fn test_complex_logic() -> anyhow::Result<()> { sgt r1 10 5 slt r2 5 10 and r3 r1 r2 - move r8 r3 #logic + move r8 r3 " } ); @@ -113,7 +113,7 @@ fn test_math_with_logic() -> anyhow::Result<()> { j main main: sgt r1 3 1 - move r8 r1 #logic + move r8 r1 " } ); @@ -137,7 +137,7 @@ fn test_boolean_in_logic() -> anyhow::Result<()> { j main main: and r1 1 0 - move r8 r1 #res + move r8 r1 " } ); @@ -163,11 +163,11 @@ fn test_invert_a_boolean() -> anyhow::Result<()> { " j main main: - move r8 1 #i + move r8 1 seq r1 r8 0 - move r9 r1 #y + move r9 r1 seq r2 r9 0 - move r10 r2 #result + move r10 r2 " } ); diff --git a/rust_compiler/libs/compiler/src/test/loops.rs b/rust_compiler/libs/compiler/src/test/loops.rs index b9c38e3..83745f0 100644 --- a/rust_compiler/libs/compiler/src/test/loops.rs +++ b/rust_compiler/libs/compiler/src/test/loops.rs @@ -21,10 +21,10 @@ fn test_infinite_loop() -> anyhow::Result<()> { " j main main: - move r8 0 #a + move r8 0 L1: add r1 r8 1 - move r8 r1 #a + move r8 r1 j L1 L2: " @@ -56,12 +56,12 @@ fn test_loop_break() -> anyhow::Result<()> { " j main main: - move r8 0 #a + move r8 0 L1: add r1 r8 1 - move r8 r1 #a + move r8 r1 sgt r2 r8 10 - beq r2 0 L3 + beqz r2 L3 j L2 L3: j L1 @@ -92,12 +92,12 @@ fn test_while_loop() -> anyhow::Result<()> { " j main main: - move r8 0 #a + move r8 0 L1: slt r1 r8 10 - beq r1 0 L2 + beqz r1 L2 add r2 r8 1 - move r8 r2 #a + move r8 r2 j L1 L2: " @@ -130,12 +130,12 @@ fn test_loop_continue() -> anyhow::Result<()> { " j main main: - move r8 0 #a + move r8 0 L1: add r1 r8 1 - move r8 r1 #a + move r8 r1 slt r2 r8 5 - beq r2 0 L3 + beqz r2 L3 j L1 L3: j L2 diff --git a/rust_compiler/libs/compiler/src/test/math_syscall.rs b/rust_compiler/libs/compiler/src/test/math_syscall.rs index f102135..cf3b3e0 100644 --- a/rust_compiler/libs/compiler/src/test/math_syscall.rs +++ b/rust_compiler/libs/compiler/src/test/math_syscall.rs @@ -19,7 +19,7 @@ fn test_acos() -> Result<()> { j main main: acos r15 123 - move r8 r15 #i + move r8 r15 " } ); @@ -43,7 +43,7 @@ fn test_asin() -> Result<()> { j main main: asin r15 123 - move r8 r15 #i + move r8 r15 " } ); @@ -67,7 +67,7 @@ fn test_atan() -> Result<()> { j main main: atan r15 123 - move r8 r15 #i + move r8 r15 " } ); @@ -91,7 +91,7 @@ fn test_atan2() -> Result<()> { j main main: atan2 r15 123 456 - move r8 r15 #i + move r8 r15 " } ); @@ -115,7 +115,7 @@ fn test_abs() -> Result<()> { j main main: abs r15 -123 - move r8 r15 #i + move r8 r15 " } ); @@ -139,7 +139,7 @@ fn test_ceil() -> Result<()> { j main main: ceil r15 123.90 - move r8 r15 #i + move r8 r15 " } ); @@ -163,7 +163,7 @@ fn test_cos() -> Result<()> { j main main: cos r15 123 - move r8 r15 #i + move r8 r15 " } ); @@ -187,7 +187,7 @@ fn test_floor() -> Result<()> { j main main: floor r15 123 - move r8 r15 #i + move r8 r15 " } ); @@ -211,7 +211,7 @@ fn test_log() -> Result<()> { j main main: log r15 123 - move r8 r15 #i + move r8 r15 " } ); @@ -235,7 +235,7 @@ fn test_max() -> Result<()> { j main main: max r15 123 456 - move r8 r15 #i + move r8 r15 " } ); @@ -259,9 +259,9 @@ fn test_max_from_game() -> Result<()> { " j main main: - move r8 0 #item + move r8 0 max r15 3 2 - move r8 r15 #item + move r8 r15 " } ); @@ -285,7 +285,7 @@ fn test_min() -> Result<()> { j main main: min r15 123 456 - move r8 r15 #i + move r8 r15 " } ); @@ -309,7 +309,7 @@ fn test_rand() -> Result<()> { j main main: rand r15 - move r8 r15 #i + move r8 r15 " } ); @@ -333,7 +333,7 @@ fn test_sin() -> Result<()> { j main main: sin r15 3 - move r8 r15 #i + move r8 r15 " } ); @@ -357,7 +357,7 @@ fn test_sqrt() -> Result<()> { j main main: sqrt r15 3 - move r8 r15 #i + move r8 r15 " } ); @@ -381,7 +381,7 @@ fn test_tan() -> Result<()> { j main main: tan r15 3 - move r8 r15 #i + move r8 r15 " } ); @@ -405,7 +405,7 @@ fn test_trunc() -> Result<()> { j main main: trunc r15 3.234 - move r8 r15 #i + move r8 r15 " } ); diff --git a/rust_compiler/libs/compiler/src/test/syscall.rs b/rust_compiler/libs/compiler/src/test/syscall.rs index a8e5b9e..8456448 100644 --- a/rust_compiler/libs/compiler/src/test/syscall.rs +++ b/rust_compiler/libs/compiler/src/test/syscall.rs @@ -44,7 +44,7 @@ fn test_sleep() -> anyhow::Result<()> { j main main: sleep 3 - move r8 15 #sleepAmount + move r8 15 sleep r8 mul r1 r8 2 sleep r1 @@ -73,7 +73,7 @@ fn test_set_on_device() -> anyhow::Result<()> { " j main main: - move r8 293.15 #internalTemp + move r8 293.15 sgt r1 r8 298.15 s d0 On r1 " @@ -150,7 +150,7 @@ fn test_load_from_device() -> anyhow::Result<()> { j main main: l r15 d0 On - move r8 r15 #setting + move r8 r15 " } ); @@ -176,7 +176,7 @@ fn test_load_from_slot() -> anyhow::Result<()> { j main main: ls r15 d0 0 Occupied - move r8 r15 #setting + move r8 r15 " } ); diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 911c29a..5ed8986 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -1,6 +1,7 @@ #![allow(clippy::result_large_err)] use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; -use helpers::prelude::*; +use helpers::{Span, prelude::*}; +use il::{Instruction, InstructionNode, Operand}; use parser::{ Parser as ASTParser, sys_call::{Math, SysCall, System}, @@ -8,9 +9,10 @@ use parser::{ AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression, DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression, InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression, - LoopExpression, MemberAccessExpression, Span, Spanned, TernaryExpression, WhileExpression, + LoopExpression, MemberAccessExpression, Spanned, TernaryExpression, WhileExpression, }, }; +use rust_decimal::Decimal; use std::{ borrow::Cow, collections::HashMap, @@ -19,28 +21,10 @@ use std::{ use thiserror::Error; use tokenizer::token::Number; -macro_rules! debug { - ($self: expr, $debug_value: expr) => { - if $self.config.debug { - format!($debug_value) - } else { - "".into() - } - }; - - ($self: expr, $debug_value: expr, $args: expr) => { - if $self.config.debug { - format!($debug_value, $args) - } else { - "".into() - } - }; -} - fn extract_literal<'a>( literal: Literal<'a>, allow_strings: bool, -) -> Result, Error<'a>> { +) -> Result, Error<'a>> { if !allow_strings && matches!(literal, Literal::String(_)) { return Err(Error::Unknown( "Literal strings are not allowed in this context".to_string(), @@ -48,9 +32,9 @@ fn extract_literal<'a>( )); } Ok(match literal { - Literal::String(s) => s, - Literal::Number(n) => Cow::from(n.to_string()), - Literal::Boolean(b) => Cow::from(if b { "1" } else { "0" }), + Literal::String(s) => Operand::LogicType(s), + Literal::Number(n) => Operand::Number(n.into()), + Literal::Boolean(b) => Operand::Number(Number::from(b).into()), }) } @@ -164,6 +148,11 @@ pub struct Compiler<'a, 'w, W: std::io::Write> { function_metadata: HashMap, Vec>>, devices: HashMap, Cow<'a, str>>, output: &'w mut BufWriter, + + // This holds the IL code which will be used in the + // optimizer + pub instructions: Vec>, + current_line: usize, declared_main: bool, config: CompilerConfig, @@ -189,6 +178,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { function_metadata: HashMap::new(), devices: HashMap::new(), output: writer, + instructions: Vec::new(), current_line: 1, declared_main: false, config: config.unwrap_or_default(), @@ -241,8 +231,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { }; let spanned_root = Spanned { node: expr, span }; - - if let Err(e) = self.write_output("j main", Some(span)) { + if let Err(e) = self.write_instruction( + Instruction::Jump(Operand::Label(Cow::from("main"))), + Some(span), + ) { self.errors.push(e); return CompilationResult { errors: self.errors, @@ -263,23 +255,17 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } } - fn write_output( + /// Performs a write to the output buffer as well as a push to the IL instructions vec + fn write_instruction( &mut self, - output: impl Into, + instr: Instruction<'a>, span: Option, ) -> Result<(), Error<'a>> { - self.output.write_all(output.into().as_bytes())?; + self.output.write_all(format!("{}", instr).as_bytes())?; self.output.write_all(b"\n")?; - - if let Some(span) = span { - self.source_map - .entry(self.current_line) - .or_default() - .push(span); - } - self.current_line += 1; + self.instructions.push(InstructionNode::new(instr, span)); Ok(()) } @@ -360,9 +346,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let temp_loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; self.emit_variable_assignment( - temp_name.clone(), &temp_loc, - Cow::from(format!("r{}", VariableScope::RETURN_REGISTER)), + Operand::Register(VariableScope::RETURN_REGISTER), )?; Ok(Some(CompileLocation { location: temp_loc, @@ -381,21 +366,16 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Literal::Number(num) => { let temp_name = self.next_temp_name(); let loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; - self.emit_variable_assignment( - temp_name.clone(), - &loc, - Cow::from(num.to_string()), - )?; + self.emit_variable_assignment(&loc, Operand::Number(num.into()))?; Ok(Some(CompileLocation { location: loc, temp_name: Some(temp_name), })) } Literal::Boolean(b) => { - let val = if b { "1" } else { "0" }; let temp_name = self.next_temp_name(); let loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; - self.emit_variable_assignment(temp_name.clone(), &loc, Cow::from(val))?; + self.emit_variable_assignment(&loc, Operand::Number(Number::from(b).into()))?; Ok(Some(CompileLocation { location: loc, temp_name: Some(temp_name), @@ -432,7 +412,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let MemberAccessExpression { object, member } = access.node; // 1. Resolve the object to a device string (e.g., "d0" or "rX") - let (device_str, cleanup) = self.resolve_device(*object, scope)?; + let (device, cleanup) = self.resolve_device(*object, scope)?; // 2. Allocate a temp register for the result let result_name = self.next_temp_name(); @@ -440,8 +420,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let reg = self.resolve_register(&loc)?; // 3. Emit load instruction: l rX device member - self.write_output( - format!("l {} {} {}", reg, device_str, member.node), + self.write_instruction( + Instruction::Load( + Operand::Register(reg), + device, + Operand::LogicType(member.node), + ), Some(expr.span), )?; @@ -475,7 +459,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; - self.write_output(format!("sub {result_reg} 0 {inner_str}"), Some(expr.span))?; + self.write_instruction( + Instruction::Sub( + Operand::Register(result_reg), + Operand::Number(0.into()), + inner_str, + ), + Some(expr.span), + )?; if let Some(name) = cleanup { scope.free_temp(name, None)?; @@ -502,12 +493,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, - ) -> Result<(Cow<'a, str>, Option>), Error<'a>> { + ) -> Result<(Operand<'a>, Option>), Error<'a>> { // If it's a direct variable reference, check if it's a known device alias first if let Expression::Variable(ref name) = expr.node && let Some(device_id) = self.devices.get(&name.node) { - return Ok((device_id.clone(), None)); + return Ok((Operand::Device(device_id.clone()), None)); } // Otherwise, compile it as an operand (e.g. it might be a register holding a device hash/id) @@ -516,22 +507,18 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { fn emit_variable_assignment( &mut self, - var_name: Cow<'a, str>, location: &VariableLocation<'a>, - source_value: Cow<'a, str>, + source_value: Operand<'a>, ) -> Result<(), Error<'a>> { - let debug_tag = if self.config.debug { - format!(" #{var_name}") - } else { - String::new() - }; - match location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { - self.write_output(format!("move r{reg} {}{debug_tag}", source_value), None)?; + self.write_instruction( + Instruction::Move(Operand::Register(*reg), source_value), + None, + )?; } VariableLocation::Stack(_) => { - self.write_output(format!("push {}{debug_tag}", source_value), None)?; + self.write_instruction(Instruction::Push(source_value), None)?; } VariableLocation::Constant(_) => { return Err(Error::Unknown( @@ -570,11 +557,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { { let loc = scope.add_variable(name_str.clone(), LocationRequest::Persist, Some(name_span))?; - self.emit_variable_assignment( - name_str.clone(), - &loc, - Cow::from(format!("-{neg_num}")), - )?; + + self.emit_variable_assignment(&loc, Operand::Number((-*neg_num).into()))?; return Ok(Some(CompileLocation { location: loc, temp_name: None, @@ -590,22 +574,20 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Some(name_span), )?; - self.emit_variable_assignment( - name_str.clone(), - &var_location, - Cow::from(num.to_string()), - )?; + self.emit_variable_assignment(&var_location, Operand::Number(num.into()))?; (var_location, None) } Literal::Boolean(b) => { - let val = if b { "1" } else { "0" }; let var_location = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; - self.emit_variable_assignment(name_str, &var_location, Cow::from(val))?; + self.emit_variable_assignment( + &var_location, + Operand::Number(Number::from(b).into()), + )?; (var_location, None) } _ => return Ok(None), @@ -619,9 +601,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Some(name_span), )?; self.emit_variable_assignment( - name_str, &loc, - Cow::from(format!("r{}", VariableScope::RETURN_REGISTER)), + Operand::Register(VariableScope::RETURN_REGISTER), )?; (loc, None) } @@ -649,9 +630,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Some(name_span), )?; self.emit_variable_assignment( - name_str, &loc, - Cow::from(format!("r{}", VariableScope::RETURN_REGISTER)), + Operand::Register(VariableScope::RETURN_REGISTER), )?; (loc, None) @@ -670,12 +650,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { .. } = result { - self.emit_variable_assignment(name_str, &var_loc, Cow::from(num.to_string()))?; + self.emit_variable_assignment(&var_loc, Operand::Number(num.into()))?; (var_loc, None) } else { // Move result from temp to new persistent variable let result_reg = self.resolve_register(&result.location)?; - self.emit_variable_assignment(name_str, &var_loc, result_reg)?; + self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?; // Free the temp result if let Some(name) = result.temp_name { @@ -694,7 +674,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { // Move result from temp to new persistent variable let result_reg = self.resolve_register(&result.location)?; - self.emit_variable_assignment(name_str, &var_loc, result_reg)?; + self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?; // Free the temp result if let Some(name) = result.temp_name { @@ -721,24 +701,34 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )?; // Handle loading from stack if necessary - let src_str = match src_loc { + let src = match src_loc { VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { - format!("r{r}") + Operand::Register(r) } VariableLocation::Stack(offset) => { - self.write_output( - format!("sub r{0} sp {offset}", VariableScope::TEMP_STACK_REGISTER), + self.write_instruction( + Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::StackPointer, + Operand::Number(offset.into()), + ), Some(expr.span), )?; - self.write_output( - format!("get r{0} db r{0}", VariableScope::TEMP_STACK_REGISTER), + + self.write_instruction( + Instruction::Get( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + ), Some(expr.span), )?; - format!("r{}", VariableScope::TEMP_STACK_REGISTER) + + Operand::Register(VariableScope::TEMP_STACK_REGISTER) } VariableLocation::Constant(_) | VariableLocation::Device(_) => unreachable!(), }; - self.emit_variable_assignment(name_str, &var_loc, Cow::from(src_str))?; + self.emit_variable_assignment(&var_loc, src)?; (var_loc, None) } Expression::Priority(inner) => { @@ -776,7 +766,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )?; let result_reg = self.resolve_register(&comp_res.location)?; - self.emit_variable_assignment(name_str, &var_loc, result_reg)?; + self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?; if let Some(temp) = comp_res.temp_name { scope.free_temp(temp, None)?; @@ -794,7 +784,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )?; let res_register = self.resolve_register(&res.location)?; - self.emit_variable_assignment(name_str, &var_loc, res_register)?; + self.emit_variable_assignment(&var_loc, Operand::Register(res_register))?; if let Some(name) = res.temp_name { scope.free_temp(name, None)?; @@ -876,32 +866,32 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } }; - let (val_str, cleanup) = self.compile_operand(*expression, scope)?; - - let debug_tag = if self.config.debug { - format!(" #{}", identifier.node) - } else { - String::new() - }; + let (val, cleanup) = self.compile_operand(*expression, scope)?; match location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { - self.write_output( - format!("move r{reg} {val_str}{debug_tag}"), + self.write_instruction( + Instruction::Move(Operand::Register(reg), val), Some(expr_span), )?; } VariableLocation::Stack(offset) => { // Calculate address: sp - offset - self.write_output( - format!("sub r{0} sp {offset}", VariableScope::TEMP_STACK_REGISTER), + self.write_instruction( + Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::StackPointer, + Operand::Number(offset.into()), + ), Some(expr_span), )?; + // Store value to stack/db at address - self.write_output( - format!( - "put db r{0} {val_str}{debug_tag}", - VariableScope::TEMP_STACK_REGISTER + self.write_instruction( + Instruction::Put( + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + val, ), Some(expr_span), )?; @@ -922,11 +912,11 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { // Set instruction: s device member value let MemberAccessExpression { object, member } = access.node; - let (device_str, dev_cleanup) = self.resolve_device(*object, scope)?; - let (val_str, val_cleanup) = self.compile_operand(*expression, scope)?; + let (device, dev_cleanup) = self.resolve_device(*object, scope)?; + let (val, val_cleanup) = self.compile_operand(*expression, scope)?; - self.write_output( - format!("s {} {} {}", device_str, member.node, val_str,), + self.write_instruction( + Instruction::Store(device, Operand::LogicType(member.node), val), Some(member.span), )?; @@ -989,18 +979,25 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { LocationRequest::Stack, None, )?; - self.write_output(format!("push r{register}"), Some(name.span))?; + self.write_instruction( + Instruction::Push(Operand::Register(*register)), + Some(name.span), + )?; } for arg in arguments { match arg.node { Expression::Literal(spanned_lit) => match spanned_lit.node { Literal::Number(num) => { - let num_str = num.to_string(); - self.write_output(format!("push {num_str}"), Some(spanned_lit.span))?; + self.write_instruction( + Instruction::Push(Operand::Number(num.into())), + Some(spanned_lit.span), + )?; } Literal::Boolean(b) => { - let val = if b { "1" } else { "0" }; - self.write_output(format!("push {val}"), Some(spanned_lit.span))?; + self.write_instruction( + Instruction::Push(Operand::Number(Number::from(b).into())), + Some(spanned_lit.span), + )?; } _ => {} }, @@ -1016,28 +1013,40 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { match loc { VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => { - self.write_output(format!("push r{reg}"), Some(var_name.span))?; + self.write_instruction( + Instruction::Push(Operand::Register(reg)), + Some(var_name.span), + )?; } VariableLocation::Constant(lit) => { - self.write_output( - format!("push {}", extract_literal(lit, false)?), + self.write_instruction( + Instruction::Push(extract_literal(lit, false)?), Some(var_name.span), )?; } VariableLocation::Stack(stack_offset) => { - self.write_output( - format!( - "sub r{0} sp {stack_offset}", - VariableScope::TEMP_STACK_REGISTER + self.write_instruction( + Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::StackPointer, + Operand::Number(stack_offset.into()), ), Some(var_name.span), )?; - self.write_output( - format!("get r{0} db r{0}", VariableScope::TEMP_STACK_REGISTER), + + self.write_instruction( + Instruction::Get( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + ), Some(var_name.span), )?; - self.write_output( - format!("push r{0}", VariableScope::TEMP_STACK_REGISTER), + + self.write_instruction( + Instruction::Push(Operand::Register( + VariableScope::TEMP_STACK_REGISTER, + )), Some(var_name.span), )?; } @@ -1053,8 +1062,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let span = bin_expr.span; // Compile the binary expression to a temp register let result = self.expression_binary(bin_expr, &mut stack)?; - let reg_str = self.resolve_register(&result.location)?; - self.write_output(format!("push {reg_str}"), Some(span))?; + let reg = self.resolve_register(&result.location)?; + self.write_instruction(Instruction::Push(Operand::Register(reg)), Some(span))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } @@ -1063,8 +1072,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let span = log_expr.span; // Compile the logical expression to a temp register let result = self.expression_logical(log_expr, &mut stack)?; - let reg_str = self.resolve_register(&result.location)?; - self.write_output(format!("push {reg_str}"), Some(span))?; + let reg = self.resolve_register(&result.location)?; + self.write_instruction(Instruction::Push(Operand::Register(reg)), Some(span))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } @@ -1087,12 +1096,18 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { if let Some(result) = result_opt { let reg_str = self.resolve_register(&result.location)?; - self.write_output(format!("push {reg_str}"), Some(span))?; + self.write_instruction( + Instruction::Push(Operand::Register(reg_str)), + Some(span), + )?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } else { - self.write_output("push 0", Some(span))?; // Should fail ideally + self.write_instruction( + Instruction::Push(Operand::Number(Decimal::from(0))), + Some(span), + )?; } } _ => { @@ -1108,7 +1123,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } // jump to the function and store current line in ra - self.write_output(format!("jal {}", name.node), Some(name.span))?; + self.write_instruction( + Instruction::JumpAndLink(Operand::Label(name.node)), + Some(name.span), + )?; for register in active_registers { let VariableLocation::Stack(stack_offset) = stack @@ -1121,25 +1139,32 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Some(name.span), )); }; - self.write_output( - format!( - "sub r{0} sp {stack_offset}", - VariableScope::TEMP_STACK_REGISTER + self.write_instruction( + Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::StackPointer, + Operand::Number(stack_offset.into()), ), Some(name.span), )?; - self.write_output( - format!( - "get r{register} db r{0}", - VariableScope::TEMP_STACK_REGISTER + + self.write_instruction( + Instruction::Get( + Operand::Register(register), + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(name.span), )?; } if stack.stack_offset() > 0 { - self.write_output( - format!("sub sp sp {}", stack.stack_offset()), + self.write_instruction( + Instruction::Sub( + Operand::StackPointer, + Operand::StackPointer, + Operand::Number(Decimal::from(stack.stack_offset())), + ), Some(name.span), )?; } @@ -1181,10 +1206,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let cond_span = expr.condition.span; // Compile Condition - let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?; + let (cond, cleanup) = self.compile_operand(*expr.condition, scope)?; // If condition is FALSE (0), jump to else_label - self.write_output(format!("beq {cond_str} 0 {else_label}"), Some(cond_span))?; + self.write_instruction( + Instruction::BranchEqZero(cond, Operand::Label(else_label.clone())), + Some(cond_span), + )?; if let Some(name) = cleanup { scope.free_temp(name, None)?; @@ -1196,8 +1224,11 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { // If we have an else branch, we need to jump over it after the 'if' body if let Some(else_branch) = expr.else_branch { - self.write_output(format!("j {end_label}"), Some(else_branch.span))?; - self.write_output(format!("{else_label}:"), Some(else_branch.span))?; + self.write_instruction( + Instruction::Jump(Operand::Label(end_label.clone())), + Some(else_branch.span), + )?; + self.write_instruction(Instruction::LabelDef(else_label), Some(else_branch.span))?; match else_branch.node { Expression::Block(block) => self.expression_block(block.node, scope)?, @@ -1206,7 +1237,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } } - self.write_output(format!("{end_label}:"), Some(expr.body.span))?; + self.write_instruction(Instruction::LabelDef(end_label), Some(expr.body.span))?; Ok(()) } @@ -1223,14 +1254,20 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.loop_stack .push((start_label.clone(), end_label.clone())); - self.write_output(format!("{start_label}:"), Some(expr.body.span))?; + self.write_instruction( + Instruction::LabelDef(start_label.clone()), + Some(expr.body.span), + )?; // Compile Body self.expression_block(expr.body.node, scope)?; // Jump back to start - self.write_output(format!("j {start_label}"), Some(expr.body.span))?; - self.write_output(format!("{end_label}:"), Some(expr.body.span))?; + self.write_instruction( + Instruction::Jump(Operand::Label(start_label)), + Some(expr.body.span), + )?; + self.write_instruction(Instruction::LabelDef(end_label), Some(expr.body.span))?; self.loop_stack.pop(); @@ -1250,13 +1287,16 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { .push((start_label.clone(), end_label.clone())); let span = expr.condition.span; - self.write_output(format!("{start_label}:"), Some(span))?; + self.write_instruction(Instruction::LabelDef(start_label.clone()), Some(span))?; // Compile Condition - let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?; + let (cond, cleanup) = self.compile_operand(*expr.condition, scope)?; // If condition is FALSE, jump to end - self.write_output(format!("beq {cond_str} 0 {end_label}"), Some(span))?; + self.write_instruction( + Instruction::BranchEqZero(cond, Operand::Label(end_label.clone())), + Some(span), + )?; if let Some(name) = cleanup { scope.free_temp(name, None)?; @@ -1266,8 +1306,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.expression_block(expr.body, scope)?; // Jump back to start - self.write_output(format!("j {start_label}"), Some(span))?; - self.write_output(format!("{end_label}:"), Some(span))?; + self.write_instruction(Instruction::Jump(Operand::Label(start_label)), Some(span))?; + self.write_instruction(Instruction::LabelDef(end_label), Some(span))?; self.loop_stack.pop(); @@ -1276,7 +1316,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { fn expression_break(&mut self, span: Span) -> Result<(), Error<'a>> { if let Some((_, end_label)) = self.loop_stack.last() { - self.write_output(format!("j {end_label}"), Some(span))?; + self.write_instruction( + Instruction::Jump(Operand::Label(end_label.clone())), + Some(span), + )?; Ok(()) } else { Err(Error::Unknown( @@ -1288,7 +1331,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { fn expression_continue(&mut self, span: Span) -> Result<(), Error<'a>> { if let Some((start_label, _)) = self.loop_stack.last() { - self.write_output(format!("j {start_label}"), Some(span))?; + self.write_instruction( + Instruction::Jump(Operand::Label(start_label.clone())), + Some(span), + )?; Ok(()) } else { Err(Error::Unknown( @@ -1324,8 +1370,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; - self.write_output( - format!("select {} {} {} {}", result_reg, cond, true_val, false_val), + self.write_instruction( + Instruction::Select(Operand::Register(result_reg), cond, true_val, false_val), Some(span), )?; @@ -1347,11 +1393,9 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { /// Helper to resolve a location to a register string (e.g., "r0"). /// Note: This does not handle Stack locations automatically, as they require /// instruction emission to load. Use `compile_operand` for general handling. - fn resolve_register(&self, loc: &VariableLocation) -> Result, Error<'a>> { + fn resolve_register(&self, loc: &VariableLocation) -> Result> { match loc { - VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { - Ok(Cow::from(format!("r{r}"))) - } + VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => Ok(*r), VariableLocation::Constant(_) => Err(Error::Unknown( "Cannot resolve a constant value to register".into(), None, @@ -1375,17 +1419,17 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, - ) -> Result<(Cow<'a, str>, Option>), Error<'a>> { + ) -> Result<(Operand<'a>, Option>), Error<'a>> { // Optimization for literals if let Expression::Literal(spanned_lit) = &expr.node { if let Literal::Number(n) = spanned_lit.node { - return Ok((Cow::from(n.to_string()), None)); + return Ok((Operand::Number(n.into()), None)); } if let Literal::Boolean(b) = spanned_lit.node { - return Ok((Cow::from(if b { "1" } else { "0" }), None)); + return Ok((Operand::Number(Decimal::from(if b { 1 } else { 0 })), None)); } if let Literal::String(ref s) = spanned_lit.node { - return Ok((s.clone(), None)); + return Ok((Operand::LogicType(s.clone()), None)); } } @@ -1395,7 +1439,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { && let Expression::Literal(spanned_lit) = &inner.node && let Literal::Number(n) = spanned_lit.node { - return Ok((Cow::from(format!("-{}", n)), None)); + return Ok((Operand::Number((-n).into()), None)); } let result_opt = self.expression(expr, scope)?; @@ -1404,18 +1448,18 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Some(r) => r, None => { // Expression failed or returned void. Recover with dummy. - return Ok((Cow::from("r0"), None)); + return Ok((Operand::Register(0), None)); } }; match result.location { VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { - Ok((Cow::from(format!("r{r}")), result.temp_name)) + Ok((Operand::Register(r), result.temp_name)) } VariableLocation::Constant(lit) => match lit { - Literal::Number(n) => Ok((Cow::from(n.to_string()), None)), - Literal::Boolean(b) => Ok((Cow::from(if b { "1" } else { "0" }), None)), - Literal::String(s) => Ok((s, None)), + Literal::Number(n) => Ok((Operand::Number(n.into()), None)), + Literal::Boolean(b) => Ok((Operand::Number(Number::from(b).into()), None)), + Literal::String(s) => Ok((Operand::LogicType(s), None)), }, VariableLocation::Stack(offset) => { // If it's on the stack, we must load it into a temp to use it as an operand @@ -1424,21 +1468,29 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; let temp_reg = self.resolve_register(&temp_loc)?; - self.write_output( - format!("sub r{0} sp {offset}", VariableScope::TEMP_STACK_REGISTER), + self.write_instruction( + Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::StackPointer, + Operand::Number(Decimal::from(offset)), + ), None, )?; - self.write_output( - format!("get {temp_reg} db r{0}", VariableScope::TEMP_STACK_REGISTER), + self.write_instruction( + Instruction::Get( + Operand::Register(temp_reg), + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + ), None, )?; // If the original result had a temp name (unlikely for Stack, but possible logic), // we technically should free it if it's not needed, but Stack usually implies it's safe there. // We return the NEW temp name to be freed. - Ok((temp_reg, Some(temp_name))) + Ok((Operand::Register(temp_reg), Some(temp_name))) } - VariableLocation::Device(d) => Ok((d, None)), + VariableLocation::Device(d) => Ok((Operand::Device(d), None)), } } @@ -1446,7 +1498,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { &mut self, val: LiteralOrVariable<'a>, scope: &mut VariableScope<'a, '_>, - ) -> Result<(Cow<'a, str>, Option>), Error<'a>> { + ) -> Result<(Operand<'a>, Option>), Error<'a>> { let dummy_span = Span { start_line: 0, start_col: 0, @@ -1527,13 +1579,30 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { }); }; - let (op_str, left_expr, right_expr) = match expr.node { - BinaryExpression::Add(l, r) => ("add", l, r), - BinaryExpression::Multiply(l, r) => ("mul", l, r), - BinaryExpression::Divide(l, r) => ("div", l, r), - BinaryExpression::Subtract(l, r) => ("sub", l, r), - BinaryExpression::Exponent(l, r) => ("pow", l, r), - BinaryExpression::Modulo(l, r) => ("mod", l, r), + #[allow(clippy::type_complexity)] + let (op_instr, left_expr, right_expr): ( + fn(Operand<'a>, Operand<'a>, Operand<'a>) -> Instruction<'a>, + Box>>, + Box>>, + ) = match expr.node { + BinaryExpression::Add(l, r) => { + (|into, lhs, rhs| Instruction::Add(into, lhs, rhs), l, r) + } + BinaryExpression::Multiply(l, r) => { + (|into, lhs, rhs| Instruction::Mul(into, lhs, rhs), l, r) + } + BinaryExpression::Divide(l, r) => { + (|into, lhs, rhs| Instruction::Div(into, lhs, rhs), l, r) + } + BinaryExpression::Subtract(l, r) => { + (|into, lhs, rhs| Instruction::Sub(into, lhs, rhs), l, r) + } + BinaryExpression::Exponent(l, r) => { + (|into, lhs, rhs| Instruction::Pow(into, lhs, rhs), l, r) + } + BinaryExpression::Modulo(l, r) => { + (|into, lhs, rhs| Instruction::Mod(into, lhs, rhs), l, r) + } }; let span = Span { @@ -1544,9 +1613,9 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { }; // Compile LHS - let (lhs_str, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; + let (lhs, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; // Compile RHS - let (rhs_str, rhs_cleanup) = self.compile_operand(*right_expr, scope)?; + let (rhs, rhs_cleanup) = self.compile_operand(*right_expr, scope)?; // Allocate result register let result_name = self.next_temp_name(); @@ -1554,8 +1623,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let result_reg = self.resolve_register(&result_loc)?; // Emit instruction: op result lhs rhs - self.write_output( - format!("{op_str} {result_reg} {lhs_str} {rhs_str}"), + self.write_instruction( + op_instr(Operand::Register(result_reg), lhs, rhs), Some(span), )?; @@ -1589,7 +1658,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let result_reg = self.resolve_register(&result_loc)?; // seq rX rY 0 => if rY == 0 set rX = 1 else rX = 0 - self.write_output(format!("seq {result_reg} {inner_str} 0"), Some(span))?; + self.write_instruction( + Instruction::SetEq( + Operand::Register(result_reg), + inner_str, + Operand::Number(0.into()), + ), + Some(span), + )?; if let Some(name) = cleanup { scope.free_temp(name, None)?; @@ -1601,15 +1677,36 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { }) } _ => { - let (op_str, left_expr, right_expr) = match expr.node { - LogicalExpression::And(l, r) => ("and", l, r), - LogicalExpression::Or(l, r) => ("or", l, r), - LogicalExpression::Equal(l, r) => ("seq", l, r), - LogicalExpression::NotEqual(l, r) => ("sne", l, r), - LogicalExpression::GreaterThan(l, r) => ("sgt", l, r), - LogicalExpression::GreaterThanOrEqual(l, r) => ("sge", l, r), - LogicalExpression::LessThan(l, r) => ("slt", l, r), - LogicalExpression::LessThanOrEqual(l, r) => ("sle", l, r), + #[allow(clippy::type_complexity)] + let (op_instr, left_expr, right_expr): ( + fn(Operand<'a>, Operand<'a>, Operand<'a>) -> Instruction<'a>, + Box>>, + Box>>, + ) = match expr.node { + LogicalExpression::And(l, r) => { + (|into, lhs, rhs| Instruction::And(into, lhs, rhs), l, r) + } + LogicalExpression::Or(l, r) => { + (|into, lhs, rhs| Instruction::Or(into, lhs, rhs), l, r) + } + LogicalExpression::Equal(l, r) => { + (|into, lhs, rhs| Instruction::SetEq(into, lhs, rhs), l, r) + } + LogicalExpression::NotEqual(l, r) => { + (|into, lhs, rhs| Instruction::SetNe(into, lhs, rhs), l, r) + } + LogicalExpression::GreaterThan(l, r) => { + (|into, lhs, rhs| Instruction::SetGt(into, lhs, rhs), l, r) + } + LogicalExpression::GreaterThanOrEqual(l, r) => { + (|into, lhs, rhs| Instruction::SetGe(into, lhs, rhs), l, r) + } + LogicalExpression::LessThan(l, r) => { + (|into, lhs, rhs| Instruction::SetLt(into, lhs, rhs), l, r) + } + LogicalExpression::LessThanOrEqual(l, r) => { + (|into, lhs, rhs| Instruction::SetLe(into, lhs, rhs), l, r) + } LogicalExpression::Not(_) => unreachable!(), }; @@ -1621,9 +1718,9 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { }; // Compile LHS - let (lhs_str, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; + let (lhs, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; // Compile RHS - let (rhs_str, rhs_cleanup) = self.compile_operand(*right_expr, scope)?; + let (rhs, rhs_cleanup) = self.compile_operand(*right_expr, scope)?; // Allocate result register let result_name = self.next_temp_name(); @@ -1632,8 +1729,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let result_reg = self.resolve_register(&result_loc)?; // Emit instruction: op result lhs rhs - self.write_output( - format!("{op_str} {result_reg} {lhs_str} {rhs_str}"), + self.write_instruction( + op_instr(Operand::Register(result_reg), lhs, rhs), Some(span), )?; @@ -1687,7 +1784,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { ) && !parent_scope.has_parent() { - self.write_output("main:", Some(expr.span))?; + self.write_instruction(Instruction::LabelDef(Cow::from("main")), Some(expr.span))?; self.declared_main = true; } @@ -1714,7 +1811,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } if scope.stack_offset() > 0 { - self.write_output(format!("sub sp sp {}", scope.stack_offset()), None)?; + self.write_instruction( + Instruction::Sub( + Operand::StackPointer, + Operand::StackPointer, + Operand::Number(scope.stack_offset().into()), + ), + None, + )?; } Ok(()) @@ -1733,11 +1837,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { && let Literal::Number(neg_num) = &spanned_lit.node { let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER); - self.emit_variable_assignment( - Cow::from("returnValue"), - &loc, - Cow::from(format!("-{neg_num}")), - )?; + self.emit_variable_assignment(&loc, Operand::Number((-*neg_num).into()))?; return Ok(loc); }; @@ -1747,39 +1847,38 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Ok(loc) => match loc { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { - self.write_output( - format!( - "move r{} r{reg} {}", - VariableScope::RETURN_REGISTER, - debug!(self, "#returnValue") + self.write_instruction( + Instruction::Move( + Operand::Register(VariableScope::RETURN_REGISTER), + Operand::Register(reg), ), Some(span), )?; } VariableLocation::Constant(lit) => { - let str = extract_literal(lit, false)?; - self.write_output( - format!( - "move r{} {str} {}", - VariableScope::RETURN_REGISTER, - debug!(self, "#returnValue") - ), - Some(span), - )? - } - VariableLocation::Stack(offset) => { - self.write_output( - format!( - "sub r{} sp {offset}", - VariableScope::TEMP_STACK_REGISTER + let op = extract_literal(lit, false)?; + self.write_instruction( + Instruction::Move( + Operand::Register(VariableScope::RETURN_REGISTER), + op, ), Some(span), )?; - self.write_output( - format!( - "get r{} db r{}", - VariableScope::RETURN_REGISTER, - VariableScope::TEMP_STACK_REGISTER + } + VariableLocation::Stack(offset) => { + self.write_instruction( + Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::StackPointer, + Operand::Number(offset.into()), + ), + Some(span), + )?; + self.write_instruction( + Instruction::Get( + Operand::Register(VariableScope::RETURN_REGISTER), + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(span), )?; @@ -1803,17 +1902,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Expression::Literal(spanned_lit) => match spanned_lit.node { Literal::Number(num) => { self.emit_variable_assignment( - Cow::from("returnValue"), &VariableLocation::Persistant(VariableScope::RETURN_REGISTER), - Cow::from(num.to_string()), + Operand::Number(num.into()), )?; } Literal::Boolean(b) => { - let val = if b { "1" } else { "0" }; self.emit_variable_assignment( - Cow::from("returnValue"), &VariableLocation::Persistant(VariableScope::RETURN_REGISTER), - Cow::from(val.to_string()), + Operand::Number(Number::from(b).into()), )?; } _ => {} @@ -1822,10 +1918,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let span = bin_expr.span; let result = self.expression_binary(bin_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; - self.write_output( - format!("move r{} {}", VariableScope::RETURN_REGISTER, result_reg), + self.write_instruction( + Instruction::Move( + Operand::Register(VariableScope::RETURN_REGISTER), + Operand::Register(result_reg), + ), Some(span), )?; + if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } @@ -1834,10 +1934,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let span = log_expr.span; let result = self.expression_logical(log_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; - self.write_output( - format!("move r{} {}", VariableScope::RETURN_REGISTER, result_reg), + self.write_instruction( + Instruction::Move( + Operand::Register(VariableScope::RETURN_REGISTER), + Operand::Register(result_reg), + ), Some(span), )?; + if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } @@ -1854,10 +1958,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )?; if let Some(res) = res_opt { let reg = self.resolve_register(&res.location)?; - self.write_output( - format!("move r{} {}", VariableScope::RETURN_REGISTER, reg), + self.write_instruction( + Instruction::Move( + Operand::Register(VariableScope::RETURN_REGISTER), + Operand::Register(reg), + ), Some(span), )?; + if let Some(temp) = res.temp_name { scope.free_temp(temp, Some(span))?; } @@ -1873,7 +1981,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } if let Some(label) = &self.current_return_label { - self.write_output(format!("j {}", label), None)?; + self.write_instruction(Instruction::Jump(Operand::Label(label.clone())), None)?; } else { return Err(Error::Unknown( "Return statement used outside of function context.".into(), @@ -1903,15 +2011,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } match expr { System::Yield => { - self.write_output("yield", Some(span))?; + self.write_instruction(Instruction::Yield, Some(span))?; Ok(None) } System::Sleep(amt) => { - let (var, var_cleanup) = self.compile_operand(*amt, scope)?; - self.write_output(format!("sleep {var}"), Some(span))?; + let (op, var_cleanup) = self.compile_operand(*amt, scope)?; + self.write_instruction(Instruction::Sleep(op), Some(span))?; cleanup!(var_cleanup); - Ok(None) } System::Hash(hash_arg) => { @@ -1975,11 +2082,14 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )); }; - self.write_output( - format!("s {} {} {}", device_val, logic_type, variable), + self.write_instruction( + Instruction::Store( + Operand::Device(device_val), + Operand::LogicType(logic_type), + variable, + ), Some(span), )?; - cleanup!(var_cleanup); Ok(None) @@ -1999,11 +2109,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )); }; - self.write_output( - format!("sb {} {} {}", device_hash_val, logic_type, var), + self.write_instruction( + Instruction::StoreBatch(device_hash_val, Operand::LogicType(logic_type), var), Some(span), )?; - cleanup!(var_cleanup, device_hash_cleanup); Ok(None) @@ -2021,11 +2130,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope, )?; - self.write_output( - format!("sbn {} {} {} {}", device_hash, name_hash, logic_type, value), + self.write_instruction( + Instruction::StoreBatchNamed(device_hash, name_hash, logic_type, value), Some(span), )?; - cleanup!( value_cleanup, device_hash_cleanup, @@ -2073,12 +2181,11 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )); }; - self.write_output( - format!( - "l r{} {} {}", - VariableScope::RETURN_REGISTER, - device_val, - logic_type + self.write_instruction( + Instruction::Load( + Operand::Register(VariableScope::RETURN_REGISTER), + Operand::Device(device_val), + Operand::LogicType(logic_type), ), Some(span), )?; @@ -2100,17 +2207,15 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope, )?; - self.write_output( - format!( - "lb r{} {} {} {}", - VariableScope::RETURN_REGISTER, + self.write_instruction( + Instruction::LoadBatch( + Operand::Register(VariableScope::RETURN_REGISTER), device_hash, logic_type, - batch_mode + batch_mode, ), Some(span), )?; - cleanup!(device_hash_cleanup, logic_type_cleanup, batch_mode_cleanup); Ok(Some(CompileLocation { @@ -2132,18 +2237,16 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope, )?; - self.write_output( - format!( - "lbn r{} {} {} {} {}", - VariableScope::RETURN_REGISTER, + self.write_instruction( + Instruction::LoadBatchNamed( + Operand::Register(VariableScope::RETURN_REGISTER), device_hash, name_hash, logic_type, - batch_mode + batch_mode, ), Some(span), )?; - cleanup!( device_hash_cleanup, name_hash_cleanup, @@ -2168,17 +2271,15 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { scope, )?; - self.write_output( - format!( - "ls r{} {} {} {}", - VariableScope::RETURN_REGISTER, + self.write_instruction( + Instruction::LoadSlot( + Operand::Register(VariableScope::RETURN_REGISTER), dev_hash, slot_index, - logic_type + logic_type, ), Some(span), )?; - cleanup!(hash_cleanup, slot_cleanup, logic_cleanup); Ok(Some(CompileLocation { @@ -2199,11 +2300,10 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )?; let (var, var_cleanup) = self.compile_operand(*var, scope)?; - self.write_output( - format!("ss {} {} {} {}", dev_name, slot_index, logic_type, var), + self.write_instruction( + Instruction::StoreSlot(dev_name, slot_index, logic_type, var), Some(span), )?; - cleanup!(name_cleanup, index_cleanup, type_cleanup, var_cleanup); Ok(None) @@ -2229,12 +2329,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { match expr { Math::Acos(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("acos r{} {}", VariableScope::RETURN_REGISTER, var), + self.write_instruction( + Instruction::Acos(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(cleanup); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2242,12 +2342,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Asin(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("asin r{} {}", VariableScope::RETURN_REGISTER, var), + + self.write_instruction( + Instruction::Asin(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(cleanup); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2255,12 +2356,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Atan(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("atan r{} {}", VariableScope::RETURN_REGISTER, var), + + self.write_instruction( + Instruction::Atan(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(cleanup); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2270,16 +2372,16 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { let (var1, var1_cleanup) = self.compile_operand(*expr1, scope)?; let (var2, var2_cleanup) = self.compile_operand(*expr2, scope)?; - self.write_output( - format!( - "atan2 r{} {} {}", - VariableScope::RETURN_REGISTER, + self.write_instruction( + Instruction::Atan2( + Operand::Register(VariableScope::RETURN_REGISTER), var1, - var2 + var2, ), Some(span), )?; cleanup!(var1_cleanup, var2_cleanup); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2287,12 +2389,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Abs(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("abs r{} {}", VariableScope::RETURN_REGISTER, var), + + self.write_instruction( + Instruction::Abs(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(cleanup); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2300,12 +2403,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Ceil(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("ceil r{} {}", VariableScope::RETURN_REGISTER, var), + + self.write_instruction( + Instruction::Ceil(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(cleanup); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2313,12 +2417,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Cos(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("cos r{} {}", VariableScope::RETURN_REGISTER, var), + self.write_instruction( + Instruction::Cos(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(cleanup); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2326,12 +2430,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Floor(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("floor r{} {}", VariableScope::RETURN_REGISTER, var), + + self.write_instruction( + Instruction::Floor(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(cleanup); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2339,12 +2444,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Log(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("log r{} {}", VariableScope::RETURN_REGISTER, var), + + self.write_instruction( + Instruction::Log(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(cleanup); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2353,12 +2459,17 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Math::Max(expr1, expr2) => { let (var1, clean1) = self.compile_operand(*expr1, scope)?; let (var2, clean2) = self.compile_operand(*expr2, scope)?; - self.write_output( - format!("max r{} {} {}", VariableScope::RETURN_REGISTER, var1, var2), + + self.write_instruction( + Instruction::Max( + Operand::Register(VariableScope::RETURN_REGISTER), + var1, + var2, + ), Some(span), )?; - cleanup!(clean1, clean2); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2367,20 +2478,25 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Math::Min(expr1, expr2) => { let (var1, clean1) = self.compile_operand(*expr1, scope)?; let (var2, clean2) = self.compile_operand(*expr2, scope)?; - self.write_output( - format!("min r{} {} {}", VariableScope::RETURN_REGISTER, var1, var2), + + self.write_instruction( + Instruction::Min( + Operand::Register(VariableScope::RETURN_REGISTER), + var1, + var2, + ), Some(span), )?; - cleanup!(clean1, clean2); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Rand => { - self.write_output( - format!("rand r{}", VariableScope::RETURN_REGISTER), + self.write_instruction( + Instruction::Rand(Operand::Register(VariableScope::RETURN_REGISTER)), Some(span), )?; @@ -2391,12 +2507,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Sin(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("sin r{} {}", VariableScope::RETURN_REGISTER, var), + + self.write_instruction( + Instruction::Sin(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(clean); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2404,12 +2521,13 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Sqrt(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("sqrt r{} {}", VariableScope::RETURN_REGISTER, var), + + self.write_instruction( + Instruction::Sqrt(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(clean); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2417,12 +2535,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Tan(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("tan r{} {}", VariableScope::RETURN_REGISTER, var), + self.write_instruction( + Instruction::Tan(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(clean); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2430,12 +2548,12 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Math::Trunc(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; - self.write_output( - format!("trunc r{} {}", VariableScope::RETURN_REGISTER, var), + self.write_instruction( + Instruction::Trunc(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; - cleanup!(clean); + Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, @@ -2472,7 +2590,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { ); // Declare the function as a line identifier - self.write_output(format!("{}:", name.node), Some(name.span))?; + self.write_instruction(Instruction::LabelDef(name.node.clone()), Some(span))?; self.function_locations .insert(name.node.clone(), self.current_line); @@ -2498,8 +2616,8 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { match loc { VariableLocation::Persistant(loc) => { - self.write_output( - format!("pop r{loc} {}", debug!(self, "#{}", var_name.node)), + self.write_instruction( + Instruction::Pop(Operand::Register(loc)), Some(var_name.span), )?; } @@ -2532,7 +2650,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { )?; } - self.write_output("push ra", Some(span))?; + self.write_instruction(Instruction::Push(Operand::ReturnAddress), Some(span))?; let return_label = self.next_label_name(); @@ -2585,28 +2703,38 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.current_return_label = prev_return_label; - self.write_output(format!("{}:", return_label), Some(span))?; + self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?; - self.write_output( - format!( - "sub r{0} sp {ra_stack_offset}", - VariableScope::TEMP_STACK_REGISTER + self.write_instruction( + Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::StackPointer, + Operand::Number(ra_stack_offset.into()), ), Some(span), )?; - self.write_output( - format!("get ra db r{0}", VariableScope::TEMP_STACK_REGISTER), + + self.write_instruction( + Instruction::Get( + Operand::ReturnAddress, + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + ), Some(span), )?; if block_scope.stack_offset() > 0 { - self.write_output( - format!("sub sp sp {}", block_scope.stack_offset()), + self.write_instruction( + Instruction::Sub( + Operand::StackPointer, + Operand::StackPointer, + Operand::Number(block_scope.stack_offset().into()), + ), Some(span), )?; } - self.write_output("j ra", Some(span))?; + self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; Ok(()) } } diff --git a/rust_compiler/libs/compiler/src/variable_manager.rs b/rust_compiler/libs/compiler/src/variable_manager.rs index 96b2271..95e482c 100644 --- a/rust_compiler/libs/compiler/src/variable_manager.rs +++ b/rust_compiler/libs/compiler/src/variable_manager.rs @@ -3,8 +3,9 @@ // r1 - r7 : Temporary Variables // r8 - r14 : Persistant Variables +use helpers::Span; use lsp_types::{Diagnostic, DiagnosticSeverity}; -use parser::tree_node::{Literal, Span}; +use parser::tree_node::Literal; use std::{ borrow::Cow, collections::{HashMap, VecDeque}, diff --git a/rust_compiler/libs/helpers/Cargo.toml b/rust_compiler/libs/helpers/Cargo.toml index 97e7548..6b5bb3e 100644 --- a/rust_compiler/libs/helpers/Cargo.toml +++ b/rust_compiler/libs/helpers/Cargo.toml @@ -5,3 +5,4 @@ edition = "2024" [dependencies] crc32fast = { workspace = true } +lsp-types = { workspace = true } diff --git a/rust_compiler/libs/helpers/src/lib.rs b/rust_compiler/libs/helpers/src/lib.rs index 680897e..aea7024 100644 --- a/rust_compiler/libs/helpers/src/lib.rs +++ b/rust_compiler/libs/helpers/src/lib.rs @@ -2,6 +2,44 @@ mod helper_funcs; mod macros; mod syscall; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Span { + pub start_line: usize, + pub end_line: usize, + pub start_col: usize, + pub end_col: usize, +} + +impl From for lsp_types::Range { + fn from(value: Span) -> Self { + Self { + start: lsp_types::Position { + line: value.start_line as u32, + character: value.start_col as u32, + }, + end: lsp_types::Position { + line: value.end_line as u32, + character: value.end_col as u32, + }, + } + } +} + +impl From<&Span> for lsp_types::Range { + fn from(value: &Span) -> Self { + Self { + start: lsp_types::Position { + line: value.start_line as u32, + character: value.start_col as u32, + }, + end: lsp_types::Position { + line: value.end_line as u32, + character: value.end_col as u32, + }, + } + } +} + /// This trait will allow the LSP to emit documentation for various tokens and expressions. /// You can easily create documentation for large enums with the `documented!` macro. pub trait Documentation { diff --git a/rust_compiler/libs/il/Cargo.toml b/rust_compiler/libs/il/Cargo.toml new file mode 100644 index 0000000..8eb8461 --- /dev/null +++ b/rust_compiler/libs/il/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "il" +version = "0.1.0" +edition = "2024" + +[dependencies] +helpers = { path = "../helpers" } +rust_decimal = { workspace = true } diff --git a/rust_compiler/libs/il/src/lib.rs b/rust_compiler/libs/il/src/lib.rs new file mode 100644 index 0000000..e8a33f9 --- /dev/null +++ b/rust_compiler/libs/il/src/lib.rs @@ -0,0 +1,286 @@ +use helpers::Span; +use rust_decimal::Decimal; +use std::borrow::Cow; +use std::fmt; + +pub struct InstructionNode<'a> { + pub instruction: Instruction<'a>, + pub span: Option, +} + +impl<'a> InstructionNode<'a> { + pub fn new(instr: Instruction<'a>, span: Option) -> Self { + Self { + span, + instruction: instr, + } + } +} + +/// Represents the different types of operands available in IC10. +#[derive(Debug, Clone, PartialEq)] +pub enum Operand<'a> { + /// A hardware register (r0-r15) + Register(u8), + /// A device alias or direct connection (d0-d5, db) + Device(Cow<'a, str>), + /// A numeric literal (integer or float) + Number(Decimal), + /// A label used for jumping + Label(Cow<'a, str>), + /// A logic type string (e.g., "Temperature", "Open") + LogicType(Cow<'a, str>), + /// Special register: Stack Pointer + StackPointer, + /// Special register: Return Address + ReturnAddress, +} + +impl<'a> fmt::Display for Operand<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Operand::Register(r) => write!(f, "r{}", r), + Operand::Device(d) => write!(f, "{}", d), + Operand::Number(n) => write!(f, "{}", n), + Operand::Label(l) => write!(f, "{}", l), + Operand::LogicType(t) => write!(f, "{}", t), + Operand::StackPointer => write!(f, "sp"), + Operand::ReturnAddress => write!(f, "ra"), + } + } +} + +/// Represents a single IC10 MIPS instruction. +#[derive(Debug, Clone, PartialEq)] +pub enum Instruction<'a> { + /// `move dst val` - Copy value to register + Move(Operand<'a>, Operand<'a>), + + /// `add dst a b` - Addition + Add(Operand<'a>, Operand<'a>, Operand<'a>), + /// `sub dst a b` - Subtraction + Sub(Operand<'a>, Operand<'a>, Operand<'a>), + /// `mul dst a b` - Multiplication + Mul(Operand<'a>, Operand<'a>, Operand<'a>), + /// `div dst a b` - Division + Div(Operand<'a>, Operand<'a>, Operand<'a>), + /// `mod dst a b` - Modulo + Mod(Operand<'a>, Operand<'a>, Operand<'a>), + /// `pow dst a b` - Power + Pow(Operand<'a>, Operand<'a>, Operand<'a>), + /// `acos dst a` + Acos(Operand<'a>, Operand<'a>), + /// `asin dst a` + Asin(Operand<'a>, Operand<'a>), + /// `atan dst a` + Atan(Operand<'a>, Operand<'a>), + /// `atan2 dst a b` + Atan2(Operand<'a>, Operand<'a>, Operand<'a>), + /// `abs dst a` + Abs(Operand<'a>, Operand<'a>), + /// `ceil dst a` + Ceil(Operand<'a>, Operand<'a>), + /// `cos dst a` + Cos(Operand<'a>, Operand<'a>), + /// `floor dst a` + Floor(Operand<'a>, Operand<'a>), + /// `log dst a` + Log(Operand<'a>, Operand<'a>), + /// `max dst a b` + Max(Operand<'a>, Operand<'a>, Operand<'a>), + /// `min dst a b` + Min(Operand<'a>, Operand<'a>, Operand<'a>), + /// `rand dst` + Rand(Operand<'a>), + /// `sin dst a` + Sin(Operand<'a>, Operand<'a>), + /// `sqrt dst a` + Sqrt(Operand<'a>, Operand<'a>), + /// `tan dst a` + Tan(Operand<'a>, Operand<'a>), + /// `trunc dst a` + Trunc(Operand<'a>, Operand<'a>), + + /// `l register device type` - Load from device + Load(Operand<'a>, Operand<'a>, Operand<'a>), + /// `s device type value` - Set on device + Store(Operand<'a>, Operand<'a>, Operand<'a>), + + /// `ls register device slot type` - Load Slot + LoadSlot(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>), + /// `ss device slot type value` - Set Slot + StoreSlot(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>), + + /// `lb register deviceHash type batchMode` - Load Batch + LoadBatch(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>), + /// `sb deviceHash type value` - Set Batch + StoreBatch(Operand<'a>, Operand<'a>, Operand<'a>), + + /// `lbn register deviceHash nameHash type batchMode` - Load Batch Named + LoadBatchNamed( + Operand<'a>, + Operand<'a>, + Operand<'a>, + Operand<'a>, + Operand<'a>, + ), + /// `sbn deviceHash nameHash type value` - Set Batch Named + StoreBatchNamed(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>), + + /// `j label` - Unconditional Jump + Jump(Operand<'a>), + /// `jal label` - Jump and Link (Function Call) + JumpAndLink(Operand<'a>), + /// `jr offset` - Jump Relative + JumpRelative(Operand<'a>), + + /// `beq a b label` - Branch if Equal + BranchEq(Operand<'a>, Operand<'a>, Operand<'a>), + /// `bne a b label` - Branch if Not Equal + BranchNe(Operand<'a>, Operand<'a>, Operand<'a>), + /// `bgt a b label` - Branch if Greater Than + BranchGt(Operand<'a>, Operand<'a>, Operand<'a>), + /// `blt a b label` - Branch if Less Than + BranchLt(Operand<'a>, Operand<'a>, Operand<'a>), + /// `bge a b label` - Branch if Greater or Equal + BranchGe(Operand<'a>, Operand<'a>, Operand<'a>), + /// `ble a b label` - Branch if Less or Equal + BranchLe(Operand<'a>, Operand<'a>, Operand<'a>), + /// `beqz a label` - Branch if Equal Zero + BranchEqZero(Operand<'a>, Operand<'a>), + /// `bnez a label` - Branch if Not Equal Zero + BranchNeZero(Operand<'a>, Operand<'a>), + + /// `seq dst a b` - Set if Equal + SetEq(Operand<'a>, Operand<'a>, Operand<'a>), + /// `sne dst a b` - Set if Not Equal + SetNe(Operand<'a>, Operand<'a>, Operand<'a>), + /// `sgt dst a b` - Set if Greater Than + SetGt(Operand<'a>, Operand<'a>, Operand<'a>), + /// `slt dst a b` - Set if Less Than + SetLt(Operand<'a>, Operand<'a>, Operand<'a>), + /// `sge dst a b` - Set if Greater or Equal + SetGe(Operand<'a>, Operand<'a>, Operand<'a>), + /// `sle dst a b` - Set if Less or Equal + SetLe(Operand<'a>, Operand<'a>, Operand<'a>), + + /// `and dst a b` - Logical AND + And(Operand<'a>, Operand<'a>, Operand<'a>), + /// `or dst a b` - Logical OR + Or(Operand<'a>, Operand<'a>, Operand<'a>), + /// `xor dst a b` - Logical XOR + Xor(Operand<'a>, Operand<'a>, Operand<'a>), + + /// `push val` - Push to Stack + Push(Operand<'a>), + /// `pop dst` - Pop from Stack + Pop(Operand<'a>), + /// `peek dst` - Peek from Stack (Usually sp - 1) + Peek(Operand<'a>), + /// `get dst dev num` + Get(Operand<'a>, Operand<'a>, Operand<'a>), + /// put dev addr val + Put(Operand<'a>, Operand<'a>, Operand<'a>), + + /// `select dst cond a b` - Ternary Select + Select(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>), + + /// `yield` - Pause execution + Yield, + /// `sleep val` - Sleep for seconds + Sleep(Operand<'a>), + + /// `alias name target` - Define Alias (Usually handled by compiler, but good for IR) + Alias(Cow<'a, str>, Operand<'a>), + /// `define name val` - Define Constant (Usually handled by compiler) + Define(Cow<'a, str>, f64), + + /// A label definition `Label:` + LabelDef(Cow<'a, str>), + + /// A comment `# text` + Comment(Cow<'a, str>), +} + +impl<'a> fmt::Display for Instruction<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Instruction::Move(dst, val) => write!(f, "move {} {}", dst, val), + Instruction::Add(dst, a, b) => write!(f, "add {} {} {}", dst, a, b), + Instruction::Sub(dst, a, b) => write!(f, "sub {} {} {}", dst, a, b), + Instruction::Mul(dst, a, b) => write!(f, "mul {} {} {}", dst, a, b), + Instruction::Div(dst, a, b) => write!(f, "div {} {} {}", dst, a, b), + Instruction::Mod(dst, a, b) => write!(f, "mod {} {} {}", dst, a, b), + Instruction::Pow(dst, a, b) => write!(f, "pow {} {} {}", dst, a, b), + Instruction::Acos(dst, a) => write!(f, "acos {} {}", dst, a), + Instruction::Asin(dst, a) => write!(f, "asin {} {}", dst, a), + Instruction::Atan(dst, a) => write!(f, "atan {} {}", dst, a), + Instruction::Atan2(dst, a, b) => write!(f, "atan2 {} {} {}", dst, a, b), + Instruction::Abs(dst, a) => write!(f, "abs {} {}", dst, a), + Instruction::Ceil(dst, a) => write!(f, "ceil {} {}", dst, a), + Instruction::Cos(dst, a) => write!(f, "cos {} {}", dst, a), + Instruction::Floor(dst, a) => write!(f, "floor {} {}", dst, a), + Instruction::Log(dst, a) => write!(f, "log {} {}", dst, a), + Instruction::Max(dst, a, b) => write!(f, "max {} {} {}", dst, a, b), + Instruction::Min(dst, a, b) => write!(f, "min {} {} {}", dst, a, b), + Instruction::Rand(dst) => write!(f, "rand {}", dst), + Instruction::Sin(dst, a) => write!(f, "sin {} {}", dst, a), + Instruction::Sqrt(dst, a) => write!(f, "sqrt {} {}", dst, a), + Instruction::Tan(dst, a) => write!(f, "tan {} {}", dst, a), + Instruction::Trunc(dst, a) => write!(f, "trunc {} {}", dst, a), + + Instruction::Load(reg, dev, typ) => write!(f, "l {} {} {}", reg, dev, typ), + Instruction::Store(dev, typ, val) => write!(f, "s {} {} {}", dev, typ, val), + Instruction::LoadSlot(reg, dev, slot, typ) => { + write!(f, "ls {} {} {} {}", reg, dev, slot, typ) + } + Instruction::StoreSlot(dev, slot, typ, val) => { + write!(f, "ss {} {} {} {}", dev, slot, typ, val) + } + Instruction::LoadBatch(reg, hash, typ, mode) => { + write!(f, "lb {} {} {} {}", reg, hash, typ, mode) + } + Instruction::StoreBatch(hash, typ, val) => write!(f, "sb {} {} {}", hash, typ, val), + Instruction::LoadBatchNamed(reg, d_hash, n_hash, typ, mode) => { + write!(f, "lbn {} {} {} {} {}", reg, d_hash, n_hash, typ, mode) + } + Instruction::StoreBatchNamed(d_hash, n_hash, typ, val) => { + write!(f, "sbn {} {} {} {}", d_hash, n_hash, typ, val) + } + Instruction::Jump(lbl) => write!(f, "j {}", lbl), + Instruction::JumpAndLink(lbl) => write!(f, "jal {}", lbl), + Instruction::JumpRelative(off) => write!(f, "jr {}", off), + Instruction::BranchEq(a, b, lbl) => write!(f, "beq {} {} {}", a, b, lbl), + Instruction::BranchNe(a, b, lbl) => write!(f, "bne {} {} {}", a, b, lbl), + Instruction::BranchGt(a, b, lbl) => write!(f, "bgt {} {} {}", a, b, lbl), + Instruction::BranchLt(a, b, lbl) => write!(f, "blt {} {} {}", a, b, lbl), + Instruction::BranchGe(a, b, lbl) => write!(f, "bge {} {} {}", a, b, lbl), + Instruction::BranchLe(a, b, lbl) => write!(f, "ble {} {} {}", a, b, lbl), + Instruction::BranchEqZero(a, lbl) => write!(f, "beqz {} {}", a, lbl), + Instruction::BranchNeZero(a, lbl) => write!(f, "bnez {} {}", a, lbl), + Instruction::SetEq(dst, a, b) => write!(f, "seq {} {} {}", dst, a, b), + Instruction::SetNe(dst, a, b) => write!(f, "sne {} {} {}", dst, a, b), + Instruction::SetGt(dst, a, b) => write!(f, "sgt {} {} {}", dst, a, b), + Instruction::SetLt(dst, a, b) => write!(f, "slt {} {} {}", dst, a, b), + Instruction::SetGe(dst, a, b) => write!(f, "sge {} {} {}", dst, a, b), + Instruction::SetLe(dst, a, b) => write!(f, "sle {} {} {}", 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::Xor(dst, a, b) => write!(f, "xor {} {} {}", dst, a, b), + Instruction::Push(val) => write!(f, "push {}", val), + Instruction::Pop(dst) => write!(f, "pop {}", dst), + Instruction::Peek(dst) => write!(f, "peek {}", dst), + Instruction::Get(dst, dev, val) => write!(f, "get {} {} {}", dst, dev, val), + Instruction::Put(dev, addr, val) => write!(f, "put {} {} {}", dev, addr, val), + Instruction::Select(dst, cond, a, b) => { + write!(f, "select {} {} {} {}", dst, cond, a, b) + } + Instruction::Yield => write!(f, "yield"), + Instruction::Sleep(val) => write!(f, "sleep {}", val), + Instruction::Alias(name, target) => write!(f, "alias {} {}", name, target), + Instruction::Define(name, val) => write!(f, "define {} {}", name, val), + Instruction::LabelDef(lbl) => write!(f, "{}:", lbl), + Instruction::Comment(c) => write!(f, "# {}", c), + } + } +} diff --git a/rust_compiler/libs/optimizer/Cargo.toml b/rust_compiler/libs/optimizer/Cargo.toml new file mode 100644 index 0000000..29f308d --- /dev/null +++ b/rust_compiler/libs/optimizer/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "optimizer" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/rust_compiler/libs/optimizer/src/lib.rs b/rust_compiler/libs/optimizer/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/rust_compiler/libs/optimizer/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/rust_compiler/libs/parser/src/lib.rs b/rust_compiler/libs/parser/src/lib.rs index 35afdb2..66d0d99 100644 --- a/rust_compiler/libs/parser/src/lib.rs +++ b/rust_compiler/libs/parser/src/lib.rs @@ -4,6 +4,7 @@ mod test; pub mod tree_node; use crate::sys_call::{Math, System}; +use helpers::Span; use std::{borrow::Cow, io::SeekFrom}; use sys_call::SysCall; use thiserror::Error; diff --git a/rust_compiler/libs/parser/src/tree_node.rs b/rust_compiler/libs/parser/src/tree_node.rs index 42d624c..2f21aef 100644 --- a/rust_compiler/libs/parser/src/tree_node.rs +++ b/rust_compiler/libs/parser/src/tree_node.rs @@ -1,5 +1,6 @@ use super::sys_call::SysCall; use crate::sys_call; +use helpers::Span; use safer_ffi::prelude::*; use std::{borrow::Cow, ops::Deref}; use tokenizer::token::Number; @@ -301,44 +302,6 @@ impl<'a> std::fmt::Display for WhileExpression<'a> { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Span { - pub start_line: usize, - pub end_line: usize, - pub start_col: usize, - pub end_col: usize, -} - -impl From for lsp_types::Range { - fn from(value: Span) -> Self { - Self { - start: lsp_types::Position { - line: value.start_line as u32, - character: value.start_col as u32, - }, - end: lsp_types::Position { - line: value.end_line as u32, - character: value.end_col as u32, - }, - } - } -} - -impl From<&Span> for lsp_types::Range { - fn from(value: &Span) -> Self { - Self { - start: lsp_types::Position { - line: value.start_line as u32, - character: value.start_col as u32, - }, - end: lsp_types::Position { - line: value.end_line as u32, - character: value.end_col as u32, - }, - } - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct Spanned { pub span: Span, diff --git a/rust_compiler/libs/tokenizer/src/token.rs b/rust_compiler/libs/tokenizer/src/token.rs index 2b14066..ca70146 100644 --- a/rust_compiler/libs/tokenizer/src/token.rs +++ b/rust_compiler/libs/tokenizer/src/token.rs @@ -403,6 +403,12 @@ pub enum Number { Decimal(Decimal), } +impl From for Number { + fn from(value: bool) -> Self { + Self::Integer(if value { 1 } else { 0 }) + } +} + impl From for Decimal { fn from(value: Number) -> Self { match value { diff --git a/rust_compiler/src/ffi/mod.rs b/rust_compiler/src/ffi/mod.rs index a213572..e85f0ec 100644 --- a/rust_compiler/src/ffi/mod.rs +++ b/rust_compiler/src/ffi/mod.rs @@ -1,6 +1,6 @@ use compiler::{CompilationResult, Compiler}; -use helpers::Documentation; -use parser::{sys_call::SysCall, tree_node::Span, Parser}; +use helpers::{Documentation, Span}; +use parser::{sys_call::SysCall, Parser}; use safer_ffi::prelude::*; use std::io::BufWriter; use tokenizer::{ From 0be2e644e46231a2657aa693e5ea86e82eee6f34 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 12 Dec 2025 17:23:04 -0700 Subject: [PATCH 2/5] WIP optimization code --- rust_compiler/Cargo.lock | 7 + rust_compiler/Cargo.toml | 10 +- rust_compiler/libs/compiler/src/v1.rs | 5 + rust_compiler/libs/optimizer/Cargo.toml | 4 + .../libs/optimizer/src/leaf_function.rs | 44 + rust_compiler/libs/optimizer/src/lib.rs | 818 +++++++++++++++++- rust_compiler/src/main.rs | 16 +- 7 files changed, 889 insertions(+), 15 deletions(-) create mode 100644 rust_compiler/libs/optimizer/src/leaf_function.rs diff --git a/rust_compiler/Cargo.lock b/rust_compiler/Cargo.lock index 30b4270..20fd557 100644 --- a/rust_compiler/Cargo.lock +++ b/rust_compiler/Cargo.lock @@ -576,6 +576,12 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "optimizer" version = "0.1.0" +dependencies = [ + "anyhow", + "helpers", + "il", + "rust_decimal", +] [[package]] name = "parser" @@ -931,6 +937,7 @@ dependencies = [ "compiler", "helpers", "lsp-types", + "optimizer", "parser", "rust_decimal", "safer-ffi", diff --git a/rust_compiler/Cargo.toml b/rust_compiler/Cargo.toml index fdf339b..2bf6da4 100644 --- a/rust_compiler/Cargo.toml +++ b/rust_compiler/Cargo.toml @@ -9,9 +9,10 @@ members = ["libs/*"] [workspace.dependencies] thiserror = "2" rust_decimal = "1" -safer-ffi = { version = "0.1" } # Safely share structs in memory between C# and Rust -lsp-types = { version = "0.97" } # Allows for LSP style reporting to the frontend -crc32fast = "1.5" # This is for `HASH(..)` calls to be optimized away +safer-ffi = { version = "0.1" } # Safely share structs in memory between C# and Rust +lsp-types = { version = "0.97" } # Allows for LSP style reporting to the frontend +crc32fast = "1.5" # This is for `HASH(..)` calls to be optimized away +anyhow = { version = "^1.0", features = ["backtrace"] } [features] headers = ["safer-ffi/headers"] @@ -42,7 +43,8 @@ tokenizer = { path = "libs/tokenizer" } parser = { path = "libs/parser" } compiler = { path = "libs/compiler" } helpers = { path = "libs/helpers" } +optimizer = { path = "libs/optimizer" } safer-ffi = { workspace = true } -anyhow = { version = "^1.0", features = ["backtrace"] } +anyhow = { workspace = true } [dev-dependencies] diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 5ed8986..c54fac0 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -140,6 +140,7 @@ struct CompileLocation<'a> { pub struct CompilationResult<'a> { pub errors: Vec>, pub source_map: HashMap>, + pub instructions: Vec>, } pub struct Compiler<'a, 'w, W: std::io::Write> { @@ -206,6 +207,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { return CompilationResult { source_map: self.source_map, errors: self.errors, + instructions: self.instructions, }; } Err(e) => { @@ -214,6 +216,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { return CompilationResult { errors: self.errors, source_map: self.source_map, + instructions: self.instructions, }; } }; @@ -239,6 +242,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { return CompilationResult { errors: self.errors, source_map: self.source_map, + instructions: self.instructions, }; } @@ -252,6 +256,7 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { CompilationResult { errors: self.errors, source_map: self.source_map, + instructions: self.instructions, } } diff --git a/rust_compiler/libs/optimizer/Cargo.toml b/rust_compiler/libs/optimizer/Cargo.toml index 29f308d..8194092 100644 --- a/rust_compiler/libs/optimizer/Cargo.toml +++ b/rust_compiler/libs/optimizer/Cargo.toml @@ -4,3 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +il = { path = "../il" } +helpers = { path = "../helpers" } +rust_decimal = { workspace = true } +anyhow = { workspace = true } diff --git a/rust_compiler/libs/optimizer/src/leaf_function.rs b/rust_compiler/libs/optimizer/src/leaf_function.rs new file mode 100644 index 0000000..f8783f1 --- /dev/null +++ b/rust_compiler/libs/optimizer/src/leaf_function.rs @@ -0,0 +1,44 @@ +use il::{Instruction, InstructionNode}; +use std::collections::HashSet; + +/// Scans the instruction set to identify "leaf functions". +/// A leaf function is defined as a function (delimited by LabelDefs) that does not +/// contain any `jal` (JumpAndLink) instructions. +/// +/// Returns a Set containing the names of all identified leaf functions. +pub fn find_leaf_functions(instructions: &[InstructionNode]) -> HashSet { + let mut leaf_functions = HashSet::new(); + let mut current_label: Option = None; + let mut is_current_leaf = true; + + for node in instructions { + match &node.instruction { + Instruction::LabelDef(label) => { + // If we were tracking a function, and it remained a leaf until now, save it. + if let Some(name) = current_label.take() + && is_current_leaf + { + leaf_functions.insert(name); + } + + // Start tracking the new function + current_label = Some(label.to_string()); + is_current_leaf = true; + } + Instruction::JumpAndLink(_) => { + // If we see a JAL, this function is NOT a leaf. + is_current_leaf = false; + } + _ => {} + } + } + + // Handle the final function in the file + if let Some(name) = current_label + && is_current_leaf + { + leaf_functions.insert(name); + } + + leaf_functions +} diff --git a/rust_compiler/libs/optimizer/src/lib.rs b/rust_compiler/libs/optimizer/src/lib.rs index b93cf3f..88a016d 100644 --- a/rust_compiler/libs/optimizer/src/lib.rs +++ b/rust_compiler/libs/optimizer/src/lib.rs @@ -1,14 +1,814 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +use il::{Instruction, InstructionNode, Operand}; +use rust_decimal::Decimal; +use std::collections::{HashMap, HashSet}; + +mod leaf_function; +use leaf_function::find_leaf_functions; + +/// Entry point for the optimizer. +pub fn optimize<'a>(mut instructions: Vec>) -> Vec> { + let mut changed = true; + let mut pass_count = 0; + const MAX_PASSES: usize = 10; + + // Iterative passes for code simplification + while changed && pass_count < MAX_PASSES { + changed = false; + pass_count += 1; + + // Pass 1: Constant Propagation + let (new_inst, c1) = constant_propagation(instructions); + instructions = new_inst; + changed |= c1; + + // Pass 2: Register Forwarding (Intermediate Move Elimination) + let (new_inst, c2) = register_forwarding(instructions); + instructions = new_inst; + changed |= c2; + + // Pass 3: Function Call Optimization (Remove unused push/pop around calls) + let (new_inst, c3) = optimize_function_calls(instructions); + instructions = new_inst; + changed |= c3; + + // Pass 4: Leaf Function Optimization (Remove RA save/restore for leaf functions) + // This is separate from pass 3 as it deals with the function *definition*, not the call site. + let (new_inst, c4) = optimize_leaf_functions(instructions); + instructions = new_inst; + changed |= c4; + + // Pass 5: Redundant Move Elimination + let (new_inst, c5) = remove_redundant_moves(instructions); + instructions = new_inst; + changed |= c5; + + // Pass 6: Dead Code Elimination + let (new_inst, c6) = remove_unreachable_code(instructions); + instructions = new_inst; + changed |= c6; + } + + // Final Pass: Resolve Labels to Line Numbers + resolve_labels(instructions) } -#[cfg(test)] -mod tests { - use super::*; +/// 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>( + mut input: Vec>, +) -> (Vec>, bool) { + let leaves = find_leaf_functions(&input); + if leaves.is_empty() { + return (input, false); + } - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + let mut changed = false; + let mut to_remove = HashSet::new(); + let mut current_function: Option = None; + + // Map of FunctionName -> The stack offset where RA was stored. + // We need this to adjust other stack accesses (arguments vs locals). + let mut func_ra_offsets = 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) => { + current_function = Some(label.to_string()); + } + Instruction::Push(Operand::ReturnAddress) => { + if let Some(func) = ¤t_function { + if leaves.contains(func) { + to_remove.insert(i); + changed = true; + } + } + } + Instruction::Get(Operand::ReturnAddress, _, Operand::Register(_)) => { + // This is the restore instruction: `get ra db r0` + if let Some(func) = ¤t_function { + if leaves.contains(func) { + to_remove.insert(i); + // Look back for the address calc: `sub r0 sp OFFSET` + if i > 0 { + if let Instruction::Sub(_, Operand::StackPointer, Operand::Number(n)) = + &input[i - 1].instruction + { + func_ra_offsets.insert(func.clone(), *n); + to_remove.insert(i - 1); + } + } + } + } + } + _ => {} + } + } + + if !changed { + return (input, false); + } + + // Second scan: Rebuild with adjustments + let mut output = Vec::with_capacity(input.len()); + let mut processing_function: Option = None; + + for (i, mut node) in input.into_iter().enumerate() { + if to_remove.contains(&i) { + continue; + } + + if let Instruction::LabelDef(l) = &node.instruction { + processing_function = Some(l.to_string()); + } + + // Apply Stack Adjustments if we are inside a leaf function that we optimized + if let Some(func) = &processing_function { + if let Some(ra_offset) = func_ra_offsets.get(func) { + // If this is the stack cleanup `sub sp sp N`, decrement N by 1 (since we removed push ra) + if let Instruction::Sub( + Operand::StackPointer, + Operand::StackPointer, + Operand::Number(n), + ) = &mut node.instruction + { + let new_n = *n - Decimal::from(1); + if new_n.is_zero() { + continue; // Remove instruction if 0 + } + *n = new_n; + } + + // Adjust stack variable accesses relative to the removed RA. + // Compiler layout: [Args] [RA] [Locals/Temps] + // Stack grows up (increment sp on push). + // Access is `sp - offset`. + // Deeper items (Args) have LARGER offsets than RA. + // Shallower items (Locals) have SMALLER offsets than RA. + // Since RA is gone, items deeper than RA (Args) effectively shift "down" (index - 1). + if let Instruction::Sub(_, Operand::StackPointer, Operand::Number(n)) = + &mut node.instruction + { + if *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> { + 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 { + if let Some(reg) = get_destination_reg(&node.instruction) { + if let Some(set) = clobbers.get_mut(label) { + set.insert(reg); + } + } + + // Note: If we call another function, we technically clobber whatever IT clobbers + // (unless we save/restore it, which counts as a write anyway). + // This simple pass relies on the fact that any register modification (including restore) is a 'write'. + } + } + 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>( + mut input: Vec>, +) -> (Vec>, bool) { + let clobbers = analyze_clobbers(&input); + let mut changed = false; + let mut to_remove = HashSet::new(); + let mut stack_adjustments = HashMap::new(); // Index of `sub sp sp N` -> amount to subtract + + 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 we don't have info on the function (e.g. extern or complex), skip + 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 + // Compiler emits: sub r0 sp Offset, get Reg db r0. + let mut restores = Vec::new(); // (index_of_get, register, index_of_sub) + let mut scan_fwd = i + 1; + while scan_fwd < input.len() { + // Skip over the 'sub r0 sp X' address calculation lines + 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() { + if 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. Check for Stack Cleanup `sub sp sp N` + let cleanup_idx = scan_fwd; + + let has_cleanup = if cleanup_idx < input.len() { + if let Instruction::Sub( + Operand::StackPointer, + Operand::StackPointer, + Operand::Number(_), + ) = &input[cleanup_idx].instruction + { + true + } else { + false + } + } else { + false + }; + + // "All or Nothing" strategy for the safe subset: + let all_pushes_safe = pushes.iter().all(|(_, r)| !func_clobbers.contains(r)); + + let push_set: HashSet = pushes.iter().map(|(_, r)| *r).collect(); + let restore_set: HashSet = restores.iter().map(|(_, r, _)| *r).collect(); + + if all_pushes_safe && has_cleanup && push_set == restore_set { + // We can remove ALL saves/restores for this call! + 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_set.len() 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) { + if 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>, +) -> (Vec>, 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) { + if 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>) -> Vec> { + let mut label_map: HashMap = 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> { + 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(a, b, op) + | Instruction::BranchNe(a, b, op) + | Instruction::BranchGt(a, b, op) + | Instruction::BranchLt(a, b, op) + | Instruction::BranchGe(a, b, op) + | Instruction::BranchLe(a, b, op) => { + if let Some(num) = get_line(op) { + *op = num; + } + } + Instruction::BranchEqZero(a, op) | Instruction::BranchNeZero(a, 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 { + 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::Pop(Operand::Register(r)) => Some(*r), + _ => None, } } + +fn set_destination_reg<'a>(instr: &Instruction<'a>, new_reg: u8) -> Option> { + // 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::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::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 (Same as before) --- + +fn constant_propagation<'a>(input: Vec>) -> (Vec>, bool) { + let mut output = Vec::with_capacity(input.len()); + let mut changed = false; + let mut registers: [Option; 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) => { + if let Some(val) = resolve_value(src, ®isters) { + Some(Instruction::Move(dst.clone(), Operand::Number(val))) + } else { + None + } + } + 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 { + if l.is_empty() { + changed = true; + continue; + } + } + + output.push(node); + } + (output, changed) +} + +fn resolve_value(op: &Operand, regs: &[Option; 16]) -> Option { + 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; 16], + op: F, +) -> Option> +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; 16], + check: F, +) -> Option> +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>) -> (Vec>, 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 { + if dst == src { + changed = true; + continue; + } + } + output.push(node); + } + (output, changed) +} + +fn remove_unreachable_code<'a>( + input: Vec>, +) -> (Vec>, 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; + } + match node.instruction { + Instruction::Jump(_) | Instruction::Jump(Operand::ReturnAddress) => dead = true, + _ => {} + } + output.push(node); + } + (output, changed) +} diff --git a/rust_compiler/src/main.rs b/rust_compiler/src/main.rs index cdd7bc0..b6ddb9d 100644 --- a/rust_compiler/src/main.rs +++ b/rust_compiler/src/main.rs @@ -88,9 +88,15 @@ fn run_logic<'a>() -> Result<(), Error<'a>> { None => BufWriter::new(Box::new(std::io::stdout())), }; - let compiler = Compiler::new(parser, &mut writer, None); + let mut tmp = BufWriter::new(vec![]); - let CompilationResult { errors, .. } = compiler.compile(); + let compiler = Compiler::new(parser, &mut tmp, None); + + let CompilationResult { + errors, + instructions, + .. + } = compiler.compile(); if !errors.is_empty() { let mut std_error = stderr(); @@ -103,6 +109,12 @@ fn run_logic<'a>() -> Result<(), Error<'a>> { } } + let instructions = optimizer::optimize(instructions); + + for instruction in instructions { + writer.write_all(format!("{}\n", instruction.instruction).as_bytes())?; + } + writer.flush()?; Ok(()) From 20f7cb9a4be15f4e6acf8697da5eeea89864437e Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 12 Dec 2025 17:36:57 -0700 Subject: [PATCH 3/5] wip --- rust_compiler/libs/optimizer/src/lib.rs | 1 + rust_compiler/src/ffi/mod.rs | 14 +++-- test.slang | 72 +++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 test.slang diff --git a/rust_compiler/libs/optimizer/src/lib.rs b/rust_compiler/libs/optimizer/src/lib.rs index 88a016d..aaac6a4 100644 --- a/rust_compiler/libs/optimizer/src/lib.rs +++ b/rust_compiler/libs/optimizer/src/lib.rs @@ -812,3 +812,4 @@ fn remove_unreachable_code<'a>( } (output, changed) } + diff --git a/rust_compiler/src/ffi/mod.rs b/rust_compiler/src/ffi/mod.rs index e85f0ec..f302a53 100644 --- a/rust_compiler/src/ffi/mod.rs +++ b/rust_compiler/src/ffi/mod.rs @@ -1,8 +1,9 @@ use compiler::{CompilationResult, Compiler}; use helpers::{Documentation, Span}; +use optimizer::optimize; use parser::{sys_call::SysCall, Parser}; use safer_ffi::prelude::*; -use std::io::BufWriter; +use std::io::{BufWriter, Write}; use tokenizer::{ token::{Token, TokenType}, Tokenizer, @@ -127,11 +128,11 @@ pub fn free_docs_vec(v: safer_ffi::Vec) { pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> FfiCompilationResult { let res = std::panic::catch_unwind(|| { let input = String::from_utf16_lossy(input.as_slice()); - let mut writer = BufWriter::new(Vec::new()); + let mut tmp = BufWriter::new(Vec::new()); let tokenizer = Tokenizer::from(input.as_str()); let parser = Parser::new(tokenizer); - let compiler = Compiler::new(parser, &mut writer, None); + let compiler = Compiler::new(parser, &mut tmp, None); let res = compiler.compile(); @@ -139,6 +140,13 @@ pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> FfiCompilat return (safer_ffi::String::EMPTY, res.source_map); } + let mut writer = BufWriter::new(Vec::new()); + + for instruction in optimize(res.instructions) { + _ = writer.write_all(instruction.instruction.to_string().as_bytes()); + _ = writer.write_all(b"\n"); + } + let Ok(compiled_vec) = writer.into_inner() else { return (safer_ffi::String::EMPTY, res.source_map); }; diff --git a/test.slang b/test.slang new file mode 100644 index 0000000..b02f6d1 --- /dev/null +++ b/test.slang @@ -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; +} From 9de59ee3b15eed97c265cdfd7a1f57276c8f9fc7 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Fri, 12 Dec 2025 21:48:25 -0700 Subject: [PATCH 4/5] Fix source maps --- rust_compiler/libs/compiler/src/test/mod.rs | 10 +- rust_compiler/libs/compiler/src/v1.rs | 38 +-- rust_compiler/libs/il/src/lib.rs | 64 ++++ rust_compiler/libs/optimizer/src/lib.rs | 335 ++++++++++++-------- rust_compiler/src/ffi/mod.rs | 33 +- rust_compiler/src/main.rs | 10 +- spilling.slang | 43 +++ 7 files changed, 333 insertions(+), 200 deletions(-) create mode 100644 spilling.slang diff --git a/rust_compiler/libs/compiler/src/test/mod.rs b/rust_compiler/libs/compiler/src/test/mod.rs index 011e834..68e35e9 100644 --- a/rust_compiler/libs/compiler/src/test/mod.rs +++ b/rust_compiler/libs/compiler/src/test/mod.rs @@ -12,18 +12,16 @@ macro_rules! compile { let mut writer = std::io::BufWriter::new(Vec::new()); let compiler = ::Compiler::new( parser::Parser::new(tokenizer::Tokenizer::from(String::from($source))), - &mut writer, None, ); - compiler.compile(); + let res = compiler.compile(); + res.instructions.write(&mut writer)?; output!(writer) }}; (result $source:expr) => {{ - let mut writer = std::io::BufWriter::new(Vec::new()); let compiler = crate::Compiler::new( parser::Parser::new(tokenizer::Tokenizer::from($source)), - &mut writer, Some(crate::CompilerConfig { debug: true }), ); compiler.compile().errors @@ -33,10 +31,10 @@ macro_rules! compile { let mut writer = std::io::BufWriter::new(Vec::new()); let compiler = crate::Compiler::new( parser::Parser::new(tokenizer::Tokenizer::from($source)), - &mut writer, Some(crate::CompilerConfig { debug: true }), ); - compiler.compile(); + let res = compiler.compile(); + res.instructions.write(&mut writer)?; output!(writer) }}; } diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index c54fac0..e5647fb 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -1,7 +1,7 @@ #![allow(clippy::result_large_err)] use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; use helpers::{Span, prelude::*}; -use il::{Instruction, InstructionNode, Operand}; +use il::{Instruction, InstructionNode, Instructions, Operand}; use parser::{ Parser as ASTParser, sys_call::{Math, SysCall, System}, @@ -13,11 +13,7 @@ use parser::{ }, }; use rust_decimal::Decimal; -use std::{ - borrow::Cow, - collections::HashMap, - io::{BufWriter, Write}, -}; +use std::{borrow::Cow, collections::HashMap}; use thiserror::Error; use tokenizer::token::Number; @@ -139,24 +135,22 @@ struct CompileLocation<'a> { pub struct CompilationResult<'a> { pub errors: Vec>, - pub source_map: HashMap>, - pub instructions: Vec>, + pub instructions: Instructions<'a>, } -pub struct Compiler<'a, 'w, W: std::io::Write> { +pub struct Compiler<'a> { pub parser: ASTParser<'a>, function_locations: HashMap, usize>, function_metadata: HashMap, Vec>>, devices: HashMap, Cow<'a, str>>, - output: &'w mut BufWriter, // This holds the IL code which will be used in the // optimizer - pub instructions: Vec>, + pub instructions: Instructions<'a>, current_line: usize, declared_main: bool, - config: CompilerConfig, + _config: CompilerConfig, temp_counter: usize, label_counter: usize, loop_stack: Vec<(Cow<'a, str>, Cow<'a, str>)>, // Stores (start_label, end_label) @@ -167,22 +161,17 @@ pub struct Compiler<'a, 'w, W: std::io::Write> { pub errors: Vec>, } -impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { - pub fn new( - parser: ASTParser<'a>, - writer: &'w mut BufWriter, - config: Option, - ) -> Self { +impl<'a> Compiler<'a> { + pub fn new(parser: ASTParser<'a>, config: Option) -> Self { Self { parser, function_locations: HashMap::new(), function_metadata: HashMap::new(), devices: HashMap::new(), - output: writer, - instructions: Vec::new(), + instructions: Instructions::default(), current_line: 1, declared_main: false, - config: config.unwrap_or_default(), + _config: config.unwrap_or_default(), temp_counter: 0, label_counter: 0, loop_stack: Vec::new(), @@ -205,7 +194,6 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { Ok(Some(expr)) => expr, Ok(None) => { return CompilationResult { - source_map: self.source_map, errors: self.errors, instructions: self.instructions, }; @@ -215,7 +203,6 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.errors.push(Error::Parse(e)); return CompilationResult { errors: self.errors, - source_map: self.source_map, instructions: self.instructions, }; } @@ -241,7 +228,6 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { self.errors.push(e); return CompilationResult { errors: self.errors, - source_map: self.source_map, instructions: self.instructions, }; } @@ -255,7 +241,6 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { CompilationResult { errors: self.errors, - source_map: self.source_map, instructions: self.instructions, } } @@ -266,8 +251,6 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { instr: Instruction<'a>, span: Option, ) -> Result<(), Error<'a>> { - self.output.write_all(format!("{}", instr).as_bytes())?; - self.output.write_all(b"\n")?; self.current_line += 1; self.instructions.push(InstructionNode::new(instr, span)); @@ -781,7 +764,6 @@ impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { } Expression::Ternary(ternary) => { let res = self.expression_ternary(ternary.node, scope)?; - println!("{res:?}"); let var_loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, diff --git a/rust_compiler/libs/il/src/lib.rs b/rust_compiler/libs/il/src/lib.rs index e8a33f9..fae551c 100644 --- a/rust_compiler/libs/il/src/lib.rs +++ b/rust_compiler/libs/il/src/lib.rs @@ -1,13 +1,77 @@ use helpers::Span; use rust_decimal::Decimal; use std::borrow::Cow; +use std::collections::HashMap; use std::fmt; +use std::io::{BufWriter, Write}; +use std::ops::{Deref, DerefMut}; + +#[derive(Default)] +pub struct Instructions<'a>(Vec>); + +impl<'a> Deref for Instructions<'a> { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for Instructions<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> Instructions<'a> { + pub fn new(instructions: Vec>) -> Self { + Self(instructions) + } + pub fn into_inner(self) -> Vec> { + self.0 + } + pub fn write(self, writer: &mut BufWriter) -> Result<(), std::io::Error> { + for node in self.0 { + writer.write_all(node.to_string().as_bytes())?; + writer.write_all(b"\n")?; + } + + writer.flush()?; + Ok(()) + } + pub fn source_map(&self) -> HashMap { + let mut map = HashMap::new(); + + for (line_num, node) in self.0.iter().enumerate() { + if let Some(span) = node.span { + map.insert(line_num, span); + } + } + + map + } +} + +impl<'a> std::fmt::Display for Instructions<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for node in &self.0 { + writeln!(f, "{node}")?; + } + Ok(()) + } +} pub struct InstructionNode<'a> { pub instruction: Instruction<'a>, pub span: Option, } +impl<'a> std::fmt::Display for InstructionNode<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.instruction) + } +} + impl<'a> InstructionNode<'a> { pub fn new(instr: Instruction<'a>, span: Option) -> Self { Self { diff --git a/rust_compiler/libs/optimizer/src/lib.rs b/rust_compiler/libs/optimizer/src/lib.rs index aaac6a4..2204488 100644 --- a/rust_compiler/libs/optimizer/src/lib.rs +++ b/rust_compiler/libs/optimizer/src/lib.rs @@ -1,4 +1,4 @@ -use il::{Instruction, InstructionNode, Operand}; +use il::{Instruction, InstructionNode, Instructions, Operand}; use rust_decimal::Decimal; use std::collections::{HashMap, HashSet}; @@ -6,7 +6,8 @@ mod leaf_function; use leaf_function::find_leaf_functions; /// Entry point for the optimizer. -pub fn optimize<'a>(mut instructions: Vec>) -> Vec> { +pub fn optimize<'a>(instructions: Instructions<'a>) -> Instructions<'a> { + let mut instructions = instructions.into_inner(); let mut changed = true; let mut pass_count = 0; const MAX_PASSES: usize = 10; @@ -49,13 +50,35 @@ pub fn optimize<'a>(mut instructions: Vec>) -> Vec 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>( - mut input: Vec>, + input: Vec>, ) -> (Vec>, bool) { let leaves = find_leaf_functions(&input); if leaves.is_empty() { @@ -64,40 +87,44 @@ fn optimize_leaf_functions<'a>( let mut changed = false; let mut to_remove = HashSet::new(); - let mut current_function: Option = None; - // Map of FunctionName -> The stack offset where RA was stored. - // We need this to adjust other stack accesses (arguments vs locals). + // 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 = 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) => { current_function = Some(label.to_string()); + function_start_indices.insert(label.to_string(), i); } Instruction::Push(Operand::ReturnAddress) => { - if let Some(func) = ¤t_function { - if leaves.contains(func) { - to_remove.insert(i); - changed = true; - } + 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 { - if leaves.contains(func) { - to_remove.insert(i); - // Look back for the address calc: `sub r0 sp OFFSET` - if i > 0 { - if let Instruction::Sub(_, Operand::StackPointer, Operand::Number(n)) = - &input[i - 1].instruction - { - func_ra_offsets.insert(func.clone(), *n); - to_remove.insert(i - 1); - } - } + 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); } } } @@ -105,54 +132,80 @@ fn optimize_leaf_functions<'a>( } } + // 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 + // Second scan: Rebuild with adjustments, but only for SAFE functions let mut output = Vec::with_capacity(input.len()); let mut processing_function: Option = None; for (i, mut node) in input.into_iter().enumerate() { - if to_remove.contains(&i) { - continue; + if to_remove.contains(&i) + && let Some(func) = &processing_function + && safe_functions.contains(func) + { + continue; // SKIP (Remove) } if let Instruction::LabelDef(l) = &node.instruction { processing_function = Some(l.to_string()); } - // Apply Stack Adjustments if we are inside a leaf function that we optimized - if let Some(func) = &processing_function { - if let Some(ra_offset) = func_ra_offsets.get(func) { - // If this is the stack cleanup `sub sp sp N`, decrement N by 1 (since we removed push ra) - if let Instruction::Sub( - Operand::StackPointer, - Operand::StackPointer, - Operand::Number(n), - ) = &mut node.instruction - { - let new_n = *n - Decimal::from(1); - if new_n.is_zero() { - continue; // Remove instruction if 0 - } - *n = new_n; + // 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; + } - // Adjust stack variable accesses relative to the removed RA. - // Compiler layout: [Args] [RA] [Locals/Temps] - // Stack grows up (increment sp on push). - // Access is `sp - offset`. - // Deeper items (Args) have LARGER offsets than RA. - // Shallower items (Locals) have SMALLER offsets than RA. - // Since RA is gone, items deeper than RA (Args) effectively shift "down" (index - 1). - if let Instruction::Sub(_, Operand::StackPointer, Operand::Number(n)) = - &mut node.instruction - { - if *n > *ra_offset { - *n -= Decimal::from(1); - } - } + // 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); } } @@ -173,16 +226,11 @@ fn analyze_clobbers(instructions: &[InstructionNode]) -> HashMap HashMap( - mut input: Vec>, + input: Vec>, ) -> (Vec>, bool) { let clobbers = analyze_clobbers(&input); let mut changed = false; let mut to_remove = HashSet::new(); - let mut stack_adjustments = HashMap::new(); // Index of `sub sp sp N` -> amount to subtract + 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 we don't have info on the function (e.g. extern or complex), skip + if let Some(func_clobbers) = clobbers.get(&target_key) { // 1. Identify Pushes immediately preceding the JAL let mut pushes = Vec::new(); // (index, register) @@ -221,54 +269,67 @@ fn optimize_function_calls<'a>( } // 2. Identify Restores immediately following the JAL - // Compiler emits: sub r0 sp Offset, get Reg db r0. let mut restores = Vec::new(); // (index_of_get, register, index_of_sub) let mut scan_fwd = i + 1; while scan_fwd < input.len() { - // Skip over the 'sub r0 sp X' address calculation lines + // 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() { - if let Instruction::Get(Operand::Register(r), _, Operand::Register(0)) = + 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; - } + { + restores.push((scan_fwd + 1, *r, scan_fwd)); + scan_fwd += 2; + continue; } } break; } - // 3. Check for Stack Cleanup `sub sp sp N` + // 3. Stack Cleanup let cleanup_idx = scan_fwd; - let has_cleanup = if cleanup_idx < input.len() { - if let Instruction::Sub( - Operand::StackPointer, - Operand::StackPointer, - Operand::Number(_), - ) = &input[cleanup_idx].instruction - { - true - } else { - false - } + matches!( + input[cleanup_idx].instruction, + Instruction::Sub( + Operand::StackPointer, + Operand::StackPointer, + Operand::Number(_) + ) + ) } else { false }; - // "All or Nothing" strategy for the safe subset: + // 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)); - let push_set: HashSet = pushes.iter().map(|(_, r)| *r).collect(); - let restore_set: HashSet = restores.iter().map(|(_, r, _)| *r).collect(); - - if all_pushes_safe && has_cleanup && push_set == restore_set { - // We can remove ALL saves/restores for this call! + 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); } @@ -278,7 +339,7 @@ fn optimize_function_calls<'a>( } // Reduce stack cleanup amount - let num_removed = push_set.len() as i64; + let num_removed = push_counts.values().sum::() as i64; stack_adjustments.insert(cleanup_idx, num_removed); changed = true; } @@ -295,15 +356,14 @@ fn optimize_function_calls<'a>( } // Apply stack adjustment - if let Some(reduction) = stack_adjustments.get(&idx) { - if 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)); + 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); @@ -357,10 +417,16 @@ fn register_forwarding<'a>( 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) { - if redef == temp_reg { - break; - } + if let Some(redef) = get_destination_reg(&node.instruction) + && redef == temp_reg + { + break; + } + + // Reg15 is a return register. + + if temp_reg == 15 { + break; } // If we hit a label/jump, we assume liveness might leak (conservative safety) if matches!( @@ -436,17 +502,17 @@ fn resolve_labels<'a>(input: Vec>) -> Vec { + 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(a, op) | Instruction::BranchNeZero(a, op) => { + Instruction::BranchEqZero(_, op) | Instruction::BranchNeZero(_, op) => { if let Some(num) = get_line(op) { *op = num; } @@ -634,8 +700,7 @@ fn reg_is_read(instr: &Instruction, reg: u8) -> bool { } } -// --- Constant Propagation & Dead Code (Same as before) --- - +/// --- Constant Propagation & Dead Code --- fn constant_propagation<'a>(input: Vec>) -> (Vec>, bool) { let mut output = Vec::with_capacity(input.len()); let mut changed = false; @@ -648,13 +713,8 @@ fn constant_propagation<'a>(input: Vec>) -> (Vec { - if let Some(val) = resolve_value(src, ®isters) { - Some(Instruction::Move(dst.clone(), Operand::Number(val))) - } else { - None - } - } + 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), @@ -718,11 +778,11 @@ fn constant_propagation<'a>(input: Vec>) -> (Vec(input: Vec>) -> (Vec( changed = true; continue; } - match node.instruction { - Instruction::Jump(_) | Instruction::Jump(Operand::ReturnAddress) => dead = true, - _ => {} + if let Instruction::Jump(_) = node.instruction { + dead = true } output.push(node); } diff --git a/rust_compiler/src/ffi/mod.rs b/rust_compiler/src/ffi/mod.rs index f302a53..76195a3 100644 --- a/rust_compiler/src/ffi/mod.rs +++ b/rust_compiler/src/ffi/mod.rs @@ -1,9 +1,8 @@ use compiler::{CompilationResult, Compiler}; use helpers::{Documentation, Span}; -use optimizer::optimize; use parser::{sys_call::SysCall, Parser}; use safer_ffi::prelude::*; -use std::io::{BufWriter, Write}; +use std::io::BufWriter; use tokenizer::{ token::{Token, TokenType}, Tokenizer, @@ -128,33 +127,32 @@ pub fn free_docs_vec(v: safer_ffi::Vec) { pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> FfiCompilationResult { let res = std::panic::catch_unwind(|| { let input = String::from_utf16_lossy(input.as_slice()); - let mut tmp = BufWriter::new(Vec::new()); let tokenizer = Tokenizer::from(input.as_str()); let parser = Parser::new(tokenizer); - let compiler = Compiler::new(parser, &mut tmp, None); + let compiler = Compiler::new(parser, None); let res = compiler.compile(); if !res.errors.is_empty() { - return (safer_ffi::String::EMPTY, res.source_map); + return (safer_ffi::String::EMPTY, res.instructions.source_map()); } let mut writer = BufWriter::new(Vec::new()); - for instruction in optimize(res.instructions) { - _ = writer.write_all(instruction.instruction.to_string().as_bytes()); - _ = writer.write_all(b"\n"); - } + // writing into a Vec. This should not fail. + let optimized = optimizer::optimize(res.instructions); + let map = optimized.source_map(); + _ = optimized.write(&mut writer); let Ok(compiled_vec) = writer.into_inner() else { - return (safer_ffi::String::EMPTY, res.source_map); + return (safer_ffi::String::EMPTY, map); }; // Safety: I know the compiler only outputs valid utf8 ( safer_ffi::String::from(unsafe { String::from_utf8_unchecked(compiled_vec) }), - res.source_map, + map, ) }); @@ -162,13 +160,9 @@ pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> FfiCompilat FfiCompilationResult { source_map: source_map .into_iter() - .flat_map(|(k, v)| { - v.into_iter() - .map(|span| FfiSourceMapEntry { - span: span.into(), - line_number: k as u32, - }) - .collect::>() + .map(|(line_num, span)| FfiSourceMapEntry { + span: span.into(), + line_number: line_num as u32, }) .collect::>() .into(), @@ -244,9 +238,8 @@ pub fn diagnose_source(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec< let res = std::panic::catch_unwind(|| { let input = String::from_utf16_lossy(input.as_slice()); - let mut writer = BufWriter::new(Vec::new()); let tokenizer = Tokenizer::from(input.as_str()); - let compiler = Compiler::new(Parser::new(tokenizer), &mut writer, None); + let compiler = Compiler::new(Parser::new(tokenizer), None); let CompilationResult { errors: diagnosis, .. diff --git a/rust_compiler/src/main.rs b/rust_compiler/src/main.rs index b6ddb9d..da60b84 100644 --- a/rust_compiler/src/main.rs +++ b/rust_compiler/src/main.rs @@ -88,9 +88,7 @@ fn run_logic<'a>() -> Result<(), Error<'a>> { None => BufWriter::new(Box::new(std::io::stdout())), }; - let mut tmp = BufWriter::new(vec![]); - - let compiler = Compiler::new(parser, &mut tmp, None); + let compiler = Compiler::new(parser, None); let CompilationResult { errors, @@ -109,11 +107,7 @@ fn run_logic<'a>() -> Result<(), Error<'a>> { } } - let instructions = optimizer::optimize(instructions); - - for instruction in instructions { - writer.write_all(format!("{}\n", instruction.instruction).as_bytes())?; - } + optimizer::optimize(instructions).write(&mut writer)?; writer.flush()?; diff --git a/spilling.slang b/spilling.slang new file mode 100644 index 0000000..515d4f5 --- /dev/null +++ b/spilling.slang @@ -0,0 +1,43 @@ +device self = "db"; +device gasSensor = "d0"; +device atmosAnal = "d1"; +device atmosValve = "d2"; +device atmosTank = "d3"; +device atmosInlet = "d4"; + +atmosInlet.Lock = true; +atmosInlet.Mode = 1; +atmosValve.On = false; +atmosValve.Lock = true; + +let isPumping = false; +let tempPressure = 0; + +loop { + yield(); + let temp = gasSensor.Temperature; + let pres = atmosAnal.Pressure; + let liqV = atmosAnal.VolumeOfLiquid; + let tempVol = atmosAnal.Volume; + + let stress = 5_000 * liqV / tempVol; + + tempPressure = isPumping ? 1_000 : 10_000; + + let shouldTurnOnInlet = ( + temp > 0c && + pres < tempPressure && + stress < 50 + ); + + isPumping = ( + !shouldTurnOnInlet && + atmosTank.Pressure < 35_000 && + atmosAnal.RatioPollutant == 0 && + atmosAnal.RatioLiquidPollutant == 0 && + atmosAnal.Pressure > 1_000 + ); + + atmosValve.On = isPumping; + atmosInlet.On = shouldTurnOnInlet; +} From 378c7e18cdda380adbf42d5d1e2fc72f68f9f2cb Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Sat, 13 Dec 2025 00:35:31 -0700 Subject: [PATCH 5/5] version bump --- Changelog.md | 6 ++++++ ModData/About/About.xml | 2 +- csharp_mod/Plugin.cs | 2 +- csharp_mod/stationeersSlang.csproj | 2 +- rust_compiler/Cargo.lock | 2 +- rust_compiler/Cargo.toml | 2 +- rust_compiler/libs/il/src/lib.rs | 2 +- rust_compiler/libs/optimizer/src/lib.rs | 6 ------ 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Changelog.md b/Changelog.md index 46736d6..0670e13 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,11 @@ # Changelog +[0.3.0] + +- Implemented a multi-pass optimizer + - This should significantly reduce line count in the final output +- Fixed source map to line up with newly optimized code + [0.2.4] - Groundwork laid to collect and track source maps diff --git a/ModData/About/About.xml b/ModData/About/About.xml index ef16119..9b34754 100644 --- a/ModData/About/About.xml +++ b/ModData/About/About.xml @@ -2,7 +2,7 @@ Slang JoeDiertay - 0.2.4 + 0.3.0 [h1]Slang: High-Level Programming for Stationeers[/h1] diff --git a/csharp_mod/Plugin.cs b/csharp_mod/Plugin.cs index 6f8266c..b5f2570 100644 --- a/csharp_mod/Plugin.cs +++ b/csharp_mod/Plugin.cs @@ -41,7 +41,7 @@ namespace Slang { public const string PluginGuid = "com.biddydev.slang"; public const string PluginName = "Slang"; - public const string PluginVersion = "0.2.4"; + public const string PluginVersion = "0.3.0"; public static Mod MOD = new Mod(PluginName, PluginVersion); diff --git a/csharp_mod/stationeersSlang.csproj b/csharp_mod/stationeersSlang.csproj index 3564bc4..7be1826 100644 --- a/csharp_mod/stationeersSlang.csproj +++ b/csharp_mod/stationeersSlang.csproj @@ -5,7 +5,7 @@ enable StationeersSlang Slang Compiler Bridge - 0.2.4 + 0.3.0 true latest diff --git a/rust_compiler/Cargo.lock b/rust_compiler/Cargo.lock index 20fd557..ca51170 100644 --- a/rust_compiler/Cargo.lock +++ b/rust_compiler/Cargo.lock @@ -930,7 +930,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slang" -version = "0.2.4" +version = "0.3.0" dependencies = [ "anyhow", "clap", diff --git a/rust_compiler/Cargo.toml b/rust_compiler/Cargo.toml index 2bf6da4..047478e 100644 --- a/rust_compiler/Cargo.toml +++ b/rust_compiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "slang" -version = "0.2.4" +version = "0.3.0" edition = "2021" [workspace] diff --git a/rust_compiler/libs/il/src/lib.rs b/rust_compiler/libs/il/src/lib.rs index fae551c..62affd8 100644 --- a/rust_compiler/libs/il/src/lib.rs +++ b/rust_compiler/libs/il/src/lib.rs @@ -30,7 +30,7 @@ impl<'a> Instructions<'a> { pub fn into_inner(self) -> Vec> { self.0 } - pub fn write(self, writer: &mut BufWriter) -> Result<(), std::io::Error> { + pub fn write(self, writer: &mut BufWriter) -> Result<(), std::io::Error> { for node in self.0 { writer.write_all(node.to_string().as_bytes())?; writer.write_all(b"\n")?; diff --git a/rust_compiler/libs/optimizer/src/lib.rs b/rust_compiler/libs/optimizer/src/lib.rs index 2204488..e4397e6 100644 --- a/rust_compiler/libs/optimizer/src/lib.rs +++ b/rust_compiler/libs/optimizer/src/lib.rs @@ -423,11 +423,6 @@ fn register_forwarding<'a>( break; } - // Reg15 is a return register. - - if temp_reg == 15 { - break; - } // If we hit a label/jump, we assume liveness might leak (conservative safety) if matches!( node.instruction, @@ -871,4 +866,3 @@ fn remove_unreachable_code<'a>( } (output, changed) } -