Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e47a274
feat: Implement core disposal logic for explicit resource management
abhinavs1920 Mar 14, 2026
992583f
Merge branch 'main' into feat/res2
abhinavs1920 Mar 15, 2026
81a3f59
fix: opcode enum
abhinavs1920 Mar 15, 2026
92b7fa0
fix: formatting and lint issues
abhinavs1920 Mar 16, 2026
d3aa12e
Merge branch 'main' into feat/res2
abhinavs1920 Mar 16, 2026
201be32
Merge branch 'main' into feat/res2
abhinavs1920 Mar 16, 2026
d0517fe
Merge branch 'main' into feat/res2
abhinavs1920 Mar 16, 2026
2b99ecd
refactor: encode using declaration count statically in DisposeResourc…
abhinavs1920 Mar 16, 2026
42af9af
Merge branch 'main' into feat/res2
abhinavs1920 Mar 17, 2026
6935488
Merge branch 'main' into feat/res2
abhinavs1920 Mar 18, 2026
af80f7a
fix: restore Reserved1/2/3, remove global opcodes re-added by mistake
abhinavs1920 Mar 18, 2026
373889d
feat: gate using declarations behind experimental feature flag
abhinavs1920 Mar 18, 2026
a684036
Merge branch 'main' into feat/res2
abhinavs1920 Mar 18, 2026
abc7fc7
Merge branch 'main' into feat/res2
abhinavs1920 Mar 20, 2026
601309c
Merge branch 'main' into feat/res2
abhinavs1920 Mar 21, 2026
2d6f4d9
Merge branch 'main' into feat/res2
abhinavs1920 Mar 26, 2026
a438cee
feat: integrate try-finally semantics for using declarations
abhinavs1920 Apr 6, 2026
3d22954
fix: wrap function bodies with `using` declarations in try-finally fo…
abhinavs1920 Apr 11, 2026
16dbb0d
Merge branch 'main' into feat/res2
abhinavs1920 Apr 11, 2026
e3149cb
fix: formatting issue
abhinavs1920 Apr 11, 2026
17abdf8
feat: Add SuppressedError builtin
abhinavs1920 Apr 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions core/engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2259,9 +2259,8 @@ impl<'ctx> ByteCompiler<'ctx> {
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);
Expand All @@ -2275,7 +2274,8 @@ impl<'ctx> ByteCompiler<'ctx> {
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,
Expand Down
28 changes: 27 additions & 1 deletion core/engine/src/bytecompiler/statement/block.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
35 changes: 24 additions & 11 deletions core/engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -874,11 +892,11 @@ impl CodeBlock {
| Instruction::SuperCallSpread
| Instruction::PopPrivateEnvironment
| Instruction::Generator
| Instruction::AsyncGenerator => String::new(),
Instruction::Reserved1
| Instruction::Reserved2
| Instruction::Reserved3
| Instruction::Reserved4
| Instruction::AsyncGenerator
| Instruction::AddDisposableResource { .. }
| Instruction::DisposeResources
| Instruction::PushDisposalScope => String::new(),
Instruction::Reserved4
| Instruction::Reserved5
| Instruction::Reserved6
| Instruction::Reserved7
Expand Down Expand Up @@ -929,12 +947,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"),
}
}
}
Expand Down
39 changes: 28 additions & 11 deletions core/engine/src/vm/flowgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"),
}
}

Expand Down
37 changes: 37 additions & 0 deletions core/engine/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize>,
Comment thread
abhinavs1920 marked this conversation as resolved.
Outdated

#[cfg(feature = "trace")]
pub(crate) trace: bool,
#[cfg(feature = "trace")]
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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)]
Expand Down
47 changes: 47 additions & 0 deletions core/engine/src/vm/opcode/disposal/add_disposable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::{
Context, JsResult,
vm::opcode::{Operation, RegisterOperand},
};

/// `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;
}
68 changes: 68 additions & 0 deletions core/engine/src/vm/opcode/disposal/dispose_resources.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::{Context, JsError, JsNativeError, JsResult, vm::opcode::Operation};

/// `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<JsError> = 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}");

// Attach the original error as a property
// This is a temporary solution until SuppressedError is implemented
JsNativeError::error().with_message(message).into()
}
7 changes: 7 additions & 0 deletions core/engine/src/vm/opcode/disposal/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
21 changes: 21 additions & 0 deletions core/engine/src/vm/opcode/disposal/push_scope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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;
}
Loading
Loading