From e47a274d5511a7027427d993f4d569b9112fec55 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Sun, 15 Mar 2026 04:25:59 +0530 Subject: [PATCH 1/6] feat: Implement core disposal logic for explicit resource management Signed-off-by: Abhinav Sharma --- core/engine/src/bytecompiler/mod.rs | 16 +- .../src/bytecompiler/statement/block.rs | 28 ++- core/engine/src/vm/code_block.rs | 10 +- core/engine/src/vm/mod.rs | 37 ++++ .../src/vm/opcode/disposal/add_disposable.rs | 45 +++++ .../vm/opcode/disposal/dispose_resources.rs | 74 +++++++ core/engine/src/vm/opcode/disposal/mod.rs | 7 + .../src/vm/opcode/disposal/push_scope.rs | 25 +++ core/engine/src/vm/opcode/mod.rs | 83 +++++++- core/engine/tests/disposal.rs | 181 ++++++++++++++++++ 10 files changed, 487 insertions(+), 19 deletions(-) create mode 100644 core/engine/src/vm/opcode/disposal/add_disposable.rs create mode 100644 core/engine/src/vm/opcode/disposal/dispose_resources.rs create mode 100644 core/engine/src/vm/opcode/disposal/mod.rs create mode 100644 core/engine/src/vm/opcode/disposal/push_scope.rs create mode 100644 core/engine/tests/disposal.rs diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index db4607d1abd..5c32e1d3053 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -2258,11 +2258,10 @@ impl<'ctx> ByteCompiler<'ctx> { } else { self.bytecode.emit_store_undefined(value.variable()); } - - // TODO(@abhinavs1920): Add resource to disposal stack - // For now, we just bind the variable like a let declaration - // Full implementation will add: AddDisposableResource opcode - + + // Add resource to disposal stack + self.bytecode.emit_add_disposable_resource(value.variable()); + self.emit_binding(BindingOpcode::InitLexical, ident, &value); self.register_allocator.dealloc(value); } @@ -2274,9 +2273,10 @@ impl<'ctx> ByteCompiler<'ctx> { } else { self.bytecode.emit_store_undefined(value.variable()); } - - // TODO: Same as above - + + // Add resource to disposal stack + self.bytecode.emit_add_disposable_resource(value.variable()); + self.compile_declaration_pattern( pattern, BindingOpcode::InitLexical, diff --git a/core/engine/src/bytecompiler/statement/block.rs b/core/engine/src/bytecompiler/statement/block.rs index 99da2023627..28331593cba 100644 --- a/core/engine/src/bytecompiler/statement/block.rs +++ b/core/engine/src/bytecompiler/statement/block.rs @@ -1,12 +1,38 @@ use crate::bytecompiler::ByteCompiler; -use boa_ast::statement::Block; +use boa_ast::{ + declaration::LexicalDeclaration, + operations::{LexicallyScopedDeclaration, lexically_scoped_declarations}, + statement::Block, +}; impl ByteCompiler<'_> { /// Compile a [`Block`] `boa_ast` node pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) { let scope = self.push_declarative_scope(block.scope()); self.block_declaration_instantiation(block); + + // Check if this block has any using declarations + let has_using = lexically_scoped_declarations(block) + .iter() + .any(|decl| matches!( + decl, + LexicallyScopedDeclaration::LexicalDeclaration( + LexicalDeclaration::Using(_) | LexicalDeclaration::AwaitUsing(_) + ) + )); + + // Push disposal scope if this block has using declarations + if has_using { + self.bytecode.emit_push_disposal_scope(); + } + self.compile_statement_list(block.statement_list(), use_expr, true); + + // Dispose resources if this block has using declarations + if has_using { + self.bytecode.emit_dispose_resources(); + } + self.pop_declarative_scope(scope); } } diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 13e8b0abced..81c4adcf2eb 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -865,10 +865,12 @@ impl CodeBlock { | Instruction::PopPrivateEnvironment | Instruction::Generator | Instruction::AsyncGenerator => String::new(), - Instruction::Reserved1 - | Instruction::Reserved2 - | Instruction::Reserved3 - | Instruction::Reserved4 + Instruction::AddDisposableResource { value } => { + format!("value: {value}") + } + Instruction::DisposeResources => String::new(), + Instruction::PushDisposalScope => String::new(), + Instruction::Reserved4 | Instruction::Reserved5 | Instruction::Reserved6 | Instruction::Reserved7 diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index f01c9bb47dd..f1d2e7f8f7c 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -98,6 +98,16 @@ pub struct Vm { pub(crate) shadow_stack: ShadowStack, + /// Stack of disposable resources for explicit resource management. + /// + /// Resources are added via `using` declarations and disposed in reverse order (LIFO) + /// when the scope exits. Each entry contains (value, dispose_method, scope_depth). + pub(crate) disposal_stack: Vec<(JsValue, JsValue)>, + + /// Tracks the disposal stack depth for each scope level. + /// When a scope exits, we dispose resources back to this depth. + pub(crate) disposal_scope_depths: Vec, + #[cfg(feature = "trace")] pub(crate) trace: bool, #[cfg(feature = "trace")] @@ -349,6 +359,8 @@ impl Vm { native_active_function: None, host_call_depth: 0, shadow_stack: ShadowStack::default(), + disposal_stack: Vec::new(), + disposal_scope_depths: Vec::new(), #[cfg(feature = "trace")] trace: false, #[cfg(feature = "trace")] @@ -598,6 +610,31 @@ impl Vm { pub(crate) fn take_return_value(&mut self) -> JsValue { std::mem::take(&mut self.return_value) } + + /// Push a disposable resource onto the disposal stack. + pub(crate) fn push_disposable_resource(&mut self, value: JsValue, method: JsValue) { + self.disposal_stack.push((value, method)); + } + + /// Pop a disposable resource from the disposal stack. + pub(crate) fn pop_disposable_resource(&mut self) -> Option<(JsValue, JsValue)> { + self.disposal_stack.pop() + } + + /// Mark the current disposal stack depth for a new scope. + pub(crate) fn push_disposal_scope(&mut self) { + self.disposal_scope_depths.push(self.disposal_stack.len()); + } + + /// Get the disposal stack depth for the current scope. + pub(crate) fn current_disposal_scope_depth(&self) -> usize { + self.disposal_scope_depths.last().copied().unwrap_or(0) + } + + /// Pop the disposal scope depth marker. + pub(crate) fn pop_disposal_scope(&mut self) { + self.disposal_scope_depths.pop(); + } } #[allow(clippy::print_stdout)] diff --git a/core/engine/src/vm/opcode/disposal/add_disposable.rs b/core/engine/src/vm/opcode/disposal/add_disposable.rs new file mode 100644 index 00000000000..b526cf35f51 --- /dev/null +++ b/core/engine/src/vm/opcode/disposal/add_disposable.rs @@ -0,0 +1,45 @@ +use crate::{ + vm::opcode::{Operation, RegisterOperand}, + Context, JsResult, +}; + +/// `AddDisposableResource` implements the AddDisposableResource operation. +/// +/// This opcode adds a resource to the disposal stack for later cleanup. +/// +/// Operation: +/// - Stack: **=>** +/// - Registers: +/// - Input: value +pub(crate) struct AddDisposableResource; + +impl AddDisposableResource { + pub(crate) fn operation(value: RegisterOperand, context: &mut Context) -> JsResult<()> { + let value = context.vm.get_register(value.into()).clone(); + + // Per spec: If value is null or undefined, return + if value.is_null_or_undefined() { + return Ok(()); + } + + // Get the dispose method (value[Symbol.dispose]) + let key = crate::JsSymbol::dispose(); + let dispose_method = value.get_method(key, context)?; + + // If dispose method is None, return + let Some(dispose_method) = dispose_method else { + return Ok(()); + }; + + // Add to disposal stack + context.vm.push_disposable_resource(value, dispose_method.into()); + + Ok(()) + } +} + +impl Operation for AddDisposableResource { + const NAME: &'static str = "AddDisposableResource"; + const INSTRUCTION: &'static str = "INST - AddDisposableResource"; + const COST: u8 = 3; +} diff --git a/core/engine/src/vm/opcode/disposal/dispose_resources.rs b/core/engine/src/vm/opcode/disposal/dispose_resources.rs new file mode 100644 index 00000000000..543aca9c8d1 --- /dev/null +++ b/core/engine/src/vm/opcode/disposal/dispose_resources.rs @@ -0,0 +1,74 @@ +use crate::{ + vm::opcode::Operation, + Context, JsError, JsNativeError, JsResult, +}; + +/// `DisposeResources` implements the DisposeResources operation. +/// +/// This opcode disposes all resources in the current disposal stack. +/// +/// Operation: +/// - Stack: **=>** +pub(crate) struct DisposeResources; + +impl DisposeResources { + pub(crate) fn operation((): (), context: &mut Context) -> JsResult<()> { + let mut suppressed_error: Option = None; + + // Get the scope depth to know how many resources to dispose + let scope_depth = context.vm.current_disposal_scope_depth(); + + // Dispose resources in reverse order (LIFO) until we reach the scope depth + while context.vm.disposal_stack.len() > scope_depth { + if let Some((value, method)) = context.vm.pop_disposable_resource() { + // Call the dispose method + let result = method.call(&value, &[], context); + + // If an error occurs, aggregate it + if let Err(err) = result { + suppressed_error = Some(match suppressed_error { + None => err, + Some(previous) => { + // Create a SuppressedError + create_suppressed_error(err, previous, context) + } + }); + } + } + } + + // Pop the disposal scope depth marker + context.vm.pop_disposal_scope(); + + // If there were any errors, throw the aggregated error + if let Some(err) = suppressed_error { + return Err(err); + } + + Ok(()) + } +} + +impl Operation for DisposeResources { + const NAME: &'static str = "DisposeResources"; + const INSTRUCTION: &'static str = "INST - DisposeResources"; + const COST: u8 = 5; +} + +/// Helper function to create a SuppressedError +fn create_suppressed_error(_error: JsError, suppressed: JsError, _context: &mut Context) -> JsError { + // For now, we'll create a simple error that contains both errors + // TODO: Implement proper SuppressedError builtin in Phase 2 + let message = format!( + "An error was suppressed during disposal: {}", + suppressed + ); + + let err = JsNativeError::error() + .with_message(message) + .into(); + + // Attach the original error as a property + // This is a temporary solution until SuppressedError is implemented + err +} diff --git a/core/engine/src/vm/opcode/disposal/mod.rs b/core/engine/src/vm/opcode/disposal/mod.rs new file mode 100644 index 00000000000..f2e900be465 --- /dev/null +++ b/core/engine/src/vm/opcode/disposal/mod.rs @@ -0,0 +1,7 @@ +mod add_disposable; +mod dispose_resources; +mod push_scope; + +pub(crate) use add_disposable::*; +pub(crate) use dispose_resources::*; +pub(crate) use push_scope::*; diff --git a/core/engine/src/vm/opcode/disposal/push_scope.rs b/core/engine/src/vm/opcode/disposal/push_scope.rs new file mode 100644 index 00000000000..75100444f76 --- /dev/null +++ b/core/engine/src/vm/opcode/disposal/push_scope.rs @@ -0,0 +1,25 @@ +use crate::{ + vm::opcode::Operation, + Context, JsResult, +}; + +/// `PushDisposalScope` marks the current disposal stack depth for a new scope. +/// +/// This opcode is emitted at the beginning of blocks that contain `using` declarations. +/// +/// Operation: +/// - Stack: **=>** +pub(crate) struct PushDisposalScope; + +impl PushDisposalScope { + pub(crate) fn operation((): (), context: &mut Context) -> JsResult<()> { + context.vm.push_disposal_scope(); + Ok(()) + } +} + +impl Operation for PushDisposalScope { + const NAME: &'static str = "PushDisposalScope"; + const INSTRUCTION: &'static str = "INST - PushDisposalScope"; + const COST: u8 = 1; +} diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 8b61ad67b73..732918ef529 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -20,6 +20,7 @@ mod control_flow; mod copy; mod define; mod delete; +mod disposal; mod environment; mod function; mod generator; @@ -59,6 +60,8 @@ pub(crate) use define::*; #[doc(inline)] pub(crate) use delete::*; #[doc(inline)] +pub(crate) use disposal::*; +#[doc(inline)] pub(crate) use environment::*; #[doc(inline)] pub(crate) use function::*; @@ -2137,12 +2140,80 @@ generate_opcodes! { /// - Output: dst CreateUnmappedArgumentsObject { dst: RegisterOperand }, - /// Reserved [`Opcode`]. - Reserved1 => Reserved, - /// Reserved [`Opcode`]. - Reserved2 => Reserved, - /// Reserved [`Opcode`]. - Reserved3 => Reserved, + /// Performs [`HasRestrictedGlobalProperty ( N )`][spec] + /// + /// - Operands: + /// - index: `VaryingOperand` + /// - Registers: + /// - Output: dst + /// + /// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty + HasRestrictedGlobalProperty { dst: RegisterOperand, index: VaryingOperand }, + + /// Performs [`CanDeclareGlobalFunction ( N )`][spec] + /// + /// - Operands: + /// - index: `VaryingOperand` + /// - Registers: + /// - Output: dst + /// + /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction + CanDeclareGlobalFunction { dst: RegisterOperand, index: VaryingOperand }, + + /// Performs [`CanDeclareGlobalVar ( N )`][spec] + /// + /// - Operands: + /// - index: `VaryingOperand` + /// - Registers: + /// - Output: dst + /// + /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar + CanDeclareGlobalVar { dst: RegisterOperand, index: VaryingOperand }, + + /// Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec] + /// + /// - Operands: + /// - configurable: `bool` + /// - name_index: `VaryingOperand` + /// - Registers: + /// - Input: src + /// + /// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding + CreateGlobalFunctionBinding { src: RegisterOperand, configurable: VaryingOperand, name_index: VaryingOperand }, + + /// Performs [`CreateGlobalVarBinding ( N, V, D )`][spec] + /// + /// - Operands: + /// - configurable: `bool` + /// - name_index: `VaryingOperand` + /// + /// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding + CreateGlobalVarBinding { configurable: VaryingOperand, name_index: VaryingOperand }, + + /// Add a disposable resource to the disposal stack. + /// + /// This opcode implements the AddDisposableResource abstract operation. + /// It gets the dispose method from the value and adds it to the disposal stack. + /// + /// - Registers: + /// - Input: value + AddDisposableResource { value: RegisterOperand }, + + /// Dispose all resources in the current disposal stack. + /// + /// This opcode implements the DisposeResources abstract operation. + /// It calls all dispose methods in reverse order (LIFO). + /// + /// - Stack: **=>** + DisposeResources, + + /// Push a new disposal scope. + /// + /// This marks the current disposal stack depth for a new scope. + /// When DisposeResources is called, it will dispose resources back to this depth. + /// + /// - Stack: **=>** + PushDisposalScope, /// Reserved [`Opcode`]. Reserved4 => Reserved, /// Reserved [`Opcode`]. diff --git a/core/engine/tests/disposal.rs b/core/engine/tests/disposal.rs new file mode 100644 index 00000000000..02436166fef --- /dev/null +++ b/core/engine/tests/disposal.rs @@ -0,0 +1,181 @@ +use boa_engine::{Context, JsResult, JsValue, Source}; + +#[test] +fn basic_disposal() { + let mut context = Context::default(); + + let result = context.eval(Source::from_bytes( + r" + let disposed = false; + { + using x = { + [Symbol.dispose]() { + disposed = true; + } + }; + } + disposed; + ", + )); + + assert!(result.is_ok()); + let value = result.unwrap(); + assert_eq!(value, JsValue::from(true)); +} + +#[test] +fn disposal_order() { + let mut context = Context::default(); + + let result = context.eval(Source::from_bytes( + r" + let order = []; + { + using a = { + [Symbol.dispose]() { + order.push('a'); + } + }; + using b = { + [Symbol.dispose]() { + order.push('b'); + } + }; + } + order.join(','); + ", + )); + + assert!(result.is_ok()); + let value = result.unwrap(); + // Should dispose in reverse order: b, then a + assert_eq!(value.to_string(&mut context).unwrap(), "b,a"); +} + +#[test] +fn null_undefined_disposal() { + let mut context = Context::default(); + + let result = context.eval(Source::from_bytes( + r" + { + using x = null; + using y = undefined; + } + 'ok'; + ", + )); + + assert!(result.is_ok()); + let value = result.unwrap(); + assert_eq!(value.to_string(&mut context).unwrap(), "ok"); +} + +#[test] +fn disposal_with_no_method() { + let mut context = Context::default(); + + let result = context.eval(Source::from_bytes( + r" + { + using x = { + // No Symbol.dispose method + }; + } + 'ok'; + ", + )); + + assert!(result.is_ok()); + let value = result.unwrap(); + assert_eq!(value.to_string(&mut context).unwrap(), "ok"); +} + +#[test] +#[ignore = "Disposal on exception requires try-finally integration - will be implemented in next phase"] +fn disposal_on_exception() { + let mut context = Context::default(); + + let result = context.eval(Source::from_bytes( + r" + let disposed = false; + try { + using x = { + [Symbol.dispose]() { + disposed = true; + } + }; + throw new Error('test error'); + } catch (e) { + // Disposal should happen before catch + } + disposed; + ", + )); + + assert!(result.is_ok()); + let value = result.unwrap(); + assert_eq!(value, JsValue::from(true)); +} + +#[test] +fn nested_scopes() { + let mut context = Context::default(); + + let result = context.eval(Source::from_bytes( + r" + let order = []; + { + using a = { + [Symbol.dispose]() { + order.push('a'); + } + }; + { + using b = { + [Symbol.dispose]() { + order.push('b'); + } + }; + } + // b should be disposed here + order.push('middle'); + } + // a should be disposed here + order.join(','); + ", + )); + + assert!(result.is_ok()); + let value = result.unwrap(); + // Should dispose b first, then a + assert_eq!(value.to_string(&mut context).unwrap(), "b,middle,a"); +} + +#[test] +fn multiple_resources_in_one_declaration() { + let mut context = Context::default(); + + let result = context.eval(Source::from_bytes( + r" + let order = []; + { + using a = { + [Symbol.dispose]() { + order.push('a'); + } + }, b = { + [Symbol.dispose]() { + order.push('b'); + } + }; + } + order.join(','); + ", + )); + + assert!(result.is_ok()); + let value = result.unwrap(); + // Should dispose in reverse order: b, then a + assert_eq!(value.to_string(&mut context).unwrap(), "b,a"); +} From 81a3f595ca2cf449d3f79111b83fbfc1bbc1396d Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Sun, 15 Mar 2026 12:35:28 +0530 Subject: [PATCH 2/6] fix: opcode enum Signed-off-by: Abhinav Sharma --- core/engine/src/vm/code_block.rs | 25 +++-- core/engine/src/vm/flowgraph/mod.rs | 39 ++++++-- core/engine/src/vm/opcode/global.rs | 150 ++++++++++++++++++++++++++++ core/engine/src/vm/opcode/mod.rs | 23 ++--- core/engine/tests/disposal.rs | 9 +- 5 files changed, 213 insertions(+), 33 deletions(-) create mode 100644 core/engine/src/vm/opcode/global.rs diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index a3a436c6e1c..b111cd4b8e1 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -396,6 +396,24 @@ impl CodeBlock { | Instruction::CreateUnmappedArgumentsObject { dst } | Instruction::RestParameterInit { dst } | Instruction::StoreNewArray { dst } => format!("dst:{dst}"), + Instruction::HasRestrictedGlobalProperty { dst, index } + | Instruction::CanDeclareGlobalFunction { dst, index } + | Instruction::CanDeclareGlobalVar { dst, index } => { + format!("dst: {dst}, index: {index}") + } + Instruction::CreateGlobalFunctionBinding { + src, + configurable, + name_index, + } => { + format!("src: {src}, configurable: {configurable}, name_index: {name_index}") + } + Instruction::CreateGlobalVarBinding { + configurable, + name_index, + } => { + format!("configurable: {configurable}, name_index: {name_index}") + } Instruction::Add { lhs, rhs, dst } | Instruction::Sub { lhs, rhs, dst } | Instruction::Div { lhs, rhs, dst } @@ -931,12 +949,7 @@ impl CodeBlock { | Instruction::Reserved52 | Instruction::Reserved53 | Instruction::Reserved54 - | Instruction::Reserved55 - | Instruction::Reserved56 - | Instruction::Reserved57 - | Instruction::Reserved58 - | Instruction::Reserved59 - | Instruction::Reserved60 => unreachable!("Reserved opcodes are unreachable"), + | Instruction::Reserved55 => unreachable!("Reserved opcodes are unreachable"), } } } diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 2f96e6edcbd..87be537cf0e 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -367,17 +367,39 @@ impl CodeBlock { | Instruction::CheckReturn | Instruction::BindThisValue { .. } | Instruction::CreateMappedArgumentsObject { .. } - | Instruction::CreateUnmappedArgumentsObject { .. } => { + | Instruction::CreateUnmappedArgumentsObject { .. } + | Instruction::HasRestrictedGlobalProperty { .. } + | Instruction::CanDeclareGlobalFunction { .. } + | Instruction::CanDeclareGlobalVar { .. } + | Instruction::CreateGlobalFunctionBinding { .. } + | Instruction::CreateGlobalVarBinding { .. } => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } Instruction::Return => { graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::Red); } - Instruction::Reserved1 - | Instruction::Reserved2 - | Instruction::Reserved3 - | Instruction::Reserved4 + Instruction::AddDisposableResource { value } => { + let label = format!("AddDisposableResource value: {value}"); + graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); + } + Instruction::DisposeResources => { + graph.add_node( + previous_pc, + NodeShape::None, + "DisposeResources".into(), + Color::None, + ); + } + Instruction::PushDisposalScope => { + graph.add_node( + previous_pc, + NodeShape::None, + "PushDisposalScope".into(), + Color::None, + ); + } + Instruction::Reserved4 | Instruction::Reserved5 | Instruction::Reserved6 | Instruction::Reserved7 @@ -428,12 +450,7 @@ impl CodeBlock { | Instruction::Reserved52 | Instruction::Reserved53 | Instruction::Reserved54 - | Instruction::Reserved55 - | Instruction::Reserved56 - | Instruction::Reserved57 - | Instruction::Reserved58 - | Instruction::Reserved59 - | Instruction::Reserved60 => unreachable!("Reserved opcodes are unreachable"), + | Instruction::Reserved55 => unreachable!("Reserved opcodes are unreachable"), } } diff --git a/core/engine/src/vm/opcode/global.rs b/core/engine/src/vm/opcode/global.rs new file mode 100644 index 00000000000..d2bd6565c92 --- /dev/null +++ b/core/engine/src/vm/opcode/global.rs @@ -0,0 +1,150 @@ +use crate::{ + vm::opcode::{Operation, RegisterOperand, IndexOperand}, + Context, JsResult, +}; + +/// `HasRestrictedGlobalProperty` implements the Opcode Operation for `Opcode::HasRestrictedGlobalProperty` +/// +/// Operation: +/// - Performs [`HasRestrictedGlobalProperty ( N )`][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty +pub(crate) struct HasRestrictedGlobalProperty; + +impl HasRestrictedGlobalProperty { + pub(crate) fn operation( + (dst, index): (RegisterOperand, IndexOperand), + context: &mut Context, + ) -> JsResult<()> { + let code_block = context.vm.frame().code_block(); + let name = code_block.constant_string(index.into()); + let result = context.has_restricted_global_property(&name)?; + context.vm.set_register(dst.into(), result.into()); + Ok(()) + } +} + +impl Operation for HasRestrictedGlobalProperty { + const NAME: &'static str = "HasRestrictedGlobalProperty"; + const INSTRUCTION: &'static str = "INST - HasRestrictedGlobalProperty"; + const COST: u8 = 4; +} + +/// `CanDeclareGlobalFunction` implements the Opcode Operation for `Opcode::CanDeclareGlobalFunction` +/// +/// Operation: +/// - Performs [`CanDeclareGlobalFunction ( N )`][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction +pub(crate) struct CanDeclareGlobalFunction; + +impl CanDeclareGlobalFunction { + pub(crate) fn operation( + (dst, index): (RegisterOperand, IndexOperand), + context: &mut Context, + ) -> JsResult<()> { + let code_block = context.vm.frame().code_block(); + let name = code_block.constant_string(index.into()); + let result = context.can_declare_global_function(&name)?; + context.vm.set_register(dst.into(), result.into()); + Ok(()) + } +} + +impl Operation for CanDeclareGlobalFunction { + const NAME: &'static str = "CanDeclareGlobalFunction"; + const INSTRUCTION: &'static str = "INST - CanDeclareGlobalFunction"; + const COST: u8 = 4; +} + +/// `CanDeclareGlobalVar` implements the Opcode Operation for `Opcode::CanDeclareGlobalVar` +/// +/// Operation: +/// - Performs [`CanDeclareGlobalVar ( N )`][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar +pub(crate) struct CanDeclareGlobalVar; + +impl CanDeclareGlobalVar { + pub(crate) fn operation( + (dst, index): (RegisterOperand, IndexOperand), + context: &mut Context, + ) -> JsResult<()> { + let code_block = context.vm.frame().code_block(); + let name = code_block.constant_string(index.into()); + let result = context.can_declare_global_var(&name)?; + context.vm.set_register(dst.into(), result.into()); + Ok(()) + } +} + +impl Operation for CanDeclareGlobalVar { + const NAME: &'static str = "CanDeclareGlobalVar"; + const INSTRUCTION: &'static str = "INST - CanDeclareGlobalVar"; + const COST: u8 = 4; +} + +/// `CreateGlobalFunctionBinding` implements the Opcode Operation for `Opcode::CreateGlobalFunctionBinding` +/// +/// Operation: +/// - Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding +pub(crate) struct CreateGlobalFunctionBinding; + +impl CreateGlobalFunctionBinding { + pub(crate) fn operation( + (src, configurable, name_index): (RegisterOperand, IndexOperand, IndexOperand), + context: &mut Context, + ) -> JsResult<()> { + let code_block = context.vm.frame().code_block(); + let name = code_block.constant_string(name_index.into()); + let value = context.vm.get_register(src.into()).clone(); + let configurable = u32::from(configurable) != 0; + + // Convert JsValue to JsObject + let function = value + .as_object() + .ok_or_else(|| { + crate::JsNativeError::typ() + .with_message("value is not an object") + })? + .clone(); + + context.create_global_function_binding(name, function, configurable)?; + Ok(()) + } +} + +impl Operation for CreateGlobalFunctionBinding { + const NAME: &'static str = "CreateGlobalFunctionBinding"; + const INSTRUCTION: &'static str = "INST - CreateGlobalFunctionBinding"; + const COST: u8 = 4; +} + +/// `CreateGlobalVarBinding` implements the Opcode Operation for `Opcode::CreateGlobalVarBinding` +/// +/// Operation: +/// - Performs [`CreateGlobalVarBinding ( N, D )`][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding +pub(crate) struct CreateGlobalVarBinding; + +impl CreateGlobalVarBinding { + pub(crate) fn operation( + (configurable, name_index): (IndexOperand, IndexOperand), + context: &mut Context, + ) -> JsResult<()> { + let code_block = context.vm.frame().code_block(); + let name = code_block.constant_string(name_index.into()); + let configurable = u32::from(configurable) != 0; + context.create_global_var_binding(name, configurable)?; + Ok(()) + } +} + +impl Operation for CreateGlobalVarBinding { + const NAME: &'static str = "CreateGlobalVarBinding"; + const INSTRUCTION: &'static str = "INST - CreateGlobalVarBinding"; + const COST: u8 = 4; +} diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 90454a152a8..d1b9a3a77c7 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -25,6 +25,7 @@ mod environment; mod function; mod generator; mod get; +mod global; mod iteration; mod meta; mod new; @@ -70,6 +71,8 @@ pub(crate) use generator::*; #[doc(inline)] pub(crate) use get::*; #[doc(inline)] +pub(crate) use global::*; +#[doc(inline)] pub(crate) use iteration::*; #[doc(inline)] pub(crate) use meta::*; @@ -2150,7 +2153,7 @@ generate_opcodes! { /// - Output: dst /// /// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty - HasRestrictedGlobalProperty { dst: RegisterOperand, index: VaryingOperand }, + HasRestrictedGlobalProperty { dst: RegisterOperand, index: IndexOperand }, /// Performs [`CanDeclareGlobalFunction ( N )`][spec] /// @@ -2160,7 +2163,7 @@ generate_opcodes! { /// - Output: dst /// /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction - CanDeclareGlobalFunction { dst: RegisterOperand, index: VaryingOperand }, + CanDeclareGlobalFunction { dst: RegisterOperand, index: IndexOperand }, /// Performs [`CanDeclareGlobalVar ( N )`][spec] /// @@ -2170,7 +2173,7 @@ generate_opcodes! { /// - Output: dst /// /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar - CanDeclareGlobalVar { dst: RegisterOperand, index: VaryingOperand }, + CanDeclareGlobalVar { dst: RegisterOperand, index: IndexOperand }, /// Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec] /// @@ -2181,7 +2184,7 @@ generate_opcodes! { /// - Input: src /// /// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding - CreateGlobalFunctionBinding { src: RegisterOperand, configurable: VaryingOperand, name_index: VaryingOperand }, + CreateGlobalFunctionBinding { src: RegisterOperand, configurable: IndexOperand, name_index: IndexOperand }, /// Performs [`CreateGlobalVarBinding ( N, V, D )`][spec] /// @@ -2190,7 +2193,7 @@ generate_opcodes! { /// - name_index: `VaryingOperand` /// /// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding - CreateGlobalVarBinding { configurable: VaryingOperand, name_index: VaryingOperand }, + CreateGlobalVarBinding { configurable: IndexOperand, name_index: IndexOperand }, /// Add a disposable resource to the disposal stack. /// @@ -2320,14 +2323,4 @@ generate_opcodes! { Reserved54 => Reserved, /// Reserved [`Opcode`]. Reserved55 => Reserved, - /// Reserved [`Opcode`]. - Reserved56 => Reserved, - /// Reserved [`Opcode`]. - Reserved57 => Reserved, - /// Reserved [`Opcode`]. - Reserved58 => Reserved, - /// Reserved [`Opcode`]. - Reserved59 => Reserved, - /// Reserved [`Opcode`]. - Reserved60 => Reserved, } diff --git a/core/engine/tests/disposal.rs b/core/engine/tests/disposal.rs index 02436166fef..f042770eebe 100644 --- a/core/engine/tests/disposal.rs +++ b/core/engine/tests/disposal.rs @@ -1,4 +1,11 @@ -use boa_engine::{Context, JsResult, JsValue, Source}; +//! Tests for explicit resource management (using declarations). +//! +//! This module tests the core disposal mechanism for `using` declarations, +//! verifying that resources are properly disposed when scopes exit. + +#![allow(unused_crate_dependencies)] + +use boa_engine::{Context, JsValue, Source}; #[test] fn basic_disposal() { From 92b7fa06322d503697308a9c422cd2fb7c971395 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Mon, 16 Mar 2026 14:31:52 +0530 Subject: [PATCH 3/6] fix: formatting and lint issues Signed-off-by: Abhinav Sharma --- core/engine/src/bytecompiler/mod.rs | 8 ++--- .../src/bytecompiler/statement/block.rs | 18 +++++------ core/engine/src/vm/code_block.rs | 10 +++---- core/engine/src/vm/mod.rs | 10 +++---- .../src/vm/opcode/disposal/add_disposable.rs | 6 ++-- .../vm/opcode/disposal/dispose_resources.rs | 30 ++++++++----------- .../src/vm/opcode/disposal/push_scope.rs | 8 ++--- core/engine/src/vm/opcode/global.rs | 11 +++---- core/engine/src/vm/opcode/mod.rs | 4 +-- 9 files changed, 46 insertions(+), 59 deletions(-) diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 5c32e1d3053..62d731c652f 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -2258,10 +2258,10 @@ impl<'ctx> ByteCompiler<'ctx> { } else { self.bytecode.emit_store_undefined(value.variable()); } - + // Add resource to disposal stack self.bytecode.emit_add_disposable_resource(value.variable()); - + self.emit_binding(BindingOpcode::InitLexical, ident, &value); self.register_allocator.dealloc(value); } @@ -2273,10 +2273,10 @@ impl<'ctx> ByteCompiler<'ctx> { } else { self.bytecode.emit_store_undefined(value.variable()); } - + // Add resource to disposal stack self.bytecode.emit_add_disposable_resource(value.variable()); - + self.compile_declaration_pattern( pattern, BindingOpcode::InitLexical, diff --git a/core/engine/src/bytecompiler/statement/block.rs b/core/engine/src/bytecompiler/statement/block.rs index 28331593cba..0cf8f68e7cb 100644 --- a/core/engine/src/bytecompiler/statement/block.rs +++ b/core/engine/src/bytecompiler/statement/block.rs @@ -10,29 +10,29 @@ impl ByteCompiler<'_> { pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) { let scope = self.push_declarative_scope(block.scope()); self.block_declaration_instantiation(block); - + // Check if this block has any using declarations - let has_using = lexically_scoped_declarations(block) - .iter() - .any(|decl| matches!( + let has_using = lexically_scoped_declarations(block).iter().any(|decl| { + matches!( decl, LexicallyScopedDeclaration::LexicalDeclaration( LexicalDeclaration::Using(_) | LexicalDeclaration::AwaitUsing(_) ) - )); - + ) + }); + // Push disposal scope if this block has using declarations if has_using { self.bytecode.emit_push_disposal_scope(); } - + self.compile_statement_list(block.statement_list(), use_expr, true); - + // Dispose resources if this block has using declarations if has_using { self.bytecode.emit_dispose_resources(); } - + self.pop_declarative_scope(scope); } } diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index b111cd4b8e1..5f63eb630fe 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -892,12 +892,10 @@ impl CodeBlock { | Instruction::SuperCallSpread | Instruction::PopPrivateEnvironment | Instruction::Generator - | Instruction::AsyncGenerator => String::new(), - Instruction::AddDisposableResource { value } => { - format!("value: {value}") - } - Instruction::DisposeResources => String::new(), - Instruction::PushDisposalScope => String::new(), + | Instruction::AsyncGenerator + | Instruction::AddDisposableResource { .. } + | Instruction::DisposeResources + | Instruction::PushDisposalScope => String::new(), Instruction::Reserved4 | Instruction::Reserved5 | Instruction::Reserved6 diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index f1d2e7f8f7c..e93de81ca4e 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -101,9 +101,9 @@ pub struct Vm { /// Stack of disposable resources for explicit resource management. /// /// Resources are added via `using` declarations and disposed in reverse order (LIFO) - /// when the scope exits. Each entry contains (value, dispose_method, scope_depth). + /// when the scope exits. Each entry contains (`value`, `dispose_method`, `scope_depth`). pub(crate) disposal_stack: Vec<(JsValue, JsValue)>, - + /// Tracks the disposal stack depth for each scope level. /// When a scope exits, we dispose resources back to this depth. pub(crate) disposal_scope_depths: Vec, @@ -620,17 +620,17 @@ impl Vm { pub(crate) fn pop_disposable_resource(&mut self) -> Option<(JsValue, JsValue)> { self.disposal_stack.pop() } - + /// Mark the current disposal stack depth for a new scope. pub(crate) fn push_disposal_scope(&mut self) { self.disposal_scope_depths.push(self.disposal_stack.len()); } - + /// Get the disposal stack depth for the current scope. pub(crate) fn current_disposal_scope_depth(&self) -> usize { self.disposal_scope_depths.last().copied().unwrap_or(0) } - + /// Pop the disposal scope depth marker. pub(crate) fn pop_disposal_scope(&mut self) { self.disposal_scope_depths.pop(); diff --git a/core/engine/src/vm/opcode/disposal/add_disposable.rs b/core/engine/src/vm/opcode/disposal/add_disposable.rs index b526cf35f51..a0402baca07 100644 --- a/core/engine/src/vm/opcode/disposal/add_disposable.rs +++ b/core/engine/src/vm/opcode/disposal/add_disposable.rs @@ -1,6 +1,6 @@ use crate::{ - vm::opcode::{Operation, RegisterOperand}, Context, JsResult, + vm::opcode::{Operation, RegisterOperand}, }; /// `AddDisposableResource` implements the AddDisposableResource operation. @@ -32,7 +32,9 @@ impl AddDisposableResource { }; // Add to disposal stack - context.vm.push_disposable_resource(value, dispose_method.into()); + context + .vm + .push_disposable_resource(value, dispose_method.into()); Ok(()) } diff --git a/core/engine/src/vm/opcode/disposal/dispose_resources.rs b/core/engine/src/vm/opcode/disposal/dispose_resources.rs index 543aca9c8d1..ee0d953e3df 100644 --- a/core/engine/src/vm/opcode/disposal/dispose_resources.rs +++ b/core/engine/src/vm/opcode/disposal/dispose_resources.rs @@ -1,7 +1,4 @@ -use crate::{ - vm::opcode::Operation, - Context, JsError, JsNativeError, JsResult, -}; +use crate::{Context, JsError, JsNativeError, JsResult, vm::opcode::Operation}; /// `DisposeResources` implements the DisposeResources operation. /// @@ -14,7 +11,7 @@ pub(crate) struct DisposeResources; impl DisposeResources { pub(crate) fn operation((): (), context: &mut Context) -> JsResult<()> { let mut suppressed_error: Option = None; - + // Get the scope depth to know how many resources to dispose let scope_depth = context.vm.current_disposal_scope_depth(); @@ -30,13 +27,13 @@ impl DisposeResources { None => err, Some(previous) => { // Create a SuppressedError - create_suppressed_error(err, previous, context) + create_suppressed_error(err, &previous, context) } }); } } } - + // Pop the disposal scope depth marker context.vm.pop_disposal_scope(); @@ -56,19 +53,16 @@ impl Operation for DisposeResources { } /// Helper function to create a SuppressedError -fn create_suppressed_error(_error: JsError, suppressed: JsError, _context: &mut Context) -> JsError { +fn create_suppressed_error( + _error: JsError, + suppressed: &JsError, + _context: &mut Context, +) -> JsError { // For now, we'll create a simple error that contains both errors // TODO: Implement proper SuppressedError builtin in Phase 2 - let message = format!( - "An error was suppressed during disposal: {}", - suppressed - ); - - let err = JsNativeError::error() - .with_message(message) - .into(); - + let message = format!("An error was suppressed during disposal: {suppressed}"); + // Attach the original error as a property // This is a temporary solution until SuppressedError is implemented - err + JsNativeError::error().with_message(message).into() } diff --git a/core/engine/src/vm/opcode/disposal/push_scope.rs b/core/engine/src/vm/opcode/disposal/push_scope.rs index 75100444f76..de0d8a64ba3 100644 --- a/core/engine/src/vm/opcode/disposal/push_scope.rs +++ b/core/engine/src/vm/opcode/disposal/push_scope.rs @@ -1,7 +1,4 @@ -use crate::{ - vm::opcode::Operation, - Context, JsResult, -}; +use crate::{Context, vm::opcode::Operation}; /// `PushDisposalScope` marks the current disposal stack depth for a new scope. /// @@ -12,9 +9,8 @@ use crate::{ pub(crate) struct PushDisposalScope; impl PushDisposalScope { - pub(crate) fn operation((): (), context: &mut Context) -> JsResult<()> { + pub(crate) fn operation((): (), context: &mut Context) { context.vm.push_disposal_scope(); - Ok(()) } } diff --git a/core/engine/src/vm/opcode/global.rs b/core/engine/src/vm/opcode/global.rs index d2bd6565c92..e850cbd9536 100644 --- a/core/engine/src/vm/opcode/global.rs +++ b/core/engine/src/vm/opcode/global.rs @@ -1,6 +1,6 @@ use crate::{ - vm::opcode::{Operation, RegisterOperand, IndexOperand}, Context, JsResult, + vm::opcode::{IndexOperand, Operation, RegisterOperand}, }; /// `HasRestrictedGlobalProperty` implements the Opcode Operation for `Opcode::HasRestrictedGlobalProperty` @@ -101,16 +101,13 @@ impl CreateGlobalFunctionBinding { let name = code_block.constant_string(name_index.into()); let value = context.vm.get_register(src.into()).clone(); let configurable = u32::from(configurable) != 0; - + // Convert JsValue to JsObject let function = value .as_object() - .ok_or_else(|| { - crate::JsNativeError::typ() - .with_message("value is not an object") - })? + .ok_or_else(|| crate::JsNativeError::typ().with_message("value is not an object"))? .clone(); - + context.create_global_function_binding(name, function, configurable)?; Ok(()) } diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index d1b9a3a77c7..26e41cd1aca 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -2202,7 +2202,7 @@ generate_opcodes! { /// /// - Registers: /// - Input: value - AddDisposableResource { value: RegisterOperand }, + AddDisposableResource { #[allow(dead_code)] value: RegisterOperand }, /// Dispose all resources in the current disposal stack. /// @@ -2211,7 +2211,7 @@ generate_opcodes! { /// /// - Stack: **=>** DisposeResources, - + /// Push a new disposal scope. /// /// This marks the current disposal stack depth for a new scope. From 2b99ecdba0404b3e1e8c074941bc6f8e5a0de9df Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Tue, 17 Mar 2026 03:27:42 +0530 Subject: [PATCH 4/6] refactor: encode using declaration count statically in DisposeResources opcode, removing runtime scope depth tracking Signed-off-by: Abhinav Sharma --- .../src/bytecompiler/statement/block.rs | 34 +++++++++---------- core/engine/src/vm/code_block.rs | 10 +++--- core/engine/src/vm/flowgraph/mod.rs | 21 +++--------- core/engine/src/vm/mod.rs | 22 +----------- .../vm/opcode/disposal/dispose_resources.rs | 30 ++++++---------- core/engine/src/vm/opcode/disposal/mod.rs | 2 -- .../src/vm/opcode/disposal/push_scope.rs | 21 ------------ core/engine/src/vm/opcode/mod.rs | 15 +++----- 8 files changed, 44 insertions(+), 111 deletions(-) delete mode 100644 core/engine/src/vm/opcode/disposal/push_scope.rs diff --git a/core/engine/src/bytecompiler/statement/block.rs b/core/engine/src/bytecompiler/statement/block.rs index 0cf8f68e7cb..491aaa6d7ff 100644 --- a/core/engine/src/bytecompiler/statement/block.rs +++ b/core/engine/src/bytecompiler/statement/block.rs @@ -11,26 +11,26 @@ impl ByteCompiler<'_> { let scope = self.push_declarative_scope(block.scope()); self.block_declaration_instantiation(block); - // Check if this block has any using declarations - let has_using = lexically_scoped_declarations(block).iter().any(|decl| { - matches!( - decl, - LexicallyScopedDeclaration::LexicalDeclaration( - LexicalDeclaration::Using(_) | LexicalDeclaration::AwaitUsing(_) - ) - ) - }); - - // Push disposal scope if this block has using declarations - if has_using { - self.bytecode.emit_push_disposal_scope(); - } + // Count how many `using` bindings are in this block (statically known at compile time) + let using_count: u32 = lexically_scoped_declarations(block) + .iter() + .filter_map(|decl| { + if let LexicallyScopedDeclaration::LexicalDeclaration( + LexicalDeclaration::Using(u) | LexicalDeclaration::AwaitUsing(u), + ) = decl + { + Some(u.as_ref().len() as u32) + } else { + None + } + }) + .sum(); self.compile_statement_list(block.statement_list(), use_expr, true); - // Dispose resources if this block has using declarations - if has_using { - self.bytecode.emit_dispose_resources(); + // Emit DisposeResources with the static count if there are any using declarations + if using_count > 0 { + self.bytecode.emit_dispose_resources(using_count.into()); } self.pop_declarative_scope(scope); diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 5f63eb630fe..6b143088f63 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -892,10 +892,9 @@ impl CodeBlock { | Instruction::SuperCallSpread | Instruction::PopPrivateEnvironment | Instruction::Generator - | Instruction::AsyncGenerator - | Instruction::AddDisposableResource { .. } - | Instruction::DisposeResources - | Instruction::PushDisposalScope => String::new(), + | Instruction::AsyncGenerator => String::new(), + Instruction::AddDisposableResource { value } => format!("value: {value}"), + Instruction::DisposeResources { count } => format!("count: {count}"), Instruction::Reserved4 | Instruction::Reserved5 | Instruction::Reserved6 @@ -947,7 +946,8 @@ impl CodeBlock { | Instruction::Reserved52 | Instruction::Reserved53 | Instruction::Reserved54 - | Instruction::Reserved55 => unreachable!("Reserved opcodes are unreachable"), + | Instruction::Reserved55 + | Instruction::Reserved56 => unreachable!("Reserved opcodes are unreachable"), } } } diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 87be537cf0e..9ee81f99cfb 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -383,21 +383,9 @@ impl CodeBlock { let label = format!("AddDisposableResource value: {value}"); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); } - Instruction::DisposeResources => { - graph.add_node( - previous_pc, - NodeShape::None, - "DisposeResources".into(), - Color::None, - ); - } - Instruction::PushDisposalScope => { - graph.add_node( - previous_pc, - NodeShape::None, - "PushDisposalScope".into(), - Color::None, - ); + Instruction::DisposeResources { count } => { + let label = format!("DisposeResources count: {count}"); + graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); } Instruction::Reserved4 | Instruction::Reserved5 @@ -450,7 +438,8 @@ impl CodeBlock { | Instruction::Reserved52 | Instruction::Reserved53 | Instruction::Reserved54 - | Instruction::Reserved55 => unreachable!("Reserved opcodes are unreachable"), + | Instruction::Reserved55 + | Instruction::Reserved56 => unreachable!("Reserved opcodes are unreachable"), } } diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index e93de81ca4e..033b9d9295d 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -101,13 +101,9 @@ pub struct Vm { /// Stack of disposable resources for explicit resource management. /// /// Resources are added via `using` declarations and disposed in reverse order (LIFO) - /// when the scope exits. Each entry contains (`value`, `dispose_method`, `scope_depth`). + /// when the scope exits. pub(crate) disposal_stack: Vec<(JsValue, JsValue)>, - /// Tracks the disposal stack depth for each scope level. - /// When a scope exits, we dispose resources back to this depth. - pub(crate) disposal_scope_depths: Vec, - #[cfg(feature = "trace")] pub(crate) trace: bool, #[cfg(feature = "trace")] @@ -360,7 +356,6 @@ impl Vm { host_call_depth: 0, shadow_stack: ShadowStack::default(), disposal_stack: Vec::new(), - disposal_scope_depths: Vec::new(), #[cfg(feature = "trace")] trace: false, #[cfg(feature = "trace")] @@ -620,21 +615,6 @@ impl Vm { pub(crate) fn pop_disposable_resource(&mut self) -> Option<(JsValue, JsValue)> { self.disposal_stack.pop() } - - /// Mark the current disposal stack depth for a new scope. - pub(crate) fn push_disposal_scope(&mut self) { - self.disposal_scope_depths.push(self.disposal_stack.len()); - } - - /// Get the disposal stack depth for the current scope. - pub(crate) fn current_disposal_scope_depth(&self) -> usize { - self.disposal_scope_depths.last().copied().unwrap_or(0) - } - - /// Pop the disposal scope depth marker. - pub(crate) fn pop_disposal_scope(&mut self) { - self.disposal_scope_depths.pop(); - } } #[allow(clippy::print_stdout)] diff --git a/core/engine/src/vm/opcode/disposal/dispose_resources.rs b/core/engine/src/vm/opcode/disposal/dispose_resources.rs index ee0d953e3df..1da9b7cfb80 100644 --- a/core/engine/src/vm/opcode/disposal/dispose_resources.rs +++ b/core/engine/src/vm/opcode/disposal/dispose_resources.rs @@ -1,43 +1,36 @@ -use crate::{Context, JsError, JsNativeError, JsResult, vm::opcode::Operation}; +use crate::{ + Context, JsError, JsNativeError, JsResult, + vm::opcode::{IndexOperand, Operation}, +}; /// `DisposeResources` implements the DisposeResources operation. /// -/// This opcode disposes all resources in the current disposal stack. +/// This opcode disposes the last `count` resources from the disposal stack. +/// The count is statically determined by the bytecompiler. /// /// Operation: /// - Stack: **=>** pub(crate) struct DisposeResources; impl DisposeResources { - pub(crate) fn operation((): (), context: &mut Context) -> JsResult<()> { + pub(crate) fn operation(count: IndexOperand, context: &mut Context) -> JsResult<()> { + let count = u32::from(count) as usize; let mut suppressed_error: Option = None; - // Get the scope depth to know how many resources to dispose - let scope_depth = context.vm.current_disposal_scope_depth(); - - // Dispose resources in reverse order (LIFO) until we reach the scope depth - while context.vm.disposal_stack.len() > scope_depth { + // Dispose exactly `count` resources in reverse order (LIFO) + for _ in 0..count { if let Some((value, method)) = context.vm.pop_disposable_resource() { - // Call the dispose method let result = method.call(&value, &[], context); - // If an error occurs, aggregate it if let Err(err) = result { suppressed_error = Some(match suppressed_error { None => err, - Some(previous) => { - // Create a SuppressedError - create_suppressed_error(err, &previous, context) - } + Some(previous) => create_suppressed_error(err, &previous, context), }); } } } - // Pop the disposal scope depth marker - context.vm.pop_disposal_scope(); - - // If there were any errors, throw the aggregated error if let Some(err) = suppressed_error { return Err(err); } @@ -62,7 +55,6 @@ fn create_suppressed_error( // TODO: Implement proper SuppressedError builtin in Phase 2 let message = format!("An error was suppressed during disposal: {suppressed}"); - // Attach the original error as a property // This is a temporary solution until SuppressedError is implemented JsNativeError::error().with_message(message).into() } diff --git a/core/engine/src/vm/opcode/disposal/mod.rs b/core/engine/src/vm/opcode/disposal/mod.rs index f2e900be465..28518342a7e 100644 --- a/core/engine/src/vm/opcode/disposal/mod.rs +++ b/core/engine/src/vm/opcode/disposal/mod.rs @@ -1,7 +1,5 @@ mod add_disposable; mod dispose_resources; -mod push_scope; pub(crate) use add_disposable::*; pub(crate) use dispose_resources::*; -pub(crate) use push_scope::*; diff --git a/core/engine/src/vm/opcode/disposal/push_scope.rs b/core/engine/src/vm/opcode/disposal/push_scope.rs deleted file mode 100644 index de0d8a64ba3..00000000000 --- a/core/engine/src/vm/opcode/disposal/push_scope.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::{Context, vm::opcode::Operation}; - -/// `PushDisposalScope` marks the current disposal stack depth for a new scope. -/// -/// This opcode is emitted at the beginning of blocks that contain `using` declarations. -/// -/// Operation: -/// - Stack: **=>** -pub(crate) struct PushDisposalScope; - -impl PushDisposalScope { - pub(crate) fn operation((): (), context: &mut Context) { - context.vm.push_disposal_scope(); - } -} - -impl Operation for PushDisposalScope { - const NAME: &'static str = "PushDisposalScope"; - const INSTRUCTION: &'static str = "INST - PushDisposalScope"; - const COST: u8 = 1; -} diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 26e41cd1aca..585a775e030 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -2207,18 +2207,11 @@ generate_opcodes! { /// Dispose all resources in the current disposal stack. /// /// This opcode implements the DisposeResources abstract operation. - /// It calls all dispose methods in reverse order (LIFO). + /// It calls the last `count` dispose methods in reverse order (LIFO). + /// The count is statically determined by the bytecompiler. /// /// - Stack: **=>** - DisposeResources, - - /// Push a new disposal scope. - /// - /// This marks the current disposal stack depth for a new scope. - /// When DisposeResources is called, it will dispose resources back to this depth. - /// - /// - Stack: **=>** - PushDisposalScope, + DisposeResources { count: IndexOperand }, /// Reserved [`Opcode`]. Reserved4 => Reserved, /// Reserved [`Opcode`]. @@ -2323,4 +2316,6 @@ generate_opcodes! { Reserved54 => Reserved, /// Reserved [`Opcode`]. Reserved55 => Reserved, + /// Reserved [`Opcode`]. + Reserved56 => Reserved, } From af80f7aaf37a97b2317f51c8df2c0b4d17691df3 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Wed, 18 Mar 2026 22:29:00 +0530 Subject: [PATCH 5/6] fix: restore Reserved1/2/3, remove global opcodes re-added by mistake Signed-off-by: Abhinav Sharma --- core/engine/src/vm/code_block.rs | 27 ++--- core/engine/src/vm/flowgraph/mod.rs | 16 +-- core/engine/src/vm/opcode/global.rs | 147 ---------------------------- core/engine/src/vm/opcode/mod.rs | 63 ++---------- 4 files changed, 25 insertions(+), 228 deletions(-) delete mode 100644 core/engine/src/vm/opcode/global.rs diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 6b143088f63..587ee193c1e 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -396,24 +396,6 @@ impl CodeBlock { | Instruction::CreateUnmappedArgumentsObject { dst } | Instruction::RestParameterInit { dst } | Instruction::StoreNewArray { dst } => format!("dst:{dst}"), - Instruction::HasRestrictedGlobalProperty { dst, index } - | Instruction::CanDeclareGlobalFunction { dst, index } - | Instruction::CanDeclareGlobalVar { dst, index } => { - format!("dst: {dst}, index: {index}") - } - Instruction::CreateGlobalFunctionBinding { - src, - configurable, - name_index, - } => { - format!("src: {src}, configurable: {configurable}, name_index: {name_index}") - } - Instruction::CreateGlobalVarBinding { - configurable, - name_index, - } => { - format!("configurable: {configurable}, name_index: {name_index}") - } Instruction::Add { lhs, rhs, dst } | Instruction::Sub { lhs, rhs, dst } | Instruction::Div { lhs, rhs, dst } @@ -895,7 +877,10 @@ impl CodeBlock { | Instruction::AsyncGenerator => String::new(), Instruction::AddDisposableResource { value } => format!("value: {value}"), Instruction::DisposeResources { count } => format!("count: {count}"), - Instruction::Reserved4 + Instruction::Reserved1 + | Instruction::Reserved2 + | Instruction::Reserved3 + | Instruction::Reserved4 | Instruction::Reserved5 | Instruction::Reserved6 | Instruction::Reserved7 @@ -947,7 +932,9 @@ impl CodeBlock { | Instruction::Reserved53 | Instruction::Reserved54 | Instruction::Reserved55 - | Instruction::Reserved56 => unreachable!("Reserved opcodes are unreachable"), + | Instruction::Reserved56 + | Instruction::Reserved57 + | Instruction::Reserved58 => unreachable!("Reserved opcodes are unreachable"), } } } diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 9ee81f99cfb..ab6087a89e4 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -367,12 +367,7 @@ impl CodeBlock { | Instruction::CheckReturn | Instruction::BindThisValue { .. } | Instruction::CreateMappedArgumentsObject { .. } - | Instruction::CreateUnmappedArgumentsObject { .. } - | Instruction::HasRestrictedGlobalProperty { .. } - | Instruction::CanDeclareGlobalFunction { .. } - | Instruction::CanDeclareGlobalVar { .. } - | Instruction::CreateGlobalFunctionBinding { .. } - | Instruction::CreateGlobalVarBinding { .. } => { + | Instruction::CreateUnmappedArgumentsObject { .. } => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } @@ -387,7 +382,10 @@ impl CodeBlock { let label = format!("DisposeResources count: {count}"); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); } - Instruction::Reserved4 + Instruction::Reserved1 + | Instruction::Reserved2 + | Instruction::Reserved3 + | Instruction::Reserved4 | Instruction::Reserved5 | Instruction::Reserved6 | Instruction::Reserved7 @@ -439,7 +437,9 @@ impl CodeBlock { | Instruction::Reserved53 | Instruction::Reserved54 | Instruction::Reserved55 - | Instruction::Reserved56 => unreachable!("Reserved opcodes are unreachable"), + | Instruction::Reserved56 + | Instruction::Reserved57 + | Instruction::Reserved58 => unreachable!("Reserved opcodes are unreachable"), } } diff --git a/core/engine/src/vm/opcode/global.rs b/core/engine/src/vm/opcode/global.rs deleted file mode 100644 index e850cbd9536..00000000000 --- a/core/engine/src/vm/opcode/global.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::{ - Context, JsResult, - vm::opcode::{IndexOperand, Operation, RegisterOperand}, -}; - -/// `HasRestrictedGlobalProperty` implements the Opcode Operation for `Opcode::HasRestrictedGlobalProperty` -/// -/// Operation: -/// - Performs [`HasRestrictedGlobalProperty ( N )`][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty -pub(crate) struct HasRestrictedGlobalProperty; - -impl HasRestrictedGlobalProperty { - pub(crate) fn operation( - (dst, index): (RegisterOperand, IndexOperand), - context: &mut Context, - ) -> JsResult<()> { - let code_block = context.vm.frame().code_block(); - let name = code_block.constant_string(index.into()); - let result = context.has_restricted_global_property(&name)?; - context.vm.set_register(dst.into(), result.into()); - Ok(()) - } -} - -impl Operation for HasRestrictedGlobalProperty { - const NAME: &'static str = "HasRestrictedGlobalProperty"; - const INSTRUCTION: &'static str = "INST - HasRestrictedGlobalProperty"; - const COST: u8 = 4; -} - -/// `CanDeclareGlobalFunction` implements the Opcode Operation for `Opcode::CanDeclareGlobalFunction` -/// -/// Operation: -/// - Performs [`CanDeclareGlobalFunction ( N )`][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction -pub(crate) struct CanDeclareGlobalFunction; - -impl CanDeclareGlobalFunction { - pub(crate) fn operation( - (dst, index): (RegisterOperand, IndexOperand), - context: &mut Context, - ) -> JsResult<()> { - let code_block = context.vm.frame().code_block(); - let name = code_block.constant_string(index.into()); - let result = context.can_declare_global_function(&name)?; - context.vm.set_register(dst.into(), result.into()); - Ok(()) - } -} - -impl Operation for CanDeclareGlobalFunction { - const NAME: &'static str = "CanDeclareGlobalFunction"; - const INSTRUCTION: &'static str = "INST - CanDeclareGlobalFunction"; - const COST: u8 = 4; -} - -/// `CanDeclareGlobalVar` implements the Opcode Operation for `Opcode::CanDeclareGlobalVar` -/// -/// Operation: -/// - Performs [`CanDeclareGlobalVar ( N )`][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar -pub(crate) struct CanDeclareGlobalVar; - -impl CanDeclareGlobalVar { - pub(crate) fn operation( - (dst, index): (RegisterOperand, IndexOperand), - context: &mut Context, - ) -> JsResult<()> { - let code_block = context.vm.frame().code_block(); - let name = code_block.constant_string(index.into()); - let result = context.can_declare_global_var(&name)?; - context.vm.set_register(dst.into(), result.into()); - Ok(()) - } -} - -impl Operation for CanDeclareGlobalVar { - const NAME: &'static str = "CanDeclareGlobalVar"; - const INSTRUCTION: &'static str = "INST - CanDeclareGlobalVar"; - const COST: u8 = 4; -} - -/// `CreateGlobalFunctionBinding` implements the Opcode Operation for `Opcode::CreateGlobalFunctionBinding` -/// -/// Operation: -/// - Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding -pub(crate) struct CreateGlobalFunctionBinding; - -impl CreateGlobalFunctionBinding { - pub(crate) fn operation( - (src, configurable, name_index): (RegisterOperand, IndexOperand, IndexOperand), - context: &mut Context, - ) -> JsResult<()> { - let code_block = context.vm.frame().code_block(); - let name = code_block.constant_string(name_index.into()); - let value = context.vm.get_register(src.into()).clone(); - let configurable = u32::from(configurable) != 0; - - // Convert JsValue to JsObject - let function = value - .as_object() - .ok_or_else(|| crate::JsNativeError::typ().with_message("value is not an object"))? - .clone(); - - context.create_global_function_binding(name, function, configurable)?; - Ok(()) - } -} - -impl Operation for CreateGlobalFunctionBinding { - const NAME: &'static str = "CreateGlobalFunctionBinding"; - const INSTRUCTION: &'static str = "INST - CreateGlobalFunctionBinding"; - const COST: u8 = 4; -} - -/// `CreateGlobalVarBinding` implements the Opcode Operation for `Opcode::CreateGlobalVarBinding` -/// -/// Operation: -/// - Performs [`CreateGlobalVarBinding ( N, D )`][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding -pub(crate) struct CreateGlobalVarBinding; - -impl CreateGlobalVarBinding { - pub(crate) fn operation( - (configurable, name_index): (IndexOperand, IndexOperand), - context: &mut Context, - ) -> JsResult<()> { - let code_block = context.vm.frame().code_block(); - let name = code_block.constant_string(name_index.into()); - let configurable = u32::from(configurable) != 0; - context.create_global_var_binding(name, configurable)?; - Ok(()) - } -} - -impl Operation for CreateGlobalVarBinding { - const NAME: &'static str = "CreateGlobalVarBinding"; - const INSTRUCTION: &'static str = "INST - CreateGlobalVarBinding"; - const COST: u8 = 4; -} diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 585a775e030..e096d81394d 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -25,7 +25,6 @@ mod environment; mod function; mod generator; mod get; -mod global; mod iteration; mod meta; mod new; @@ -71,8 +70,6 @@ pub(crate) use generator::*; #[doc(inline)] pub(crate) use get::*; #[doc(inline)] -pub(crate) use global::*; -#[doc(inline)] pub(crate) use iteration::*; #[doc(inline)] pub(crate) use meta::*; @@ -2145,56 +2142,6 @@ generate_opcodes! { /// - Output: dst CreateUnmappedArgumentsObject { dst: RegisterOperand }, - /// Performs [`HasRestrictedGlobalProperty ( N )`][spec] - /// - /// - Operands: - /// - index: `VaryingOperand` - /// - Registers: - /// - Output: dst - /// - /// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty - HasRestrictedGlobalProperty { dst: RegisterOperand, index: IndexOperand }, - - /// Performs [`CanDeclareGlobalFunction ( N )`][spec] - /// - /// - Operands: - /// - index: `VaryingOperand` - /// - Registers: - /// - Output: dst - /// - /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction - CanDeclareGlobalFunction { dst: RegisterOperand, index: IndexOperand }, - - /// Performs [`CanDeclareGlobalVar ( N )`][spec] - /// - /// - Operands: - /// - index: `VaryingOperand` - /// - Registers: - /// - Output: dst - /// - /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar - CanDeclareGlobalVar { dst: RegisterOperand, index: IndexOperand }, - - /// Performs [`CreateGlobalFunctionBinding ( N, V, D )`][spec] - /// - /// - Operands: - /// - configurable: `bool` - /// - name_index: `VaryingOperand` - /// - Registers: - /// - Input: src - /// - /// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding - CreateGlobalFunctionBinding { src: RegisterOperand, configurable: IndexOperand, name_index: IndexOperand }, - - /// Performs [`CreateGlobalVarBinding ( N, V, D )`][spec] - /// - /// - Operands: - /// - configurable: `bool` - /// - name_index: `VaryingOperand` - /// - /// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding - CreateGlobalVarBinding { configurable: IndexOperand, name_index: IndexOperand }, - /// Add a disposable resource to the disposal stack. /// /// This opcode implements the AddDisposableResource abstract operation. @@ -2213,6 +2160,12 @@ generate_opcodes! { /// - Stack: **=>** DisposeResources { count: IndexOperand }, /// Reserved [`Opcode`]. + Reserved1 => Reserved, + /// Reserved [`Opcode`]. + Reserved2 => Reserved, + /// Reserved [`Opcode`]. + Reserved3 => Reserved, + /// Reserved [`Opcode`]. Reserved4 => Reserved, /// Reserved [`Opcode`]. Reserved5 => Reserved, @@ -2318,4 +2271,8 @@ generate_opcodes! { Reserved55 => Reserved, /// Reserved [`Opcode`]. Reserved56 => Reserved, + /// Reserved [`Opcode`]. + Reserved57 => Reserved, + /// Reserved [`Opcode`]. + Reserved58 => Reserved, } From 373889db71efc90184032ee28e51121411a61426 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Thu, 19 Mar 2026 01:01:10 +0530 Subject: [PATCH 6/6] feat: gate using declarations behind experimental feature flag Signed-off-by: Abhinav Sharma --- core/engine/src/bytecompiler/mod.rs | 27 ++++++++++++++++--- .../src/bytecompiler/statement/block.rs | 5 ++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 62d731c652f..ac48df7a52f 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -2260,8 +2260,14 @@ impl<'ctx> ByteCompiler<'ctx> { } // Add resource to disposal stack + #[cfg(feature = "experimental")] self.bytecode.emit_add_disposable_resource(value.variable()); + #[cfg(not(feature = "experimental"))] + self.emit_type_error( + "using declarations require the 'experimental' feature", + ); + self.emit_binding(BindingOpcode::InitLexical, ident, &value); self.register_allocator.dealloc(value); } @@ -2275,8 +2281,14 @@ impl<'ctx> ByteCompiler<'ctx> { } // Add resource to disposal stack + #[cfg(feature = "experimental")] self.bytecode.emit_add_disposable_resource(value.variable()); + #[cfg(not(feature = "experimental"))] + self.emit_type_error( + "using declarations require the 'experimental' feature", + ); + self.compile_declaration_pattern( pattern, BindingOpcode::InitLexical, @@ -2300,9 +2312,11 @@ impl<'ctx> ByteCompiler<'ctx> { self.bytecode.emit_store_undefined(value.variable()); } - // TODO: Add resource to async disposal stack - // For now, we just bind the variable like a let declaration - // Full implementation will add: AddAsyncDisposableResource opcode + // await using is not yet implemented even under experimental + #[cfg(not(feature = "experimental"))] + self.emit_type_error( + "await using declarations require the 'experimental' feature", + ); self.emit_binding(BindingOpcode::InitLexical, ident, &value); self.register_allocator.dealloc(value); @@ -2316,7 +2330,12 @@ impl<'ctx> ByteCompiler<'ctx> { self.bytecode.emit_store_undefined(value.variable()); } - // TODO: SAME + // await using is not yet implemented even under experimental + #[cfg(not(feature = "experimental"))] + self.emit_type_error( + "await using declarations require the 'experimental' feature", + ); + self.compile_declaration_pattern( pattern, BindingOpcode::InitLexical, diff --git a/core/engine/src/bytecompiler/statement/block.rs b/core/engine/src/bytecompiler/statement/block.rs index 491aaa6d7ff..16690a2e843 100644 --- a/core/engine/src/bytecompiler/statement/block.rs +++ b/core/engine/src/bytecompiler/statement/block.rs @@ -1,4 +1,7 @@ use crate::bytecompiler::ByteCompiler; +#[cfg(not(feature = "experimental"))] +use boa_ast::statement::Block; +#[cfg(feature = "experimental")] use boa_ast::{ declaration::LexicalDeclaration, operations::{LexicallyScopedDeclaration, lexically_scoped_declarations}, @@ -12,6 +15,7 @@ impl ByteCompiler<'_> { self.block_declaration_instantiation(block); // Count how many `using` bindings are in this block (statically known at compile time) + #[cfg(feature = "experimental")] let using_count: u32 = lexically_scoped_declarations(block) .iter() .filter_map(|decl| { @@ -29,6 +33,7 @@ impl ByteCompiler<'_> { self.compile_statement_list(block.statement_list(), use_expr, true); // Emit DisposeResources with the static count if there are any using declarations + #[cfg(feature = "experimental")] if using_count > 0 { self.bytecode.emit_dispose_resources(using_count.into()); }