From a53e0018cf9cb361863e9691f551e12e410095ed Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 17 Feb 2025 15:09:43 -0500 Subject: [PATCH] Add comparison tests for lists; Edit README.md; Add benches to Cargo.toml --- README.md | 4 + dust-lang/Cargo.toml | 12 +++ dust-lang/benches/threads.rs | 12 ++- dust-lang/src/compiler/mod.rs | 105 ++++++++++---------- dust-lang/src/instruction/mod.rs | 2 +- dust-lang/src/instruction/operation.rs | 2 +- dust-lang/src/instruction/test.rs | 4 +- dust-lang/src/vm/thread.rs | 14 ++- dust-lang/tests/comparison/equal.rs | 48 +++++++++ dust-lang/tests/comparison/greater.rs | 48 +++++++++ dust-lang/tests/comparison/greater_equal.rs | 48 +++++++++ dust-lang/tests/comparison/less.rs | 48 +++++++++ dust-lang/tests/comparison/less_equal.rs | 48 +++++++++ dust-lang/tests/comparison/not_equal.rs | 48 +++++++++ 14 files changed, 382 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 9fc044e..11b0107 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,10 @@ fn fib (n: int) -> int { write_line(fib(25)) ``` +> [!IMPORTANT] +> Dust is still experimental. Currently, development is more focused on exploring ideas for +> optimization and performance than on stability or feature completeness. This will change as the +> project matures. ## Highlights diff --git a/dust-lang/Cargo.toml b/dust-lang/Cargo.toml index 1c240a9..f0d5d51 100644 --- a/dust-lang/Cargo.toml +++ b/dust-lang/Cargo.toml @@ -31,6 +31,18 @@ criterion = { version = "0.5.1", features = ["html_reports"] } name = "addictive_addition" harness = false +[[bench]] +name = "addictive_subtraction" +harness = false + +[[bench]] +name = "addictive_multiplication" +harness = false + +[[bench]] +name = "addictive_division" +harness = false + [[bench]] name = "fibonacci" harness = false diff --git a/dust-lang/benches/threads.rs b/dust-lang/benches/threads.rs index 8c4ef2e..f818aff 100644 --- a/dust-lang/benches/threads.rs +++ b/dust-lang/benches/threads.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; use dust_lang::run; const SOURCE: &str = r#" @@ -9,9 +9,13 @@ const SOURCE: &str = r#" while i < 1_000 { i += 1 - spawn( - fn () { random_int(0, 10); } - ) + spawn(fn () { + let mut j = 0 + + while j < 5_000_000 { + j += 1 + } + }) } "#; diff --git a/dust-lang/src/compiler/mod.rs b/dust-lang/src/compiler/mod.rs index 29558dd..b92c0d3 100644 --- a/dust-lang/src/compiler/mod.rs +++ b/dust-lang/src/compiler/mod.rs @@ -855,6 +855,7 @@ impl<'src> Compiler<'src> { let operand = instruction.as_operand(); let push_back = match instruction.operation() { Operation::LOAD_ENCODED + | Operation::LOAD_LIST | Operation::LOAD_SELF | Operation::ADD | Operation::SUBTRACT @@ -997,6 +998,22 @@ impl<'src> Compiler<'src> { position: self.current_position, }); } + let (left_instruction, left_type, left_position) = + self.instructions + .pop() + .ok_or_else(|| CompileError::ExpectedExpression { + found: self.previous_token.to_owned(), + position: self.previous_position, + })?; + + // TODO: Check if the left type is a valid type for comparison + + let (left, push_back_left) = self.handle_binary_argument(&left_instruction); + + if push_back_left { + self.instructions + .push((left_instruction, left_type, left_position)); + } let operator = self.current_token; let operator_position = self.current_position; @@ -1012,30 +1029,18 @@ impl<'src> Compiler<'src> { found: self.previous_token.to_owned(), position: self.previous_position, })?; - let (left_instruction, left_type, left_position) = - self.instructions - .pop() - .ok_or_else(|| CompileError::ExpectedExpression { - found: self.previous_token.to_owned(), - position: self.previous_position, - })?; - let (left, push_back_left) = self.handle_binary_argument(&left_instruction); - let (right, push_back_right) = self.handle_binary_argument(&right_instruction); - // TODO: Check if the left type is a valid type for comparison // TODO: Check if the right type is a valid type for comparison - // TODO: Check if the left and right types are compatible - if push_back_left { - self.instructions - .push((left_instruction, left_type, left_position)); - } + let (right, push_back_right) = self.handle_binary_argument(&right_instruction); if push_back_right { self.instructions .push((right_instruction, right_type, right_position)); } + // TODO: Check if the left and right types are compatible + let comparison = match operator { Token::DoubleEqual => Instruction::equal(true, left, right), Token::BangEqual => Instruction::equal(false, left, right), @@ -1074,9 +1079,6 @@ impl<'src> Compiler<'src> { } fn parse_logical_binary(&mut self) -> Result<(), CompileError> { - let operator = self.current_token; - let operator_position = self.current_position; - let rule = ParseRule::from(&operator); let (left_instruction, left_type, left_position) = self.instructions .pop() @@ -1084,42 +1086,19 @@ impl<'src> Compiler<'src> { found: self.previous_token.to_owned(), position: self.previous_position, })?; - let operand_register = if left_instruction.operation() == Operation::MOVE { - let Move { operand: to, .. } = Move::from(&left_instruction); - let local = self.get_local(to.index())?; - - local.register_index - } else if left_instruction.yields_value() { - let register = left_instruction.a_field(); - - self.instructions - .push((left_instruction, left_type, left_position)); - - register - } else { - return Err(CompileError::ExpectedExpression { - found: self.previous_token.to_owned(), - position: self.previous_position, - }); - }; // TODO: Check if the left type is boolean - self.advance()?; - self.parse_sub_expression(&rule.precedence)?; + let (left, push_back_left) = self.handle_binary_argument(&left_instruction); - let (mut right_instruction, right_type, right_position) = self - .instructions - .pop() - .ok_or_else(|| CompileError::ExpectedExpression { - found: self.previous_token.to_owned(), - position: self.previous_position, - })?; - - // TODO: Check if the right type is boolean - - right_instruction.set_a_field(operand_register); + if push_back_left { + self.instructions + .push((left_instruction, left_type.clone(), left_position)); + } + let operator = self.current_token; + let operator_position = self.current_position; + let rule = ParseRule::from(&operator); let test_boolean = match operator { Token::DoubleAmpersand => true, Token::DoublePipe => false, @@ -1131,13 +1110,35 @@ impl<'src> Compiler<'src> { }); } }; - let test = Instruction::test(operand_register, test_boolean); + let test = Instruction::test(left.index(), test_boolean); let jump = Instruction::jump(1, true); self.emit_instruction(test, Type::None, operator_position); self.emit_instruction(jump, Type::None, operator_position); - self.instructions - .push((right_instruction, right_type, right_position)); + + self.advance()?; + self.parse_sub_expression(&rule.precedence)?; + + // TODO: Check if the right type is boolean + + if matches!( + self.get_last_operations(), + Some([ + Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, + Operation::JUMP, + Operation::LOAD_ENCODED | Operation::LOAD_CONSTANT, + Operation::LOAD_ENCODED | Operation::LOAD_CONSTANT, + ]) + ) { + let instruction_count = self.instructions.len(); + let loaders = self + .instructions + .get_many_mut([instruction_count - 1, instruction_count - 2]) + .unwrap(); // Safe because the indices in bounds and do not overlap + + loaders[0].0.set_a_field(left.index()); + loaders[1].0.set_a_field(left.index()); + } let instructions_length = self.instructions.len(); diff --git a/dust-lang/src/instruction/mod.rs b/dust-lang/src/instruction/mod.rs index 373632e..ed9252b 100644 --- a/dust-lang/src/instruction/mod.rs +++ b/dust-lang/src/instruction/mod.rs @@ -660,7 +660,7 @@ impl Instruction { Operation::EQUAL => Equal::from(*self).to_string(), Operation::LESS => Less::from(*self).to_string(), Operation::LESS_EQUAL => LessEqual::from(*self).to_string(), - Operation::TEST => Test::from(*self).to_string(), + Operation::TEST => Test::from(self).to_string(), Operation::TEST_SET => TestSet::from(*self).to_string(), Operation::CALL => Call::from(*self).to_string(), Operation::CALL_NATIVE => CallNative::from(*self).to_string(), diff --git a/dust-lang/src/instruction/operation.rs b/dust-lang/src/instruction/operation.rs index 4693ad6..eb361bf 100644 --- a/dust-lang/src/instruction/operation.rs +++ b/dust-lang/src/instruction/operation.rs @@ -11,7 +11,7 @@ pub struct Operation(pub u8); impl Operation { pub const NO_OP: Operation = Operation(0); - // Stack manipulation + // Register manipulation pub const MOVE: Operation = Operation(1); pub const CLOSE: Operation = Operation(2); diff --git a/dust-lang/src/instruction/test.rs b/dust-lang/src/instruction/test.rs index 779de20..a34e87e 100644 --- a/dust-lang/src/instruction/test.rs +++ b/dust-lang/src/instruction/test.rs @@ -9,8 +9,8 @@ pub struct Test { pub test_value: bool, } -impl From for Test { - fn from(instruction: Instruction) -> Self { +impl From<&Instruction> for Test { + fn from(instruction: &Instruction) -> Self { let operand_register = instruction.b_field(); let test_value = instruction.c_field() != 0; diff --git a/dust-lang/src/vm/thread.rs b/dust-lang/src/vm/thread.rs index aca67c9..96a9224 100644 --- a/dust-lang/src/vm/thread.rs +++ b/dust-lang/src/vm/thread.rs @@ -51,7 +51,7 @@ impl Thread { let instruction = &instructions[ip]; - info!("Run instruction {}", instruction.operation()); + info!("IP = {ip} Run {}", instruction.operation()); match instruction.operation() { Operation::MOVE => { @@ -1334,7 +1334,19 @@ impl Thread { } _ => unreachable!("Invalid LESS_EQUAL instruction"), }, + Operation::TEST => { + let operand_register_index = instruction.b_field() as usize; + let test_value = instruction.c_field() != 0; + let operand_boolean = current_frame + .registers + .booleans + .get(operand_register_index) + .copy_value(); + if operand_boolean == test_value { + current_frame.ip += 1; + } + } Operation::JUMP => { let offset = instruction.b_field() as usize; let is_positive = instruction.c_field() != 0; diff --git a/dust-lang/tests/comparison/equal.rs b/dust-lang/tests/comparison/equal.rs index 289a344..0ec4aa2 100644 --- a/dust-lang/tests/comparison/equal.rs +++ b/dust-lang/tests/comparison/equal.rs @@ -159,3 +159,51 @@ fn equal_strings() { assert_eq!(chunk, compile(source).unwrap()); assert_eq!(return_value, run(source).unwrap()); } + +#[test] +fn equal_lists() { + let source = "[1, 2, 3] == [4, 5, 6]"; + let chunk = Chunk { + r#type: FunctionType::new([], [], Type::Boolean), + instructions: vec![ + Instruction::load_constant(0, 0, TypeCode::INTEGER, false), + Instruction::load_constant(1, 1, TypeCode::INTEGER, false), + Instruction::load_constant(2, 2, TypeCode::INTEGER, false), + Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false), + Instruction::load_constant(3, 3, TypeCode::INTEGER, false), + Instruction::load_constant(4, 4, TypeCode::INTEGER, false), + Instruction::load_constant(5, 5, TypeCode::INTEGER, false), + Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false), + Instruction::equal( + true, + Operand::Register(0, TypeCode::LIST), + Operand::Register(1, TypeCode::LIST), + ), + Instruction::jump(1, true), + Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true), + Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false), + Instruction::r#return(true, 0, TypeCode::BOOLEAN), + ], + positions: vec![ + Span(1, 2), + Span(4, 5), + Span(7, 8), + Span(0, 9), + Span(14, 15), + Span(17, 18), + Span(20, 21), + Span(13, 22), + Span(0, 22), + Span(0, 22), + Span(0, 22), + Span(0, 22), + Span(22, 22), + ], + integer_constants: vec![1, 2, 3, 4, 5, 6], + ..Chunk::default() + }; + let return_value = Some(Value::boolean(false)); + + assert_eq!(chunk, compile(source).unwrap()); + assert_eq!(return_value, run(source).unwrap()); +} diff --git a/dust-lang/tests/comparison/greater.rs b/dust-lang/tests/comparison/greater.rs index 1deb1a4..771373d 100644 --- a/dust-lang/tests/comparison/greater.rs +++ b/dust-lang/tests/comparison/greater.rs @@ -153,3 +153,51 @@ fn greater_strings() { assert_eq!(chunk, compile(source).unwrap()); assert_eq!(return_value, run(source).unwrap()); } + +#[test] +fn greater_lists() { + let source = "[1, 2, 3] > [4, 5, 6]"; + let chunk = Chunk { + r#type: FunctionType::new([], [], Type::Boolean), + instructions: vec![ + Instruction::load_constant(0, 0, TypeCode::INTEGER, false), + Instruction::load_constant(1, 1, TypeCode::INTEGER, false), + Instruction::load_constant(2, 2, TypeCode::INTEGER, false), + Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false), + Instruction::load_constant(3, 3, TypeCode::INTEGER, false), + Instruction::load_constant(4, 4, TypeCode::INTEGER, false), + Instruction::load_constant(5, 5, TypeCode::INTEGER, false), + Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false), + Instruction::less_equal( + false, + Operand::Register(0, TypeCode::LIST), + Operand::Register(1, TypeCode::LIST), + ), + Instruction::jump(1, true), + Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true), + Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false), + Instruction::r#return(true, 0, TypeCode::BOOLEAN), + ], + positions: vec![ + Span(1, 2), + Span(4, 5), + Span(7, 8), + Span(0, 9), + Span(13, 14), + Span(16, 17), + Span(19, 20), + Span(12, 21), + Span(0, 21), + Span(0, 21), + Span(0, 21), + Span(0, 21), + Span(21, 21), + ], + integer_constants: vec![1, 2, 3, 4, 5, 6], + ..Chunk::default() + }; + let return_value = Some(Value::boolean(false)); + + assert_eq!(chunk, compile(source).unwrap()); + assert_eq!(return_value, run(source).unwrap()); +} diff --git a/dust-lang/tests/comparison/greater_equal.rs b/dust-lang/tests/comparison/greater_equal.rs index 11c9747..ada4c57 100644 --- a/dust-lang/tests/comparison/greater_equal.rs +++ b/dust-lang/tests/comparison/greater_equal.rs @@ -194,3 +194,51 @@ fn greater_equal_strings() { assert_eq!(chunk, compile(source).unwrap()); assert_eq!(return_value, run(source).unwrap()); } + +#[test] +fn greater_equal_lists() { + let source = "[1, 2, 3] >= [4, 5, 6]"; + let chunk = Chunk { + r#type: FunctionType::new([], [], Type::Boolean), + instructions: vec![ + Instruction::load_constant(0, 0, TypeCode::INTEGER, false), + Instruction::load_constant(1, 1, TypeCode::INTEGER, false), + Instruction::load_constant(2, 2, TypeCode::INTEGER, false), + Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false), + Instruction::load_constant(3, 3, TypeCode::INTEGER, false), + Instruction::load_constant(4, 4, TypeCode::INTEGER, false), + Instruction::load_constant(5, 5, TypeCode::INTEGER, false), + Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false), + Instruction::less( + false, + Operand::Register(0, TypeCode::LIST), + Operand::Register(1, TypeCode::LIST), + ), + Instruction::jump(1, true), + Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true), + Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false), + Instruction::r#return(true, 0, TypeCode::BOOLEAN), + ], + positions: vec![ + Span(1, 2), + Span(4, 5), + Span(7, 8), + Span(0, 9), + Span(14, 15), + Span(17, 18), + Span(20, 21), + Span(13, 22), + Span(0, 22), + Span(0, 22), + Span(0, 22), + Span(0, 22), + Span(22, 22), + ], + integer_constants: vec![1, 2, 3, 4, 5, 6], + ..Chunk::default() + }; + let return_value = Some(Value::boolean(false)); + + assert_eq!(chunk, compile(source).unwrap()); + assert_eq!(return_value, run(source).unwrap()); +} diff --git a/dust-lang/tests/comparison/less.rs b/dust-lang/tests/comparison/less.rs index 13c2c69..2d75c61 100644 --- a/dust-lang/tests/comparison/less.rs +++ b/dust-lang/tests/comparison/less.rs @@ -188,3 +188,51 @@ fn less_strings() { assert_eq!(chunk, compile(source).unwrap()); assert_eq!(return_value, run(source).unwrap()); } + +#[test] +fn less_lists() { + let source = "[1, 2, 3] < [4, 5, 6]"; + let chunk = Chunk { + r#type: FunctionType::new([], [], Type::Boolean), + instructions: vec![ + Instruction::load_constant(0, 0, TypeCode::INTEGER, false), + Instruction::load_constant(1, 1, TypeCode::INTEGER, false), + Instruction::load_constant(2, 2, TypeCode::INTEGER, false), + Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false), + Instruction::load_constant(3, 3, TypeCode::INTEGER, false), + Instruction::load_constant(4, 4, TypeCode::INTEGER, false), + Instruction::load_constant(5, 5, TypeCode::INTEGER, false), + Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false), + Instruction::less( + true, + Operand::Register(0, TypeCode::LIST), + Operand::Register(1, TypeCode::LIST), + ), + Instruction::jump(1, true), + Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true), + Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false), + Instruction::r#return(true, 0, TypeCode::BOOLEAN), + ], + positions: vec![ + Span(1, 2), + Span(4, 5), + Span(7, 8), + Span(0, 9), + Span(13, 14), + Span(16, 17), + Span(19, 20), + Span(12, 21), + Span(0, 21), + Span(0, 21), + Span(0, 21), + Span(0, 21), + Span(21, 21), + ], + integer_constants: vec![1, 2, 3, 4, 5, 6], + ..Chunk::default() + }; + let return_value = Some(Value::boolean(true)); + + assert_eq!(chunk, compile(source).unwrap()); + assert_eq!(return_value, run(source).unwrap()); +} diff --git a/dust-lang/tests/comparison/less_equal.rs b/dust-lang/tests/comparison/less_equal.rs index 81c3188..98e6170 100644 --- a/dust-lang/tests/comparison/less_equal.rs +++ b/dust-lang/tests/comparison/less_equal.rs @@ -194,3 +194,51 @@ fn less_equal_strings() { assert_eq!(chunk, compile(source).unwrap()); assert_eq!(return_value, run(source).unwrap()); } + +#[test] +fn less_equal_lists() { + let source = "[1, 2, 3] <= [4, 5, 6]"; + let chunk = Chunk { + r#type: FunctionType::new([], [], Type::Boolean), + instructions: vec![ + Instruction::load_constant(0, 0, TypeCode::INTEGER, false), + Instruction::load_constant(1, 1, TypeCode::INTEGER, false), + Instruction::load_constant(2, 2, TypeCode::INTEGER, false), + Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false), + Instruction::load_constant(3, 3, TypeCode::INTEGER, false), + Instruction::load_constant(4, 4, TypeCode::INTEGER, false), + Instruction::load_constant(5, 5, TypeCode::INTEGER, false), + Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false), + Instruction::less_equal( + true, + Operand::Register(0, TypeCode::LIST), + Operand::Register(1, TypeCode::LIST), + ), + Instruction::jump(1, true), + Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true), + Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false), + Instruction::r#return(true, 0, TypeCode::BOOLEAN), + ], + positions: vec![ + Span(1, 2), + Span(4, 5), + Span(7, 8), + Span(0, 9), + Span(14, 15), + Span(17, 18), + Span(20, 21), + Span(13, 22), + Span(0, 22), + Span(0, 22), + Span(0, 22), + Span(0, 22), + Span(22, 22), + ], + integer_constants: vec![1, 2, 3, 4, 5, 6], + ..Chunk::default() + }; + let return_value = Some(Value::boolean(true)); + + assert_eq!(chunk, compile(source).unwrap()); + assert_eq!(return_value, run(source).unwrap()); +} diff --git a/dust-lang/tests/comparison/not_equal.rs b/dust-lang/tests/comparison/not_equal.rs index 7976216..e02fe57 100644 --- a/dust-lang/tests/comparison/not_equal.rs +++ b/dust-lang/tests/comparison/not_equal.rs @@ -194,3 +194,51 @@ fn not_equal_strings() { assert_eq!(chunk, compile(source).unwrap()); assert_eq!(return_value, run(source).unwrap()); } + +#[test] +fn not_equal_lists() { + let source = "[1, 2, 3] != [4, 5, 6]"; + let chunk = Chunk { + r#type: FunctionType::new([], [], Type::Boolean), + instructions: vec![ + Instruction::load_constant(0, 0, TypeCode::INTEGER, false), + Instruction::load_constant(1, 1, TypeCode::INTEGER, false), + Instruction::load_constant(2, 2, TypeCode::INTEGER, false), + Instruction::load_list(0, TypeCode::INTEGER, 0, 2, false), + Instruction::load_constant(3, 3, TypeCode::INTEGER, false), + Instruction::load_constant(4, 4, TypeCode::INTEGER, false), + Instruction::load_constant(5, 5, TypeCode::INTEGER, false), + Instruction::load_list(1, TypeCode::INTEGER, 3, 5, false), + Instruction::equal( + false, + Operand::Register(0, TypeCode::LIST), + Operand::Register(1, TypeCode::LIST), + ), + Instruction::jump(1, true), + Instruction::load_encoded(0, true as u8, TypeCode::BOOLEAN, true), + Instruction::load_encoded(0, false as u8, TypeCode::BOOLEAN, false), + Instruction::r#return(true, 0, TypeCode::BOOLEAN), + ], + positions: vec![ + Span(1, 2), + Span(4, 5), + Span(7, 8), + Span(0, 9), + Span(14, 15), + Span(17, 18), + Span(20, 21), + Span(13, 22), + Span(0, 22), + Span(0, 22), + Span(0, 22), + Span(0, 22), + Span(22, 22), + ], + integer_constants: vec![1, 2, 3, 4, 5, 6], + ..Chunk::default() + }; + let return_value = Some(Value::boolean(true)); + + assert_eq!(chunk, compile(source).unwrap()); + assert_eq!(return_value, run(source).unwrap()); +}