Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
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);
}
}
31 changes: 22 additions & 9 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 @@ -875,10 +893,9 @@ 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 { count } => format!("count: {count}"),
Instruction::Reserved4
| Instruction::Reserved5
| Instruction::Reserved6
| Instruction::Reserved7
Expand Down Expand Up @@ -930,11 +947,7 @@ impl CodeBlock {
| Instruction::Reserved53
| Instruction::Reserved54
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58
| Instruction::Reserved59
| Instruction::Reserved60 => unreachable!("Reserved opcodes are unreachable"),
| Instruction::Reserved56 => unreachable!("Reserved opcodes are unreachable"),
}
}
}
Expand Down
26 changes: 16 additions & 10 deletions core/engine/src/vm/flowgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,17 +367,27 @@ 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 { count } => {
let label = format!("DisposeResources count: {count}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
}
Instruction::Reserved4
| Instruction::Reserved5
| Instruction::Reserved6
| Instruction::Reserved7
Expand Down Expand Up @@ -429,11 +439,7 @@ impl CodeBlock {
| Instruction::Reserved53
| Instruction::Reserved54
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58
| Instruction::Reserved59
| Instruction::Reserved60 => unreachable!("Reserved opcodes are unreachable"),
| Instruction::Reserved56 => 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::*;
Loading
Loading