Merge pull request #33 from dbidwell94/reagent

[0.3.4]

- Added support for `loadReagent`, which maps to the `lr` IC10 instruction
  - Shorthand is `lr`
  - Longform is `loadReagent`
- Update various Rust dependencies
- Added more optimizations, prioritizing `pop` instead of `get` when available
  when backing up / restoring registers for function invocations. This should
  save approximately 2 lines per backed up register
This commit is contained in:
2025-12-17 21:18:16 -07:00
committed by GitHub
16 changed files with 234 additions and 103 deletions

View File

@@ -1,5 +1,15 @@
# Changelog
[0.3.4]
- Added support for `loadReagent`, which maps to the `lr` IC10 instruction
- Shorthand is `lr`
- Longform is `loadReagent`
- Update various Rust dependencies
- Added more optimizations, prioritizing `pop` instead of `get` when available
when backing up / restoring registers for function invocations. This should
save approximately 2 lines per backed up register
[0.3.3]
- Fixed bug where negative temperature literals were converted to Kelvin

View File

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

View File

@@ -41,7 +41,7 @@ namespace Slang
{
public const string PluginGuid = "com.biddydev.slang";
public const string PluginName = "Slang";
public const string PluginVersion = "0.3.3";
public const string PluginVersion = "0.3.4";
public static Mod MOD = new Mod(PluginName, PluginVersion);

View File

@@ -172,9 +172,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.19.0"
version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]]
name = "bytecheck"
@@ -930,7 +930,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "slang"
version = "0.3.2"
version = "0.3.4"
dependencies = [
"anyhow",
"clap",
@@ -1063,18 +1063,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.7.3"
version = "0.7.4+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
checksum = "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.23.7"
version = "0.23.10+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
dependencies = [
"indexmap",
"toml_datetime",
@@ -1084,9 +1084,9 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.0.4"
version = "1.0.5+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
checksum = "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c"
dependencies = [
"winnow",
]

View File

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

View File

@@ -54,9 +54,7 @@ fn nested_binary_expressions() -> Result<()> {
move r15 r2
j __internal_L1
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
main:
push 10

View File

@@ -18,9 +18,7 @@ fn no_arguments() -> anyhow::Result<()> {
doSomething:
push ra
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
main:
jal doSomething
@@ -61,9 +59,7 @@ fn let_var_args() -> anyhow::Result<()> {
move r15 r1
j __internal_L1
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
main:
__internal_L2:
@@ -71,9 +67,7 @@ fn let_var_args() -> anyhow::Result<()> {
push r8
push r8
jal mul2
sub r0 sp 1
get r8 db r0
sub sp sp 1
pop r8
move r9 r15
pow r1 r9 2
move r9 r1
@@ -129,9 +123,7 @@ fn inline_literal_args() -> anyhow::Result<()> {
move r15 5
j __internal_L1
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
main:
move r8 123
@@ -139,9 +131,7 @@ fn inline_literal_args() -> anyhow::Result<()> {
push 12
push 34
jal doSomething
sub r0 sp 1
get r8 db r0
sub sp sp 1
pop r8
move r9 r15
"
}
@@ -171,9 +161,7 @@ fn mixed_args() -> anyhow::Result<()> {
pop r9
push ra
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
main:
move r8 123
@@ -181,9 +169,7 @@ fn mixed_args() -> anyhow::Result<()> {
push r8
push 456
jal doSomething
sub r0 sp 1
get r8 db r0
sub sp sp 1
pop r8
move r9 r15
"
}
@@ -216,9 +202,7 @@ fn with_return_statement() -> anyhow::Result<()> {
move r15 456
j __internal_L1
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
main:
push 123
@@ -252,9 +236,7 @@ fn with_negative_return_literal() -> anyhow::Result<()> {
push ra
move r15 -1
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
main:
jal doSomething

View File

@@ -135,9 +135,7 @@ fn test_boolean_return() -> anyhow::Result<()> {
move r15 1
j __internal_L1
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
main:
jal getTrue

View File

@@ -5,7 +5,12 @@ use pretty_assertions::assert_eq;
fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
let compiled = compile!(debug r#"
// we need more than 4 params to 'spill' into a stack var
fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {};
fn doSomething(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
return arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9;
};
let item1 = 1;
let returned = doSomething(item1, 2, 3, 4, 5, 6, 7, 8, 9);
"#);
assert_eq!(
@@ -21,11 +26,39 @@ fn test_function_declaration_with_spillover_params() -> anyhow::Result<()> {
pop r13
pop r14
push ra
sub r0 sp 3
get r1 db r0
sub r0 sp 2
get r2 db r0
add r3 r1 r2
add r4 r3 r14
add r5 r4 r13
add r6 r5 r12
add r7 r6 r11
add r1 r7 r10
add r2 r1 r9
add r3 r2 r8
move r15 r3
j __internal_L1
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 3
pop ra
sub sp sp 2
j ra
main:
move r8 1
push r8
push r8
push 2
push 3
push 4
push 5
push 6
push 7
push 8
push 9
jal doSomething
pop r8
move r9 r15
"}
);
@@ -60,9 +93,7 @@ fn test_early_return() -> anyhow::Result<()> {
move r8 3
j __internal_L1
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
main:
jal doSomething
@@ -91,9 +122,7 @@ fn test_function_declaration_with_register_params() -> anyhow::Result<()> {
pop r9
push ra
__internal_L1:
sub r0 sp 1
get ra db r0
sub sp sp 1
pop ra
j ra
"}
);

View File

@@ -208,3 +208,29 @@ fn test_set_slot() -> anyhow::Result<()> {
Ok(())
}
#[test]
fn test_load_reagent() -> anyhow::Result<()> {
let compiled = compile! {
debug
r#"
device thingy = "d0";
let something = lr(thingy, "Contents", hash("Iron"));
"#
};
assert_eq!(
compiled,
indoc! {
"
j main
main:
lr r15 d0 Contents -666742878
move r8 r15
"
}
);
Ok(())
}

View File

@@ -1115,43 +1115,26 @@ impl<'a> Compiler<'a> {
Some(name.span),
)?;
for register in active_registers {
let VariableLocation::Stack(stack_offset) = stack
.get_location_of(&Cow::from(format!("temp_{register}")), None)
.map_err(Error::Scope)?
else {
// This shouldn't happen if we just added it
return Err(Error::Unknown(
format!("Failed to recover temp_{register}"),
Some(name.span),
));
};
// cleanup spilled temporary variables
let total_stack_usage = stack.stack_offset();
let saved_regs_count = active_registers.len() as u16;
if total_stack_usage > saved_regs_count {
let spill_amount = total_stack_usage - saved_regs_count;
self.write_instruction(
Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number(stack_offset.into()),
),
Some(name.span),
)?;
self.write_instruction(
Instruction::Get(
Operand::Register(register),
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number(spill_amount.into()),
),
Some(name.span),
)?;
}
if stack.stack_offset() > 0 {
// restore the registers in reverse order from the stack, now using `pop`
for register in active_registers.iter().rev() {
self.write_instruction(
Instruction::Sub(
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(Decimal::from(stack.stack_offset())),
),
Instruction::Pop(Operand::Register(*register)),
Some(name.span),
)?;
}
@@ -2296,6 +2279,48 @@ impl<'a> Compiler<'a> {
Ok(None)
}
System::LoadReagent(device, reagent_mode, 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_mode, reagent_cleanup) = self.compile_literal_or_variable(
LiteralOrVariable::Literal(reagent_mode.node),
scope,
)?;
let (reagent_hash, reagent_hash_cleanup) =
self.compile_operand(*reagent_hash, scope)?;
self.write_instruction(
Instruction::LoadReagent(
Operand::Register(VariableScope::RETURN_REGISTER),
device,
reagent_mode,
reagent_hash,
),
Some(span),
)?;
cleanup!(reagent_cleanup, reagent_hash_cleanup, device_cleanup);
Ok(Some(CompileLocation {
location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER),
temp_name: None,
}))
}
}
}
@@ -2693,33 +2718,49 @@ impl<'a> Compiler<'a> {
self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?;
self.write_instruction(
Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number(ra_stack_offset.into()),
),
Some(span),
)?;
if ra_stack_offset == 1 {
self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?;
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 {
let remaining_cleanup = block_scope.stack_offset() - 1;
if remaining_cleanup > 0 {
self.write_instruction(
Instruction::Sub(
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(remaining_cleanup.into()),
),
Some(span),
)?;
}
} else {
self.write_instruction(
Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(block_scope.stack_offset().into()),
Operand::Number(ra_stack_offset.into()),
),
Some(span),
)?;
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_instruction(
Instruction::Sub(
Operand::StackPointer,
Operand::StackPointer,
Operand::Number(block_scope.stack_offset().into()),
),
Some(span),
)?;
}
}
self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?;

View File

@@ -10,6 +10,7 @@ macro_rules! with_syscalls {
"loadBatched",
"loadBatchedNamed",
"loadSlot",
"loadReagent",
"set",
"setBatched",
"setBatchedNamed",
@@ -35,6 +36,7 @@ macro_rules! with_syscalls {
"lb",
"lbn",
"ls",
"lr",
"s",
"sb",
"sbn",

View File

@@ -191,6 +191,9 @@ pub enum Instruction<'a> {
/// `sbn deviceHash nameHash type value` - Set Batch Named
StoreBatchNamed(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>),
/// `lr register device reagentMode int`
LoadReagent(Operand<'a>, Operand<'a>, Operand<'a>, Operand<'a>),
/// `j label` - Unconditional Jump
Jump(Operand<'a>),
/// `jal label` - Jump and Link (Function Call)
@@ -311,6 +314,9 @@ impl<'a> fmt::Display for Instruction<'a> {
Instruction::StoreBatchNamed(d_hash, n_hash, typ, val) => {
write!(f, "sbn {} {} {} {}", d_hash, n_hash, typ, val)
}
Instruction::LoadReagent(reg, device, reagent_mode, reagent_hash) => {
write!(f, "lr {} {} {} {}", reg, device, reagent_mode, reagent_hash)
}
Instruction::Jump(lbl) => write!(f, "j {}", lbl),
Instruction::JumpAndLink(lbl) => write!(f, "jal {}", lbl),
Instruction::JumpRelative(off) => write!(f, "jr {}", off),

View File

@@ -565,6 +565,7 @@ fn get_destination_reg(instr: &Instruction) -> Option<u8> {
| Instruction::Sqrt(Operand::Register(r), _)
| Instruction::Tan(Operand::Register(r), _)
| Instruction::Trunc(Operand::Register(r), _)
| Instruction::LoadReagent(Operand::Register(r), _, _, _)
| Instruction::Pop(Operand::Register(r)) => Some(*r),
_ => None,
}
@@ -595,6 +596,9 @@ fn set_destination_reg<'a>(instr: &Instruction<'a>, new_reg: u8) -> Option<Instr
c.clone(),
d.clone(),
)),
Instruction::LoadReagent(_, b, c, d) => {
Some(Instruction::LoadReagent(r, b.clone(), c.clone(), d.clone()))
}
Instruction::SetEq(_, a, b) => Some(Instruction::SetEq(r, a.clone(), b.clone())),
Instruction::SetNe(_, a, b) => Some(Instruction::SetNe(r, a.clone(), b.clone())),
Instruction::SetGt(_, a, b) => Some(Instruction::SetGt(r, a.clone(), b.clone())),
@@ -657,6 +661,14 @@ fn reg_is_read(instr: &Instruction, reg: u8) -> bool {
Instruction::BranchEqZero(a, _) | Instruction::BranchNeZero(a, _) => check(a),
Instruction::LoadReagent(_, device, _, item_hash) => check(device) || check(item_hash),
Instruction::LoadSlot(_, dev, slot, _) => check(dev) || check(slot),
Instruction::LoadBatch(_, dev, _, mode) => check(dev) || check(mode),
Instruction::LoadBatchNamed(_, d_hash, n_hash, _, mode) => {
check(d_hash) || check(n_hash) || check(mode)
}
Instruction::SetEq(_, a, b)
| Instruction::SetNe(_, a, b)
| Instruction::SetGt(_, a, b)

View File

@@ -1909,6 +1909,20 @@ impl<'a> Parser<'a> {
Box::new(expr),
)))
}
"loadReagent" | "lr" => {
let mut args = args!(3);
let next = args.next();
let device = literal_or_variable!(next);
let next = args.next();
let reagent_mode = get_arg!(Literal, literal_or_variable!(next));
let reagent_hash = args.next().ok_or(Error::UnexpectedEOF)?;
Ok(SysCall::System(System::LoadReagent(
device,
reagent_mode,
Box::new(reagent_hash),
)))
}
// Math SysCalls
"acos" => {

View File

@@ -237,6 +237,18 @@ documented! {
Spanned<Literal<'a>>,
Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>>
),
/// Loads reagent of device's ReagentMode where a hash of the reagent type to check for
///
/// ## IC10
/// `lr r? device(d?|r?|id) reagentMode int`
/// ## Slang
/// `let result = loadReagent(deviceHash, "ReagentMode", reagentHash);`
/// `let result = lr(deviceHash, "ReagentMode", reagentHash);`
LoadReagent(
Spanned<LiteralOrVariable<'a>>,
Spanned<Literal<'a>>,
Box<Spanned<Expression<'a>>>
)
}
}
@@ -261,6 +273,7 @@ impl<'a> std::fmt::Display for System<'a> {
}
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::LoadReagent(a, b, c) => write!(f, "loadReagent({}, {}, {})", a, b, c),
}
}
}