From b02180d48e1e63d204a2f1f782842d37c7a4370d Mon Sep 17 00:00:00 2001 From: tkshsbcue Date: Sun, 15 Mar 2026 21:21:53 +0530 Subject: [PATCH] feat(vm): emit `Opcode::Debugger` for `debugger;` statements Previously, the bytecompiler silently ignored `debugger;` statements (empty match arm). This adds a dedicated `Opcode::Debugger` so the statement is represented in bytecode and visible during tracing. Currently a no-op, this lays groundwork for future debugger integration to intercept these points. --- core/engine/src/bytecompiler/statement/mod.rs | 5 ++++- core/engine/src/vm/code_block.rs | 6 +++--- core/engine/src/vm/flowgraph/mod.rs | 7 +++++-- core/engine/src/vm/opcode/debugger/mod.rs | 20 +++++++++++++++++++ core/engine/src/vm/opcode/mod.rs | 17 ++++++++++++++-- 5 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 core/engine/src/vm/opcode/debugger/mod.rs diff --git a/core/engine/src/bytecompiler/statement/mod.rs b/core/engine/src/bytecompiler/statement/mod.rs index 0b10b4ba224..2b9fc0f817a 100644 --- a/core/engine/src/bytecompiler/statement/mod.rs +++ b/core/engine/src/bytecompiler/statement/mod.rs @@ -98,7 +98,10 @@ impl ByteCompiler<'_> { } } Statement::With(with) => self.compile_with(with, use_expr), - Statement::Empty | Statement::Debugger => {} + Statement::Empty => {} + Statement::Debugger => { + self.bytecode.emit_debugger(); + } } } diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index ac04a4b1bf4..01c007cfdb1 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -858,7 +858,8 @@ impl CodeBlock { | Instruction::SuperCallSpread | Instruction::PopPrivateEnvironment | Instruction::Generator - | Instruction::AsyncGenerator => String::new(), + | Instruction::AsyncGenerator + | Instruction::Debugger => String::new(), Instruction::Reserved1 | Instruction::Reserved2 | Instruction::Reserved3 @@ -917,8 +918,7 @@ impl CodeBlock { | Instruction::Reserved56 | Instruction::Reserved57 | Instruction::Reserved58 - | Instruction::Reserved59 - | Instruction::Reserved60 => unreachable!("Reserved opcodes are unreachable"), + | Instruction::Reserved59 => unreachable!("Reserved opcodes are unreachable"), } } } diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index ea16ed893ca..af405a4780d 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -432,8 +432,11 @@ impl CodeBlock { | Instruction::Reserved56 | Instruction::Reserved57 | Instruction::Reserved58 - | Instruction::Reserved59 - | Instruction::Reserved60 => unreachable!("Reserved opcodes are unreachable"), + | Instruction::Reserved59 => unreachable!("Reserved opcodes are unreachable"), + Instruction::Debugger => { + graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); + graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); + } } } diff --git a/core/engine/src/vm/opcode/debugger/mod.rs b/core/engine/src/vm/opcode/debugger/mod.rs new file mode 100644 index 00000000000..07d39812fc0 --- /dev/null +++ b/core/engine/src/vm/opcode/debugger/mod.rs @@ -0,0 +1,20 @@ +use crate::{Context, vm::opcode::Operation}; + +/// `Debugger` implements the Opcode Operation for `Opcode::Debugger` +/// +/// Operation: +/// - No-op for now. Emitted for `debugger;` statements so they are +/// represented in bytecode and visible during tracing. +#[derive(Debug, Clone, Copy)] +pub(crate) struct Debugger; + +impl Debugger { + #[inline(always)] + pub(super) fn operation((): (), _: &mut Context) {} +} + +impl Operation for Debugger { + const NAME: &'static str = "Debugger"; + const INSTRUCTION: &'static str = "INST - Debugger"; + const COST: u8 = 1; +} diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 8a4ab8c72ee..3a9bd82cfeb 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -18,6 +18,7 @@ mod call; mod concat; mod control_flow; mod copy; +mod debugger; mod define; mod delete; mod environment; @@ -55,6 +56,8 @@ pub(crate) use control_flow::*; #[doc(inline)] pub(crate) use copy::*; #[doc(inline)] +pub(crate) use debugger::*; +#[doc(inline)] pub(crate) use define::*; #[doc(inline)] pub(crate) use delete::*; @@ -2141,6 +2144,18 @@ generate_opcodes! { /// - Output: dst CreateUnmappedArgumentsObject { dst: RegisterOperand }, + /// The `debugger` statement. + /// + /// Currently a no-op. This opcode is emitted for `debugger;` statements + /// so they are represented in the bytecode, enabling future debugger + /// integration to intercept them. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-debugger-statement + Debugger, + /// Reserved [`Opcode`]. Reserved1 => Reserved, /// Reserved [`Opcode`]. @@ -2259,6 +2274,4 @@ generate_opcodes! { Reserved58 => Reserved, /// Reserved [`Opcode`]. Reserved59 => Reserved, - /// Reserved [`Opcode`]. - Reserved60 => Reserved, }