diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index c7ab89a6..a01d33ef 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -45,7 +45,7 @@ pub(crate) enum Label { ForInStart(u32), ForInEnd(u32), SwitchEnd(u32), - SwitchCase(u32), + SwitchCase(u32, u32), CatchBody(u32), FinallyBody(u32), TryEnd(u32), @@ -111,6 +111,14 @@ impl CompileCtx { self.label_counter = 0; } + pub(crate) fn reg_checkpoint(&self) -> u8 { + self.next_reg + } + + pub(crate) fn restore_reg_checkpoint(&mut self, checkpoint: u8) { + self.next_reg = checkpoint; + } + pub(crate) fn add_constant(&mut self, c: Constant) -> u16 { if let Some(idx) = self.constants.iter().position(|x| x == &c) { return idx as u16; diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs index 1b27d24d..924c5a3a 100644 --- a/crates/oxide_compiler/src/counter.rs +++ b/crates/oxide_compiler/src/counter.rs @@ -143,6 +143,7 @@ impl Compiler { ctx.push_switch(end_label); self.count_expression(&sw.discriminant, ctx); + let compare_reg_checkpoint = ctx.reg_checkpoint(); let cases = &sw.cases; for case in cases.iter() { @@ -151,6 +152,7 @@ impl Compiler { ctx.projected_pc += 1; // EQ ctx.alloc_reg(); // eq result ctx.projected_pc += 1; // JMP_IF_TRUE + ctx.restore_reg_checkpoint(compare_reg_checkpoint); } } @@ -160,7 +162,7 @@ impl Compiler { } for (case_idx, case) in cases.iter().enumerate() { - let case_label = Label::SwitchCase(id * 256 + case_idx as u32); + let case_label = Label::SwitchCase(id, case_idx as u32); ctx.label_map.insert(case_label, ctx.projected_pc); for s in &case.consequent { self.count_statement(s, ctx); diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index 163253de..29ee0f5c 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -299,6 +299,7 @@ impl Compiler { ctx.push_switch(end_label); let disc_reg = self.emit_expression(&sw.discriminant, ctx)?; + let compare_reg_checkpoint = ctx.reg_checkpoint(); let cases = &sw.cases; for (case_idx, case) in cases.iter().enumerate() { @@ -307,10 +308,11 @@ impl Compiler { let eq_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::EQ, eq_reg, disc_reg, test_reg)); - let case_label = Label::SwitchCase(id * 256 + case_idx as u32); + let case_label = Label::SwitchCase(id, case_idx as u32); let body_pos = ctx.resolve_label(case_label)?; let offset = (body_pos as isize) - (ctx.bytecode.len() as isize); ctx.emit(opcode::encode_jmp_if_true(eq_reg, offset as i16)); + ctx.restore_reg_checkpoint(compare_reg_checkpoint); } } diff --git a/crates/oxide_compiler/tests/compile_stmt_tests.rs b/crates/oxide_compiler/tests/compile_stmt_tests.rs index bd93d593..520d453c 100644 --- a/crates/oxide_compiler/tests/compile_stmt_tests.rs +++ b/crates/oxide_compiler/tests/compile_stmt_tests.rs @@ -281,3 +281,25 @@ fn compile_continue_in_switch_errors() { }); assert!(result.is_err(), "continue inside switch should error"); } + +#[test] +fn compile_large_adjacent_switches_do_not_collide_labels() { + let mut source = String::from("var r=0;switch(256){"); + for i in 0..=256 { + source.push_str(&format!("case {i}:")); + if i == 256 { + source.push_str("r=1;break;"); + } + } + source.push_str("}switch(0){case 0:r=r+1;break;}r"); + let module = compile_source(&source); + assert!( + module + .bytecode + .iter() + .filter(|&&i| opcode::opcode(i) == OpCode::JMP_IF_TRUE) + .count() + >= 258, + "expected both large switches to compile with distinct case labels" + ); +} diff --git a/crates/oxide_vm/tests/control_flow_tests.rs b/crates/oxide_vm/tests/control_flow_tests.rs index 316de53a..40b65d09 100644 --- a/crates/oxide_vm/tests/control_flow_tests.rs +++ b/crates/oxide_vm/tests/control_flow_tests.rs @@ -256,3 +256,16 @@ fn eval_break_in_switch_in_loop() { "2" ); } + +#[test] +fn eval_large_adjacent_switches_do_not_collide_labels() { + let mut source = String::from("var r=0;switch(256){"); + for i in 0..=256 { + source.push_str(&format!("case {i}:")); + if i == 256 { + source.push_str("r=1;break;"); + } + } + source.push_str("}switch(0){case 0:r=r+1;break;}r"); + assert_eq!(eval(&source), "2"); +}