feat: 添加 session arena 与逃逸晋升

This commit is contained in:
爱特每羊队
2026-06-16 12:36:52 +08:00
parent 11d6696531
commit 94beeb350b
10 changed files with 544 additions and 23 deletions
+4 -5
View File
@@ -127,14 +127,13 @@ pub fn array_is_array(vm: &mut Vm, args: &[u8]) -> NativeResult {
pub fn array_push(vm: &mut Vm, args: &[u8]) -> NativeResult {
let arr_ptr = array_ptr!(vm, args);
let arr = unsafe { &mut *arr_ptr };
let mut len = arr.prop_count();
let mut len = unsafe { &*arr_ptr }.prop_count();
for &arg_reg in args.iter().skip(1) {
let val = vm.reg(arg_reg);
arr.set_prop_at(len, val);
let val = vm.promote_if_needed_for_write_ptr(arr_ptr, vm.reg(arg_reg));
unsafe { &mut *arr_ptr }.set_prop_at(len, val);
len += 1;
}
arr.set_prop_count(len);
unsafe { &mut *arr_ptr }.set_prop_count(len);
NativeResult::Ok(JsValue::int(len as i32))
}
+22 -14
View File
@@ -136,24 +136,25 @@ impl Vm {
let prop_name_si = self.property_key_si(self.regs[b]);
if self.kernel_core.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") {
let obj = unsafe { &mut *obj_ptr };
if self.is_object_prototype(obj_ptr) && !self.regs[a].is_null() {
let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
self.pc += 3;
return Ok(());
}
obj.set_proto(self.regs[a]).map_err(|e| e.to_string())?;
let obj = unsafe { &mut *obj_ptr };
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
self.pc += 3;
return Ok(());
}
let obj = unsafe { &mut *obj_ptr };
let ext0 = self.bytecode[self.pc];
let ext1 = self.bytecode[self.pc + 1];
let _ext2 = self.bytecode[self.pc + 2];
self.pc += 3;
let value = self.regs[a];
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
if obj.has_prop_meta() {
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
return Ok(());
@@ -209,17 +210,20 @@ impl Vm {
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "SET_PROP on non-object")? else {
return Ok(());
};
let obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.property_key_si(self.regs[b]);
if self.kernel_core.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") {
if self.is_object_prototype(obj_ptr) && !self.regs[a].is_null() {
let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
return Ok(());
}
obj.set_proto(self.regs[a]).map_err(|e| e.to_string())?;
let obj = unsafe { &mut *obj_ptr };
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
return Ok(());
}
self.ordinary_set_dispatch(obj, prop_name_si, self.regs[a], self.regs[rd])?;
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]);
let obj = unsafe { &mut *obj_ptr };
self.ordinary_set_dispatch(obj, prop_name_si, value, self.regs[rd])?;
Ok(())
}
@@ -240,17 +244,20 @@ impl Vm {
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "SET_PROP_DYNAMIC on non-object")? else {
return Ok(());
};
let obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.property_key_si(self.regs[a]);
if self.kernel_core.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") {
if self.is_object_prototype(obj_ptr) && !self.regs[b].is_null() {
let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
return Ok(());
}
obj.set_proto(self.regs[b]).map_err(|e| e.to_string())?;
let obj = unsafe { &mut *obj_ptr };
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
return Ok(());
}
self.ordinary_set_dispatch(obj, prop_name_si, self.regs[b], self.regs[rd])?;
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
let obj = unsafe { &mut *obj_ptr };
self.ordinary_set_dispatch(obj, prop_name_si, value, self.regs[rd])?;
Ok(())
}
@@ -265,8 +272,9 @@ impl Vm {
} else {
0
};
let value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]);
let obj = unsafe { &mut *obj_ptr };
obj.set_prop_at(idx, self.regs[b]);
obj.set_prop_at(idx, value);
Ok(())
}
}
+1
View File
@@ -5,6 +5,7 @@ pub mod builtins;
pub mod coercion;
mod dispatch;
pub mod native;
mod session_arena;
pub mod vm;
mod vm_dispatch_ctrl;
mod vm_dispatch_misc;
+213
View File
@@ -0,0 +1,213 @@
use std::collections::HashMap;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::vm::Vm;
impl Vm {
pub(crate) fn is_session_escape_root_ptr(&self, target_ptr: *mut JsObject) -> bool {
if target_ptr.is_null() {
return false;
}
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
std::ptr::eq(target_ptr, global_ptr) || unsafe { (&*target_ptr).is_session_epoch() }
}
pub(crate) fn promote_object(&mut self, src: *mut JsObject) -> *mut JsObject {
let mut forwarding = HashMap::new();
self.promote_object_inner(src, &mut forwarding)
}
pub(crate) fn promote_object_inner(
&mut self, src: *mut JsObject, forwarding: &mut HashMap<*mut JsObject, *mut JsObject>,
) -> *mut JsObject {
if src.is_null() {
return src;
}
let src_ref = unsafe { &*src };
if src_ref.is_session_epoch() || !self.epoch.is_epoch_ptr(src.cast::<u8>()) {
return src;
}
if let Some(dst) = forwarding.get(&src).copied() {
return dst;
}
let clone = src_ref.clone_for_session_epoch();
let dst = self.session_epoch.alloc(clone) as *mut JsObject;
forwarding.insert(src, dst);
let dst_ref = unsafe { &mut *dst };
dst_ref.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding));
dst
}
pub(crate) fn promote_value_if_epoch_object(
&mut self, value: JsValue, forwarding: &mut HashMap<*mut JsObject, *mut JsObject>,
) -> JsValue {
if !value.is_object() {
return value;
}
let ptr = value.as_js_object_ptr();
if ptr.is_null() || !self.epoch.is_epoch_ptr(ptr.cast::<u8>()) {
return value;
}
JsValue::from_js_object(self.promote_object_inner(ptr, forwarding))
}
pub(crate) fn promote_if_needed_for_write_ptr(&mut self, target_ptr: *mut JsObject, value: JsValue) -> JsValue {
if !value.is_object() || !self.is_session_escape_root_ptr(target_ptr) {
return value;
}
let value_ptr = value.as_js_object_ptr();
if value_ptr.is_null() || !self.epoch.is_epoch_ptr(value_ptr.cast::<u8>()) {
return value;
}
JsValue::from_js_object(self.promote_object(value_ptr))
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{PropAttributes, PropMetaEntry};
fn plain_object(vm: &mut Vm) -> *mut JsObject {
let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
vm.epoch
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)))
}
fn is_epoch_object(vm: &Vm, value: JsValue) -> bool {
value.is_object() && vm.epoch.is_epoch_ptr(value.as_js_object_ptr().cast::<u8>())
}
#[test]
fn session_arena_promotion_preserves_cycle_identity() {
let mut vm = Vm::new();
let root = plain_object(&mut vm);
unsafe {
(*root).set_prop_at(0, JsValue::from_js_object(root));
}
let promoted = vm.promote_object(root);
let promoted_obj = unsafe { &*promoted };
assert!(promoted_obj.is_session_epoch());
assert!(std::ptr::eq(promoted_obj.get_prop_at(0).as_js_object_ptr(), promoted));
}
#[test]
fn session_arena_promotion_preserves_duplicate_child_reference() {
let mut vm = Vm::new();
let root = plain_object(&mut vm);
let child = plain_object(&mut vm);
unsafe {
(*root).set_prop_at(0, JsValue::from_js_object(child));
(*root).set_prop_at(1, JsValue::from_js_object(child));
}
let promoted = vm.promote_object(root);
let promoted_obj = unsafe { &*promoted };
let left = promoted_obj.get_prop_at(0).as_js_object_ptr();
let right = promoted_obj.get_prop_at(1).as_js_object_ptr();
assert!(std::ptr::eq(left, right));
assert!(!std::ptr::eq(left, child));
assert!(unsafe { (&*left).is_session_epoch() });
}
#[test]
fn session_arena_captured_and_home_object_links_promote() {
let mut vm = Vm::new();
let root = plain_object(&mut vm);
let proto = plain_object(&mut vm);
let captured = plain_object(&mut vm);
let home = plain_object(&mut vm);
let getter = plain_object(&mut vm);
let setter = plain_object(&mut vm);
unsafe {
(*root).set_proto(JsValue::from_js_object(proto)).expect("proto");
(*root).set_captured_this(JsValue::from_js_object(captured));
(*root).set_home_object(JsValue::from_js_object(home));
(*root).set_prop_at(0, JsValue::undefined());
(*root).set_accessor_meta(
0,
JsValue::from_js_object(getter),
JsValue::from_js_object(setter),
PropAttributes::DEFAULT_DATA,
);
}
let promoted = vm.promote_object(root);
let promoted_obj = unsafe { &*promoted };
let meta = promoted_obj.prop_meta_at(0).expect("meta");
assert!(!is_epoch_object(&vm, promoted_obj.proto()));
assert!(!is_epoch_object(&vm, promoted_obj.captured_this()));
assert!(!is_epoch_object(&vm, promoted_obj.home_object()));
assert!(!is_epoch_object(&vm, meta.get));
assert!(!is_epoch_object(&vm, meta.set));
}
#[test]
fn session_arena_barrier_promotes_global_root_write() {
let mut vm = Vm::new();
let value = JsValue::from_js_object(plain_object(&mut vm));
let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject;
let promoted = vm.promote_if_needed_for_write_ptr(global_ptr, value);
assert!(promoted.is_object());
assert!(!is_epoch_object(&vm, promoted));
assert!(unsafe { (&*promoted.as_js_object_ptr()).is_session_epoch() });
}
#[test]
fn session_arena_barrier_promotes_already_session_target_write() {
let mut vm = Vm::new();
let target_epoch = plain_object(&mut vm);
let target = vm.promote_object(target_epoch);
let value = JsValue::from_js_object(plain_object(&mut vm));
let promoted = vm.promote_if_needed_for_write_ptr(target, value);
assert!(!is_epoch_object(&vm, promoted));
assert!(unsafe { (&*promoted.as_js_object_ptr()).is_session_epoch() });
}
#[test]
fn session_arena_barrier_leaves_non_escape_target_write_unchanged() {
let mut vm = Vm::new();
let target = plain_object(&mut vm);
let value = JsValue::from_js_object(plain_object(&mut vm));
let unchanged = vm.promote_if_needed_for_write_ptr(target, value);
assert_eq!(unchanged, value);
}
#[test]
fn session_arena_promotion_rewrites_accessor_metadata_values() {
let mut vm = Vm::new();
let root = plain_object(&mut vm);
let getter = plain_object(&mut vm);
let setter = plain_object(&mut vm);
unsafe {
(*root).set_prop_at(0, JsValue::undefined());
(*root).set_accessor_meta(
0,
JsValue::from_js_object(getter),
JsValue::from_js_object(setter),
PropAttributes::DEFAULT_DATA,
);
}
let promoted = vm.promote_object(root);
let meta: PropMetaEntry = unsafe { &*promoted }.prop_meta_at(0).expect("meta");
assert!(!is_epoch_object(&vm, meta.get));
assert!(!is_epoch_object(&vm, meta.set));
}
}
+2
View File
@@ -142,6 +142,7 @@ pub struct Vm {
pub(crate) session: KernelSession,
pub(crate) interned_strings: Vec<u32>,
pub epoch: Epoch,
pub(crate) session_epoch: bumpalo::Bump,
pub object_prototype: P<JsObject>,
pub math_rng_state: u64,
pub(crate) sub_modules: Vec<CompiledModule>,
@@ -835,6 +836,7 @@ impl Vm {
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");
+7
View File
@@ -114,6 +114,7 @@ impl Vm {
fn ordinary_set_inner(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, use_frame_push: bool,
) -> Result<(), String> {
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
if let Some(pos) = self.get_own_property_slot(obj, prop_name_si) {
if let Some(meta) = obj.prop_meta_at(pos) {
if meta.is_accessor {
@@ -216,6 +217,7 @@ impl Vm {
pub(crate) fn set_member_prop(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
) -> Result<(), String> {
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
if obj.has_prop_meta() {
self.ordinary_set(obj, prop_name_si, val, receiver)?;
@@ -230,6 +232,7 @@ impl Vm {
}
pub(crate) fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_at(pos, val);
} else {
@@ -243,6 +246,7 @@ impl Vm {
pub(crate) fn define_data_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
) -> Result<(), String> {
let val = self.promote_if_needed_for_write_ptr(obj as *mut JsObject, val);
let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
pos
} else {
@@ -277,6 +281,9 @@ impl Vm {
pub(crate) fn define_accessor_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, get: JsValue, set: JsValue, attributes: PropAttributes,
) -> Result<(), String> {
let target_ptr = obj as *mut JsObject;
let get = self.promote_if_needed_for_write_ptr(target_ptr, get);
let set = self.promote_if_needed_for_write_ptr(target_ptr, set);
let pos = if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
pos
} else {
+22
View File
@@ -30,6 +30,7 @@ impl Vm {
session,
interned_strings: Vec::new(),
epoch: Epoch::new(),
session_epoch: bumpalo::Bump::new(),
object_prototype: obj_proto,
math_rng_state: 0,
sub_modules: Vec::new(),
@@ -67,6 +68,7 @@ impl Vm {
session,
interned_strings: Vec::new(),
epoch: Epoch::new(),
session_epoch: bumpalo::Bump::new(),
object_prototype: obj_proto,
math_rng_state: 0,
sub_modules: Vec::new(),
@@ -117,6 +119,7 @@ impl Vm {
self.bytecode.clear();
self.constants.clear();
self.epoch.reset();
self.session_epoch.reset();
self.interned_strings.clear();
self.symbol_counter = 0;
self.symbol_descriptions.clear();
@@ -404,6 +407,25 @@ mod tests {
assert!(!vm.session.is_dirty_since_snapshot());
}
#[test]
fn session_epoch_survives_reset() {
let mut vm = Vm::new();
let session_ptr = vm.session_epoch.alloc(123i32) as *mut i32;
vm.reset();
assert!(unsafe { *session_ptr } == 123);
}
#[test]
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!(production.contains("fn clear_full_reset_state(&mut self)"));
assert!(production.contains("self.session_epoch.reset();"));
}
#[test]
fn full_reset_refreshes_object_prototype_after_object_dirty() {
let mut vm = Vm::new();