refactor: Vm 状态分组、dispatch 拆分为分发表、IC 辅助函数独立成模块
This commit is contained in:
@@ -33,9 +33,9 @@ pub fn symbol_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
String::new()
|
||||
};
|
||||
|
||||
vm.symbol_counter = vm.symbol_counter.wrapping_add(1);
|
||||
let idx = vm.symbol_descriptions.len() as u32;
|
||||
vm.symbol_descriptions.push(description);
|
||||
vm.symbols.symbol_counter = vm.symbols.symbol_counter.wrapping_add(1);
|
||||
let idx = vm.symbols.symbol_descriptions.len() as u32;
|
||||
vm.symbols.symbol_descriptions.push(description);
|
||||
NativeResult::Ok(JsValue::symbol(idx))
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ pub fn symbol_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
|
||||
let idx = this_val.as_symbol_index();
|
||||
let desc = vm.symbol_descriptions.get(idx as usize).cloned().unwrap_or_default();
|
||||
let desc = vm.symbols.symbol_descriptions.get(idx as usize).cloned().unwrap_or_default();
|
||||
let result = format!("Symbol({})", desc);
|
||||
NativeResult::Ok(vm.new_string(&result))
|
||||
}
|
||||
@@ -61,13 +61,13 @@ pub fn symbol_for(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
"undefined".to_string()
|
||||
};
|
||||
|
||||
if let Some(&idx) = vm.symbol_registry.get(&key) {
|
||||
if let Some(&idx) = vm.symbols.symbol_registry.get(&key) {
|
||||
return NativeResult::Ok(JsValue::symbol(idx));
|
||||
}
|
||||
|
||||
let idx = vm.symbol_descriptions.len() as u32;
|
||||
vm.symbol_descriptions.push(key.clone());
|
||||
vm.symbol_registry.insert(key, idx);
|
||||
let idx = vm.symbols.symbol_descriptions.len() as u32;
|
||||
vm.symbols.symbol_descriptions.push(key.clone());
|
||||
vm.symbols.symbol_registry.insert(key, idx);
|
||||
NativeResult::Ok(JsValue::symbol(idx))
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ pub fn symbol_key_for(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
let idx = sym.as_symbol_index();
|
||||
let found = vm
|
||||
.symbols
|
||||
.symbol_registry
|
||||
.iter()
|
||||
.find(|(_, ®istered_idx)| registered_idx == idx)
|
||||
|
||||
@@ -19,4 +19,108 @@ impl Vm {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_neg(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let v = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(-v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_unary_plus(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let v = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_compound_add(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let lhs = self.coerce_primitive_bounded(self.regs[rd], false)?;
|
||||
let rhs = self.coerce_primitive_bounded(self.regs[a], false)?;
|
||||
if lhs.is_string() || rhs.is_string() {
|
||||
let ls = coercion::to_string(lhs);
|
||||
let rs = coercion::to_string(rhs);
|
||||
let concat = format!("{ls}{rs}");
|
||||
self.regs[rd] = self.new_string(&concat);
|
||||
} else {
|
||||
let ln = coercion::to_number(lhs);
|
||||
let rn = coercion::to_number(rhs);
|
||||
self.regs[rd] = JsValue::float(ln + rn);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_compound_sub(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let l = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let r = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(l - r);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_compound_mul(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let l = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let r = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(l * r);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_compound_div(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let l = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let r = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(l / r);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_compound_mod(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let l = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let r = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(l % r);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_compound_exp(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let l = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let r = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(l.powf(r));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_inc_pre(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let result = JsValue::float(n + 1.0);
|
||||
self.regs[rd] = result;
|
||||
self.regs[a] = result;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_inc_post(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
self.regs[a] = JsValue::float(n);
|
||||
self.regs[rd] = JsValue::float(n + 1.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_dec_pre(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let result = JsValue::float(n - 1.0);
|
||||
self.regs[rd] = result;
|
||||
self.regs[a] = result;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_dec_post(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
self.regs[a] = JsValue::float(n);
|
||||
self.regs[rd] = JsValue::float(n - 1.0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::native::NativeFn;
|
||||
use crate::vm::{native_fn_ptr_to_fn, FrameContinuation, Vm};
|
||||
use crate::vm::{native_fn_ptr_to_fn, CallFrame, FrameContinuation, Vm};
|
||||
use oxide_bytecode::opcode;
|
||||
use oxide_kernel::{builtins_debug, builtins_trace};
|
||||
use oxide_runtime_api::NativeResult;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl Vm {
|
||||
#[inline(always)]
|
||||
@@ -73,3 +75,241 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
pub(crate) fn dispatch_create_closure(&mut self, rd: usize, instr: u32) {
|
||||
let sub_idx = opcode::imm16(instr) as u32;
|
||||
debug_assert!(sub_idx > 0 && (sub_idx as usize) <= self.sub_modules.len());
|
||||
let sub = &self.sub_modules[sub_idx as usize - 1];
|
||||
let result = self.create_function_object(
|
||||
sub_idx,
|
||||
sub.is_arrow,
|
||||
sub.is_class_constructor,
|
||||
sub.is_derived_constructor,
|
||||
sub.needs_home_object,
|
||||
);
|
||||
self.regs[rd] = result;
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_create_regexp(&mut self, rd: usize, a: usize, b: usize) -> Result<Option<JsValue>, String> {
|
||||
let pat_val = self.regs[a];
|
||||
let flags_val = self.regs[b];
|
||||
let ctor_ptr = self.session.builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||||
let ctor = unsafe { &*ctor_ptr };
|
||||
let Some(native_fn) = ctor.native_fn() else {
|
||||
self.raise_error_kind("TypeError", "RegExp constructor unavailable")?;
|
||||
return Ok(Some(JsValue::undefined()));
|
||||
};
|
||||
let saved_0 = self.regs[0];
|
||||
let saved_1 = self.regs[1];
|
||||
let saved_2 = self.regs[2];
|
||||
self.regs[0] = JsValue::undefined();
|
||||
self.regs[1] = pat_val;
|
||||
self.regs[2] = flags_val;
|
||||
let func = unsafe { native_fn_ptr_to_fn(native_fn) };
|
||||
let result = match func(self, &[0, 1, 2]) {
|
||||
NativeResult::Ok(v) => v,
|
||||
NativeResult::Err(e) => {
|
||||
return Err(self.error_message_text("TypeError", &self.error_text(e)));
|
||||
}
|
||||
NativeResult::TailCall { .. } => {
|
||||
return Err(self.error_message_text("TypeError", "unexpected tail call"));
|
||||
}
|
||||
};
|
||||
self.regs[0] = saved_0;
|
||||
self.regs[1] = saved_1;
|
||||
self.regs[2] = saved_2;
|
||||
self.regs[rd] = result;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_super_call(&mut self, rd: usize, a: usize) -> Result<bool, String> {
|
||||
let first_arg_reg = a as u8;
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let arg_count = (ext & 0xFF) as usize;
|
||||
|
||||
let Some(frame) = self.frames.last() else {
|
||||
self.raise_error_kind("ReferenceError", "super() used outside class constructor")?;
|
||||
return Ok(true);
|
||||
};
|
||||
if !frame.is_derived_constructor {
|
||||
self.raise_error_kind("ReferenceError", "super() used outside derived constructor")?;
|
||||
return Ok(true);
|
||||
}
|
||||
if !self.regs[254].is_undefined() {
|
||||
self.raise_error_kind("ReferenceError", "super() called more than once")?;
|
||||
return Ok(true);
|
||||
}
|
||||
let Some(derived_this) = frame.constructed_this else {
|
||||
self.raise_error_kind("ReferenceError", "super() without derived this")?;
|
||||
return Ok(true);
|
||||
};
|
||||
|
||||
let new_target = self.regs[255];
|
||||
if !new_target.is_object() {
|
||||
self.raise_error_kind("TypeError", "super() new.target is not an object")?;
|
||||
return Ok(true);
|
||||
}
|
||||
let new_target_obj = unsafe { &*new_target.as_js_object_ptr() };
|
||||
let super_ctor = new_target_obj.proto();
|
||||
if !super_ctor.is_object() {
|
||||
self.raise_error_kind("TypeError", "super constructor is not an object")?;
|
||||
return Ok(true);
|
||||
}
|
||||
let super_obj = unsafe { &*super_ctor.as_js_object_ptr() };
|
||||
if !super_obj.is_function() {
|
||||
self.raise_error_kind("TypeError", "super constructor is not a function")?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if super_obj.native_fn().is_some() {
|
||||
self.regs[253] = derived_this;
|
||||
self.regs[254] = super_ctor; // bind_dispatcher reads regs[254] as the wrapper callee
|
||||
let (args_buf, len) = Self::build_native_args(first_arg_reg, arg_count, 253);
|
||||
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
|
||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
||||
let func: NativeFn = unsafe { native_fn_ptr_to_fn(super_obj.native_fn().unwrap()) };
|
||||
match func(self, &args_buf[..len]) {
|
||||
NativeResult::Ok(val) => {
|
||||
self.regs[254] = if val.is_object() { val } else { derived_this };
|
||||
self.regs[rd] = self.regs[254];
|
||||
}
|
||||
NativeResult::Err(err_val) => {
|
||||
self.exception_value = Some(err_val);
|
||||
self.pending_error_kind = Some(self.thrown_error_kind(err_val));
|
||||
match self.unwind() {
|
||||
Ok(()) => return Ok(true),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
NativeResult::TailCall { callee, this, args } => {
|
||||
// e.g. bound function: resolve the tail call, use its return
|
||||
// value as the constructed instance (or fall back to derived_this).
|
||||
match self.call_function_sync(callee, this, &args) {
|
||||
Ok(val) => {
|
||||
self.regs[254] = if val.is_object() { val } else { derived_this };
|
||||
self.regs[rd] = self.regs[254];
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if super_obj.sub_module_index() > 0 {
|
||||
let sub_idx = super_obj.sub_module_index() as usize - 1;
|
||||
if sub_idx >= self.sub_modules.len() {
|
||||
return Err(format!(
|
||||
"SUPER_CALL: sub_module_index {} out of bounds (max {})",
|
||||
sub_idx,
|
||||
self.sub_modules.len()
|
||||
));
|
||||
}
|
||||
if self.frames.len() >= self.kernel_core.config.max_call_depth {
|
||||
return Err(self.error_message_text("RangeError", "Maximum call stack size exceeded"));
|
||||
}
|
||||
|
||||
let sub_bytecode = self.sub_modules[sub_idx].bytecode.clone();
|
||||
let sub_n_args = self.sub_modules[sub_idx].n_args as usize;
|
||||
let sub_n_registers = self.sub_modules[sub_idx].n_registers;
|
||||
let sub_param_base = self.sub_modules[sub_idx].param_base as usize;
|
||||
let caller_reg_limit = self.active_reg_limit.max(1);
|
||||
let saved_reg_offset = self.save_stack.len() as u32;
|
||||
self.save_stack.extend_from_slice(&self.regs[..caller_reg_limit as usize]);
|
||||
let saved_this = self.regs[254];
|
||||
let saved_new_target = self.regs[255];
|
||||
|
||||
for i in 0..sub_n_args {
|
||||
let src_reg = first_arg_reg.wrapping_add(i as u8) as usize;
|
||||
self.regs[sub_param_base + i] = self.regs[src_reg];
|
||||
}
|
||||
self.regs[254] = derived_this;
|
||||
self.regs[255] = new_target;
|
||||
|
||||
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
||||
self.saved_immutables_stack.push(self.active_immutables);
|
||||
self.frames.push(CallFrame {
|
||||
return_addr: self.pc,
|
||||
function_name: self.sub_modules[sub_idx]
|
||||
.function_name
|
||||
.as_deref()
|
||||
.map(|name| self.kernel_core.perm_interner().intern(name).0)
|
||||
.unwrap_or(0),
|
||||
caller_reg_limit,
|
||||
saved_reg_offset,
|
||||
saved_this,
|
||||
saved_new_target,
|
||||
callee: super_ctor,
|
||||
construct_result_reg: Some(254),
|
||||
constructed_this: Some(derived_this),
|
||||
is_derived_constructor: super_obj.is_derived_constructor(),
|
||||
continuation: FrameContinuation::None,
|
||||
});
|
||||
|
||||
self.bytecode = sub_bytecode;
|
||||
let subs = Arc::clone(&self.sub_modules);
|
||||
self.activate_immutables(sub_idx + 1, &subs[sub_idx].constants);
|
||||
for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
let global = self.session.global_object();
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
|
||||
self.regs[*reg as usize] = global.get_prop_at(pos);
|
||||
}
|
||||
}
|
||||
self.active_reg_limit = sub_n_registers.max(1);
|
||||
self.pc = 0;
|
||||
return Ok(true);
|
||||
} else {
|
||||
self.raise_error_kind("TypeError", "super constructor is not callable")?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_super_get_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<bool, String> {
|
||||
let key_val = self.regs[b];
|
||||
let prop_name_si = self.property_key_si(key_val);
|
||||
let Some(frame) = self.frames.last() else {
|
||||
self.raise_error_kind("ReferenceError", "super property used outside function")?;
|
||||
return Ok(true);
|
||||
};
|
||||
if !frame.callee.is_object() {
|
||||
self.raise_error_kind("ReferenceError", "super property has no home object")?;
|
||||
return Ok(true);
|
||||
}
|
||||
let callee_obj = unsafe { &*frame.callee.as_js_object_ptr() };
|
||||
let home_object = callee_obj.home_object();
|
||||
if !home_object.is_object() {
|
||||
self.raise_error_kind("ReferenceError", "super property has no home object")?;
|
||||
return Ok(true);
|
||||
}
|
||||
let home_obj = unsafe { &*home_object.as_js_object_ptr() };
|
||||
let super_base = home_obj.proto();
|
||||
if !super_base.is_object() {
|
||||
self.regs[rd] = JsValue::undefined();
|
||||
} else {
|
||||
let super_obj = unsafe { &*super_base.as_js_object_ptr() };
|
||||
let val = self.ordinary_get_with_target(super_obj, prop_name_si, self.regs[a], rd as u8)?;
|
||||
if self.accessor_frame_target_reg.take().is_none() {
|
||||
self.regs[rd] = val;
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_set_home_object(&mut self, rd: usize, a: usize) -> Result<bool, String> {
|
||||
let func_val = self.regs[rd];
|
||||
let home_val = self.regs[a];
|
||||
if !func_val.is_object() || !home_val.is_object() {
|
||||
self.raise_error_kind("TypeError", "SET_HOME_OBJECT expects function and object")?;
|
||||
return Ok(true);
|
||||
}
|
||||
let home_val = self.promote_if_needed_for_write_ptr(func_val.as_js_object_ptr(), home_val);
|
||||
let func_obj = unsafe { &mut *func_val.as_js_object_ptr() };
|
||||
if !func_obj.is_function() {
|
||||
self.raise_error_kind("TypeError", "SET_HOME_OBJECT target is not a function")?;
|
||||
return Ok(true);
|
||||
}
|
||||
func_obj.set_home_object(home_val);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,41 @@
|
||||
use crate::coercion;
|
||||
use crate::vm::Vm;
|
||||
use oxide_bytecode::opcode::{self, Instr};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
impl Vm {
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_jmp(&mut self, instr: Instr) {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
self.pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_jmp_if_false(&mut self, rd: usize, instr: Instr) {
|
||||
let cond = coercion::to_boolean(self.regs[rd]);
|
||||
if !cond {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
self.pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_jmp_if_true(&mut self, rd: usize, instr: Instr) {
|
||||
let cond = coercion::to_boolean(self.regs[rd]);
|
||||
if cond {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
self.pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_jmp_if_nullish(&mut self, rd: usize, instr: Instr) {
|
||||
if self.regs[rd].is_nullish() {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
self.pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_and(&mut self, rd: usize, a: usize, b: usize) {
|
||||
let val = self.regs[a];
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::vm::Vm;
|
||||
use oxide_bytecode::opcode;
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
impl Vm {
|
||||
#[inline(always)]
|
||||
@@ -40,4 +44,51 @@ impl Vm {
|
||||
};
|
||||
self.regs[rd] = self.new_string(result);
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_load_var(&mut self, rd: usize, a: usize) -> Result<bool, String> {
|
||||
if a == 254
|
||||
&& self.frames.last().map(|frame| frame.is_derived_constructor).unwrap_or(false)
|
||||
&& self.regs[a].is_undefined()
|
||||
{
|
||||
self.raise_error_kind("ReferenceError", "must call super constructor before using 'this'")?;
|
||||
return Ok(true);
|
||||
}
|
||||
self.regs[rd] = self.regs[a];
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_store_var(&mut self, rd: usize, a: usize, b: usize) -> Result<bool, String> {
|
||||
if b != 0 {
|
||||
// const guard: check if already initialized
|
||||
if !self.regs[rd].is_undefined() {
|
||||
self.raise_error_kind("TypeError", "Assignment to constant variable")?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
self.regs[rd] = self.regs[a];
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_new_object(&mut self, rd: usize) {
|
||||
let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject;
|
||||
let obj = self
|
||||
.epoch
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
|
||||
self.regs[rd] = JsValue::object(obj as *mut u8);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_new_array(&mut self, rd: usize, instr: u32) {
|
||||
let proto_ptr = self.session.builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let n = opcode::imm16(instr) as usize;
|
||||
let bump = self.epoch.bump();
|
||||
let obj = self.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr), n, bump));
|
||||
self.regs[rd] = JsValue::object(obj as *mut u8);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_void(&mut self, rd: usize) {
|
||||
self.regs[rd] = JsValue::undefined();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,14 +198,14 @@ impl Vm {
|
||||
|
||||
if let Some(value) = ic_get_hit(obj, cached_shape_id, cached_slot) {
|
||||
self.regs[a] = value;
|
||||
self.ic_hits.set(self.ic_hits.get() + 1);
|
||||
self.profiling.ic_hits.set(self.profiling.ic_hits.get() + 1);
|
||||
ic_trace!("IC_GET hit shape={} slot={}", cached_shape_id, cached_slot);
|
||||
} else if let Some(template) = self.kernel_core.prop_forge().get_template(obj.shape_id()) {
|
||||
self.ic_misses.set(self.ic_misses.get() + 1);
|
||||
self.profiling.ic_misses.set(self.profiling.ic_misses.get() + 1);
|
||||
prop_cache_miss();
|
||||
if template.prop_name == prop_name_si {
|
||||
if template.position < obj.prop_vec_len() as u32 {
|
||||
self.write_ic_back(obj.shape_id(), template.position);
|
||||
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), template.position);
|
||||
ic_debug!(
|
||||
"IC_GET propforge hit shape={} prop={} slot={}",
|
||||
obj.shape_id(),
|
||||
@@ -226,7 +226,7 @@ impl Vm {
|
||||
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
|
||||
let resolved = self.ordinary_get(obj, prop_name_si, val)?;
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
self.write_ic_back(obj.shape_id(), pos);
|
||||
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos);
|
||||
}
|
||||
self.regs[a] = resolved;
|
||||
}
|
||||
@@ -268,22 +268,22 @@ impl Vm {
|
||||
let cached_slot = ext1;
|
||||
|
||||
if ic_set_hit(obj, cached_shape_id, cached_slot, value) {
|
||||
self.ic_hits.set(self.ic_hits.get() + 1);
|
||||
self.profiling.ic_hits.set(self.profiling.ic_hits.get() + 1);
|
||||
ic_trace!("IC_SET hit shape={} slot={}", cached_shape_id, cached_slot);
|
||||
} else if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
self.ic_misses.set(self.ic_misses.get() + 1);
|
||||
self.profiling.ic_misses.set(self.profiling.ic_misses.get() + 1);
|
||||
prop_cache_miss();
|
||||
obj.set_prop_at(pos, value);
|
||||
self.write_ic_back(obj.shape_id(), pos);
|
||||
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos);
|
||||
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
|
||||
} else {
|
||||
self.ic_misses.set(self.ic_misses.get() + 1);
|
||||
self.profiling.ic_misses.set(self.profiling.ic_misses.get() + 1);
|
||||
prop_cache_miss();
|
||||
let old_shape = obj.shape_id();
|
||||
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
|
||||
if old_shape != obj.shape_id() {
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
self.write_ic_back(obj.shape_id(), pos);
|
||||
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos);
|
||||
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
|
||||
self.kernel_core.prop_forge().upsert(
|
||||
obj.shape_id(),
|
||||
@@ -394,3 +394,34 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
pub(crate) fn dispatch_delete_prop_static(&mut self, rd: usize) -> Result<bool, String> {
|
||||
let prop_idx = self.bytecode[self.pc] as usize;
|
||||
self.pc += 1;
|
||||
let key_val = self.immutables().get(prop_idx).copied().unwrap_or_else(JsValue::undefined);
|
||||
let prop_name_si = self.property_key_si(key_val);
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
|
||||
return Ok(true);
|
||||
};
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
obj.set_prop_at(pos, JsValue::undefined());
|
||||
}
|
||||
self.regs[rd] = JsValue::bool(true);
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_delete_prop_dynamic(&mut self, rd: usize, b: usize) -> Result<bool, String> {
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
|
||||
return Ok(true);
|
||||
};
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
obj.set_prop_at(pos, JsValue::undefined());
|
||||
}
|
||||
self.regs[rd] = JsValue::bool(true);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
//! Inline-cache (IC) bytecode-stream helpers.
|
||||
//!
|
||||
//! These operate purely on the bytecode `Instr` slice — they read/clear/write
|
||||
//! the IC extension words embedded after IC-bearing opcodes. They take no `Vm`
|
||||
//! state, so they live outside `Vm` for clearer ownership and to ease the future
|
||||
//! IC side-table migration.
|
||||
|
||||
use oxide_bytecode::opcode::{self, Instr};
|
||||
|
||||
/// Write a resolved `(shape_id, slot)` back into the three IC extension words
|
||||
/// that precede the current `pc`. Called on an IC miss after resolving a
|
||||
/// property, so the next execution of this site hits the cache.
|
||||
pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, slot_index: u32) {
|
||||
debug_assert!(pc >= 3, "IC write-back requires 3 extension words before pc");
|
||||
bytecode[pc - 3] = shape_id & 0x00FF_FFFF;
|
||||
bytecode[pc - 2] = slot_index;
|
||||
bytecode[pc - 1] = 0;
|
||||
}
|
||||
|
||||
/// Zero every IC extension word in the stream, invalidating all cached shapes.
|
||||
/// Used on `rerun()` so re-execution never derefs a stale cached pointer.
|
||||
pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) {
|
||||
let mut i = 0;
|
||||
while i < bytecode.len() {
|
||||
let op = opcode::opcode(bytecode[i]);
|
||||
if op.has_ic_ext_words() {
|
||||
if i + 3 < bytecode.len() {
|
||||
bytecode[i + 1] = 0;
|
||||
bytecode[i + 2] = 0;
|
||||
bytecode[i + 3] = 0;
|
||||
}
|
||||
i += 4;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub mod bindings;
|
||||
pub mod builtins;
|
||||
pub mod coercion;
|
||||
mod dispatch;
|
||||
mod ic_helper;
|
||||
pub mod native;
|
||||
mod session_arena;
|
||||
pub mod session_gc;
|
||||
@@ -13,5 +14,6 @@ mod vm_dispatch_misc;
|
||||
pub mod vm_pool;
|
||||
mod vm_props;
|
||||
mod vm_runtime;
|
||||
mod vm_state;
|
||||
mod vm_support;
|
||||
pub use oxide_types::value::JsValue;
|
||||
|
||||
@@ -17,10 +17,10 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn promote_object(&mut self, src: *mut JsObject) -> *mut JsObject {
|
||||
let mut forwarding = std::mem::take(&mut self.forwarding);
|
||||
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
|
||||
let result = self.promote_object_inner(src, &mut forwarding);
|
||||
forwarding.clear();
|
||||
self.forwarding = forwarding;
|
||||
self.gc_state.forwarding = forwarding;
|
||||
result
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@ impl Vm {
|
||||
}
|
||||
|
||||
let clone = src_ref.clone_for_session_epoch();
|
||||
let dst = self.session_epoch.alloc(clone) as *mut JsObject;
|
||||
let dst = self.gc_state.session_epoch.alloc(clone) as *mut JsObject;
|
||||
forwarding.insert(src, dst);
|
||||
self.session_object_ptrs.push(dst);
|
||||
self.session_bytes_allocated += std::mem::size_of::<JsObject>();
|
||||
self.gc_state.session_object_ptrs.push(dst);
|
||||
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsObject>();
|
||||
|
||||
let dst_ref = unsafe { &mut *dst };
|
||||
dst_ref.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding));
|
||||
@@ -243,11 +243,11 @@ mod tests {
|
||||
assert!(!promoted.is_null());
|
||||
// The shared forwarding map must be cleared after each promote so a
|
||||
// later promote (or GC sweep) never observes stale old->new mappings.
|
||||
assert!(vm.forwarding.is_empty());
|
||||
assert!(vm.gc_state.forwarding.is_empty());
|
||||
|
||||
let second = plain_object(&mut vm);
|
||||
let promoted2 = vm.promote_object(second);
|
||||
assert!(!promoted2.is_null());
|
||||
assert!(vm.forwarding.is_empty());
|
||||
assert!(vm.gc_state.forwarding.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ impl SessionGc {
|
||||
|
||||
impl SessionGc {
|
||||
pub(crate) fn clear_all_marks(&mut self, vm: &mut Vm) {
|
||||
for &ptr in &vm.session_object_ptrs {
|
||||
for &ptr in &vm.gc_state.session_object_ptrs {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
@@ -251,8 +251,8 @@ impl SessionGc {
|
||||
}
|
||||
|
||||
pub(crate) fn sweep(&mut self, vm: &mut Vm) -> u64 {
|
||||
let old_ptrs = std::mem::take(&mut vm.session_object_ptrs);
|
||||
let mut forwarding = std::mem::take(&mut vm.forwarding);
|
||||
let old_ptrs = std::mem::take(&mut vm.gc_state.session_object_ptrs);
|
||||
let mut forwarding = std::mem::take(&mut vm.gc_state.forwarding);
|
||||
let new_arena = bumpalo::Bump::new();
|
||||
let mut survivors = 0u64;
|
||||
let mut dead = 0u64;
|
||||
@@ -315,11 +315,11 @@ impl SessionGc {
|
||||
|
||||
rewrite_vm_roots(vm, &forwarding);
|
||||
|
||||
vm.session_object_ptrs = forwarding.values().copied().collect();
|
||||
vm.gc_state.session_object_ptrs = forwarding.values().copied().collect();
|
||||
forwarding.clear();
|
||||
vm.forwarding = forwarding;
|
||||
vm.session_epoch = new_arena;
|
||||
vm.session_bytes_allocated = vm.session_object_ptrs.len() * size_of::<JsObject>();
|
||||
vm.gc_state.forwarding = forwarding;
|
||||
vm.gc_state.session_epoch = new_arena;
|
||||
vm.gc_state.session_bytes_allocated = vm.gc_state.session_object_ptrs.len() * size_of::<JsObject>();
|
||||
|
||||
self.clear_all_marks(vm);
|
||||
|
||||
@@ -355,7 +355,7 @@ impl SessionGc {
|
||||
/// `session_bytes_allocated` to object-only), re-adding surviving string bytes. Returns the
|
||||
/// bytes freed.
|
||||
pub(crate) fn sweep_session_strings(&mut self, vm: &mut Vm) -> u64 {
|
||||
let old = std::mem::take(&mut vm.session_string_ptrs);
|
||||
let old = std::mem::take(&mut vm.gc_state.session_string_ptrs);
|
||||
let mut freed = 0u64;
|
||||
let mut live_bytes = 0usize;
|
||||
let mut live = Vec::with_capacity(old.len());
|
||||
@@ -373,8 +373,8 @@ impl SessionGc {
|
||||
freed += unsafe { Self::drop_dead_session_string(ptr) };
|
||||
}
|
||||
}
|
||||
vm.session_string_ptrs = live;
|
||||
vm.session_bytes_allocated = vm.session_bytes_allocated.saturating_add(live_bytes);
|
||||
vm.gc_state.session_string_ptrs = live;
|
||||
vm.gc_state.session_bytes_allocated = vm.gc_state.session_bytes_allocated.saturating_add(live_bytes);
|
||||
|
||||
self.total_bytes_freed = self.total_bytes_freed.saturating_add(freed);
|
||||
self.last_collection_bytes_freed = self.last_collection_bytes_freed.saturating_add(freed);
|
||||
@@ -382,8 +382,8 @@ impl SessionGc {
|
||||
}
|
||||
|
||||
pub(crate) fn should_collect(&self, vm: &Vm) -> bool {
|
||||
(!vm.session_object_ptrs.is_empty() || !vm.session_string_ptrs.is_empty())
|
||||
&& vm.session_bytes_allocated >= vm.kernel_core().config().session_gc_threshold
|
||||
(!vm.gc_state.session_object_ptrs.is_empty() || !vm.gc_state.session_string_ptrs.is_empty())
|
||||
&& vm.gc_state.session_bytes_allocated >= vm.kernel_core().config().session_gc_threshold
|
||||
}
|
||||
|
||||
pub(crate) fn collect(&mut self, vm: &mut Vm) {
|
||||
@@ -471,12 +471,12 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje
|
||||
}
|
||||
vm.exception_value = vm.exception_value.map(|value| rewrite_forwarded_value(value, forwarding));
|
||||
vm.pending_exception = vm.pending_exception.map(|value| rewrite_forwarded_value(value, forwarding));
|
||||
for value in &mut vm.for_of_iters {
|
||||
for value in &mut vm.iters.for_of_iters {
|
||||
*value = rewrite_forwarded_value(*value, forwarding);
|
||||
}
|
||||
vm.last_for_of_result = rewrite_forwarded_value(vm.last_for_of_result, forwarding);
|
||||
vm.iters.last_for_of_result = rewrite_forwarded_value(vm.iters.last_for_of_result, forwarding);
|
||||
// Converted immutables are non-session (scalars + perm-strings) — not rewritten.
|
||||
for iter in &mut vm.for_in_iters {
|
||||
for iter in &mut vm.iters.for_in_iters {
|
||||
if iter.is_null() {
|
||||
continue;
|
||||
}
|
||||
@@ -556,8 +556,8 @@ mod tests {
|
||||
vm.regs[1] = JsValue::from_js_object(child_session);
|
||||
vm.exception_value = Some(JsValue::from_js_object(root_session));
|
||||
vm.pending_exception = Some(JsValue::from_js_object(child_session));
|
||||
vm.for_of_iters.push(JsValue::from_js_object(child_session));
|
||||
vm.last_for_of_result = JsValue::from_js_object(root_session);
|
||||
vm.iters.for_of_iters.push(JsValue::from_js_object(child_session));
|
||||
vm.iters.last_for_of_result = JsValue::from_js_object(root_session);
|
||||
|
||||
let mut roots = Vec::new();
|
||||
vm.for_each_root(|v| roots.push(v));
|
||||
@@ -587,9 +587,9 @@ mod tests {
|
||||
let unreachable_session = vm.promote_object(unreachable);
|
||||
|
||||
vm.regs[0] = JsValue::from_js_object(root_session);
|
||||
let mut gc = std::mem::take(&mut vm.session_gc);
|
||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
gc.mark(&vm);
|
||||
vm.session_gc = gc;
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
assert!(unsafe { (*root_session).is_gc_marked() });
|
||||
assert!(unsafe { (*reachable_session).is_gc_marked() });
|
||||
@@ -611,15 +611,19 @@ mod tests {
|
||||
let root_session = vm.promote_object(root);
|
||||
vm.regs[0] = JsValue::from_js_object(root_session);
|
||||
let dead_session = vm.promote_object(dead);
|
||||
let mut gc = std::mem::take(&mut vm.session_gc);
|
||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
gc.mark(&vm);
|
||||
let _ = gc.sweep(&mut vm);
|
||||
vm.session_gc = gc;
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
assert_eq!(vm.session_object_ptrs.len(), 2);
|
||||
assert!(!vm.session_object_ptrs.contains(&root_session));
|
||||
assert!(!vm.session_object_ptrs.contains(&dead_session));
|
||||
assert!(!vm.session_object_ptrs.iter().any(|ptr| unsafe { (*(*ptr)).is_gc_marked() }));
|
||||
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
||||
assert!(!vm.gc_state.session_object_ptrs.contains(&root_session));
|
||||
assert!(!vm.gc_state.session_object_ptrs.contains(&dead_session));
|
||||
assert!(!vm
|
||||
.gc_state
|
||||
.session_object_ptrs
|
||||
.iter()
|
||||
.any(|ptr| unsafe { (*(*ptr)).is_gc_marked() }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -633,14 +637,14 @@ mod tests {
|
||||
let root_session = vm.promote_object(root);
|
||||
vm.regs[0] = JsValue::from_js_object(root_session);
|
||||
let _ = vm.promote_object(child);
|
||||
let mut gc = std::mem::take(&mut vm.session_gc);
|
||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
gc.mark(&vm);
|
||||
let _ = gc.sweep(&mut vm);
|
||||
vm.session_gc = gc;
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
// The reused forwarding map MUST be cleared after sweep, otherwise
|
||||
// promote would observe stale old->new entries pointing into the freed arena.
|
||||
assert!(vm.forwarding.is_empty());
|
||||
assert!(vm.gc_state.forwarding.is_empty());
|
||||
}
|
||||
|
||||
fn vm_with_low_threshold() -> Vm {
|
||||
@@ -663,16 +667,16 @@ mod tests {
|
||||
let mut vm = vm_with_low_threshold();
|
||||
let obj = plain_object(&mut vm);
|
||||
vm.promote_object(obj);
|
||||
assert!(!vm.session_object_ptrs.is_empty());
|
||||
let tracked_before = vm.session_object_ptrs.len();
|
||||
assert!(!vm.gc_state.session_object_ptrs.is_empty());
|
||||
let tracked_before = vm.gc_state.session_object_ptrs.len();
|
||||
|
||||
vm.regs[0] = JsValue::undefined();
|
||||
vm.regs[1] = JsValue::undefined();
|
||||
vm.reset();
|
||||
|
||||
assert!(vm.session_object_ptrs.len() <= tracked_before);
|
||||
assert_eq!(vm.session_object_ptrs.len(), 0);
|
||||
assert_eq!(vm.session_bytes_allocated, 0);
|
||||
assert!(vm.gc_state.session_object_ptrs.len() <= tracked_before);
|
||||
assert_eq!(vm.gc_state.session_object_ptrs.len(), 0);
|
||||
assert_eq!(vm.gc_state.session_bytes_allocated, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -682,7 +686,7 @@ mod tests {
|
||||
vm.promote_object(obj);
|
||||
vm.regs[0] = JsValue::undefined();
|
||||
vm.maybe_collect_session_gc();
|
||||
let summary = vm.session_gc.stats_summary();
|
||||
let summary = vm.gc_state.session_gc.stats_summary();
|
||||
assert!(summary.contains("[GC] collection"));
|
||||
}
|
||||
|
||||
@@ -743,15 +747,15 @@ mod tests {
|
||||
let map_session = vm.promote_object(map_value.as_js_object_ptr());
|
||||
vm.regs.fill(JsValue::undefined());
|
||||
vm.regs[0] = JsValue::from_js_object(map_session);
|
||||
let mut gc = std::mem::take(&mut vm.session_gc);
|
||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
gc.collect(&mut vm);
|
||||
vm.session_gc = gc;
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
let live_map = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
||||
let edges = map::map_native_edges(live_map);
|
||||
assert_eq!(edges.len(), 2);
|
||||
assert!(edges.iter().all(|value| vm.is_session_ptr(value.as_js_object_ptr())));
|
||||
assert_eq!(vm.session_object_ptrs.len(), 3);
|
||||
assert_eq!(vm.gc_state.session_object_ptrs.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -766,15 +770,15 @@ mod tests {
|
||||
let set_session = vm.promote_object(set_value.as_js_object_ptr());
|
||||
vm.regs.fill(JsValue::undefined());
|
||||
vm.regs[0] = JsValue::from_js_object(set_session);
|
||||
let mut gc = std::mem::take(&mut vm.session_gc);
|
||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
gc.collect(&mut vm);
|
||||
vm.session_gc = gc;
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
let live_set = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
||||
let edges = set::set_native_edges(live_set);
|
||||
assert_eq!(edges.len(), 1);
|
||||
assert!(vm.is_session_ptr(edges[0].as_js_object_ptr()));
|
||||
assert_eq!(vm.session_object_ptrs.len(), 2);
|
||||
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -805,9 +809,9 @@ mod tests {
|
||||
let root_session = vm.promote_object(root);
|
||||
vm.regs.fill(JsValue::undefined());
|
||||
vm.regs[0] = JsValue::from_js_object(root_session);
|
||||
let mut gc = std::mem::take(&mut vm.session_gc);
|
||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
gc.collect(&mut vm);
|
||||
vm.session_gc = gc;
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
let live_root = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
||||
let live_typed = live_root.get_prop_at(0);
|
||||
@@ -854,16 +858,16 @@ mod tests {
|
||||
let view_session = vm.promote_object(view.as_js_object_ptr());
|
||||
vm.regs.fill(JsValue::undefined());
|
||||
vm.regs[0] = JsValue::from_js_object(view_session);
|
||||
let mut gc = std::mem::take(&mut vm.session_gc);
|
||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
gc.collect(&mut vm);
|
||||
vm.session_gc = gc;
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
let live_view = vm.regs[0];
|
||||
let live_view_obj = unsafe { &*live_view.as_js_object_ptr() };
|
||||
let edges = data_view::data_view_native_edges(live_view_obj);
|
||||
assert_eq!(edges.len(), 1);
|
||||
assert!(vm.is_session_ptr(edges[0].as_js_object_ptr()));
|
||||
assert_eq!(vm.session_object_ptrs.len(), 2);
|
||||
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
||||
|
||||
vm.regs[0] = live_view;
|
||||
vm.regs[1] = JsValue::int(0);
|
||||
@@ -871,9 +875,9 @@ mod tests {
|
||||
}
|
||||
|
||||
fn collect(vm: &mut Vm) {
|
||||
let mut gc = std::mem::take(&mut vm.session_gc);
|
||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
gc.collect(vm);
|
||||
vm.session_gc = gc;
|
||||
vm.gc_state.session_gc = gc;
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -881,12 +885,12 @@ mod tests {
|
||||
let mut vm = Vm::new();
|
||||
let dead = vm.new_string("dead-string");
|
||||
let dead_ptr = dead.as_string_ptr_mut();
|
||||
assert!(vm.session_string_ptrs.contains(&dead_ptr));
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&dead_ptr));
|
||||
|
||||
// No root references `dead` (it lives only on the Rust stack as a value wrapper).
|
||||
collect(&mut vm);
|
||||
|
||||
assert!(!vm.session_string_ptrs.contains(&dead_ptr));
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&dead_ptr));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -898,7 +902,7 @@ mod tests {
|
||||
|
||||
collect(&mut vm);
|
||||
|
||||
assert!(vm.session_string_ptrs.contains(&live_ptr));
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&live_ptr));
|
||||
// Live strings are never moved — the register still points at the same box.
|
||||
assert_eq!(vm.regs[0].as_string_ptr_mut(), live_ptr);
|
||||
assert_eq!(unsafe { (*live_ptr).as_str() }, "live-in-reg");
|
||||
@@ -921,7 +925,7 @@ mod tests {
|
||||
|
||||
collect(&mut vm);
|
||||
|
||||
assert!(vm.session_string_ptrs.contains(&s_ptr));
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&s_ptr));
|
||||
assert_eq!(unsafe { (*s_ptr).as_str() }, "prop-string");
|
||||
}
|
||||
|
||||
@@ -931,13 +935,13 @@ mod tests {
|
||||
let perm = vm.perm_string("perm");
|
||||
let perm_ptr = perm.as_string_ptr_mut();
|
||||
// Permanent strings live in PermInterner, never in the session set.
|
||||
assert!(!vm.session_string_ptrs.contains(&perm_ptr));
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&perm_ptr));
|
||||
vm.regs[0] = perm;
|
||||
|
||||
collect(&mut vm);
|
||||
|
||||
// Never dropped by the session sweep (still readable, still absent from session set).
|
||||
assert!(!vm.session_string_ptrs.contains(&perm_ptr));
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&perm_ptr));
|
||||
assert_eq!(unsafe { (*perm_ptr).as_str() }, "perm");
|
||||
}
|
||||
|
||||
@@ -948,11 +952,11 @@ mod tests {
|
||||
let _ = dead.as_string_ptr_mut();
|
||||
let expected = (size_of::<JsString>() + "0123456789".len()) as u64;
|
||||
|
||||
let mut gc = std::mem::take(&mut vm.session_gc);
|
||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||
let before = gc.total_bytes_freed;
|
||||
gc.collect(&mut vm);
|
||||
let after = gc.total_bytes_freed;
|
||||
vm.session_gc = gc;
|
||||
vm.gc_state.session_gc = gc;
|
||||
|
||||
assert!(after >= before + expected);
|
||||
}
|
||||
|
||||
+103
-384
@@ -1,6 +1,5 @@
|
||||
#![allow(clippy::arc_with_non_send_sync)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use oxide_bytecode::module::{CompiledModule, Constant};
|
||||
@@ -11,12 +10,12 @@ pub use crate::bindings::init_kernel_builtins;
|
||||
use crate::coercion;
|
||||
use crate::native::NativeFn;
|
||||
use crate::session_gc::SessionGc;
|
||||
use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState};
|
||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_runtime_api::NativeResult;
|
||||
use oxide_types::error::{JsError, JsErrorKind};
|
||||
use oxide_types::mem::{Epoch, P};
|
||||
use oxide_types::object::{JsObject, JsString, NativeFnPtr};
|
||||
use oxide_types::object::{JsObject, NativeFnPtr};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024;
|
||||
@@ -81,14 +80,6 @@ macro_rules! binary_arith {
|
||||
}}
|
||||
}
|
||||
|
||||
macro_rules! compound_arith {
|
||||
($self:ident, $rd:expr, $a:expr, $op:tt) => {{
|
||||
let l = $self.coerce_number_bounded($self.regs[$rd])?;
|
||||
let r = $self.coerce_number_bounded($self.regs[$a])?;
|
||||
$self.regs[$rd] = JsValue::float(l $op r);
|
||||
}}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum FrameContinuation {
|
||||
None,
|
||||
@@ -156,21 +147,9 @@ pub struct Vm {
|
||||
/// A fat `*const` because the cache Vec is VM-owned and run-stable (OnceLock filled once).
|
||||
pub(crate) active_immutables: *const [JsValue],
|
||||
pub(crate) frames: SmallVec<[CallFrame; 16]>,
|
||||
pub for_in_iters: Vec<*mut ForInIter<'static>>,
|
||||
pub(crate) kernel_core: Arc<KernelCore>,
|
||||
pub(crate) session: KernelSession,
|
||||
pub(crate) session_string_ptrs: Vec<*mut JsString>,
|
||||
pub epoch: Epoch,
|
||||
pub(crate) session_epoch: bumpalo::Bump,
|
||||
pub(crate) session_gc: SessionGc,
|
||||
/// Long-lived forwarding map reused by GC sweep and promote.
|
||||
/// `mem::take`'d out for use, `clear()`'d and put back — capacity retained
|
||||
/// across collections, so no per-event `HashMap` allocation. `FxBuildHasher`
|
||||
/// because the keys are raw pointers (collision resistance is irrelevant).
|
||||
pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, rustc_hash::FxBuildHasher>,
|
||||
pub(crate) epoch_object_ptrs: Vec<*mut JsObject>,
|
||||
pub(crate) session_object_ptrs: Vec<*mut JsObject>,
|
||||
pub(crate) session_bytes_allocated: usize,
|
||||
pub object_prototype: P<JsObject>,
|
||||
pub math_rng_state: u64,
|
||||
pub(crate) sub_modules: Arc<Vec<CompiledModule>>,
|
||||
@@ -184,11 +163,6 @@ pub struct Vm {
|
||||
pub(crate) exception_value: Option<JsValue>,
|
||||
pub(crate) pending_exception: Option<JsValue>,
|
||||
pub(crate) pending_error_kind: Option<&'static str>,
|
||||
pub(crate) symbol_counter: u32,
|
||||
pub(crate) symbol_descriptions: Vec<String>,
|
||||
pub(crate) symbol_registry: HashMap<String, u32>,
|
||||
pub(crate) for_of_iters: Vec<JsValue>,
|
||||
pub(crate) last_for_of_result: JsValue,
|
||||
pub(crate) root_reg_limit: u8,
|
||||
pub(crate) active_reg_limit: u8,
|
||||
pub(crate) native_call_depth: usize,
|
||||
@@ -196,9 +170,14 @@ pub struct Vm {
|
||||
/// The dispatch loop checks this flag and skips writing `regs[target_reg]` from the
|
||||
/// call result — the value will be delivered by the RETURN handler instead.
|
||||
pub(crate) accessor_frame_target_reg: Option<u8>,
|
||||
pub(crate) ic_hits: std::cell::Cell<u64>,
|
||||
pub(crate) ic_misses: std::cell::Cell<u64>,
|
||||
pub(crate) instruction_count: u64,
|
||||
/// Grouped session-arena / GC bookkeeping.
|
||||
pub(crate) gc_state: GcState,
|
||||
/// Grouped `Symbol` interning state.
|
||||
pub(crate) symbols: SymbolState,
|
||||
/// Grouped live `for-in` / `for-of` iterator state.
|
||||
pub(crate) iters: IterState,
|
||||
/// Grouped inline-cache and instruction counters.
|
||||
pub(crate) profiling: ProfilingState,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
@@ -302,7 +281,7 @@ impl Vm {
|
||||
|
||||
pub(crate) fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject {
|
||||
let ptr = self.epoch.alloc(obj);
|
||||
self.epoch_object_ptrs.push(ptr);
|
||||
self.gc_state.epoch_object_ptrs.push(ptr);
|
||||
ptr
|
||||
}
|
||||
|
||||
@@ -348,12 +327,12 @@ impl Vm {
|
||||
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()));
|
||||
for &v in &self.for_of_iters {
|
||||
for &v in &self.iters.for_of_iters {
|
||||
f(v);
|
||||
}
|
||||
f(self.last_for_of_result);
|
||||
f(self.iters.last_for_of_result);
|
||||
// Converted immutables (scalars + perm-strings) are NOT session GC roots — not scanned.
|
||||
for iter in &self.for_in_iters {
|
||||
for iter in &self.iters.for_in_iters {
|
||||
if iter.is_null() {
|
||||
continue;
|
||||
}
|
||||
@@ -366,50 +345,50 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_collect_session_gc(&mut self) {
|
||||
let mut session_gc = std::mem::take(&mut self.session_gc);
|
||||
let mut session_gc = std::mem::take(&mut self.gc_state.session_gc);
|
||||
session_gc.maybe_collect(self);
|
||||
self.session_gc = session_gc;
|
||||
self.gc_state.session_gc = session_gc;
|
||||
}
|
||||
|
||||
pub fn session_gc_stats(&self) -> &SessionGc {
|
||||
&self.session_gc
|
||||
&self.gc_state.session_gc
|
||||
}
|
||||
|
||||
pub fn session_object_count(&self) -> usize {
|
||||
self.session_object_ptrs.len()
|
||||
self.gc_state.session_object_ptrs.len()
|
||||
}
|
||||
|
||||
pub fn session_bytes_allocated(&self) -> usize {
|
||||
self.session_bytes_allocated
|
||||
self.gc_state.session_bytes_allocated
|
||||
}
|
||||
|
||||
pub fn epoch_object_count(&self) -> usize {
|
||||
self.epoch_object_ptrs.len()
|
||||
self.gc_state.epoch_object_ptrs.len()
|
||||
}
|
||||
|
||||
pub fn ic_hit_rate(&self) -> f64 {
|
||||
let total = self.ic_hits.get() + self.ic_misses.get();
|
||||
let total = self.profiling.ic_hits.get() + self.profiling.ic_misses.get();
|
||||
if total == 0 {
|
||||
0.0
|
||||
} else {
|
||||
self.ic_hits.get() as f64 / total as f64
|
||||
self.profiling.ic_hits.get() as f64 / total as f64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instruction_count(&self) -> u64 {
|
||||
self.instruction_count
|
||||
self.profiling.instruction_count
|
||||
}
|
||||
|
||||
pub fn symbol_registry_len(&self) -> usize {
|
||||
self.symbol_registry.len()
|
||||
self.symbols.symbol_registry.len()
|
||||
}
|
||||
|
||||
pub fn ic_hit_count(&self) -> u64 {
|
||||
self.ic_hits.get()
|
||||
self.profiling.ic_hits.get()
|
||||
}
|
||||
|
||||
pub fn ic_miss_count(&self) -> u64 {
|
||||
self.ic_misses.get()
|
||||
self.profiling.ic_misses.get()
|
||||
}
|
||||
|
||||
pub(crate) fn checked_object_ptr(
|
||||
@@ -728,12 +707,12 @@ impl Vm {
|
||||
steps += 1;
|
||||
if let Some(max_steps) = self.kernel_core.config.max_steps {
|
||||
if steps > max_steps {
|
||||
self.instruction_count = steps;
|
||||
self.profiling.instruction_count = steps;
|
||||
return Err(format!("VM step limit exceeded at pc={}", self.pc));
|
||||
}
|
||||
}
|
||||
if self.pc >= self.bytecode.len() {
|
||||
self.instruction_count = steps;
|
||||
self.profiling.instruction_count = steps;
|
||||
return Err("program counter out of bounds".into());
|
||||
}
|
||||
|
||||
@@ -748,7 +727,7 @@ impl Vm {
|
||||
OpCode::NOP => {}
|
||||
|
||||
OpCode::HALT => {
|
||||
self.instruction_count = steps;
|
||||
self.profiling.instruction_count = steps;
|
||||
return Ok(self.regs[0]);
|
||||
}
|
||||
|
||||
@@ -757,48 +736,13 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::CREATE_CLOSURE => {
|
||||
let sub_idx = opcode::imm16(instr) as u32;
|
||||
debug_assert!(sub_idx > 0 && (sub_idx as usize) <= self.sub_modules.len());
|
||||
let sub = &self.sub_modules[sub_idx as usize - 1];
|
||||
let result = self.create_function_object(
|
||||
sub_idx,
|
||||
sub.is_arrow,
|
||||
sub.is_class_constructor,
|
||||
sub.is_derived_constructor,
|
||||
sub.needs_home_object,
|
||||
);
|
||||
self.regs[rd] = result;
|
||||
}
|
||||
OpCode::CREATE_REGEXP => {
|
||||
let pat_val = self.regs[a];
|
||||
let flags_val = self.regs[b];
|
||||
let ctor_ptr = self.session.builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||||
let ctor = unsafe { &*ctor_ptr };
|
||||
let Some(native_fn) = ctor.native_fn() else {
|
||||
self.raise_error_kind("TypeError", "RegExp constructor unavailable")?;
|
||||
return Ok(JsValue::undefined());
|
||||
};
|
||||
let saved_0 = self.regs[0];
|
||||
let saved_1 = self.regs[1];
|
||||
let saved_2 = self.regs[2];
|
||||
self.regs[0] = JsValue::undefined();
|
||||
self.regs[1] = pat_val;
|
||||
self.regs[2] = flags_val;
|
||||
let func = unsafe { crate::vm::native_fn_ptr_to_fn(native_fn) };
|
||||
let result = match func(self, &[0, 1, 2]) {
|
||||
oxide_runtime_api::NativeResult::Ok(v) => v,
|
||||
oxide_runtime_api::NativeResult::Err(e) => {
|
||||
return Err(self.error_message_text("TypeError", &self.error_text(e)));
|
||||
}
|
||||
oxide_runtime_api::NativeResult::TailCall { .. } => {
|
||||
return Err(self.error_message_text("TypeError", "unexpected tail call"));
|
||||
}
|
||||
};
|
||||
self.regs[0] = saved_0;
|
||||
self.regs[1] = saved_1;
|
||||
self.regs[2] = saved_2;
|
||||
self.regs[rd] = result;
|
||||
self.dispatch_create_closure(rd, instr);
|
||||
}
|
||||
OpCode::CREATE_REGEXP => match self.dispatch_create_regexp(rd, a, b) {
|
||||
Ok(Some(result)) => return Ok(result),
|
||||
Ok(None) => {}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
|
||||
OpCode::ADD => {
|
||||
self.dispatch_add(rd, a, b)?;
|
||||
@@ -821,8 +765,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::NEG => {
|
||||
let v = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(-v);
|
||||
self.dispatch_neg(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::BIT_AND => {
|
||||
@@ -886,57 +829,36 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::UNARY_PLUS => {
|
||||
let v = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(v);
|
||||
self.dispatch_unary_plus(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::JMP => {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
self.pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
self.dispatch_jmp(instr);
|
||||
}
|
||||
|
||||
OpCode::JMP_IF_FALSE => {
|
||||
let cond = coercion::to_boolean(self.regs[rd]);
|
||||
if !cond {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
self.pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
}
|
||||
self.dispatch_jmp_if_false(rd, instr);
|
||||
}
|
||||
|
||||
OpCode::JMP_IF_TRUE => {
|
||||
let cond = coercion::to_boolean(self.regs[rd]);
|
||||
if cond {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
self.pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
}
|
||||
self.dispatch_jmp_if_true(rd, instr);
|
||||
}
|
||||
|
||||
OpCode::JMP_IF_NULLISH => {
|
||||
if self.regs[rd].is_nullish() {
|
||||
let offset = opcode::offset16(instr) as isize;
|
||||
self.pc = ((self.pc as isize) + offset - 1) as usize;
|
||||
}
|
||||
self.dispatch_jmp_if_nullish(rd, instr);
|
||||
}
|
||||
|
||||
OpCode::LOAD_VAR => {
|
||||
if a == 254
|
||||
&& self.frames.last().map(|frame| frame.is_derived_constructor).unwrap_or(false)
|
||||
&& self.regs[a].is_undefined()
|
||||
{
|
||||
throw_err!(self, ReferenceError, "must call super constructor before using 'this'");
|
||||
}
|
||||
self.regs[rd] = self.regs[a];
|
||||
}
|
||||
OpCode::LOAD_VAR => match self.dispatch_load_var(rd, a) {
|
||||
Ok(true) => continue,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
|
||||
OpCode::STORE_VAR => {
|
||||
if b != 0 {
|
||||
// const guard: check if already initialized
|
||||
if !self.regs[rd].is_undefined() {
|
||||
throw_err!(self, TypeError, "Assignment to constant variable");
|
||||
}
|
||||
}
|
||||
self.regs[rd] = self.regs[a];
|
||||
}
|
||||
OpCode::STORE_VAR => match self.dispatch_store_var(rd, a, b) {
|
||||
Ok(true) => continue,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
|
||||
OpCode::CALL => match self.dispatch_call(rd, a, b) {
|
||||
Ok(true) => continue,
|
||||
@@ -954,179 +876,25 @@ impl Vm {
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
|
||||
OpCode::SUPER_CALL => {
|
||||
let first_arg_reg = a as u8;
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let arg_count = (ext & 0xFF) as usize;
|
||||
|
||||
let Some(frame) = self.frames.last() else {
|
||||
throw_err!(self, ReferenceError, "super() used outside class constructor");
|
||||
};
|
||||
if !frame.is_derived_constructor {
|
||||
throw_err!(self, ReferenceError, "super() used outside derived constructor");
|
||||
}
|
||||
if !self.regs[254].is_undefined() {
|
||||
throw_err!(self, ReferenceError, "super() called more than once");
|
||||
}
|
||||
let Some(derived_this) = frame.constructed_this else {
|
||||
throw_err!(self, ReferenceError, "super() without derived this");
|
||||
};
|
||||
|
||||
let new_target = self.regs[255];
|
||||
if !new_target.is_object() {
|
||||
throw_err!(self, TypeError, "super() new.target is not an object");
|
||||
}
|
||||
let new_target_obj = unsafe { &*new_target.as_js_object_ptr() };
|
||||
let super_ctor = new_target_obj.proto();
|
||||
if !super_ctor.is_object() {
|
||||
throw_err!(self, TypeError, "super constructor is not an object");
|
||||
}
|
||||
let super_obj = unsafe { &*super_ctor.as_js_object_ptr() };
|
||||
if !super_obj.is_function() {
|
||||
throw_err!(self, TypeError, "super constructor is not a function");
|
||||
}
|
||||
|
||||
if super_obj.native_fn().is_some() {
|
||||
self.regs[253] = derived_this;
|
||||
self.regs[254] = super_ctor; // bind_dispatcher reads regs[254] as the wrapper callee
|
||||
let (args_buf, len) = Self::build_native_args(first_arg_reg, arg_count, 253);
|
||||
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
|
||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
||||
let func: NativeFn = unsafe { native_fn_ptr_to_fn(super_obj.native_fn().unwrap()) };
|
||||
match func(self, &args_buf[..len]) {
|
||||
NativeResult::Ok(val) => {
|
||||
self.regs[254] = if val.is_object() { val } else { derived_this };
|
||||
self.regs[rd] = self.regs[254];
|
||||
}
|
||||
NativeResult::Err(err_val) => {
|
||||
self.exception_value = Some(err_val);
|
||||
self.pending_error_kind = Some(self.thrown_error_kind(err_val));
|
||||
match self.unwind() {
|
||||
Ok(()) => continue,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
NativeResult::TailCall { callee, this, args } => {
|
||||
// e.g. bound function: resolve the tail call, use its return
|
||||
// value as the constructed instance (or fall back to derived_this).
|
||||
match self.call_function_sync(callee, this, &args) {
|
||||
Ok(val) => {
|
||||
self.regs[254] = if val.is_object() { val } else { derived_this };
|
||||
self.regs[rd] = self.regs[254];
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if super_obj.sub_module_index() > 0 {
|
||||
let sub_idx = super_obj.sub_module_index() as usize - 1;
|
||||
if sub_idx >= self.sub_modules.len() {
|
||||
return Err(format!(
|
||||
"SUPER_CALL: sub_module_index {} out of bounds (max {})",
|
||||
sub_idx,
|
||||
self.sub_modules.len()
|
||||
));
|
||||
}
|
||||
if self.frames.len() >= self.kernel_core.config.max_call_depth {
|
||||
return Err(self.error_message_text("RangeError", "Maximum call stack size exceeded"));
|
||||
}
|
||||
|
||||
let sub_bytecode = self.sub_modules[sub_idx].bytecode.clone();
|
||||
let sub_n_args = self.sub_modules[sub_idx].n_args as usize;
|
||||
let sub_n_registers = self.sub_modules[sub_idx].n_registers;
|
||||
let sub_param_base = self.sub_modules[sub_idx].param_base as usize;
|
||||
let caller_reg_limit = self.active_reg_limit.max(1);
|
||||
let saved_reg_offset = self.save_stack.len() as u32;
|
||||
self.save_stack.extend_from_slice(&self.regs[..caller_reg_limit as usize]);
|
||||
let saved_this = self.regs[254];
|
||||
let saved_new_target = self.regs[255];
|
||||
|
||||
for i in 0..sub_n_args {
|
||||
let src_reg = first_arg_reg.wrapping_add(i as u8) as usize;
|
||||
self.regs[sub_param_base + i] = self.regs[src_reg];
|
||||
}
|
||||
self.regs[254] = derived_this;
|
||||
self.regs[255] = new_target;
|
||||
|
||||
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
||||
self.saved_immutables_stack.push(self.active_immutables);
|
||||
self.frames.push(CallFrame {
|
||||
return_addr: self.pc,
|
||||
function_name: self.sub_modules[sub_idx]
|
||||
.function_name
|
||||
.as_deref()
|
||||
.map(|name| self.kernel_core.perm_interner().intern(name).0)
|
||||
.unwrap_or(0),
|
||||
caller_reg_limit,
|
||||
saved_reg_offset,
|
||||
saved_this,
|
||||
saved_new_target,
|
||||
callee: super_ctor,
|
||||
construct_result_reg: Some(254),
|
||||
constructed_this: Some(derived_this),
|
||||
is_derived_constructor: super_obj.is_derived_constructor(),
|
||||
continuation: FrameContinuation::None,
|
||||
});
|
||||
|
||||
self.bytecode = sub_bytecode;
|
||||
let subs = Arc::clone(&self.sub_modules);
|
||||
self.activate_immutables(sub_idx + 1, &subs[sub_idx].constants);
|
||||
for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
let global = self.session.global_object();
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
|
||||
self.regs[*reg as usize] = global.get_prop_at(pos);
|
||||
}
|
||||
}
|
||||
self.active_reg_limit = sub_n_registers.max(1);
|
||||
self.pc = 0;
|
||||
continue;
|
||||
} else {
|
||||
throw_err!(self, TypeError, "super constructor is not callable");
|
||||
}
|
||||
}
|
||||
OpCode::SUPER_CALL => match self.dispatch_super_call(rd, a) {
|
||||
Ok(true) => continue,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
|
||||
OpCode::SUPER_GET_PROP | OpCode::SUPER_STATIC_GET_PROP => {
|
||||
let key_val = self.regs[b];
|
||||
let prop_name_si = self.property_key_si(key_val);
|
||||
let Some(frame) = self.frames.last() else {
|
||||
throw_err!(self, ReferenceError, "super property used outside function");
|
||||
};
|
||||
if !frame.callee.is_object() {
|
||||
throw_err!(self, ReferenceError, "super property has no home object");
|
||||
}
|
||||
let callee_obj = unsafe { &*frame.callee.as_js_object_ptr() };
|
||||
let home_object = callee_obj.home_object();
|
||||
if !home_object.is_object() {
|
||||
throw_err!(self, ReferenceError, "super property has no home object");
|
||||
}
|
||||
let home_obj = unsafe { &*home_object.as_js_object_ptr() };
|
||||
let super_base = home_obj.proto();
|
||||
if !super_base.is_object() {
|
||||
self.regs[rd] = JsValue::undefined();
|
||||
} else {
|
||||
let super_obj = unsafe { &*super_base.as_js_object_ptr() };
|
||||
let val = self.ordinary_get_with_target(super_obj, prop_name_si, self.regs[a], rd as u8)?;
|
||||
if self.accessor_frame_target_reg.take().is_none() {
|
||||
self.regs[rd] = val;
|
||||
}
|
||||
match self.dispatch_super_get_prop(rd, a, b) {
|
||||
Ok(true) => continue,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
OpCode::SET_HOME_OBJECT => {
|
||||
let func_val = self.regs[rd];
|
||||
let home_val = self.regs[a];
|
||||
if !func_val.is_object() || !home_val.is_object() {
|
||||
throw_err!(self, TypeError, "SET_HOME_OBJECT expects function and object");
|
||||
}
|
||||
let home_val = self.promote_if_needed_for_write_ptr(func_val.as_js_object_ptr(), home_val);
|
||||
let func_obj = unsafe { &mut *func_val.as_js_object_ptr() };
|
||||
if !func_obj.is_function() {
|
||||
throw_err!(self, TypeError, "SET_HOME_OBJECT target is not a function");
|
||||
}
|
||||
func_obj.set_home_object(home_val);
|
||||
}
|
||||
OpCode::SET_HOME_OBJECT => match self.dispatch_set_home_object(rd, a) {
|
||||
Ok(true) => continue,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
|
||||
OpCode::DEFINE_ACCESSOR => {
|
||||
self.dispatch_define_accessor(rd, a, b)?;
|
||||
@@ -1153,61 +921,35 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::NEW_OBJECT => {
|
||||
let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject;
|
||||
let obj = self
|
||||
.epoch
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
|
||||
self.regs[rd] = JsValue::object(obj as *mut u8);
|
||||
self.dispatch_new_object(rd);
|
||||
}
|
||||
|
||||
OpCode::NEW_ARRAY => {
|
||||
let proto_ptr = self.session.builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let n = opcode::imm16(instr) as usize;
|
||||
let bump = self.epoch.bump();
|
||||
let obj = self.alloc_object(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(proto_ptr),
|
||||
n,
|
||||
bump,
|
||||
));
|
||||
self.regs[rd] = JsValue::object(obj as *mut u8);
|
||||
self.dispatch_new_array(rd, instr);
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_ADD => {
|
||||
let lhs = self.coerce_primitive_bounded(self.regs[rd], false)?;
|
||||
let rhs = self.coerce_primitive_bounded(self.regs[a], false)?;
|
||||
if lhs.is_string() || rhs.is_string() {
|
||||
let ls = coercion::to_string(lhs);
|
||||
let rs = coercion::to_string(rhs);
|
||||
let concat = format!("{ls}{rs}");
|
||||
self.regs[rd] = self.new_string(&concat);
|
||||
} else {
|
||||
let ln = coercion::to_number(lhs);
|
||||
let rn = coercion::to_number(rhs);
|
||||
self.regs[rd] = JsValue::float(ln + rn);
|
||||
}
|
||||
self.dispatch_compound_add(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_SUB => {
|
||||
compound_arith!(self, rd, a, -);
|
||||
self.dispatch_compound_sub(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_MUL => {
|
||||
compound_arith!(self, rd, a, *);
|
||||
self.dispatch_compound_mul(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_DIV => {
|
||||
compound_arith!(self, rd, a, /);
|
||||
self.dispatch_compound_div(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_MOD => {
|
||||
compound_arith!(self, rd, a, %);
|
||||
self.dispatch_compound_mod(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_EXP => {
|
||||
let l = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let r = self.coerce_number_bounded(self.regs[a])?;
|
||||
self.regs[rd] = JsValue::float(l.powf(r));
|
||||
self.dispatch_compound_exp(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_AND => {
|
||||
@@ -1239,39 +981,24 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::VOID => {
|
||||
self.regs[rd] = JsValue::undefined();
|
||||
self.dispatch_void(rd);
|
||||
}
|
||||
|
||||
OpCode::TEMPLATE_STR => {
|
||||
self.dispatch_template_str(rd);
|
||||
}
|
||||
|
||||
OpCode::DELETE_PROP_STATIC => {
|
||||
let prop_idx = self.bytecode[self.pc] as usize;
|
||||
self.pc += 1;
|
||||
let key_val = self.immutables().get(prop_idx).copied().unwrap_or_else(JsValue::undefined);
|
||||
let prop_name_si = self.property_key_si(key_val);
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
|
||||
continue;
|
||||
};
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
obj.set_prop_at(pos, JsValue::undefined());
|
||||
}
|
||||
self.regs[rd] = JsValue::bool(true);
|
||||
}
|
||||
OpCode::DELETE_PROP_STATIC => match self.dispatch_delete_prop_static(rd) {
|
||||
Ok(true) => continue,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
|
||||
OpCode::DELETE_PROP_DYNAMIC => {
|
||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
|
||||
continue;
|
||||
};
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
obj.set_prop_at(pos, JsValue::undefined());
|
||||
}
|
||||
self.regs[rd] = JsValue::bool(true);
|
||||
}
|
||||
OpCode::DELETE_PROP_DYNAMIC => match self.dispatch_delete_prop_dynamic(rd, b) {
|
||||
Ok(true) => continue,
|
||||
Ok(false) => {}
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
|
||||
OpCode::INSTANCEOF => {
|
||||
self.dispatch_instanceof(rd, a, b)?;
|
||||
@@ -1298,29 +1025,19 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::INC_PRE => {
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let result = JsValue::float(n + 1.0);
|
||||
self.regs[rd] = result;
|
||||
self.regs[a] = result;
|
||||
self.dispatch_inc_pre(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::INC_POST => {
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
self.regs[a] = JsValue::float(n);
|
||||
self.regs[rd] = JsValue::float(n + 1.0);
|
||||
self.dispatch_inc_post(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::DEC_PRE => {
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
let result = JsValue::float(n - 1.0);
|
||||
self.regs[rd] = result;
|
||||
self.regs[a] = result;
|
||||
self.dispatch_dec_pre(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::DEC_POST => {
|
||||
let n = self.coerce_number_bounded(self.regs[rd])?;
|
||||
self.regs[a] = JsValue::float(n);
|
||||
self.regs[rd] = JsValue::float(n - 1.0);
|
||||
self.dispatch_dec_post(rd, a)?;
|
||||
}
|
||||
|
||||
OpCode::MEMBER_INC
|
||||
@@ -1503,8 +1220,10 @@ mod tests {
|
||||
continuation: super::FrameContinuation::None,
|
||||
});
|
||||
vm.save_stack.push(JsValue::undefined());
|
||||
vm.for_in_iters.push(std::ptr::dangling_mut::<super::ForInIter<'static>>());
|
||||
vm.for_of_iters.push(JsValue::undefined());
|
||||
vm.iters
|
||||
.for_in_iters
|
||||
.push(std::ptr::dangling_mut::<super::ForInIter<'static>>());
|
||||
vm.iters.for_of_iters.push(JsValue::undefined());
|
||||
vm.saved_bytecode_stack
|
||||
.push(vec![opcode::encode(opcode::OpCode::HALT, 0, 0, 0)]);
|
||||
vm.saved_immutables_stack
|
||||
@@ -1523,8 +1242,8 @@ mod tests {
|
||||
assert_eq!(vm.pc, 0);
|
||||
assert!(vm.frames.is_empty());
|
||||
assert!(vm.save_stack.is_empty());
|
||||
assert!(vm.for_in_iters.is_empty());
|
||||
assert!(vm.for_of_iters.is_empty());
|
||||
assert!(vm.iters.for_in_iters.is_empty());
|
||||
assert!(vm.iters.for_of_iters.is_empty());
|
||||
assert!(vm.saved_bytecode_stack.is_empty());
|
||||
assert!(vm.saved_immutables_stack.is_empty());
|
||||
assert!(vm.try_stack.is_empty());
|
||||
@@ -1538,15 +1257,15 @@ mod tests {
|
||||
#[test]
|
||||
fn full_reset_clears_symbol_state() {
|
||||
let mut vm = Vm::new();
|
||||
vm.symbol_counter = 1;
|
||||
vm.symbol_descriptions.push("shared".to_string());
|
||||
vm.symbol_registry.insert("shared".to_string(), 0);
|
||||
vm.symbols.symbol_counter = 1;
|
||||
vm.symbols.symbol_descriptions.push("shared".to_string());
|
||||
vm.symbols.symbol_registry.insert("shared".to_string(), 0);
|
||||
|
||||
vm.full_reset();
|
||||
|
||||
assert_eq!(vm.symbol_counter, 0);
|
||||
assert!(vm.symbol_descriptions.is_empty());
|
||||
assert!(vm.symbol_registry.is_empty());
|
||||
assert_eq!(vm.symbols.symbol_counter, 0);
|
||||
assert!(vm.symbols.symbol_descriptions.is_empty());
|
||||
assert!(vm.symbols.symbol_registry.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1560,11 +1279,11 @@ mod tests {
|
||||
..CompiledModule::new()
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
vm.for_of_iters.push(JsValue::undefined());
|
||||
vm.iters.for_of_iters.push(JsValue::undefined());
|
||||
|
||||
vm.run(&module).expect("FOR_OF_CLOSE should tolerate non-object sentinel");
|
||||
|
||||
assert!(vm.for_of_iters.is_empty());
|
||||
assert!(vm.iters.for_of_iters.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1572,7 +1291,7 @@ mod tests {
|
||||
let mut vm = Vm::new();
|
||||
vm.bytecode = vec![0, 0, 0];
|
||||
vm.pc = 3;
|
||||
vm.write_ic_back(0x1234_5678, 7);
|
||||
crate::ic_helper::write_ic_back(&mut vm.bytecode, vm.pc, 0x1234_5678, 7);
|
||||
assert_eq!(vm.bytecode[0], 0x0034_5678);
|
||||
assert_eq!(vm.bytecode[1], 7);
|
||||
assert_eq!(vm.bytecode[2], 0);
|
||||
|
||||
@@ -309,12 +309,12 @@ impl Vm {
|
||||
}
|
||||
|
||||
let iter = self.epoch.alloc(ForInIter { keys: keys_vec, index: 0 });
|
||||
self.for_in_iters.push(iter.cast::<ForInIter<'static>>());
|
||||
self.iters.for_in_iters.push(iter.cast::<ForInIter<'static>>());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_for_in_next(&mut self, rd: usize) -> Result<(), String> {
|
||||
let iter_ptr = self.for_in_iters.last().copied().unwrap_or(std::ptr::null_mut());
|
||||
let iter_ptr = self.iters.for_in_iters.last().copied().unwrap_or(std::ptr::null_mut());
|
||||
if iter_ptr.is_null() {
|
||||
return Err("FOR_IN_NEXT without active iterator".into());
|
||||
}
|
||||
@@ -329,7 +329,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_for_in_done(&mut self, rd: usize) {
|
||||
let iter_ptr = self.for_in_iters.last().copied().unwrap_or(std::ptr::null_mut());
|
||||
let iter_ptr = self.iters.for_in_iters.last().copied().unwrap_or(std::ptr::null_mut());
|
||||
if iter_ptr.is_null() {
|
||||
self.regs[rd] = JsValue::bool(true);
|
||||
} else {
|
||||
@@ -339,15 +339,15 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_for_in_cleanup(&mut self) {
|
||||
self.for_in_iters.pop();
|
||||
self.iters.for_in_iters.pop();
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_for_of_init(&mut self, a: usize) -> Result<(), String> {
|
||||
let iterable = self.regs[a];
|
||||
match crate::builtins::iterator::make_iterator_for_value(self, iterable) {
|
||||
Ok(iterator) => {
|
||||
self.for_of_iters.push(iterator);
|
||||
self.last_for_of_result = JsValue::undefined();
|
||||
self.iters.for_of_iters.push(iterator);
|
||||
self.iters.last_for_of_result = JsValue::undefined();
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -359,7 +359,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_for_of_done(&mut self, rd: usize) -> Result<(), String> {
|
||||
let Some(iterator) = self.for_of_iters.last().copied() else {
|
||||
let Some(iterator) = self.iters.for_of_iters.last().copied() else {
|
||||
return Err("FOR_OF_DONE without active iterator".into());
|
||||
};
|
||||
if !iterator.is_object() {
|
||||
@@ -378,13 +378,13 @@ impl Vm {
|
||||
let done_si = self.kernel_core.perm_interner().intern("done").0;
|
||||
let done_val = self.ordinary_get(result_obj, done_si, result)?;
|
||||
let done = crate::coercion::to_boolean(done_val);
|
||||
self.last_for_of_result = result;
|
||||
self.iters.last_for_of_result = result;
|
||||
self.regs[rd] = JsValue::bool(!done);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_for_of_next(&mut self, rd: usize) -> Result<(), String> {
|
||||
let result = self.last_for_of_result;
|
||||
let result = self.iters.last_for_of_result;
|
||||
if !result.is_object() {
|
||||
self.regs[rd] = JsValue::undefined();
|
||||
return Ok(());
|
||||
@@ -396,7 +396,7 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_for_of_close(&mut self) -> Result<(), String> {
|
||||
let Some(iterator) = self.for_of_iters.pop() else {
|
||||
let Some(iterator) = self.iters.for_of_iters.pop() else {
|
||||
return Ok(());
|
||||
};
|
||||
if !iterator.is_object() {
|
||||
|
||||
@@ -204,7 +204,7 @@ impl Vm {
|
||||
if template.prop_name != prop_name_si {
|
||||
self.ordinary_get(obj, prop_name_si, receiver)?
|
||||
} else if template.position < obj.prop_vec_len() as u32 {
|
||||
self.write_ic_back(obj.shape_id(), template.position);
|
||||
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), template.position);
|
||||
obj.get_prop_at(template.position)
|
||||
} else {
|
||||
self.ordinary_get(obj, prop_name_si, receiver)?
|
||||
@@ -213,7 +213,7 @@ impl Vm {
|
||||
let resolved = self.ordinary_get(obj, prop_name_si, receiver)?;
|
||||
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||
if !obj.is_accessor_meta(pos) {
|
||||
self.write_ic_back(obj.shape_id(), pos);
|
||||
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos);
|
||||
}
|
||||
}
|
||||
resolved
|
||||
@@ -231,7 +231,7 @@ impl Vm {
|
||||
return Ok(());
|
||||
}
|
||||
obj.set_prop_at(pos, val);
|
||||
self.write_ic_back(obj.shape_id(), pos);
|
||||
crate::ic_helper::write_ic_back(&mut self.bytecode, self.pc, obj.shape_id(), pos);
|
||||
} else {
|
||||
self.ordinary_set(obj, prop_name_si, val, receiver)?;
|
||||
}
|
||||
@@ -314,11 +314,4 @@ impl Vm {
|
||||
obj.bump_generation();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn write_ic_back(&mut self, shape_id: u32, slot_index: u32) {
|
||||
debug_assert!(self.pc >= 3, "IC write-back requires 3 extension words before pc");
|
||||
self.bytecode[self.pc - 3] = shape_id & 0x00FF_FFFF;
|
||||
self.bytecode[self.pc - 2] = slot_index;
|
||||
self.bytecode[self.pc - 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_bytecode::opcode;
|
||||
|
||||
use crate::vm::{CallFrame, InlineSyncState, Vm};
|
||||
use oxide_types::object::JsObject;
|
||||
@@ -47,9 +46,9 @@ impl Vm {
|
||||
exception_value: self.exception_value.take(),
|
||||
pending_exception: self.pending_exception.take(),
|
||||
pending_error_kind: self.pending_error_kind.take(),
|
||||
for_in_iters: std::mem::take(&mut self.for_in_iters),
|
||||
for_of_iters: std::mem::take(&mut self.for_of_iters),
|
||||
last_for_of_result: self.last_for_of_result,
|
||||
for_in_iters: std::mem::take(&mut self.iters.for_in_iters),
|
||||
for_of_iters: std::mem::take(&mut self.iters.for_of_iters),
|
||||
last_for_of_result: self.iters.last_for_of_result,
|
||||
saved_bytecode_stack: std::mem::take(&mut self.saved_bytecode_stack),
|
||||
saved_immutables_stack: std::mem::take(&mut self.saved_immutables_stack),
|
||||
save_stack: std::mem::take(&mut self.save_stack),
|
||||
@@ -89,9 +88,9 @@ impl Vm {
|
||||
self.exception_value = saved.exception_value;
|
||||
self.pending_exception = saved.pending_exception;
|
||||
self.pending_error_kind = saved.pending_error_kind;
|
||||
self.for_in_iters = saved.for_in_iters;
|
||||
self.for_of_iters = saved.for_of_iters;
|
||||
self.last_for_of_result = saved.last_for_of_result;
|
||||
self.iters.for_in_iters = saved.for_in_iters;
|
||||
self.iters.for_of_iters = saved.for_of_iters;
|
||||
self.iters.last_for_of_result = saved.last_for_of_result;
|
||||
self.saved_bytecode_stack = saved.saved_bytecode_stack;
|
||||
self.saved_immutables_stack = saved.saved_immutables_stack;
|
||||
self.save_stack = saved.save_stack;
|
||||
@@ -119,27 +118,10 @@ impl Vm {
|
||||
pub fn rerun(&mut self) -> Result<JsValue, String> {
|
||||
self.clear_execution_state();
|
||||
self.active_reg_limit = self.root_reg_limit;
|
||||
self.clear_ic_caches();
|
||||
crate::ic_helper::clear_ic_caches(&mut self.bytecode);
|
||||
self.dispatch()
|
||||
}
|
||||
|
||||
fn clear_ic_caches(&mut self) {
|
||||
let mut i = 0;
|
||||
while i < self.bytecode.len() {
|
||||
let op = opcode::opcode(self.bytecode[i]);
|
||||
if op.has_ic_ext_words() {
|
||||
if i + 3 < self.bytecode.len() {
|
||||
self.bytecode[i + 1] = 0;
|
||||
self.bytecode[i + 2] = 0;
|
||||
self.bytecode[i + 3] = 0;
|
||||
}
|
||||
i += 4;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self, module: &CompiledModule) -> Result<JsValue, String> {
|
||||
self.clear_execution_state();
|
||||
self.sub_modules = Arc::new(module.sub_modules.clone());
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//! Grouped `Vm` state sub-structs.
|
||||
//!
|
||||
//! `Vm` owns these as fields (`gc_state`, `symbols`, `iters`, `profiling`) so
|
||||
//! that workers refactoring one subsystem touch one struct instead of the
|
||||
//! central `Vm` god-struct. Per the project's parallel-dev decisions:
|
||||
//! Symbol/Iter/Profiling are full sinks (their logic is self-contained), while
|
||||
//! `GcState` is field-classification only — GC `mark`/`sweep`/`alloc_object`
|
||||
//! scan roots across *all* sub-structs and stay as `&mut Vm` methods.
|
||||
//!
|
||||
//! This file defines fields only; methods live with `Vm`.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rustc_hash::FxBuildHasher;
|
||||
|
||||
use oxide_types::object::{JsObject, JsString};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::session_gc::SessionGc;
|
||||
use crate::vm::ForInIter;
|
||||
|
||||
/// Session-arena and garbage-collection bookkeeping.
|
||||
pub(crate) struct GcState {
|
||||
pub(crate) session_epoch: bumpalo::Bump,
|
||||
pub(crate) session_gc: SessionGc,
|
||||
pub(crate) epoch_object_ptrs: Vec<*mut JsObject>,
|
||||
pub(crate) session_object_ptrs: Vec<*mut JsObject>,
|
||||
pub(crate) session_string_ptrs: Vec<*mut JsString>,
|
||||
pub(crate) session_bytes_allocated: usize,
|
||||
/// Long-lived forwarding map reused by GC sweep and promote.
|
||||
/// `mem::take`'d out for use, `clear()`'d and put back — capacity retained
|
||||
/// across collections, so no per-event `HashMap` allocation. `FxBuildHasher`
|
||||
/// because the keys are raw pointers (collision resistance is irrelevant).
|
||||
pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
||||
}
|
||||
|
||||
/// `Symbol` interning state.
|
||||
pub(crate) struct SymbolState {
|
||||
pub(crate) symbol_counter: u32,
|
||||
pub(crate) symbol_descriptions: Vec<String>,
|
||||
pub(crate) symbol_registry: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
/// Live iterator state for `for-in` / `for-of`.
|
||||
pub(crate) struct IterState {
|
||||
pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>,
|
||||
pub(crate) for_of_iters: Vec<JsValue>,
|
||||
pub(crate) last_for_of_result: JsValue,
|
||||
}
|
||||
|
||||
/// Inline-cache hit/miss and instruction counters.
|
||||
pub(crate) struct ProfilingState {
|
||||
pub(crate) ic_hits: Cell<u64>,
|
||||
pub(crate) ic_misses: Cell<u64>,
|
||||
pub(crate) instruction_count: u64,
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use oxide_bytecode::module::Constant;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::vm::Vm;
|
||||
use crate::vm_state::{GcState, IterState, ProfilingState, SymbolState};
|
||||
use oxide_kernel::kernel::{KernelConfig, KernelCore, KernelSession};
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::mem::{Epoch, P};
|
||||
@@ -25,17 +26,9 @@ impl Vm {
|
||||
immutables_cache: Vec::new(),
|
||||
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
|
||||
frames: smallvec::SmallVec::new(),
|
||||
for_in_iters: Vec::new(),
|
||||
kernel_core: core,
|
||||
session,
|
||||
session_string_ptrs: Vec::new(),
|
||||
epoch: Epoch::new(),
|
||||
session_epoch: bumpalo::Bump::new(),
|
||||
session_gc: crate::session_gc::SessionGc::new(),
|
||||
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
|
||||
epoch_object_ptrs: Vec::new(),
|
||||
session_object_ptrs: Vec::new(),
|
||||
session_bytes_allocated: 0,
|
||||
object_prototype: obj_proto,
|
||||
math_rng_state: 0,
|
||||
sub_modules: Arc::new(Vec::new()),
|
||||
@@ -46,18 +39,34 @@ impl Vm {
|
||||
exception_value: None,
|
||||
pending_exception: None,
|
||||
pending_error_kind: None,
|
||||
symbol_counter: 0,
|
||||
symbol_descriptions: Vec::new(),
|
||||
symbol_registry: std::collections::HashMap::new(),
|
||||
for_of_iters: Vec::new(),
|
||||
last_for_of_result: JsValue::undefined(),
|
||||
root_reg_limit: 0,
|
||||
active_reg_limit: 0,
|
||||
native_call_depth: 0,
|
||||
accessor_frame_target_reg: None,
|
||||
ic_hits: std::cell::Cell::new(0),
|
||||
ic_misses: std::cell::Cell::new(0),
|
||||
instruction_count: 0,
|
||||
gc_state: GcState {
|
||||
session_epoch: bumpalo::Bump::new(),
|
||||
session_gc: crate::session_gc::SessionGc::new(),
|
||||
epoch_object_ptrs: Vec::new(),
|
||||
session_object_ptrs: Vec::new(),
|
||||
session_string_ptrs: Vec::new(),
|
||||
session_bytes_allocated: 0,
|
||||
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
|
||||
},
|
||||
symbols: SymbolState {
|
||||
symbol_counter: 0,
|
||||
symbol_descriptions: Vec::new(),
|
||||
symbol_registry: std::collections::HashMap::new(),
|
||||
},
|
||||
iters: IterState {
|
||||
for_in_iters: Vec::new(),
|
||||
for_of_iters: Vec::new(),
|
||||
last_for_of_result: JsValue::undefined(),
|
||||
},
|
||||
profiling: ProfilingState {
|
||||
ic_hits: std::cell::Cell::new(0),
|
||||
ic_misses: std::cell::Cell::new(0),
|
||||
instruction_count: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,17 +81,9 @@ impl Vm {
|
||||
immutables_cache: Vec::new(),
|
||||
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
|
||||
frames: smallvec::SmallVec::new(),
|
||||
for_in_iters: Vec::new(),
|
||||
kernel_core: core,
|
||||
session,
|
||||
session_string_ptrs: Vec::new(),
|
||||
epoch: Epoch::new(),
|
||||
session_epoch: bumpalo::Bump::new(),
|
||||
session_gc: crate::session_gc::SessionGc::new(),
|
||||
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
|
||||
epoch_object_ptrs: Vec::new(),
|
||||
session_object_ptrs: Vec::new(),
|
||||
session_bytes_allocated: 0,
|
||||
object_prototype: obj_proto,
|
||||
math_rng_state: 0,
|
||||
sub_modules: Arc::new(Vec::new()),
|
||||
@@ -93,18 +94,34 @@ impl Vm {
|
||||
exception_value: None,
|
||||
pending_exception: None,
|
||||
pending_error_kind: None,
|
||||
symbol_counter: 0,
|
||||
symbol_descriptions: Vec::new(),
|
||||
symbol_registry: std::collections::HashMap::new(),
|
||||
for_of_iters: Vec::new(),
|
||||
last_for_of_result: JsValue::undefined(),
|
||||
root_reg_limit: 0,
|
||||
active_reg_limit: 0,
|
||||
native_call_depth: 0,
|
||||
accessor_frame_target_reg: None,
|
||||
ic_hits: std::cell::Cell::new(0),
|
||||
ic_misses: std::cell::Cell::new(0),
|
||||
instruction_count: 0,
|
||||
gc_state: GcState {
|
||||
session_epoch: bumpalo::Bump::new(),
|
||||
session_gc: crate::session_gc::SessionGc::new(),
|
||||
epoch_object_ptrs: Vec::new(),
|
||||
session_object_ptrs: Vec::new(),
|
||||
session_string_ptrs: Vec::new(),
|
||||
session_bytes_allocated: 0,
|
||||
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
|
||||
},
|
||||
symbols: SymbolState {
|
||||
symbol_counter: 0,
|
||||
symbol_descriptions: Vec::new(),
|
||||
symbol_registry: std::collections::HashMap::new(),
|
||||
},
|
||||
iters: IterState {
|
||||
for_in_iters: Vec::new(),
|
||||
for_of_iters: Vec::new(),
|
||||
last_for_of_result: JsValue::undefined(),
|
||||
},
|
||||
profiling: ProfilingState {
|
||||
ic_hits: std::cell::Cell::new(0),
|
||||
ic_misses: std::cell::Cell::new(0),
|
||||
instruction_count: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,27 +155,28 @@ impl Vm {
|
||||
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
|
||||
self.free_epoch_object_heap_data();
|
||||
self.epoch.reset();
|
||||
self.epoch_object_ptrs.clear();
|
||||
self.session_epoch.reset();
|
||||
self.session_object_ptrs.clear();
|
||||
self.session_bytes_allocated = 0;
|
||||
self.session_gc = crate::session_gc::SessionGc::new();
|
||||
self.gc_state.epoch_object_ptrs.clear();
|
||||
self.gc_state.session_epoch.reset();
|
||||
self.gc_state.session_object_ptrs.clear();
|
||||
self.gc_state.session_bytes_allocated = 0;
|
||||
self.gc_state.session_gc = crate::session_gc::SessionGc::new();
|
||||
self.free_session_string_heap_data();
|
||||
self.symbol_counter = 0;
|
||||
self.symbol_descriptions.clear();
|
||||
self.symbol_registry.clear();
|
||||
self.symbols.symbol_counter = 0;
|
||||
self.symbols.symbol_descriptions.clear();
|
||||
self.symbols.symbol_registry.clear();
|
||||
self.root_reg_limit = 0;
|
||||
self.active_reg_limit = 0;
|
||||
}
|
||||
|
||||
fn free_epoch_object_heap_data(&mut self) {
|
||||
let mut freed = 0u64;
|
||||
for ptr in self.epoch_object_ptrs.drain(..) {
|
||||
for ptr in self.gc_state.epoch_object_ptrs.drain(..) {
|
||||
freed += crate::session_gc::SessionGc::drop_object_heap_data(ptr, false);
|
||||
}
|
||||
if freed > 0 {
|
||||
self.session_gc.total_bytes_freed = self.session_gc.total_bytes_freed.saturating_add(freed);
|
||||
self.session_gc.last_collection_bytes_freed = freed;
|
||||
self.gc_state.session_gc.total_bytes_freed =
|
||||
self.gc_state.session_gc.total_bytes_freed.saturating_add(freed);
|
||||
self.gc_state.session_gc.last_collection_bytes_freed = freed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,9 +189,9 @@ impl Vm {
|
||||
self.regs = [JsValue::undefined(); 256];
|
||||
self.pc = 0;
|
||||
self.frames.clear();
|
||||
self.for_in_iters.clear();
|
||||
self.for_of_iters.clear();
|
||||
self.last_for_of_result = JsValue::undefined();
|
||||
self.iters.for_in_iters.clear();
|
||||
self.iters.for_of_iters.clear();
|
||||
self.iters.last_for_of_result = JsValue::undefined();
|
||||
self.saved_bytecode_stack.clear();
|
||||
self.saved_immutables_stack.clear();
|
||||
self.save_stack.clear();
|
||||
@@ -192,15 +210,15 @@ impl Vm {
|
||||
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
|
||||
self.free_epoch_object_heap_data();
|
||||
self.epoch.reset();
|
||||
self.epoch_object_ptrs.clear();
|
||||
self.gc_state.epoch_object_ptrs.clear();
|
||||
self.root_reg_limit = 0;
|
||||
self.active_reg_limit = 0;
|
||||
}
|
||||
|
||||
pub fn new_string(&mut self, s: &str) -> JsValue {
|
||||
let ptr = Box::into_raw(Box::new(JsString::new(s.to_string())));
|
||||
self.session_string_ptrs.push(ptr);
|
||||
self.session_bytes_allocated += std::mem::size_of::<JsString>() + s.len();
|
||||
self.gc_state.session_string_ptrs.push(ptr);
|
||||
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsString>() + s.len();
|
||||
JsValue::string(ptr)
|
||||
}
|
||||
|
||||
@@ -224,7 +242,7 @@ impl Vm {
|
||||
/// can reference them. The lighter `reset()` deliberately keeps them alive,
|
||||
/// mirroring session-object survival across evals.
|
||||
fn free_session_string_heap_data(&mut self) {
|
||||
for ptr in self.session_string_ptrs.drain(..) {
|
||||
for ptr in self.gc_state.session_string_ptrs.drain(..) {
|
||||
// SAFETY: each ptr came from Box::into_raw(Box::new(JsString)) in new_string
|
||||
// and is dropped exactly once here.
|
||||
unsafe {
|
||||
@@ -260,7 +278,7 @@ impl Vm {
|
||||
let prototype_obj = self
|
||||
.epoch
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto_ptr)));
|
||||
self.epoch_object_ptrs.push(prototype_obj);
|
||||
self.gc_state.epoch_object_ptrs.push(prototype_obj);
|
||||
let prototype_val = JsValue::from_js_object(prototype_obj);
|
||||
|
||||
let constructor_si = self.kernel_core.perm_interner().intern("constructor").0;
|
||||
@@ -431,7 +449,7 @@ mod tests {
|
||||
#[test]
|
||||
fn session_epoch_survives_reset() {
|
||||
let mut vm = Vm::new();
|
||||
let session_ptr = vm.session_epoch.alloc(123i32) as *mut i32;
|
||||
let session_ptr = vm.gc_state.session_epoch.alloc(123i32) as *mut i32;
|
||||
|
||||
vm.reset();
|
||||
|
||||
@@ -454,9 +472,9 @@ mod tests {
|
||||
fn session_epoch_reset_is_only_in_full_reset_state_clear() {
|
||||
let src = include_str!("vm_support.rs");
|
||||
let production = src.split("#[cfg(test)]").next().expect("production source");
|
||||
assert_eq!(production.matches("self.session_epoch.reset()").count(), 1);
|
||||
assert_eq!(production.matches("self.gc_state.session_epoch.reset()").count(), 1);
|
||||
assert!(production.contains("fn clear_full_reset_state(&mut self)"));
|
||||
assert!(production.contains("self.session_epoch.reset();"));
|
||||
assert!(production.contains("self.gc_state.session_epoch.reset();"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user