Attempt to fold constants when folding expressions
All checks were successful
CI/CD Pipeline / test (pull_request) Successful in 37s
CI/CD Pipeline / build (pull_request) Has been skipped
CI/CD Pipeline / release (pull_request) Has been skipped

This commit is contained in:
2026-01-02 03:06:37 -07:00
parent 3c7300d2e1
commit 4c704b8960
9 changed files with 223 additions and 10 deletions

View File

@@ -287,3 +287,68 @@ fn test_load_reagent() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_clr() -> anyhow::Result<()> {
let compiled = compile! {
check
"
device stackDevice = \"d0\";
clr(stackDevice);
let deviceRef = 5;
clr(deviceRef);
"
};
assert!(
compiled.errors.is_empty(),
"Expected no errors, got: {:?}",
compiled.errors
);
assert_eq!(
compiled.output,
indoc! {
"
j main
main:
clr d0
move r8 5
clr r8
"
}
);
Ok(())
}
#[test]
fn test_rmap() -> anyhow::Result<()> {
let compiled = compile! {
check
"
device printer = \"d0\";
let reagentHash = 12345;
let itemHash = rmap(printer, reagentHash);
"
};
assert!(
compiled.errors.is_empty(),
"Expected no errors, got: {:?}",
compiled.errors
);
assert_eq!(
compiled.output,
indoc! {
"
j main
main:
move r8 12345
rmap r15 d0 r8
move r9 r15
"
}
);
Ok(())
}

View File

