From 1da61f0873db7ae50bb4db321b48b8fbf6665b9c Mon Sep 17 00:00:00 2001 From: Jeff Date: Sun, 20 Oct 2024 10:20:09 -0400 Subject: [PATCH] Begin reworking jump instructions --- dust-lang/src/chunk.rs | 34 +++------ dust-lang/src/instruction.rs | 129 +++++++++++---------------------- dust-lang/src/parser.rs | 80 ++++++++++++++++---- dust-lang/src/vm.rs | 15 +--- dust-lang/tests/expressions.rs | 81 ++++++++++++++++----- examples/assets/fibonacci.js | 11 +++ examples/fibonacci.ds | 8 +- 7 files changed, 201 insertions(+), 157 deletions(-) create mode 100644 examples/assets/fibonacci.js diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index c1acc7c..75bef76 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -59,6 +59,10 @@ impl Chunk { self.instructions.is_empty() } + pub fn instructions(&self) -> &[(Instruction, Span)] { + &self.instructions + } + pub fn instructions_mut(&mut self) -> &mut Vec<(Instruction, Span)> { &mut self.instructions } @@ -282,8 +286,8 @@ impl<'a> ChunkDisassembler<'a> { const INSTRUCTION_HEADER: [&'static str; 4] = [ "Instructions", "------------", - "INDEX BYTECODE OPERATION INFO JUMP POSITION ", - "----- -------- --------------- -------------------- -------- -------------", + "INDEX BYTECODE OPERATION INFO POSITION ", + "----- -------- --------------- ------------------------- -------------", ]; const CONSTANT_HEADER: [&'static str; 4] = @@ -495,30 +499,10 @@ impl<'a> ChunkDisassembler<'a> { for (index, (instruction, position)) in self.chunk.instructions.iter().enumerate() { let position = position.to_string(); let operation = instruction.operation().to_string(); - let (info, jump_offset) = instruction.disassembly_info(Some(self.chunk)); - let info = if let Some(info) = info { - info - } else { - " ".to_string() - }; - let jump_offset = if let Some(jump_offset) = jump_offset { - let index = index as isize; - let jump_index = { - if jump_offset > 0 { - index + (jump_offset + 1) - } else { - index + jump_offset - } - }; - - format!("{index} -> {jump_index}") - } else { - " ".to_string() - }; + let info = instruction.disassembly_info(Some(self.chunk)); let bytecode = u32::from(instruction); - let instruction_display = format!( - "{index:<5} {bytecode:<08X} {operation:15} {info:20} {jump_offset:8} {position:13}" - ); + let instruction_display = + format!("{index:<5} {bytecode:<08X} {operation:15} {info:25} {position:13}"); push_details(&instruction_display, &mut disassembly); } diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index 03a0b50..31c5f2b 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -207,11 +207,10 @@ impl Instruction { instruction } - pub fn jump(offset: u8, is_positive: bool) -> Instruction { + pub fn jump(jump_to: u8) -> Instruction { let mut instruction = Instruction(Operation::Jump as u32); - instruction.set_b(offset); - instruction.set_c(if is_positive { 1 } else { 0 }); + instruction.set_b(jump_to); instruction } @@ -346,7 +345,7 @@ impl Instruction { ) } - pub fn disassembly_info(&self, chunk: Option<&Chunk>) -> (Option, Option) { + pub fn disassembly_info(&self, chunk: Option<&Chunk>) -> String { let format_arguments = || { let first_argument = if self.b_is_constant() { format!("C{}", self.b()) @@ -361,49 +360,43 @@ impl Instruction { (first_argument, second_argument) }; - let mut jump_offset = None; - let info = match self.operation() { - Operation::Move => Some(format!("R{} = R{}", self.a(), self.b())), + match self.operation() { + Operation::Move => format!("R{} = R{}", self.a(), self.b()), Operation::Close => { let from_register = self.b(); let to_register = self.c().saturating_sub(1); - Some(format!("R{from_register}..=R{to_register}")) + format!("R{from_register}..=R{to_register}") } Operation::LoadBoolean => { let to_register = self.a(); let boolean = self.b_as_boolean(); let jump = self.c_as_boolean(); - let info = if jump { - jump_offset = Some(1); - format!("R{to_register} = {boolean} && JUMP") + if jump { + format!("R{to_register} = {boolean} && SKIP") } else { format!("R{to_register} = {boolean}") - }; - - Some(info) + } } Operation::LoadConstant => { let register_index = self.a(); let constant_index = self.b(); - let jump = if self.c_as_boolean() { - jump_offset = Some(1); + let jump = self.c_as_boolean(); - "&& JUMP" + if jump { + format!("R{register_index} = C{constant_index} && SKIP") } else { - "" - }; - - Some(format!("R{register_index} = C{constant_index} {jump}",)) + format!("R{register_index} = C{constant_index}") + } } Operation::LoadList => { let to_register = self.a(); let first_index = self.b(); let last_index = self.c(); - Some(format!("R{to_register} = [R{first_index}..=R{last_index}]",)) + format!("R{to_register} = [R{first_index}..=R{last_index}]",) } Operation::LoadSelf => { let to_register = self.a(); @@ -416,7 +409,7 @@ impl Instruction { }) .unwrap(); - Some(format!("R{to_register} = {name}")) + format!("R{to_register} = {name}") } Operation::DefineLocal => { let to_register = self.a(); @@ -431,14 +424,12 @@ impl Instruction { }; let mutable_display = if self.c_as_boolean() { "mut" } else { "" }; - Some(format!( - "L{local_index} = R{to_register} {mutable_display} {identifier_display}" - )) + format!("L{local_index} = R{to_register} {mutable_display} {identifier_display}") } Operation::GetLocal => { let local_index = self.b(); - Some(format!("R{} = L{}", self.a(), local_index)) + format!("R{} = L{}", self.a(), local_index) } Operation::SetLocal => { let local_index = self.b(); @@ -451,60 +442,43 @@ impl Instruction { "???".to_string() }; - Some(format!( - "L{} = R{} {}", - local_index, - self.a(), - identifier_display - )) + format!("L{} = R{} {}", local_index, self.a(), identifier_display) } Operation::Add => { let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); - Some(format!( - "R{to_register} = {first_argument} + {second_argument}", - )) + format!("R{to_register} = {first_argument} + {second_argument}",) } Operation::Subtract => { let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); - Some(format!( - "R{to_register} = {first_argument} - {second_argument}", - )) + format!("R{to_register} = {first_argument} - {second_argument}",) } Operation::Multiply => { let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); - Some(format!( - "R{to_register} = {first_argument} * {second_argument}", - )) + format!("R{to_register} = {first_argument} * {second_argument}",) } Operation::Divide => { let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); - Some(format!( - "R{to_register} = {first_argument} / {second_argument}", - )) + format!("R{to_register} = {first_argument} / {second_argument}",) } Operation::Modulo => { let to_register = self.a(); let (first_argument, second_argument) = format_arguments(); - Some(format!( - "R{to_register} = {first_argument} % {second_argument}", - )) + format!("R{to_register} = {first_argument} % {second_argument}",) } Operation::Test => { let to_register = self.a(); let test_value = self.c_as_boolean(); - jump_offset = Some(1); - - Some(format!("if R{to_register} != {test_value} {{ JUMP }}",)) + format!("if R{to_register} != {test_value} {{ SKIP }}") } Operation::TestSet => { let to_register = self.a(); @@ -512,39 +486,26 @@ impl Instruction { let test_value = self.c_as_boolean(); let bang = if test_value { "" } else { "!" }; - jump_offset = Some(1); - - Some(format!( - "if {bang}R{to_register} {{ R{to_register} = R{argument} }}", - )) + format!("if {bang}R{to_register} {{ R{to_register} = R{argument} }}",) } Operation::Equal => { let comparison_symbol = if self.a_as_boolean() { "==" } else { "!=" }; let (first_argument, second_argument) = format_arguments(); - jump_offset = Some(1); - Some(format!( - "if {first_argument} {comparison_symbol} {second_argument} {{ JUMP }}", - )) + format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}") } Operation::Less => { let comparison_symbol = if self.a_as_boolean() { "<" } else { ">=" }; let (first_argument, second_argument) = format_arguments(); - jump_offset = Some(1); - Some(format!( - "if {first_argument} {comparison_symbol} {second_argument}", - )) + format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}") } Operation::LessEqual => { let comparison_symbol = if self.a_as_boolean() { "<=" } else { ">" }; let (first_argument, second_argument) = format_arguments(); - jump_offset = Some(1); - Some(format!( - "if {first_argument} {comparison_symbol} {second_argument}", - )) + format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}") } Operation::Negate => { let to_register = self.a(); @@ -554,7 +515,7 @@ impl Instruction { format!("R{}", self.b()) }; - Some(format!("R{to_register} = -{argument}")) + format!("R{to_register} = -{argument}") } Operation::Not => { let to_register = self.a(); @@ -564,19 +525,12 @@ impl Instruction { format!("R{}", self.b()) }; - Some(format!("R{to_register} = !{argument}")) + format!("R{to_register} = !{argument}") } Operation::Jump => { - let offset = self.b() as isize; - let is_positive = self.c_as_boolean(); + let jump_to = self.b(); - if is_positive { - jump_offset = Some(offset); - } else { - jump_offset = Some(-offset); - } - - None + format!("JUMP TO {jump_to}") } Operation::Call => { let to_register = self.a(); @@ -598,12 +552,18 @@ impl Instruction { output.push(')'); - Some(output) + output } - Operation::Return => None, - }; + Operation::Return => { + let should_return_value = self.b_as_boolean(); - (info, jump_offset) + if should_return_value { + "->".to_string() + } else { + "".to_string() + } + } + } } } @@ -818,11 +778,10 @@ mod tests { #[test] fn jump() { - let instruction = Instruction::jump(4, true); + let instruction = Instruction::jump(4); assert_eq!(instruction.operation(), Operation::Jump); assert_eq!(instruction.b(), 4); - assert!(instruction.c_as_boolean()); } #[test] diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 07e57f5..2414cb3 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -171,6 +171,31 @@ impl<'src> Parser<'src> { Some(operations) } + fn get_last_jump_mut(&mut self) -> Option<&mut Instruction> { + self.current_statement + .iter_mut() + .rev() + .find_map(|(instruction, _)| { + if let Operation::Jump = instruction.operation() { + Some(instruction) + } else { + None + } + }) + .or_else(|| { + self.chunk + .instructions_mut() + .iter_mut() + .find_map(|(instruction, _)| { + if let Operation::Jump = instruction.operation() { + Some(instruction) + } else { + None + } + }) + }) + } + fn emit_constant(&mut self, value: Value, position: Span) -> Result<(), ParseError> { let constant_index = self.chunk.push_constant(value, position)?; let register = self.next_register(); @@ -620,7 +645,10 @@ impl<'src> Parser<'src> { let register = self.next_register(); self.emit_instruction(instruction, operator_position); - self.emit_instruction(Instruction::jump(1, true), operator_position); + + let jump_to = (self.chunk.len() + self.current_statement.len() + 2) as u8; + + self.emit_instruction(Instruction::jump(jump_to), operator_position); self.emit_instruction( Instruction::load_boolean(register, true, true), operator_position, @@ -661,7 +689,10 @@ impl<'src> Parser<'src> { self.advance()?; self.emit_instruction(left_instruction, left_position); self.emit_instruction(instruction, operator_position); - self.emit_instruction(Instruction::jump(1, true), operator_position); + + let jump_to = (self.chunk.len() + self.current_statement.len() + 2) as u8; + + self.emit_instruction(Instruction::jump(jump_to), operator_position); self.parse_sub_expression(&rule.precedence)?; Ok(()) @@ -863,6 +894,12 @@ impl<'src> Parser<'src> { self.parse_block(block_allowed)?; } + let if_end = self.chunk.len() + self.current_statement.len(); + + if let Some(if_jump) = self.get_last_jump_mut() { + if_jump.set_b(if_end as u8); + } + let last_operation = self .current_statement .last() @@ -891,6 +928,22 @@ impl<'src> Parser<'src> { if let Token::LeftCurlyBrace = self.current_token { self.parse_block(block_allowed)?; + self.commit_current_statement(); + + let else_end = (self.chunk.len() + self.current_statement.len()) as u8; + + if let Operation::LoadBoolean | Operation::LoadConstant = self + .chunk + .get_instruction(if_end, self.current_position) + .map(|(instruction, _)| instruction.operation())? + { + } else { + self.chunk.insert_instruction( + if_end, + Instruction::jump(else_end + 1), + self.current_position, + )?; + } return Ok(()); } @@ -908,6 +961,9 @@ impl<'src> Parser<'src> { fn parse_while(&mut self, allowed: Allowed) -> Result<(), ParseError> { self.advance()?; + + let jump_start = (self.chunk.len() + self.current_statement.len()) as u8; + self.parse_expression()?; if let Some( @@ -916,30 +972,25 @@ impl<'src> Parser<'src> { { self.current_statement.pop(); self.current_statement.pop(); - self.current_statement.pop(); } - let jump_start = self.chunk.len() + self.current_statement.len(); - self.parse_block(Allowed { assignment: true, explicit_return: allowed.explicit_return, implicit_return: false, })?; - let jump_end = self.chunk.len() + self.current_statement.len(); - let jump_distance = jump_end.abs_diff(jump_start) as u8 + 1; - let jump_back = Instruction::jump(jump_distance + 1, false); + let jump_end = (self.chunk.len() + self.current_statement.len()) as u8; + + if let Some(jump) = self.get_last_jump_mut() { + jump.set_b(jump_end + 1); + } + + let jump_back = Instruction::jump(jump_start); self.emit_instruction(jump_back, self.current_position); self.commit_current_statement(); - self.chunk.insert_instruction( - jump_start, - Instruction::jump(jump_distance, true), - self.current_position, - )?; - Ok(()) } @@ -956,6 +1007,7 @@ impl<'src> Parser<'src> { let parsed_expression = self .current_statement .last() + .or_else(|| self.chunk.instructions().last()) .map(|(instruction, _)| instruction.yields_value()) .unwrap_or(false); let end_of_statement = matches!( diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index df46596..2783050 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -58,10 +58,7 @@ impl Vm { self.ip - 1, position, instruction.operation(), - instruction - .disassembly_info(Some(&self.chunk)) - .0 - .unwrap_or_default() + instruction.disassembly_info(Some(&self.chunk)) ); match instruction.operation() { @@ -348,15 +345,9 @@ impl Vm { self.set(instruction.a(), not, position)?; } Operation::Jump => { - let offset = instruction.b(); - let is_positive = instruction.c_as_boolean(); - let new_ip = if is_positive { - self.ip + offset as usize - } else { - self.ip - (offset + 1) as usize - }; + let jump_to = instruction.b(); - self.ip = new_ip; + self.ip = jump_to as usize; } Operation::Call => { let to_register = instruction.a(); diff --git a/dust-lang/tests/expressions.rs b/dust-lang/tests/expressions.rs index ff3a984..c0afad4 100644 --- a/dust-lang/tests/expressions.rs +++ b/dust-lang/tests/expressions.rs @@ -59,7 +59,7 @@ fn and() { vec![ (Instruction::load_boolean(0, true, false), Span(0, 4)), (Instruction::test(0, false), Span(5, 7)), - (Instruction::jump(1, true), Span(5, 7)), + (Instruction::jump(4), Span(5, 7)), (Instruction::load_boolean(1, false, false), Span(8, 13)), (Instruction::r#return(true), Span(13, 13)), ], @@ -258,7 +258,7 @@ fn equal() { .set_c_is_constant(), Span(2, 4) ), - (Instruction::jump(1, true), Span(2, 4)), + (Instruction::jump(3), Span(2, 4)), (Instruction::load_boolean(0, true, true), Span(2, 4)), (Instruction::load_boolean(0, false, false), Span(2, 4)), (Instruction::r#return(true), Span(6, 6)), @@ -286,7 +286,7 @@ fn equality_assignment_long() { .set_c_is_constant(), Span(13, 15) ), - (Instruction::jump(1, true), Span(13, 15)), + (Instruction::jump(3), Span(13, 15)), (Instruction::load_boolean(0, true, true), Span(20, 24)), (Instruction::load_boolean(0, false, false), Span(34, 39)), (Instruction::define_local(0, 0, false), Span(4, 5)), @@ -316,7 +316,7 @@ fn equality_assignment_short() { .set_c_is_constant(), Span(10, 12) ), - (Instruction::jump(1, true), Span(10, 12)), + (Instruction::jump(3), Span(10, 12)), (Instruction::load_boolean(0, true, true), Span(10, 12)), (Instruction::load_boolean(0, false, false), Span(10, 12)), (Instruction::define_local(0, 0, false), Span(4, 5)), @@ -479,7 +479,7 @@ fn greater() { .set_c_is_constant(), Span(2, 3) ), - (Instruction::jump(1, true), Span(2, 3)), + (Instruction::jump(3), Span(2, 3)), (Instruction::load_boolean(0, true, true), Span(2, 3)), (Instruction::load_boolean(0, false, false), Span(2, 3)), (Instruction::r#return(true), Span(5, 5)), @@ -507,7 +507,7 @@ fn greater_than_or_equal() { .set_c_is_constant(), Span(2, 4) ), - (Instruction::jump(1, true), Span(2, 4)), + (Instruction::jump(3), Span(2, 4)), (Instruction::load_boolean(0, true, true), Span(2, 4)), (Instruction::load_boolean(0, false, false), Span(2, 4)), (Instruction::r#return(true), Span(6, 6)), @@ -521,7 +521,54 @@ fn greater_than_or_equal() { } #[test] -fn if_else_expression() { +fn if_else_complex() { + let source = " + if 1 == 1 { + 1; 2; 3; 4; + } else { + 1; 2; 3; 4; + }"; + + assert_eq!( + parse(source), + Ok(Chunk::with_data( + None, + vec![ + ( + *Instruction::equal(true, 0, 1) + .set_b_is_constant() + .set_c_is_constant(), + Span(9, 11) + ), + (Instruction::jump(7), Span(9, 11)), + (Instruction::load_constant(0, 0, false), Span(9, 11)), + (Instruction::load_constant(1, 1, false), Span(9, 11)), + (Instruction::load_constant(2, 2, false), Span(9, 11)), + (Instruction::load_constant(3, 3, false), Span(9, 11)), + (Instruction::jump(11), Span(9, 11)), + (Instruction::load_constant(0, 0, false), Span(9, 11)), + (Instruction::load_constant(1, 1, false), Span(9, 11)), + (Instruction::load_constant(2, 2, false), Span(9, 11)), + (Instruction::load_constant(3, 3, false), Span(9, 11)), + (Instruction::r#return(true), Span(11, 11)), + ], + vec![ + Value::integer(1), + Value::integer(2), + Value::integer(3), + Value::integer(4), + Value::integer(1), + Value::integer(2), + Value::integer(3), + Value::integer(4) + ], + vec![] + )) + ) +} + +#[test] +fn if_else_simple() { let source = "if 1 == 1 { 2 } else { 3 }"; assert_eq!( @@ -535,7 +582,7 @@ fn if_else_expression() { .set_c_is_constant(), Span(5, 7) ), - (Instruction::jump(1, true), Span(5, 7)), + (Instruction::jump(3), Span(5, 7)), (Instruction::load_constant(0, 2, true), Span(12, 13)), (Instruction::load_constant(1, 3, false), Span(23, 24)), (Instruction::r#return(true), Span(26, 26)), @@ -568,7 +615,7 @@ fn if_expression_false() { .set_c_is_constant(), Span(5, 7) ), - (Instruction::jump(1, true), Span(5, 7)), + (Instruction::jump(3), Span(5, 7)), (Instruction::load_constant(0, 2, false), Span(12, 13)), ], vec![Value::integer(1), Value::integer(2), Value::integer(2)], @@ -594,7 +641,7 @@ fn if_expression_true() { .set_c_is_constant(), Span(5, 7) ), - (Instruction::jump(1, true), Span(5, 7)), + (Instruction::jump(3), Span(5, 7)), (Instruction::load_constant(0, 2, false), Span(12, 13)), ], vec![Value::integer(1), Value::integer(1), Value::integer(2)], @@ -620,7 +667,7 @@ fn less_than() { .set_c_is_constant(), Span(2, 3) ), - (Instruction::jump(1, true), Span(2, 3)), + (Instruction::jump(3), Span(2, 3)), (Instruction::load_boolean(0, true, true), Span(2, 3)), (Instruction::load_boolean(0, false, false), Span(2, 3)), (Instruction::r#return(true), Span(5, 5)), @@ -648,7 +695,7 @@ fn less_than_or_equal() { .set_c_is_constant(), Span(2, 4) ), - (Instruction::jump(1, true), Span(2, 4)), + (Instruction::jump(3), Span(2, 4)), (Instruction::load_boolean(0, true, true), Span(2, 4)), (Instruction::load_boolean(0, false, false), Span(2, 4)), (Instruction::r#return(true), Span(6, 6)), @@ -907,7 +954,7 @@ fn not_equal() { .set_c_is_constant(), Span(2, 4) ), - (Instruction::jump(1, true), Span(2, 4)), + (Instruction::jump(3), Span(2, 4)), (Instruction::load_boolean(0, true, true), Span(2, 4)), (Instruction::load_boolean(0, false, false), Span(2, 4)), (Instruction::r#return(true), Span(6, 6)), @@ -931,7 +978,7 @@ fn or() { vec![ (Instruction::load_boolean(0, true, false), Span(0, 4)), (Instruction::test(0, true), Span(5, 7)), - (Instruction::jump(1, true), Span(5, 7)), + (Instruction::jump(4), Span(5, 7)), (Instruction::load_boolean(1, false, false), Span(8, 13)), (Instruction::r#return(true), Span(13, 13)), ], @@ -1062,7 +1109,7 @@ fn variable_and() { (Instruction::define_local(1, 1, false), Span(18, 19)), (Instruction::get_local(2, 0), Span(29, 30)), (Instruction::test(2, false), Span(31, 33)), - (Instruction::jump(1, true), Span(31, 33)), + (Instruction::jump(8), Span(31, 33)), (Instruction::get_local(3, 1), Span(34, 35)), (Instruction::r#return(true), Span(35, 35)), ], @@ -1092,9 +1139,9 @@ fn r#while() { *Instruction::less(true, 0, 1).set_c_is_constant(), Span(23, 24) ), - (Instruction::jump(2, true), Span(41, 42)), + (Instruction::jump(7), Span(23, 24)), (*Instruction::add(0, 0, 2).set_c_is_constant(), Span(39, 40)), - (Instruction::jump(3, false), Span(41, 42)), + (Instruction::jump(2), Span(41, 42)), (Instruction::get_local(1, 0), Span(41, 42)), (Instruction::r#return(true), Span(42, 42)), ], diff --git a/examples/assets/fibonacci.js b/examples/assets/fibonacci.js new file mode 100644 index 0000000..7f1e036 --- /dev/null +++ b/examples/assets/fibonacci.js @@ -0,0 +1,11 @@ +function fib(n) { + if (n <= 0) { + return 0; + } else if (n === 1) { + return 1; + } else { + return fib(n - 1) + fib(n - 2); + } +} + +console.log(fib(10)); diff --git a/examples/fibonacci.ds b/examples/fibonacci.ds index e352fbf..c624338 100644 --- a/examples/fibonacci.ds +++ b/examples/fibonacci.ds @@ -1,11 +1,11 @@ fn fib (n: int) -> int { if n <= 0 { - return 0; + 0 } else if n == 1 { - return 1; + 1 } else { - return fib(n - 1) + fib(n - 2); + fib(n - 1) + fib(n - 2) } } -fib(10) +fib(2)