Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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);

// 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);

// 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);
}
}
6 changes: 3 additions & 3 deletions core/engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,8 @@ impl CodeBlock {
| Instruction::PopPrivateEnvironment
| Instruction::Generator
| Instruction::AsyncGenerator => String::new(),
Instruction::AddDisposableResource { value } => format!("value: {value}"),
Instruction::DisposeResources { count } => format!("count: {count}"),
Instruction::Reserved1
| Instruction::Reserved2
| Instruction::Reserved3
Expand Down Expand Up @@ -932,9 +934,7 @@ impl CodeBlock {
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58
| Instruction::Reserved59
| Instruction::Reserved60 => unreachable!("Reserved opcodes are unreachable"),
| Instruction::Reserved58 => unreachable!("Reserved opcodes are unreachable"),
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions core/engine/src/vm/flowgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,14 @@ impl CodeBlock {
Instruction::Return => {
graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::Red);
}
Instruction::AddDisposableResource { value } => {
let label = format!("AddDisposableResource value: {value}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
}
Instruction::DisposeResources { count } => {
let label = format!("DisposeResources count: {count}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
}
Instruction::Reserved1
| Instruction::Reserved2
| Instruction::Reserved3
Expand Down Expand Up @@ -431,9 +439,7 @@ impl CodeBlock {
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58
| Instruction::Reserved59
| Instruction::Reserved60 => unreachable!("Reserved opcodes are unreachable"),
| Instruction::Reserved58 => unreachable!("Reserved opcodes are unreachable"),
}
}

Expand Down
17 changes: 17 additions & 0 deletions core/engine/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ 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.
pub(crate) disposal_stack: Vec<(JsValue, JsValue)>,

#[cfg(feature = "trace")]
pub(crate) trace: bool,
#[cfg(feature = "trace")]
Expand Down Expand Up @@ -349,6 +355,7 @@ impl Vm {
native_active_function: None,
host_call_depth: 0,
shadow_stack: ShadowStack::default(),
disposal_stack: Vec::new(),
#[cfg(feature = "trace")]
trace: false,
#[cfg(feature = "trace")]
Expand Down Expand Up @@ -598,6 +605,16 @@ 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()
}
}

#[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;
}
60 changes: 60 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,60 @@
use crate::{
Context, JsError, JsNativeError, JsResult,
vm::opcode::{IndexOperand, Operation},
};

/// `DisposeResources` implements the DisposeResources operation.
///
/// 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(count: IndexOperand, context: &mut Context) -> JsResult<()> {
let count = u32::from(count) as usize;
let mut suppressed_error: Option<JsError> = None;

// Dispose exactly `count` resources in reverse order (LIFO)
for _ in 0..count {
if let Some((value, method)) = context.vm.pop_disposable_resource() {
let result = method.call(&value, &[], context);

if let Err(err) = result {
suppressed_error = Some(match suppressed_error {
None => err,
Some(previous) => create_suppressed_error(err, &previous, context),
});
}
}
}

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}");

// This is a temporary solution until SuppressedError is implemented
JsNativeError::error().with_message(message).into()
}
5 changes: 5 additions & 0 deletions core/engine/src/vm/opcode/disposal/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod add_disposable;
mod dispose_resources;

pub(crate) use add_disposable::*;
pub(crate) use dispose_resources::*;
24 changes: 20 additions & 4 deletions core/engine/src/vm/opcode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod control_flow;
mod copy;
mod define;
mod delete;
mod disposal;
mod environment;
mod function;
mod generator;
Expand Down Expand Up @@ -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::*;
Expand Down Expand Up @@ -2139,6 +2142,23 @@ generate_opcodes! {
/// - Output: dst
CreateUnmappedArgumentsObject { dst: RegisterOperand },

/// 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 { #[allow(dead_code)] value: RegisterOperand },

/// Dispose all resources in the current disposal stack.
///
/// This opcode implements the DisposeResources abstract operation.
/// It calls the last `count` dispose methods in reverse order (LIFO).
/// The count is statically determined by the bytecompiler.
///
/// - Stack: **=>**
DisposeResources { count: IndexOperand },
/// Reserved [`Opcode`].
Reserved1 => Reserved,
/// Reserved [`Opcode`].
Expand Down Expand Up @@ -2255,8 +2275,4 @@ generate_opcodes! {
Reserved57 => Reserved,
/// Reserved [`Opcode`].
Reserved58 => Reserved,
/// Reserved [`Opcode`].
Reserved59 => Reserved,
/// Reserved [`Opcode`].
Reserved60 => Reserved,
}
Loading
Loading