feat: 添加访问器属性元数据与普通属性访问

This commit is contained in:
爱特每羊队
2026-06-09 15:24:37 +08:00
parent ad8e870997
commit 044c8a4303
4 changed files with 634 additions and 82 deletions
+198 -13
View File
@@ -2,6 +2,70 @@ use crate::value::JsValue;
pub type ShapeId = u32;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PropAttributes(pub u8);
impl PropAttributes {
pub const WRITABLE: u8 = 0b001;
pub const ENUMERABLE: u8 = 0b010;
pub const CONFIGURABLE: u8 = 0b100;
pub const DEFAULT_DATA: Self = Self(Self::WRITABLE | Self::ENUMERABLE | Self::CONFIGURABLE);
pub const fn new(writable: bool, enumerable: bool, configurable: bool) -> Self {
let mut bits = 0;
if writable {
bits |= Self::WRITABLE;
}
if enumerable {
bits |= Self::ENUMERABLE;
}
if configurable {
bits |= Self::CONFIGURABLE;
}
Self(bits)
}
pub const fn writable(self) -> bool {
self.0 & Self::WRITABLE != 0
}
pub const fn enumerable(self) -> bool {
self.0 & Self::ENUMERABLE != 0
}
pub const fn configurable(self) -> bool {
self.0 & Self::CONFIGURABLE != 0
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PropMetaEntry {
pub attributes: PropAttributes,
pub get: JsValue,
pub set: JsValue,
pub is_accessor: bool,
}
impl PropMetaEntry {
pub fn data(attributes: PropAttributes) -> Self {
Self {
attributes,
get: JsValue::undefined(),
set: JsValue::undefined(),
is_accessor: false,
}
}
pub fn accessor(get: JsValue, set: JsValue, attributes: PropAttributes) -> Self {
Self {
attributes,
get,
set,
is_accessor: true,
}
}
}
pub trait PropIndex {
fn to_u32(self) -> u32;
}
@@ -48,6 +112,7 @@ impl PropIndex for i32 {
/// [31] is_function
/// native_arg_count: u8 (1 byte + 3 pad)
/// hash_props: *mut u8 (8 bytes, points to Box<Vec<Box<JsValue>>>)
/// prop_meta: *mut u8 (8 bytes, points to Box<Vec<Option<PropMetaEntry>>>)
/// proto: JsValue (8 bytes)
/// generation: u32 (4 bytes + 4 pad)
/// native_fn: u64 (8 bytes, 0 = None sentinel)
@@ -60,6 +125,7 @@ pub struct JsObject {
native_arg_count: u8,
_pad: [u8; 3],
hash_props: *mut u8,
prop_meta: *mut u8,
proto: JsValue,
generation: u32,
_pad2: [u8; 4],
@@ -77,6 +143,7 @@ impl JsObject {
native_arg_count: 0,
_pad: [0; 3],
hash_props: std::ptr::null_mut(),
prop_meta: std::ptr::null_mut(),
proto,
generation: 1,
_pad2: [0; 4],
@@ -94,6 +161,7 @@ impl JsObject {
native_arg_count: 0,
_pad: [0; 3],
hash_props: std::ptr::null_mut(),
prop_meta: std::ptr::null_mut(),
proto,
generation: 1,
_pad2: [0; 4],
@@ -129,15 +197,87 @@ impl JsObject {
/// Sets the length of hash_props vec. Truncates or extends with undefined.
pub fn set_prop_count(&mut self, count: impl PropIndex) {
let vec = self.ensure_hash_props();
let target = count.to_u32() as usize;
if target < vec.len() {
vec.truncate(target);
} else {
while vec.len() < target {
vec.push(Box::new(JsValue::undefined()));
{
let vec = self.ensure_hash_props();
if target < vec.len() {
vec.truncate(target);
} else {
while vec.len() < target {
vec.push(Box::new(JsValue::undefined()));
}
}
}
if let Some(meta) = self.prop_meta_vec_mut() {
if target < meta.len() {
meta.truncate(target);
} else {
while meta.len() < target {
meta.push(None);
}
}
}
}
pub fn has_prop_meta(&self) -> bool {
!self.prop_meta.is_null()
}
pub fn ensure_prop_meta(&mut self) -> &mut Vec<Option<PropMetaEntry>> {
if self.prop_meta.is_null() {
let len = self.prop_vec_len();
let vec = Box::new(vec![None::<PropMetaEntry>; len]);
self.prop_meta = Box::into_raw(vec) as *mut u8;
}
unsafe { &mut *(self.prop_meta as *mut Vec<Option<PropMetaEntry>>) }
}
pub fn prop_meta_vec(&self) -> Option<&Vec<Option<PropMetaEntry>>> {
if self.prop_meta.is_null() {
None
} else {
unsafe { Some(&*(self.prop_meta as *const Vec<Option<PropMetaEntry>>)) }
}
}
fn prop_meta_vec_mut(&mut self) -> Option<&mut Vec<Option<PropMetaEntry>>> {
if self.prop_meta.is_null() {
None
} else {
unsafe { Some(&mut *(self.prop_meta as *mut Vec<Option<PropMetaEntry>>)) }
}
}
pub fn prop_meta_at(&self, position: impl PropIndex) -> Option<PropMetaEntry> {
let pos = position.to_u32() as usize;
self.prop_meta_vec().and_then(|vec| vec.get(pos).copied().flatten())
}
pub fn set_data_meta(&mut self, position: impl PropIndex, attributes: PropAttributes) {
self.set_meta_at(position, PropMetaEntry::data(attributes));
}
pub fn set_accessor_meta(
&mut self, position: impl PropIndex, get: JsValue, set: JsValue, attributes: PropAttributes,
) {
self.set_meta_at(position, PropMetaEntry::accessor(get, set, attributes));
}
pub fn is_accessor_meta(&self, position: impl PropIndex) -> bool {
self.prop_meta_at(position).is_some_and(|entry| entry.is_accessor)
}
fn set_meta_at(&mut self, position: impl PropIndex, entry: PropMetaEntry) {
let pos = position.to_u32() as usize;
let prop_len = self.prop_vec_len();
if pos >= prop_len {
self.set_prop_count(pos + 1);
}
let meta = self.ensure_prop_meta();
while meta.len() <= pos {
meta.push(None);
}
meta[pos] = Some(entry);
}
pub fn is_array(&self) -> bool {
@@ -198,15 +338,22 @@ impl JsObject {
/// Set property value at position index. Vec auto-grows if needed.
pub fn set_prop_at(&mut self, position: impl PropIndex, val: JsValue) {
let vec = self.ensure_hash_props();
let pos = position.to_u32() as usize;
if pos < vec.len() {
*vec[pos] = val;
} else {
while vec.len() < pos {
vec.push(Box::new(JsValue::undefined()));
{
let vec = self.ensure_hash_props();
if pos < vec.len() {
*vec[pos] = val;
} else {
while vec.len() < pos {
vec.push(Box::new(JsValue::undefined()));
}
vec.push(Box::new(val));
}
}
if let Some(meta) = self.prop_meta_vec_mut() {
while meta.len() <= pos {
meta.push(None);
}
vec.push(Box::new(val));
}
}
@@ -215,6 +362,9 @@ impl JsObject {
let vec = self.ensure_hash_props();
let pos = vec.len();
vec.push(Box::new(val));
if let Some(meta) = self.prop_meta_vec_mut() {
meta.push(None);
}
pos as u32
}
@@ -381,6 +531,7 @@ mod tests {
assert!(!obj.is_function());
assert_eq!(obj.generation(), 1);
assert!(obj.hash_props_vec().is_none());
assert!(!obj.has_prop_meta());
}
#[test]
@@ -452,4 +603,38 @@ mod tests {
assert_eq!(obj.get_prop_at(0), JsValue::int(100));
assert_eq!(obj.get_prop_at(1), JsValue::int(200));
}
#[test]
fn prop_meta_lazy_init() {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
obj.set_prop_at(0, JsValue::int(1));
assert!(!obj.has_prop_meta());
obj.set_data_meta(0, PropAttributes::new(false, true, false));
assert!(obj.has_prop_meta());
let meta = obj.prop_meta_at(0).expect("meta");
assert!(!meta.is_accessor);
assert!(!meta.attributes.writable());
assert!(meta.attributes.enumerable());
assert!(!meta.attributes.configurable());
}
#[test]
fn accessor_meta_roundtrip_and_alignment() {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
obj.set_prop_at(0, JsValue::int(1));
obj.set_accessor_meta(2, JsValue::int(10), JsValue::int(11), PropAttributes::new(false, false, true));
assert_eq!(obj.prop_count(), 3);
assert!(obj.is_accessor_meta(2));
let meta = obj.prop_meta_at(2).expect("accessor meta");
assert_eq!(meta.get, JsValue::int(10));
assert_eq!(meta.set, JsValue::int(11));
assert!(!meta.attributes.writable());
assert!(!meta.attributes.enumerable());
assert!(meta.attributes.configurable());
obj.push_prop(JsValue::int(4));
assert_eq!(obj.prop_meta_vec().expect("meta").len(), obj.prop_vec_len());
}
}
+18 -12
View File
@@ -46,11 +46,12 @@ impl Vm {
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_INC on non-object") else {
return Ok(());
};
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si);
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
let new_val = JsValue::float(n + 1.0);
self.set_member_prop(obj, prop_name_si, new_val)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.regs[a] = new_val;
Ok(())
}
@@ -59,11 +60,12 @@ impl Vm {
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_DEC on non-object") else {
return Ok(());
};
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si);
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
let new_val = JsValue::float(n - 1.0);
self.set_member_prop(obj, prop_name_si, new_val)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.regs[a] = new_val;
Ok(())
}
@@ -74,12 +76,13 @@ impl Vm {
self.raise_type_error("DYN_MEMBER_INC on non-object")?;
return Ok(());
}
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.property_key_si(self.regs[a]);
let prop_val = self.resolve_property(obj, prop_name_si).unwrap_or(JsValue::undefined());
let prop_val = self.ordinary_get(obj, prop_name_si, receiver)?;
let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
let new_val = JsValue::float(n + 1.0);
self.set_or_create_prop_value(obj, prop_name_si, new_val);
self.ordinary_set(obj, prop_name_si, new_val, receiver)?;
self.regs[b] = new_val;
Ok(())
}
@@ -90,12 +93,13 @@ impl Vm {
self.raise_type_error("DYN_MEMBER_DEC on non-object")?;
return Ok(());
}
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_name_si = self.property_key_si(self.regs[a]);
let prop_val = self.resolve_property(obj, prop_name_si).unwrap_or(JsValue::undefined());
let prop_val = self.ordinary_get(obj, prop_name_si, receiver)?;
let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
let new_val = JsValue::float(n - 1.0);
self.set_or_create_prop_value(obj, prop_name_si, new_val);
self.ordinary_set(obj, prop_name_si, new_val, receiver)?;
self.regs[b] = new_val;
Ok(())
}
@@ -104,8 +108,9 @@ impl Vm {
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "COMPOUND_MEMBER_ADD on non-object") else {
return Ok(());
};
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si);
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let rhs = self.regs[a];
let new_val = if prop_val.is_string() || rhs.is_string() {
let ls = coercion::to_string(self.kernel.string_forge().as_ref(), prop_val);
@@ -116,7 +121,7 @@ impl Vm {
let rn = coercion::to_number(rhs, self.kernel.string_forge().as_ref());
JsValue::float(ln + rn)
};
self.set_member_prop(obj, prop_name_si, new_val)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
self.regs[a] = new_val;
Ok(())
}
@@ -130,13 +135,14 @@ impl Vm {
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, error_msg) else {
return Ok(());
};
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
let prop_val = self.read_member_prop(obj, prop_name_si);
let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?;
let ln = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
let rn = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
let new_val = JsValue::float(op(ln, rn));
self.regs[a] = new_val;
self.set_member_prop(obj, prop_name_si, new_val)?;
self.set_member_prop(obj, prop_name_si, new_val, receiver)?;
Ok(())
}
}
+26 -29
View File
@@ -27,7 +27,8 @@ impl Vm {
let proto_ptr = self.kernel.builtin_world().string_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
let prop_name_si = self.property_key_si(self.regs[b]);
if let Some(resolved) = self.resolve_property(proto, prop_name_si) {
let resolved = self.ordinary_get(proto, prop_name_si, val)?;
if !resolved.is_undefined() {
self.regs[a] = resolved;
self.pc += 3;
return Ok(());
@@ -37,7 +38,8 @@ impl Vm {
let proto_ptr = self.kernel.builtin_world().number_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
let prop_name_si = self.property_key_si(self.regs[b]);
if let Some(resolved) = self.resolve_property(proto, prop_name_si) {
let resolved = self.ordinary_get(proto, prop_name_si, val)?;
if !resolved.is_undefined() {
self.regs[a] = resolved;
self.pc += 3;
return Ok(());
@@ -53,6 +55,10 @@ impl Vm {
let ext1 = self.bytecode[self.pc + 1];
let ext2 = self.bytecode[self.pc + 2];
self.pc += 3;
if obj.has_prop_meta() {
self.regs[a] = self.ordinary_get(obj, prop_name_si, val)?;
return Ok(());
}
let cached_shape_id = ext0 & 0x00FF_FFFF;
let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64);
@@ -64,20 +70,19 @@ impl Vm {
self.write_ic_back(obj.shape_id(), ptr);
self.regs[a] = unsafe { *ptr };
} else {
self.regs[a] = self.resolve_property(obj, prop_name_si).unwrap_or(JsValue::undefined());
self.regs[a] = self.ordinary_get(obj, prop_name_si, val)?;
}
} else {
self.regs[a] = self.resolve_property(obj, prop_name_si).unwrap_or(JsValue::undefined());
self.regs[a] = self.ordinary_get(obj, prop_name_si, val)?;
}
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
} else {
let resolved = self.ordinary_get(obj, prop_name_si, val)?;
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
if let Some(ptr) = obj.prop_ptr_at(pos) {
self.write_ic_back(obj.shape_id(), ptr);
}
}
self.regs[a] = val;
} else {
self.regs[a] = JsValue::undefined();
self.regs[a] = resolved;
}
Ok(())
@@ -103,22 +108,28 @@ impl Vm {
let ext1 = self.bytecode[self.pc + 1];
let ext2 = self.bytecode[self.pc + 2];
self.pc += 3;
let value = self.regs[a];
let receiver = self.regs[rd];
if obj.has_prop_meta() {
self.ordinary_set(obj, prop_name_si, value, receiver)?;
return Ok(());
}
let cached_shape_id = ext0 & 0x00FF_FFFF;
let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64);
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_ptr != 0 {
unsafe {
*(cached_ptr as *mut JsValue) = self.regs[a];
*(cached_ptr as *mut JsValue) = value;
}
} else if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_at(pos, self.regs[a]);
obj.set_prop_at(pos, value);
if let Some(ptr) = obj.prop_ptr_at(pos) {
self.write_ic_back(obj.shape_id(), ptr);
}
} else {
let new_shape_id = self.kernel.shape_forge().make_shape(obj.shape_id(), prop_name_si);
obj.set_shape_id(new_shape_id);
let new_pos = obj.push_prop(self.regs[a]);
let new_pos = obj.push_prop(value);
obj.bump_generation();
if let Some(ptr) = obj.prop_ptr_at(new_pos) {
self.write_ic_back(new_shape_id, ptr);
@@ -145,7 +156,7 @@ impl Vm {
}
let obj = unsafe { &*obj_ptr };
let prop_name_si = self.property_key_si(self.regs[b]);
self.regs[a] = self.resolve_property(obj, prop_name_si).unwrap_or(JsValue::undefined());
self.regs[a] = self.ordinary_get(obj, prop_name_si, self.regs[rd])?;
Ok(())
}
@@ -161,14 +172,7 @@ impl Vm {
obj.set_proto(self.regs[a]).map_err(|e| e.to_string())?;
return Ok(());
}
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_at(pos, self.regs[a]);
} else {
let new_shape_id = self.kernel.shape_forge().make_shape(obj.shape_id(), prop_name_si);
obj.set_shape_id(new_shape_id);
obj.push_prop(self.regs[a]);
obj.bump_generation();
}
self.ordinary_set(obj, prop_name_si, self.regs[a], self.regs[rd])?;
Ok(())
}
@@ -180,7 +184,7 @@ impl Vm {
}
let obj = unsafe { &*obj_ptr };
let prop_name_si = self.property_key_si(self.regs[a]);
self.regs[b] = self.resolve_property(obj, prop_name_si).unwrap_or(JsValue::undefined());
self.regs[b] = self.ordinary_get(obj, prop_name_si, self.regs[rd])?;
Ok(())
}
@@ -196,14 +200,7 @@ impl Vm {
obj.set_proto(self.regs[b]).map_err(|e| e.to_string())?;
return Ok(());
}
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_at(pos, self.regs[b]);
} else {
let new_shape_id = self.kernel.shape_forge().make_shape(obj.shape_id(), prop_name_si);
obj.set_shape_id(new_shape_id);
obj.push_prop(self.regs[b]);
obj.bump_generation();
}
self.ordinary_set(obj, prop_name_si, self.regs[b], self.regs[rd])?;
Ok(())
}
+392 -28
View File
@@ -427,63 +427,264 @@ impl Vm {
None
}
pub(crate) fn read_member_prop(&mut self, obj: &JsObject, prop_name_si: u32) -> JsValue {
pub(crate) fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option<u32> {
let length_si = self.kernel.string_forge().intern("length").0;
if obj.is_array() && prop_name_si == length_si {
return None;
}
self.kernel
.shape_forge()
.lookup_position(obj.shape_id(), prop_name_si)
.and_then(|pos| {
let val = obj.get_prop_at(pos);
if !val.is_undefined() || obj.prop_vec_len() > pos as usize {
Some(pos)
} else {
None
}
})
}
fn call_function_sync(&mut self, callee: JsValue, receiver: JsValue, args: &[JsValue]) -> Result<JsValue, String> {
if !callee.is_object() {
return Err("TypeError: accessor is not callable".into());
}
let callee_obj = unsafe { &*callee.as_js_object_ptr() };
if !callee_obj.is_function() {
return Err("TypeError: accessor is not callable".into());
}
if let Some(native_fn) = callee_obj.native_fn() {
let saved_regs = self.regs;
let saved_this = self.regs[254];
let saved_callee = self.regs[253];
self.regs[253] = receiver;
for (idx, arg) in args.iter().enumerate() {
self.regs[240 + idx] = *arg;
}
let mut arg_regs = [0u8; 17];
arg_regs[0] = 253;
for idx in 0..args.len().min(16) {
arg_regs[idx + 1] = 240 + idx as u8;
}
let func: NativeFn = unsafe { std::mem::transmute(native_fn) };
let result = func(self, &arg_regs[..args.len().min(16) + 1]);
self.regs = saved_regs;
self.regs[254] = saved_this;
self.regs[253] = saved_callee;
return result.map_err(|err| self.error_text(err));
}
if callee_obj.sub_module_index() == 0 {
return Err("TypeError: accessor is not callable".into());
}
let sub_idx = callee_obj.sub_module_index() as usize - 1;
if sub_idx >= self.sub_modules.len() {
return Err(format!(
"accessor sub_module_index {} out of bounds (max {})",
sub_idx,
self.sub_modules.len()
));
}
if self.frames.len() >= self.kernel.config.max_call_depth {
return Err("RangeError: Maximum call stack size exceeded".into());
}
let saved_regs = self.regs;
let saved_pc = self.pc;
let saved_bytecode = std::mem::take(&mut self.bytecode);
let saved_constants = std::mem::take(&mut self.constants);
let saved_frames = std::mem::take(&mut self.frames);
let saved_bytecode_stack = std::mem::take(&mut self.saved_bytecode_stack);
let saved_constants_stack = std::mem::take(&mut self.saved_constants_stack);
let saved_active_reg_limit = self.active_reg_limit;
let saved_exception_value = self.exception_value.take();
let saved_pending_exception = self.pending_exception.take();
let saved_pending_error_kind = self.pending_error_kind.take();
let sub = self.sub_modules[sub_idx].clone();
let sub_param_base = sub.param_base as usize;
let sub_arg_count = sub.n_args as usize;
for i in 0..sub_arg_count {
self.regs[sub_param_base + i] = args.get(i).copied().unwrap_or(JsValue::undefined());
}
self.regs[254] = if sub.is_arrow { callee_obj.captured_this() } else { receiver };
self.regs[255] = JsValue::undefined();
self.bytecode = sub.bytecode;
let converted_constants = match self.convert_constants(&sub.constants) {
Ok(constants) => constants,
Err(err) => {
self.regs = saved_regs;
self.pc = saved_pc;
self.bytecode = saved_bytecode;
self.constants = saved_constants;
self.frames = saved_frames;
self.saved_bytecode_stack = saved_bytecode_stack;
self.saved_constants_stack = saved_constants_stack;
self.active_reg_limit = saved_active_reg_limit;
self.exception_value = saved_exception_value;
self.pending_exception = saved_pending_exception;
self.pending_error_kind = saved_pending_error_kind;
return Err(err);
}
};
self.constants = converted_constants;
for (name, reg) in &sub.builtin_reg_map {
let si = self.kernel.string_forge().intern(name.as_str()).0;
let global = self.kernel.global_object();
if let Some(pos) = self.kernel.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;
let result = self.dispatch();
self.regs = saved_regs;
self.pc = saved_pc;
self.bytecode = saved_bytecode;
self.constants = saved_constants;
self.frames = saved_frames;
self.saved_bytecode_stack = saved_bytecode_stack;
self.saved_constants_stack = saved_constants_stack;
self.active_reg_limit = saved_active_reg_limit;
self.exception_value = saved_exception_value;
self.pending_exception = saved_pending_exception;
self.pending_error_kind = saved_pending_error_kind;
result
}
pub(crate) fn ordinary_get(
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue,
) -> Result<JsValue, String> {
let length_si = self.kernel.string_forge().intern("length").0;
if obj.is_array() && prop_name_si == length_si {
return Ok(JsValue::int(obj.prop_count() as i32));
}
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 {
return if meta.get.is_undefined() {
Ok(JsValue::undefined())
} else {
self.call_function_sync(meta.get, receiver, &[])
};
}
}
return Ok(obj.get_prop_at(pos));
}
let proto = obj.proto();
if proto.is_object() {
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
return self.ordinary_get(proto_obj, prop_name_si, receiver);
}
Ok(JsValue::undefined())
}
fn inherited_property_meta(&self, obj: &JsObject, prop_name_si: u32) -> Option<oxide_types::object::PropMetaEntry> {
let mut proto = obj.proto();
while proto.is_object() {
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
if let Some(pos) = self.get_own_property_slot(proto_obj, prop_name_si) {
return proto_obj.prop_meta_at(pos);
}
proto = proto_obj.proto();
}
None
}
pub(crate) fn ordinary_set(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
) -> Result<(), String> {
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 {
if meta.set.is_undefined() {
return self.raise_type_error("property has no setter");
}
self.call_function_sync(meta.set, receiver, &[val])?;
return Ok(());
}
if !meta.attributes.writable() {
return self.raise_type_error("cannot assign to read-only property");
}
}
obj.set_prop_at(pos, val);
return Ok(());
}
if let Some(meta) = self.inherited_property_meta(obj, prop_name_si) {
if meta.is_accessor {
if meta.set.is_undefined() {
return self.raise_type_error("property has no setter");
}
self.call_function_sync(meta.set, receiver, &[val])?;
return Ok(());
}
if !meta.attributes.writable() {
return self.raise_type_error("cannot assign to read-only property");
}
}
self.set_or_create_prop_value(obj, prop_name_si, val);
Ok(())
}
pub(crate) fn read_member_prop(
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue,
) -> Result<JsValue, String> {
let ext0 = self.bytecode[self.pc];
let ext1 = self.bytecode[self.pc + 1];
let ext2 = self.bytecode[self.pc + 2];
self.pc += 3;
if obj.has_prop_meta() {
return self.ordinary_get(obj, prop_name_si, receiver);
}
let cached_shape_id = ext0 & 0x00FF_FFFF;
let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64);
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_ptr != 0 {
let val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_ptr != 0 {
unsafe { *(cached_ptr as *const JsValue) }
} else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) {
if template.prop_name != prop_name_si {
self.resolve_property(obj, prop_name_si).unwrap_or(JsValue::undefined())
self.ordinary_get(obj, prop_name_si, receiver)?
} else if let Some(ptr) = self.template_prop_ptr(obj, &template) {
self.write_ic_back(obj.shape_id(), ptr);
unsafe { *ptr }
} else {
self.resolve_property(obj, prop_name_si).unwrap_or(JsValue::undefined())
self.ordinary_get(obj, prop_name_si, receiver)?
}
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
} else {
let resolved = self.ordinary_get(obj, prop_name_si, receiver)?;
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
if let Some(ptr) = obj.prop_ptr_at(pos) {
self.write_ic_back(obj.shape_id(), ptr);
if !obj.is_accessor_meta(pos) {
if let Some(ptr) = obj.prop_ptr_at(pos) {
self.write_ic_back(obj.shape_id(), ptr);
}
}
}
val
} else {
JsValue::undefined()
}
resolved
};
Ok(val)
}
pub(crate) fn set_member_prop(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue,
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
) -> Result<(), String> {
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
if obj.has_prop_meta() {
self.ordinary_set(obj, prop_name_si, val, receiver)?;
return Ok(());
}
obj.set_prop_at(pos, val);
// IC write-back: 3 extension words (shape_id + ptr_lo + ptr_hi)
if let Some(ptr) = obj.prop_ptr_at(pos) {
self.write_ic_back(obj.shape_id(), ptr);
}
} else {
let new_shape_id = self.kernel.shape_forge().make_shape(obj.shape_id(), prop_name_si);
obj.set_shape_id(new_shape_id);
let new_pos = obj.push_prop(val);
obj.bump_generation();
if let Some(ptr) = obj.prop_ptr_at(new_pos) {
self.write_ic_back(new_shape_id, ptr);
self.kernel.prop_forge().upsert(
new_shape_id,
PropTemplate {
shape_id: new_shape_id,
prop_name: prop_name_si,
position: new_pos,
generation: obj.generation(),
},
);
}
self.ordinary_set(obj, prop_name_si, val, receiver)?;
}
Ok(())
}
@@ -1648,7 +1849,65 @@ impl Default for Vm {
#[cfg(test)]
mod tests {
use super::{opcode, JsValue, TryHandler, Vm};
use crate::native::NativeResult;
use oxide_compiler::module::{CompiledModule, Constant};
use oxide_types::object::{JsObject, PropAttributes};
fn native_return_7(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
Ok(JsValue::int(7))
}
fn native_get_marker(vm: &mut Vm, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return Ok(JsValue::undefined());
}
let marker_si = vm.kernel.string_forge().intern("marker").0;
let obj = unsafe { &*this_val.as_js_object_ptr() };
Ok(vm.resolve_property(obj, marker_si).unwrap_or(JsValue::undefined()))
}
fn native_set_marker(vm: &mut Vm, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
let value = vm.reg(args[1]);
if !this_val.is_object() {
return Ok(JsValue::undefined());
}
let marker_si = vm.kernel.string_forge().intern("marker").0;
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
vm.set_or_create_prop_value(obj, marker_si, value);
Ok(JsValue::undefined())
}
fn native_function(vm: &mut Vm, f: crate::native::NativeFn) -> JsValue {
let proto = vm.kernel.builtin_world().function_proto.as_ptr() as *mut JsObject;
let mut obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
obj.set_function(true);
obj.set_native_fn(Some(f as *const ()));
JsValue::object(vm.epoch.alloc(obj) as *mut u8)
}
fn plain_object(vm: &mut Vm) -> JsValue {
let proto = vm.kernel.builtin_world().object_proto.as_ptr() as *mut JsObject;
let obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
JsValue::object(vm.epoch.alloc(obj) as *mut u8)
}
fn add_accessor(vm: &mut Vm, obj_val: JsValue, name: &str, get: JsValue, set: JsValue) {
let si = vm.kernel.string_forge().intern(name).0;
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
let shape_id = vm.kernel.shape_forge().make_shape(obj.shape_id(), si);
obj.set_shape_id(shape_id);
let pos = obj.push_prop(JsValue::undefined());
obj.set_accessor_meta(pos, get, set, PropAttributes::DEFAULT_DATA);
obj.bump_generation();
}
fn set_data(vm: &mut Vm, obj_val: JsValue, name: &str, val: JsValue) {
let si = vm.kernel.string_forge().intern(name).0;
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
vm.set_or_create_prop_value(obj, si, val);
}
#[test]
fn reset_clears_runtime_state_like_rerun() {
@@ -1784,4 +2043,109 @@ mod tests {
let err = vm.run(&module).expect_err("unimplemented opcode should fail explicitly");
assert_eq!(err, "opcode PROFILE_TYPE not yet implemented");
}
#[test]
fn ordinary_get_calls_own_native_getter() {
let mut vm = Vm::new();
let obj_val = plain_object(&mut vm);
let getter = native_function(&mut vm, native_return_7);
add_accessor(&mut vm, obj_val, "x", getter, JsValue::undefined());
let x_si = vm.kernel.string_forge().intern("x").0;
let obj = unsafe { &*obj_val.as_js_object_ptr() };
let value = vm.ordinary_get(obj, x_si, obj_val).expect("getter");
assert_eq!(value, JsValue::int(7));
}
#[test]
fn ordinary_set_calls_own_native_setter() {
let mut vm = Vm::new();
let obj_val = plain_object(&mut vm);
let setter = native_function(&mut vm, native_set_marker);
add_accessor(&mut vm, obj_val, "x", JsValue::undefined(), setter);
let x_si = vm.kernel.string_forge().intern("x").0;
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
vm.ordinary_set(obj, x_si, JsValue::int(9), obj_val).expect("setter");
let marker_si = vm.kernel.string_forge().intern("marker").0;
let obj = unsafe { &*obj_val.as_js_object_ptr() };
assert_eq!(vm.resolve_property(obj, marker_si), Some(JsValue::int(9)));
}
#[test]
fn inherited_getter_uses_original_receiver() {
let mut vm = Vm::new();
let proto_val = plain_object(&mut vm);
let child_val = plain_object(&mut vm);
let getter = native_function(&mut vm, native_get_marker);
add_accessor(&mut vm, proto_val, "x", getter, JsValue::undefined());
set_data(&mut vm, child_val, "marker", JsValue::int(42));
unsafe {
(*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto");
}
let x_si = vm.kernel.string_forge().intern("x").0;
let child = unsafe { &*child_val.as_js_object_ptr() };
let value = vm.ordinary_get(child, x_si, child_val).expect("getter");
assert_eq!(value, JsValue::int(42));
}
#[test]
fn inherited_setter_uses_original_receiver() {
let mut vm = Vm::new();
let proto_val = plain_object(&mut vm);
let child_val = plain_object(&mut vm);
let setter = native_function(&mut vm, native_set_marker);
add_accessor(&mut vm, proto_val, "x", JsValue::undefined(), setter);
unsafe {
(*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto");
}
let x_si = vm.kernel.string_forge().intern("x").0;
let child = unsafe { &mut *child_val.as_js_object_ptr() };
vm.ordinary_set(child, x_si, JsValue::int(12), child_val).expect("setter");
let marker_si = vm.kernel.string_forge().intern("marker").0;
let child = unsafe { &*child_val.as_js_object_ptr() };
let proto = unsafe { &*proto_val.as_js_object_ptr() };
assert_eq!(vm.resolve_property(child, marker_si), Some(JsValue::int(12)));
assert_eq!(vm.resolve_property(proto, marker_si), None);
}
#[test]
fn deep_inherited_setter_uses_original_receiver() {
let mut vm = Vm::new();
let grand_proto_val = plain_object(&mut vm);
let proto_val = plain_object(&mut vm);
let child_val = plain_object(&mut vm);
let setter = native_function(&mut vm, native_set_marker);
add_accessor(&mut vm, grand_proto_val, "x", JsValue::undefined(), setter);
unsafe {
(*proto_val.as_js_object_ptr()).set_proto(grand_proto_val).expect("proto");
(*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto");
}
let x_si = vm.kernel.string_forge().intern("x").0;
let child = unsafe { &mut *child_val.as_js_object_ptr() };
vm.ordinary_set(child, x_si, JsValue::int(15), child_val).expect("setter");
let marker_si = vm.kernel.string_forge().intern("marker").0;
let child = unsafe { &*child_val.as_js_object_ptr() };
assert_eq!(vm.resolve_property(child, marker_si), Some(JsValue::int(15)));
}
#[test]
fn ordinary_data_property_still_reads_and_writes_without_meta() {
let mut vm = Vm::new();
let obj_val = plain_object(&mut vm);
set_data(&mut vm, obj_val, "x", JsValue::int(1));
let x_si = vm.kernel.string_forge().intern("x").0;
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
assert!(!obj.has_prop_meta());
assert_eq!(vm.ordinary_get(obj, x_si, obj_val).expect("get"), JsValue::int(1));
vm.ordinary_set(obj, x_si, JsValue::int(2), obj_val).expect("set");
assert_eq!(vm.ordinary_get(obj, x_si, obj_val).expect("get"), JsValue::int(2));
}
}