feat: 添加访问器属性元数据与普通属性访问
This commit is contained in:
@@ -2,6 +2,70 @@ use crate::value::JsValue;
|
|||||||
|
|
||||||
pub type ShapeId = u32;
|
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 {
|
pub trait PropIndex {
|
||||||
fn to_u32(self) -> u32;
|
fn to_u32(self) -> u32;
|
||||||
}
|
}
|
||||||
@@ -48,6 +112,7 @@ impl PropIndex for i32 {
|
|||||||
/// [31] is_function
|
/// [31] is_function
|
||||||
/// native_arg_count: u8 (1 byte + 3 pad)
|
/// native_arg_count: u8 (1 byte + 3 pad)
|
||||||
/// hash_props: *mut u8 (8 bytes, points to Box<Vec<Box<JsValue>>>)
|
/// 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)
|
/// proto: JsValue (8 bytes)
|
||||||
/// generation: u32 (4 bytes + 4 pad)
|
/// generation: u32 (4 bytes + 4 pad)
|
||||||
/// native_fn: u64 (8 bytes, 0 = None sentinel)
|
/// native_fn: u64 (8 bytes, 0 = None sentinel)
|
||||||
@@ -60,6 +125,7 @@ pub struct JsObject {
|
|||||||
native_arg_count: u8,
|
native_arg_count: u8,
|
||||||
_pad: [u8; 3],
|
_pad: [u8; 3],
|
||||||
hash_props: *mut u8,
|
hash_props: *mut u8,
|
||||||
|
prop_meta: *mut u8,
|
||||||
proto: JsValue,
|
proto: JsValue,
|
||||||
generation: u32,
|
generation: u32,
|
||||||
_pad2: [u8; 4],
|
_pad2: [u8; 4],
|
||||||
@@ -77,6 +143,7 @@ impl JsObject {
|
|||||||
native_arg_count: 0,
|
native_arg_count: 0,
|
||||||
_pad: [0; 3],
|
_pad: [0; 3],
|
||||||
hash_props: std::ptr::null_mut(),
|
hash_props: std::ptr::null_mut(),
|
||||||
|
prop_meta: std::ptr::null_mut(),
|
||||||
proto,
|
proto,
|
||||||
generation: 1,
|
generation: 1,
|
||||||
_pad2: [0; 4],
|
_pad2: [0; 4],
|
||||||
@@ -94,6 +161,7 @@ impl JsObject {
|
|||||||
native_arg_count: 0,
|
native_arg_count: 0,
|
||||||
_pad: [0; 3],
|
_pad: [0; 3],
|
||||||
hash_props: std::ptr::null_mut(),
|
hash_props: std::ptr::null_mut(),
|
||||||
|
prop_meta: std::ptr::null_mut(),
|
||||||
proto,
|
proto,
|
||||||
generation: 1,
|
generation: 1,
|
||||||
_pad2: [0; 4],
|
_pad2: [0; 4],
|
||||||
@@ -129,15 +197,87 @@ impl JsObject {
|
|||||||
|
|
||||||
/// Sets the length of hash_props vec. Truncates or extends with undefined.
|
/// Sets the length of hash_props vec. Truncates or extends with undefined.
|
||||||
pub fn set_prop_count(&mut self, count: impl PropIndex) {
|
pub fn set_prop_count(&mut self, count: impl PropIndex) {
|
||||||
let vec = self.ensure_hash_props();
|
|
||||||
let target = count.to_u32() as usize;
|
let target = count.to_u32() as usize;
|
||||||
if target < vec.len() {
|
{
|
||||||
vec.truncate(target);
|
let vec = self.ensure_hash_props();
|
||||||
} else {
|
if target < vec.len() {
|
||||||
while vec.len() < target {
|
vec.truncate(target);
|
||||||
vec.push(Box::new(JsValue::undefined()));
|
} 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 {
|
pub fn is_array(&self) -> bool {
|
||||||
@@ -198,15 +338,22 @@ impl JsObject {
|
|||||||
|
|
||||||
/// Set property value at position index. Vec auto-grows if needed.
|
/// Set property value at position index. Vec auto-grows if needed.
|
||||||
pub fn set_prop_at(&mut self, position: impl PropIndex, val: JsValue) {
|
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;
|
let pos = position.to_u32() as usize;
|
||||||
if pos < vec.len() {
|
{
|
||||||
*vec[pos] = val;
|
let vec = self.ensure_hash_props();
|
||||||
} else {
|
if pos < vec.len() {
|
||||||
while vec.len() < pos {
|
*vec[pos] = val;
|
||||||
vec.push(Box::new(JsValue::undefined()));
|
} 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 vec = self.ensure_hash_props();
|
||||||
let pos = vec.len();
|
let pos = vec.len();
|
||||||
vec.push(Box::new(val));
|
vec.push(Box::new(val));
|
||||||
|
if let Some(meta) = self.prop_meta_vec_mut() {
|
||||||
|
meta.push(None);
|
||||||
|
}
|
||||||
pos as u32
|
pos as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,6 +531,7 @@ mod tests {
|
|||||||
assert!(!obj.is_function());
|
assert!(!obj.is_function());
|
||||||
assert_eq!(obj.generation(), 1);
|
assert_eq!(obj.generation(), 1);
|
||||||
assert!(obj.hash_props_vec().is_none());
|
assert!(obj.hash_props_vec().is_none());
|
||||||
|
assert!(!obj.has_prop_meta());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -452,4 +603,38 @@ mod tests {
|
|||||||
assert_eq!(obj.get_prop_at(0), JsValue::int(100));
|
assert_eq!(obj.get_prop_at(0), JsValue::int(100));
|
||||||
assert_eq!(obj.get_prop_at(1), JsValue::int(200));
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,12 @@ impl Vm {
|
|||||||
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_INC on non-object") else {
|
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_INC on non-object") else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
let receiver = self.regs[rd];
|
||||||
let obj = unsafe { &mut *obj_ptr };
|
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 n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||||
let new_val = JsValue::float(n + 1.0);
|
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;
|
self.regs[a] = new_val;
|
||||||
Ok(())
|
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 {
|
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_DEC on non-object") else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
let receiver = self.regs[rd];
|
||||||
let obj = unsafe { &mut *obj_ptr };
|
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 n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||||
let new_val = JsValue::float(n - 1.0);
|
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;
|
self.regs[a] = new_val;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -74,12 +76,13 @@ impl Vm {
|
|||||||
self.raise_type_error("DYN_MEMBER_INC on non-object")?;
|
self.raise_type_error("DYN_MEMBER_INC on non-object")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
let receiver = self.regs[rd];
|
||||||
let obj = unsafe { &mut *obj_ptr };
|
let obj = unsafe { &mut *obj_ptr };
|
||||||
let prop_name_si = self.property_key_si(self.regs[a]);
|
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 n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||||
let new_val = JsValue::float(n + 1.0);
|
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;
|
self.regs[b] = new_val;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -90,12 +93,13 @@ impl Vm {
|
|||||||
self.raise_type_error("DYN_MEMBER_DEC on non-object")?;
|
self.raise_type_error("DYN_MEMBER_DEC on non-object")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
let receiver = self.regs[rd];
|
||||||
let obj = unsafe { &mut *obj_ptr };
|
let obj = unsafe { &mut *obj_ptr };
|
||||||
let prop_name_si = self.property_key_si(self.regs[a]);
|
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 n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||||
let new_val = JsValue::float(n - 1.0);
|
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;
|
self.regs[b] = new_val;
|
||||||
Ok(())
|
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 {
|
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "COMPOUND_MEMBER_ADD on non-object") else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
let receiver = self.regs[rd];
|
||||||
let obj = unsafe { &mut *obj_ptr };
|
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 rhs = self.regs[a];
|
||||||
let new_val = if prop_val.is_string() || rhs.is_string() {
|
let new_val = if prop_val.is_string() || rhs.is_string() {
|
||||||
let ls = coercion::to_string(self.kernel.string_forge().as_ref(), prop_val);
|
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());
|
let rn = coercion::to_number(rhs, self.kernel.string_forge().as_ref());
|
||||||
JsValue::float(ln + rn)
|
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;
|
self.regs[a] = new_val;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -130,13 +135,14 @@ impl Vm {
|
|||||||
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, error_msg) else {
|
let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, error_msg) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
let receiver = self.regs[rd];
|
||||||
let obj = unsafe { &mut *obj_ptr };
|
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 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 rn = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||||
let new_val = JsValue::float(op(ln, rn));
|
let new_val = JsValue::float(op(ln, rn));
|
||||||
self.regs[a] = new_val;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ impl Vm {
|
|||||||
let proto_ptr = self.kernel.builtin_world().string_proto.as_ptr() as *mut JsObject;
|
let proto_ptr = self.kernel.builtin_world().string_proto.as_ptr() as *mut JsObject;
|
||||||
let proto = unsafe { &*proto_ptr };
|
let proto = unsafe { &*proto_ptr };
|
||||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
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.regs[a] = resolved;
|
||||||
self.pc += 3;
|
self.pc += 3;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -37,7 +38,8 @@ impl Vm {
|
|||||||
let proto_ptr = self.kernel.builtin_world().number_proto.as_ptr() as *mut JsObject;
|
let proto_ptr = self.kernel.builtin_world().number_proto.as_ptr() as *mut JsObject;
|
||||||
let proto = unsafe { &*proto_ptr };
|
let proto = unsafe { &*proto_ptr };
|
||||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
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.regs[a] = resolved;
|
||||||
self.pc += 3;
|
self.pc += 3;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -53,6 +55,10 @@ impl Vm {
|
|||||||
let ext1 = self.bytecode[self.pc + 1];
|
let ext1 = self.bytecode[self.pc + 1];
|
||||||
let ext2 = self.bytecode[self.pc + 2];
|
let ext2 = self.bytecode[self.pc + 2];
|
||||||
self.pc += 3;
|
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_shape_id = ext0 & 0x00FF_FFFF;
|
||||||
let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64);
|
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.write_ic_back(obj.shape_id(), ptr);
|
||||||
self.regs[a] = unsafe { *ptr };
|
self.regs[a] = unsafe { *ptr };
|
||||||
} else {
|
} 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 {
|
} 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(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||||
if let Some(ptr) = obj.prop_ptr_at(pos) {
|
if let Some(ptr) = obj.prop_ptr_at(pos) {
|
||||||
self.write_ic_back(obj.shape_id(), ptr);
|
self.write_ic_back(obj.shape_id(), ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.regs[a] = val;
|
self.regs[a] = resolved;
|
||||||
} else {
|
|
||||||
self.regs[a] = JsValue::undefined();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -103,22 +108,28 @@ impl Vm {
|
|||||||
let ext1 = self.bytecode[self.pc + 1];
|
let ext1 = self.bytecode[self.pc + 1];
|
||||||
let ext2 = self.bytecode[self.pc + 2];
|
let ext2 = self.bytecode[self.pc + 2];
|
||||||
self.pc += 3;
|
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_shape_id = ext0 & 0x00FF_FFFF;
|
||||||
let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64);
|
let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64);
|
||||||
|
|
||||||
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_ptr != 0 {
|
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_ptr != 0 {
|
||||||
unsafe {
|
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) {
|
} 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) {
|
if let Some(ptr) = obj.prop_ptr_at(pos) {
|
||||||
self.write_ic_back(obj.shape_id(), ptr);
|
self.write_ic_back(obj.shape_id(), ptr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let new_shape_id = self.kernel.shape_forge().make_shape(obj.shape_id(), prop_name_si);
|
let new_shape_id = self.kernel.shape_forge().make_shape(obj.shape_id(), prop_name_si);
|
||||||
obj.set_shape_id(new_shape_id);
|
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();
|
obj.bump_generation();
|
||||||
if let Some(ptr) = obj.prop_ptr_at(new_pos) {
|
if let Some(ptr) = obj.prop_ptr_at(new_pos) {
|
||||||
self.write_ic_back(new_shape_id, ptr);
|
self.write_ic_back(new_shape_id, ptr);
|
||||||
@@ -145,7 +156,7 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
let obj = unsafe { &*obj_ptr };
|
let obj = unsafe { &*obj_ptr };
|
||||||
let prop_name_si = self.property_key_si(self.regs[b]);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,14 +172,7 @@ impl Vm {
|
|||||||
obj.set_proto(self.regs[a]).map_err(|e| e.to_string())?;
|
obj.set_proto(self.regs[a]).map_err(|e| e.to_string())?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
self.ordinary_set(obj, prop_name_si, self.regs[a], self.regs[rd])?;
|
||||||
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();
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +184,7 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
let obj = unsafe { &*obj_ptr };
|
let obj = unsafe { &*obj_ptr };
|
||||||
let prop_name_si = self.property_key_si(self.regs[a]);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,14 +200,7 @@ impl Vm {
|
|||||||
obj.set_proto(self.regs[b]).map_err(|e| e.to_string())?;
|
obj.set_proto(self.regs[b]).map_err(|e| e.to_string())?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
self.ordinary_set(obj, prop_name_si, self.regs[b], self.regs[rd])?;
|
||||||
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();
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+392
-28
@@ -427,63 +427,264 @@ impl Vm {
|
|||||||
None
|
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 ext0 = self.bytecode[self.pc];
|
||||||
let ext1 = self.bytecode[self.pc + 1];
|
let ext1 = self.bytecode[self.pc + 1];
|
||||||
let ext2 = self.bytecode[self.pc + 2];
|
let ext2 = self.bytecode[self.pc + 2];
|
||||||
self.pc += 3;
|
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_shape_id = ext0 & 0x00FF_FFFF;
|
||||||
let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64);
|
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) }
|
unsafe { *(cached_ptr as *const JsValue) }
|
||||||
} else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) {
|
} else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) {
|
||||||
if template.prop_name != prop_name_si {
|
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) {
|
} else if let Some(ptr) = self.template_prop_ptr(obj, &template) {
|
||||||
self.write_ic_back(obj.shape_id(), ptr);
|
self.write_ic_back(obj.shape_id(), ptr);
|
||||||
unsafe { *ptr }
|
unsafe { *ptr }
|
||||||
} else {
|
} 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(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
||||||
if let Some(ptr) = obj.prop_ptr_at(pos) {
|
if !obj.is_accessor_meta(pos) {
|
||||||
self.write_ic_back(obj.shape_id(), ptr);
|
if let Some(ptr) = obj.prop_ptr_at(pos) {
|
||||||
|
self.write_ic_back(obj.shape_id(), ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val
|
resolved
|
||||||
} else {
|
};
|
||||||
JsValue::undefined()
|
Ok(val)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_member_prop(
|
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> {
|
) -> Result<(), String> {
|
||||||
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
|
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);
|
obj.set_prop_at(pos, val);
|
||||||
// IC write-back: 3 extension words (shape_id + ptr_lo + ptr_hi)
|
// IC write-back: 3 extension words (shape_id + ptr_lo + ptr_hi)
|
||||||
if let Some(ptr) = obj.prop_ptr_at(pos) {
|
if let Some(ptr) = obj.prop_ptr_at(pos) {
|
||||||
self.write_ic_back(obj.shape_id(), ptr);
|
self.write_ic_back(obj.shape_id(), ptr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let new_shape_id = self.kernel.shape_forge().make_shape(obj.shape_id(), prop_name_si);
|
self.ordinary_set(obj, prop_name_si, val, receiver)?;
|
||||||
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(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1648,7 +1849,65 @@ impl Default for Vm {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{opcode, JsValue, TryHandler, Vm};
|
use super::{opcode, JsValue, TryHandler, Vm};
|
||||||
|
use crate::native::NativeResult;
|
||||||
use oxide_compiler::module::{CompiledModule, Constant};
|
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]
|
#[test]
|
||||||
fn reset_clears_runtime_state_like_rerun() {
|
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");
|
let err = vm.run(&module).expect_err("unimplemented opcode should fail explicitly");
|
||||||
assert_eq!(err, "opcode PROFILE_TYPE not yet implemented");
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user