@@ -2286,7 +2286,10 @@ impl<'a> Compiler<'a> {
expr: Spanned<BinaryExpression<'a>>, expr: Spanned<BinaryExpression<'a>>,
scope: &mut VariableScope<'a, '_>, scope: &mut VariableScope<'a, '_>,
) -> Result<CompileLocation<'a>, Error<'a>> { ) -> Result<CompileLocation<'a>, Error<'a>> {
fn fold_binary_expression<'a>(expr: &BinaryExpression<'a>) -> Option<Number> { fn fold_binary_expression<'a>(
expr: &BinaryExpression<'a>,
scope: &VariableScope<'a, '_>,
) -> Option<Number> {
fn number_to_i64(n: Number) -> Option<i64> { fn number_to_i64(n: Number) -> Option<i64> {
match n { match n {
Number::Integer(i, _) => i64::try_from(i).ok(), Number::Integer(i, _) => i64::try_from(i).ok(),
@@ -2315,7 +2318,7 @@ impl<'a> Compiler<'a> {
| BinaryExpression::LeftShift(l, r) | BinaryExpression::LeftShift(l, r)
| BinaryExpression::RightShiftArithmetic(l, r) | BinaryExpression::RightShiftArithmetic(l, r)
| BinaryExpression::RightShiftLogical(l, r) => { | BinaryExpression::RightShiftLogical(l, r) => {
(fold_expression(l)?, fold_expression(r)?) (fold_expression(l, scope)?, fold_expression(r, scope)?)
} }
}; };
@@ -2359,7 +2362,10 @@ impl<'a> Compiler<'a> {
} }
} }
fn fold_expression<'a>(expr: &Expression<'a>) -> Option<Number> { fn fold_expression<'a>(
expr: &Expression<'a>,
scope: &VariableScope<'a, '_>,
) -> Option<Number> {
match expr { match expr {
// 1. Base Case: It's already a number // 1. Base Case: It's already a number
Expression::Literal(lit) => match lit.node { Expression::Literal(lit) => match lit.node {
@@ -2368,18 +2374,28 @@ impl<'a> Compiler<'a> {
}, },
// 2. Handle Parentheses: Just recurse deeper // 2. Handle Parentheses: Just recurse deeper
Expression::Priority(inner) => fold_expression(&inner.node), Expression::Priority(inner) => fold_expression(&inner.node, scope),
// 3. Handle Negation: Recurse, then negate // 3. Handle Negation: Recurse, then negate
Expression::Negation(inner) => { Expression::Negation(inner) => {
let val = fold_expression(&inner.node)?; let val = fold_expression(&inner.node, scope)?;
Some(-val) // Requires impl Neg for Number Some(-val) // Requires impl Neg for Number
} }
// 4. Handle Binary Ops: Recurse BOTH sides, then combine // 4. Handle Binary Ops: Recurse BOTH sides, then combine
Expression::Binary(bin) => fold_binary_expression(&bin.node), Expression::Binary(bin) => fold_binary_expression(&bin.node, scope),
// 5. Handle hash() syscall - evaluates to a constant at compile time // 5. Handle Variable Reference: Check if it's a const
Expression::Variable(var_id) => {
if let Ok(var_loc) = scope.get_location_of(var_id, None) {
if let VariableLocation::Constant(Literal::Number(num)) = var_loc {
return Some(num);
}
}
None
}
// 6. Handle hash() syscall - evaluates to a constant at compile time
Expression::Syscall(Spanned { Expression::Syscall(Spanned {
node: node:
SysCall::System(System::Hash(Spanned { SysCall::System(System::Hash(Spanned {
@@ -2391,7 +2407,7 @@ impl<'a> Compiler<'a> {
return Some(Number::Integer(crc_hash_signed(str_to_hash), Unit::None)); return Some(Number::Integer(crc_hash_signed(str_to_hash), Unit::None));
} }
// 6. Handle hash() macro as invocation - evaluates to a constant at compile time // 7. Handle hash() macro as invocation - evaluates to a constant at compile time
Expression::Invocation(inv) => { Expression::Invocation(inv) => {
if inv.node.name.node == "hash" && inv.node.arguments.len() == 1 { if inv.node.name.node == "hash" && inv.node.arguments.len() == 1 {
if let Expression::Literal(Spanned { if let Expression::Literal(Spanned {
@@ -2406,12 +2422,12 @@ impl<'a> Compiler<'a> {
None None
} }
// 7. Anything else cannot be compile-time folded // 8. Anything else cannot be compile-time folded
_ => None, _ => None,
} }
} }
if let Some(const_lit) = fold_binary_expression(&expr.node) { if let Some(const_lit) = fold_binary_expression(&expr.node, scope) {
return Ok(CompileLocation { return Ok(CompileLocation {
location: VariableLocation::Constant(Literal::Number(const_lit)), location: VariableLocation::Constant(Literal::Number(const_lit)),
temp_name: None, temp_name: None,
@@ -2925,6 +2941,13 @@ impl<'a> Compiler<'a> {
cleanup!(var_cleanup); cleanup!(var_cleanup);
Ok(None) Ok(None)
} }
System::Clr(device) => {
let (op, var_cleanup) = self.compile_operand(*device, scope)?;
self.write_instruction(Instruction::Clr(op), Some(span))?;
cleanup!(var_cleanup);
Ok(None)
}
System::Hash(hash_arg) => { System::Hash(hash_arg) => {
let Spanned { let Spanned {
node: Literal::String(str_lit), node: Literal::String(str_lit),
@@ -3244,6 +3267,42 @@ impl<'a> Compiler<'a> {
cleanup!(reagent_cleanup, reagent_hash_cleanup, device_cleanup); cleanup!(reagent_cleanup, reagent_hash_cleanup, device_cleanup);
Ok(Some(CompileLocation {
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
temp_name: None,
}))
}
System::Rmap(device, reagent_hash) => {
let Spanned {
node: LiteralOrVariable::Variable(device_spanned),
..
} = device
else {
return Err(Error::AgrumentMismatch(
"Arg1 expected to be a variable".into(),
span,
));
};
let (device, device_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Variable(device_spanned),
scope,
)?;
let (reagent_hash, reagent_hash_cleanup) =
self.compile_operand(*reagent_hash, scope)?;
self.write_instruction(
Instruction::Rmap(
Operand::Register(VariableScope::RETURN_REGISTER),
device,
reagent_hash,
),
Some(span),
)?;
cleanup!(reagent_hash_cleanup, device_cleanup);
Ok(Some(CompileLocation { Ok(Some(CompileLocation {
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
temp_name: None, temp_name: None,

View File

@@ -5,12 +5,14 @@ macro_rules! with_syscalls {
// Big names // Big names
"yield", "yield",
"sleep", "sleep",
"clr",
"hash", "hash",
"load", "load",
"loadBatched", "loadBatched",
"loadBatchedNamed", "loadBatchedNamed",
"loadSlot", "loadSlot",
"loadReagent", "loadReagent",
"rmap",
"set", "set",
"setBatched", "setBatched",
"setBatchedNamed", "setBatchedNamed",

View File

@@ -195,6 +195,9 @@ pub enum Instruction<'a> {
/// `lr register device reagentMode int` /// `lr register device reagentMode int`
LoadReagent(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>), LoadReagent(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>),
/// `rmap register device reagentHash` - Resolve Reagent to Item Hash
Rmap(Operand<'a>, Operand<'a>, Operand<'a>),
/// `j label` - Unconditional Jump /// `j label` - Unconditional Jump
Jump(Operand<'a>), Jump(Operand<'a>),
/// `jal label` - Jump and Link (Function Call) /// `jal label` - Jump and Link (Function Call)
@@ -267,6 +270,8 @@ pub enum Instruction<'a> {
Yield, Yield,
/// `sleep val` - Sleep for seconds /// `sleep val` - Sleep for seconds
Sleep(Operand<'a>), Sleep(Operand<'a>),
/// `clr val` - Clear stack memory on device
Clr(Operand<'a>),
/// `alias name target` - Define Alias (Usually handled by compiler, but good for IR) /// `alias name target` - Define Alias (Usually handled by compiler, but good for IR)
Alias(Cow<'a, str>, Operand<'a>), Alias(Cow<'a, str>, Operand<'a>),
@@ -328,6 +333,9 @@ impl<'a> fmt::Display for Instruction<'a> {
Instruction::LoadReagent(reg, device, reagent_mode, reagent_hash) => { Instruction::LoadReagent(reg, device, reagent_mode, reagent_hash) => {
write!(f, "lr {} {} {} {}", reg, device, reagent_mode, reagent_hash) write!(f, "lr {} {} {} {}", reg, device, reagent_mode, reagent_hash)
} }
Instruction::Rmap(reg, device, reagent_hash) => {
write!(f, "rmap {} {} {}", reg, device, reagent_hash)
}
Instruction::Jump(lbl) => write!(f, "j {}", lbl), Instruction::Jump(lbl) => write!(f, "j {}", lbl),
Instruction::JumpAndLink(lbl) => write!(f, "jal {}", lbl), Instruction::JumpAndLink(lbl) => write!(f, "jal {}", lbl),
Instruction::JumpRelative(off) => write!(f, "jr {}", off), Instruction::JumpRelative(off) => write!(f, "jr {}", off),
@@ -363,6 +371,7 @@ impl<'a> fmt::Display for Instruction<'a> {
} }
Instruction::Yield => write!(f, "yield"), Instruction::Yield => write!(f, "yield"),
Instruction::Sleep(val) => write!(f, "sleep {}", val), Instruction::Sleep(val) => write!(f, "sleep {}", val),
Instruction::Clr(val) => write!(f, "clr {}", val),
Instruction::Alias(name, target) => write!(f, "alias {} {}", name, target), Instruction::Alias(name, target) => write!(f, "alias {} {}", name, target),
Instruction::Define(name, val) => write!(f, "define {} {}", name, val), Instruction::Define(name, val) => write!(f, "define {} {}", name, val),
Instruction::LabelDef(lbl) => write!(f, "{}:", lbl), Instruction::LabelDef(lbl) => write!(f, "{}:", lbl),

View File

@@ -90,4 +90,23 @@ mod bitwise_tests {
let output = compile_with_and_without_optimization(source); let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output); insta::assert_snapshot!(output);
} }
#[test]
fn test_bitwise_with_const() {
let source = indoc! {r#"
device sorterOutput = "d0";
const ingotSortClass = 19;
const equals = 0;
loop {
yield();
clr(sorterOutput);
sorterOutput[0] = (ingotSortClass << 16) | (equals << 8) | 3;
}
"#};
let output = compile_with_and_without_optimization(source);
insta::assert_snapshot!(output);
}
} }

View File

@@ -0,0 +1,21 @@
---
source: libs/integration_tests/src/bitwise_tests.rs
expression: output
---
## Unoptimized Output
j main
main:
__internal_L1:
yield
clr d0
put d0 0 1245187
j __internal_L1
__internal_L2:
## Optimized Output
yield
clr d0
put d0 0 1245187
j 0

View File

@@ -43,6 +43,7 @@ pub fn get_destination_reg(instr: &Instruction) -> Option<u8> {
| Instruction::Tan(Operand::Register(r), _) | Instruction::Tan(Operand::Register(r), _)
| Instruction::Trunc(Operand::Register(r), _) | Instruction::Trunc(Operand::Register(r), _)
| Instruction::LoadReagent(Operand::Register(r), _, _, _) | Instruction::LoadReagent(Operand::Register(r), _, _, _)
| Instruction::Rmap(Operand::Register(r), _, _)
| Instruction::Pop(Operand::Register(r)) => Some(*r), | Instruction::Pop(Operand::Register(r)) => Some(*r),
_ => None, _ => None,
} }
@@ -107,6 +108,7 @@ pub fn set_destination_reg<'a>(instr: &Instruction<'a>, new_reg: u8) -> Option<I
Instruction::Sqrt(_, a) => Some(Instruction::Sqrt(r, a.clone())), Instruction::Sqrt(_, a) => Some(Instruction::Sqrt(r, a.clone())),
Instruction::Tan(_, a) => Some(Instruction::Tan(r, a.clone())), Instruction::Tan(_, a) => Some(Instruction::Tan(r, a.clone())),
Instruction::Trunc(_, a) => Some(Instruction::Trunc(r, a.clone())), Instruction::Trunc(_, a) => Some(Instruction::Trunc(r, a.clone())),
Instruction::Rmap(_, a, b) => Some(Instruction::Rmap(r, a.clone(), b.clone())),
_ => None, _ => None,
} }
} }
@@ -136,6 +138,7 @@ pub fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
| Instruction::BranchLe(a, b, _) => check(a) || check(b), | Instruction::BranchLe(a, b, _) => check(a) || check(b),
Instruction::BranchEqZero(a, _) | Instruction::BranchNeZero(a, _) => check(a), Instruction::BranchEqZero(a, _) | Instruction::BranchNeZero(a, _) => check(a),
Instruction::LoadReagent(_, device, _, item_hash) => check(device) || check(item_hash), Instruction::LoadReagent(_, device, _, item_hash) => check(device) || check(item_hash),
Instruction::Rmap(_, device, reagent_hash) => check(device) || check(reagent_hash),
Instruction::LoadSlot(_, dev, slot, _) => check(dev) || check(slot), Instruction::LoadSlot(_, dev, slot, _) => check(dev) || check(slot),
Instruction::LoadBatch(_, dev, _, mode) => check(dev) || check(mode), Instruction::LoadBatch(_, dev, _, mode) => check(dev) || check(mode),
Instruction::LoadBatchNamed(_, d_hash, n_hash, _, mode) => { Instruction::LoadBatchNamed(_, d_hash, n_hash, _, mode) => {

View File

@@ -1960,6 +1960,11 @@ impl<'a> Parser<'a> {
let expr = args.next().ok_or_else(|| self.unexpected_eof())?; let expr = args.next().ok_or_else(|| self.unexpected_eof())?;
Ok(SysCall::System(System::Sleep(boxed!(expr)))) Ok(SysCall::System(System::Sleep(boxed!(expr))))
} }
"clr" => {
let mut args = args!(1);
let expr = args.next().ok_or_else(|| self.unexpected_eof())?;
Ok(SysCall::System(System::Clr(boxed!(expr))))
}
"hash" => { "hash" => {
let mut args = args!(1); let mut args = args!(1);
let lit_str = literal_or_variable!(args.next()); let lit_str = literal_or_variable!(args.next());
@@ -2191,6 +2196,17 @@ impl<'a> Parser<'a> {
Box::new(reagent_hash), Box::new(reagent_hash),
))) )))
} }
"rmap" => {
let mut args = args!(2);
let next = args.next();
let device = literal_or_variable!(next);
let reagent_hash = args.next().ok_or_else(|| self.unexpected_eof())?;
Ok(SysCall::System(System::Rmap(
device,
Box::new(reagent_hash),
)))
}
// Math SysCalls // Math SysCalls
"acos" => { "acos" => {

View File

@@ -142,6 +142,12 @@ documented! {
/// ## Slang /// ## Slang
/// `sleep(number|var);` /// `sleep(number|var);`
Sleep(Box<Spanned<Expression<'a>>>), Sleep(Box<Spanned<Expression<'a>>>),
/// Clears stack memory on the provided device.
/// ## IC10
/// `clr d?`
/// ## Slang
/// `clr(device);`
Clr(Box<Spanned<Expression<'a>>>),
/// Gets the in-game hash for a specific prefab name. NOTE! This call is COMPLETELY /// Gets the in-game hash for a specific prefab name. NOTE! This call is COMPLETELY
/// optimized away unless you bind it to a `let` variable. If you use a `const` variable /// optimized away unless you bind it to a `let` variable. If you use a `const` variable
/// however, the hash is correctly computed at compile time and substitued automatically. /// however, the hash is correctly computed at compile time and substitued automatically.
@@ -249,6 +255,17 @@ documented! {
Spanned<LiteralOrVariable<'a>>, Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>, Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>> Box<Spanned<Expression<'a>>>
),
/// Maps a reagent hash to the item hash that fulfills it on a device
///
/// ## IC10
/// `rmap r? d? reagentHash(r?|num)`
/// ## Slang
/// `let itemHash = rmap(device, reagentHash);`
/// `let itemHash = rmap(device, reagentHashValue);`
Rmap(
Spanned<LiteralOrVariable<'a>>,
Box<Spanned<Expression<'a>>>
) )
} }
} }
@@ -258,6 +275,7 @@ impl<'a> std::fmt::Display for System<'a> {
match self { match self {
System::Yield => write!(f, "yield()"), System::Yield => write!(f, "yield()"),
System::Sleep(a) => write!(f, "sleep({})", a), System::Sleep(a) => write!(f, "sleep({})", a),
System::Clr(a) => write!(f, "clr({})", a),
System::Hash(a) => write!(f, "hash({})", a), System::Hash(a) => write!(f, "hash({})", a),
System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b), System::LoadFromDevice(a, b) => write!(f, "loadFromDevice({}, {})", a, b),
System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c), System::LoadBatch(a, b, c) => write!(f, "loadBatch({}, {}, {})", a, b, c),
@@ -274,6 +292,7 @@ impl<'a> std::fmt::Display for System<'a> {
System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c), System::LoadSlot(a, b, c) => write!(f, "loadSlot({}, {}, {})", a, b, c),
System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d), System::SetSlot(a, b, c, d) => write!(f, "setSlot({}, {}, {}, {})", a, b, c, d),
System::LoadReagent(a, b, c) => write!(f, "loadReagent({}, {}, {})", a, b, c), System::LoadReagent(a, b, c) => write!(f, "loadReagent({}, {}, {})", a, b, c),
System::Rmap(a, b) => write!(f, "rmap({}, {})", a, b),
} }
} }
} }