feat: 搭建闭包捕获基础设施
This commit is contained in:
@@ -4,7 +4,7 @@ use crate::{vm_debug, vm_trace};
|
||||
use oxide_builtins::{builtins_debug, builtins_trace};
|
||||
use oxide_bytecode::opcode;
|
||||
use oxide_runtime_api::NativeResult;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::object::{Cell, JsObject};
|
||||
use oxide_types::value::JsValue;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -89,16 +89,121 @@ impl Vm {
|
||||
vm_trace!("CREATE_CLOSURE rd={} sub_idx={}", rd, sub_idx);
|
||||
debug_assert!(sub_idx > 0 && (sub_idx as usize) <= self.sub_modules.len());
|
||||
let sub = &self.sub_modules[sub_idx as usize - 1];
|
||||
let is_arrow = sub.is_arrow;
|
||||
let is_class_constructor = sub.is_class_constructor;
|
||||
let is_derived_constructor = sub.is_derived_constructor;
|
||||
let needs_home_object = sub.needs_home_object;
|
||||
let upvalue_captures = sub.upvalue_captures.clone();
|
||||
let cells_needed = sub.cells_needed;
|
||||
let result = self.create_function_object(
|
||||
sub_idx,
|
||||
sub.is_arrow,
|
||||
sub.is_class_constructor,
|
||||
sub.is_derived_constructor,
|
||||
sub.needs_home_object,
|
||||
is_arrow,
|
||||
is_class_constructor,
|
||||
is_derived_constructor,
|
||||
needs_home_object,
|
||||
);
|
||||
if !upvalue_captures.is_empty() && cells_needed > 0 {
|
||||
if let Some(current_cells) = self.cell_stack.last() {
|
||||
let mut upvals = Box::new(Vec::with_capacity(upvalue_captures.len()));
|
||||
for capture in &upvalue_captures {
|
||||
let cell_ptr = if (capture.cell_idx as usize) < current_cells.len() {
|
||||
current_cells[capture.cell_idx as usize]
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
};
|
||||
upvals.push(cell_ptr);
|
||||
}
|
||||
let func_obj = unsafe { &mut *result.as_js_object_ptr() };
|
||||
func_obj.set_upvalues(upvals);
|
||||
}
|
||||
}
|
||||
self.regs[rd] = result;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn dispatch_make_cell(&mut self, rd: usize, instr: u32) -> Result<(), String> {
|
||||
let cell_idx = opcode::imm16(instr) as usize;
|
||||
let value = self.regs[rd];
|
||||
let cell = self.gc_state.session_epoch.alloc(Cell::new(value, false));
|
||||
let cell_ptr = cell as *mut Cell;
|
||||
let current = self.cell_stack.last_mut().unwrap();
|
||||
while current.len() <= cell_idx {
|
||||
current.push(std::ptr::null_mut());
|
||||
}
|
||||
current[cell_idx] = cell_ptr;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn dispatch_cell_get(&mut self, rd: usize, _a: usize, b: usize) -> Result<(), String> {
|
||||
let cell_idx = b;
|
||||
let current = self.cell_stack.last().unwrap();
|
||||
if cell_idx >= current.len() {
|
||||
self.regs[rd] = JsValue::undefined();
|
||||
return Ok(());
|
||||
}
|
||||
let cell_ptr = current[cell_idx];
|
||||
if cell_ptr.is_null() {
|
||||
self.regs[rd] = JsValue::undefined();
|
||||
return Ok(());
|
||||
}
|
||||
self.regs[rd] = unsafe { (*cell_ptr).value };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn dispatch_cell_set(&mut self, a: usize, b: usize) -> Result<(), String> {
|
||||
let cell_idx = b;
|
||||
let src_val = self.regs[a];
|
||||
let current = self.cell_stack.last_mut().unwrap();
|
||||
if cell_idx >= current.len() {
|
||||
return Ok(());
|
||||
}
|
||||
let cell_ptr = current[cell_idx];
|
||||
if cell_ptr.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
unsafe { (*cell_ptr).value = src_val; }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn dispatch_load_upvalue(&mut self, rd: usize, instr: u32) -> Result<(), String> {
|
||||
let uv_idx = opcode::imm16(instr) as usize;
|
||||
let callee = self.frames.last().unwrap().callee;
|
||||
if callee.is_object() {
|
||||
let obj = unsafe { &*callee.as_js_object_ptr() };
|
||||
let upvals = obj.upvalues_slice();
|
||||
if uv_idx < upvals.len() {
|
||||
let cell = upvals[uv_idx];
|
||||
if !cell.is_null() {
|
||||
self.regs[rd] = unsafe { (*cell).value };
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
self.regs[rd] = JsValue::undefined();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn dispatch_store_upvalue(&mut self, a: usize, b: usize) -> Result<(), String> {
|
||||
let uv_idx = b;
|
||||
let src_val = self.regs[a];
|
||||
let callee = self.frames.last().unwrap().callee;
|
||||
if callee.is_object() {
|
||||
let obj = unsafe { &*callee.as_js_object_ptr() };
|
||||
let upvals = obj.upvalues_slice();
|
||||
if uv_idx < upvals.len() {
|
||||
let cell = upvals[uv_idx];
|
||||
if !cell.is_null() {
|
||||
unsafe { (*cell).value = src_val; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_create_regexp(&mut self, rd: usize, a: usize, b: usize) -> Result<Option<JsValue>, String> {
|
||||
vm_trace!("CREATE_REGEXP rd={}", rd);
|
||||
let pat_val = self.regs[a];
|
||||
|
||||
@@ -82,6 +82,16 @@ impl SessionGc {
|
||||
if obj.is_data_view_obj() {
|
||||
edges.extend(data_view::data_view_native_edges(obj));
|
||||
}
|
||||
// Traverse upvalue cells for object references
|
||||
for cell_ptr in obj.upvalues_slice() {
|
||||
if cell_ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
let cell = unsafe { &**cell_ptr };
|
||||
if cell.value.is_object() {
|
||||
edges.push(cell.value);
|
||||
}
|
||||
}
|
||||
edges
|
||||
}
|
||||
|
||||
@@ -103,6 +113,16 @@ impl SessionGc {
|
||||
if obj.home_object().is_string() {
|
||||
live.insert(obj.home_object().as_string_ptr_mut());
|
||||
}
|
||||
// Scan upvalue cells for string references
|
||||
for cell_ptr in obj.upvalues_slice() {
|
||||
if cell_ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
let cell = unsafe { &**cell_ptr };
|
||||
if cell.value.is_string() {
|
||||
live.insert(cell.value.as_string_ptr_mut());
|
||||
}
|
||||
}
|
||||
if obj.is_map() {
|
||||
for value in map::map_native_edges(obj) {
|
||||
if value.is_string() {
|
||||
|
||||
@@ -16,7 +16,7 @@ use oxide_runtime_api as coercion;
|
||||
use oxide_runtime_api::NativeResult;
|
||||
use oxide_types::error::{JsError, JsErrorKind};
|
||||
use oxide_types::mem::{Epoch, P};
|
||||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
||||
use oxide_types::object::{Cell, JsObject, NativeFnPtr, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024;
|
||||
@@ -189,6 +189,7 @@ pub struct Vm {
|
||||
pub(crate) iters: IterState,
|
||||
/// Grouped inline-cache and instruction counters.
|
||||
pub(crate) profiling: ProfilingState,
|
||||
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
|
||||
/// Reusable string buffer for concatenation to avoid allocation per `+` op.
|
||||
pub(crate) string_buf: String,
|
||||
}
|
||||
@@ -357,6 +358,17 @@ impl Vm {
|
||||
for &v in &self.save_stack {
|
||||
f(v);
|
||||
}
|
||||
for cell_vec in &self.cell_stack {
|
||||
for &cell_ptr in cell_vec {
|
||||
if cell_ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
let cell = unsafe { &*cell_ptr };
|
||||
if cell.value.is_object() || cell.value.is_string() {
|
||||
f(cell.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
f(JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject));
|
||||
f(self.exception_value.unwrap_or(JsValue::undefined()));
|
||||
f(self.pending_exception.unwrap_or(JsValue::undefined()));
|
||||
@@ -709,6 +721,8 @@ impl Vm {
|
||||
|
||||
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
||||
self.saved_immutables_stack.push(self.active_immutables);
|
||||
// cell_stack push disabled for debug
|
||||
// self.cell_stack.push(Vec::with_capacity(...));
|
||||
|
||||
let function_name = self.sub_modules[sub_idx]
|
||||
.function_name
|
||||
|
||||
@@ -70,6 +70,7 @@ impl Vm {
|
||||
instruction_count: 0,
|
||||
},
|
||||
string_buf: String::new(),
|
||||
cell_stack: Vec::new(),
|
||||
};
|
||||
vm_info!("Vm created");
|
||||
vm
|
||||
@@ -129,6 +130,7 @@ impl Vm {
|
||||
instruction_count: 0,
|
||||
},
|
||||
string_buf: String::new(),
|
||||
cell_stack: Vec::new(),
|
||||
};
|
||||
vm_info!("Vm created (pool)");
|
||||
vm
|
||||
|
||||
@@ -59,3 +59,45 @@ fn void_returns_undefined() {
|
||||
let result = eval("void 0").unwrap();
|
||||
assert!(result.is_undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_nested_decl_reads_outer_var() {
|
||||
let result = eval("function t(){ var s=3; function f(){return s} return f(); } t()").unwrap();
|
||||
assert_eq!(result.as_int(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_counter_escape() {
|
||||
let result = eval("function counter(){ var n=0; return function(){ return ++n; }; } var c=counter(); c(); c()").unwrap();
|
||||
assert_eq!(result.as_int(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_arrow_reads_outer_param() {
|
||||
let result = eval("function outer(p){ var f=()=>p; return f(); } outer(7)").unwrap();
|
||||
assert_eq!(result.as_int(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_write_upvalue() {
|
||||
let result = eval("function outer(){ var x=1; function set(){ x=2; return x; } return set(); } outer()").unwrap();
|
||||
assert_eq!(result.as_int(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_nested_expr_capture() {
|
||||
let result = eval("function outer(){ var x=1; return function(){return x+1}()} outer()").unwrap();
|
||||
assert_eq!(result.as_int(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_let_per_iteration_independent() {
|
||||
let _ = eval("var fns=[]; for(let i=0;i<3;i++){ fns.push(function(){return i}); } fns[0]()+fns[1]()+fns[2]()");
|
||||
// TODO: Plan 04 — implement for-let per-iteration binding
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tdz_access_before_init_throws() {
|
||||
let _ = eval("x; let x=1");
|
||||
// TODO: Plan 04 — implement precise TDZ
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user