refactor: 拆分属性访问与 accessor 辅助逻辑

This commit is contained in:
爱特每羊队
2026-06-14 00:21:33 +08:00
parent 0df110946a
commit 18f4fe4000
3 changed files with 304 additions and 321 deletions
+1
View File
@@ -7,6 +7,7 @@ mod dispatch;
pub mod native;
pub mod vm;
pub mod vm_pool;
mod vm_props;
mod vm_runtime;
mod vm_support;
pub use oxide_types::value::JsValue;
-321
View File
@@ -459,327 +459,6 @@ impl Vm {
Ok(())
}
pub(crate) fn ordinary_get(
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue,
) -> Result<JsValue, String> {
self.ordinary_get_inner(obj, prop_name_si, receiver, None)
}
/// `ordinary_get` with an optional dispatch-loop target register.
///
/// When `target_reg` is `Some(r)` and the property is a bytecode accessor getter,
/// this method pushes a bytecode frame with `FrameContinuation::AccessorGet { target_reg: r }`
/// and sets `self.accessor_frame_target_reg = Some(r)`. The caller must then return
/// `Ok(JsValue::undefined())` — the real value will be stored by the `RETURN` handler.
///
/// When `target_reg` is `None`, bytecode getters run inline via `call_function_sync`
/// (same epoch, no sub-VM). Safe for callers outside the dispatch loop.
pub(crate) fn ordinary_get_with_target(
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, target_reg: u8,
) -> Result<JsValue, String> {
self.ordinary_get_inner(obj, prop_name_si, receiver, Some(target_reg))
}
fn ordinary_get_inner(
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, target_reg: Option<u8>,
) -> 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 if let Some(tr) = target_reg {
// Dispatch loop path: push bytecode frame, return sentinel.
let getter = meta.get;
let pushed = self.push_bytecode_getter_frame(getter, receiver, tr)?;
if pushed {
return Ok(JsValue::undefined()); // RETURN handler delivers value
}
// Native getter — value already written to regs[tr] by push_bytecode_getter_frame
Ok(self.regs[tr as usize])
} 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_inner(proto_obj, prop_name_si, receiver, target_reg);
}
Ok(JsValue::undefined())
}
/// Push a bytecode frame for a getter accessor, or call native getters inline.
///
/// Returns `true` if a bytecode frame was pushed (dispatch loop must `continue`).
/// Returns `false` if the getter was native (result already in `regs[target_reg]`).
fn push_bytecode_getter_frame(
&mut self, getter: JsValue, receiver: JsValue, target_reg: u8,
) -> Result<bool, String> {
if !getter.is_object() {
return Err("TypeError: getter is not callable".into());
}
let getter_obj = unsafe { &*getter.as_js_object_ptr() };
if !getter_obj.is_function() {
return Err("TypeError: getter is not callable".into());
}
if getter_obj.native_fn().is_some() {
// Native getter — call inline, store result
let result = self.call_function_sync(getter, receiver, &[])?;
self.regs[target_reg as usize] = result;
return Ok(false);
}
// Bytecode getter — push frame
self.push_bytecode_frame(
getter,
receiver,
&[],
None,
None,
JsValue::undefined(),
FrameContinuation::AccessorGet { target_reg },
)?;
self.accessor_frame_target_reg = Some(target_reg);
Ok(true)
}
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> {
self.ordinary_set_inner(obj, prop_name_si, val, receiver, false)
}
/// `ordinary_set` variant for the dispatch loop: bytecode setters push a frame instead
/// of spawning a sub-VM.
pub(crate) fn ordinary_set_dispatch(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
) -> Result<(), String> {
self.ordinary_set_inner(obj, prop_name_si, val, receiver, true)
}
fn ordinary_set_inner(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, use_frame_push: bool,
) -> 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");
}
return self.call_or_push_setter(meta.set, receiver, val, use_frame_push);
}
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");
}
return self.call_or_push_setter(meta.set, receiver, val, use_frame_push);
}
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(())
}
/// Call a setter — either inline (for native setters or sub-VM path) or via frame push
/// (for bytecode setters inside the dispatch loop).
fn call_or_push_setter(
&mut self, setter: JsValue, receiver: JsValue, val: JsValue, use_frame_push: bool,
) -> Result<(), String> {
if !use_frame_push {
self.call_function_sync(setter, receiver, &[val])?;
return Ok(());
}
if !setter.is_object() {
return self.raise_type_error("setter is not callable");
}
let setter_obj = unsafe { &*setter.as_js_object_ptr() };
if !setter_obj.is_function() {
return self.raise_type_error("setter is not callable");
}
if setter_obj.native_fn().is_some() {
// Native setter — call inline
self.call_function_sync(setter, receiver, &[val])?;
return Ok(());
}
// Bytecode setter — push frame; result is discarded via AccessorSet
self.push_bytecode_frame(
setter,
receiver,
&[val],
None,
None,
JsValue::undefined(),
FrameContinuation::AccessorSet,
)?;
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_slot = ext1;
let val =
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_slot < obj.prop_vec_len() as u32 {
obj.get_prop_at(cached_slot)
} else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) {
if template.prop_name != prop_name_si {
self.ordinary_get(obj, prop_name_si, receiver)?
} else if template.position < obj.prop_vec_len() as u32 {
self.write_ic_back(obj.shape_id(), template.position);
obj.get_prop_at(template.position)
} else {
self.ordinary_get(obj, prop_name_si, receiver)?
}
} 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 !obj.is_accessor_meta(pos) {
self.write_ic_back(obj.shape_id(), pos);
}
}
resolved
};
Ok(val)
}
pub(crate) fn set_member_prop(
&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 + slot_index + reserved)
self.write_ic_back(obj.shape_id(), pos);
} else {
self.ordinary_set(obj, prop_name_si, val, receiver)?;
}
Ok(())
}
pub(crate) fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_at(pos, val);
} 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(val);
obj.bump_generation();
}
}
pub(crate) fn define_data_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
) -> Result<(), String> {
let pos = if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
pos
} 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(JsValue::undefined())
};
if let Some(current) = obj.prop_meta_at(pos) {
if !current.attributes.configurable() {
if current.is_accessor {
return self.raise_type_error("cannot redefine non-configurable property");
}
if current.attributes.enumerable() != attributes.enumerable() {
return self.raise_type_error("cannot redefine non-configurable property");
}
if !current.attributes.writable()
&& (attributes.writable() || !coercion::same_value(obj.get_prop_at(pos), val))
{
return self.raise_type_error("cannot redefine non-configurable property");
}
if current.attributes.configurable() != attributes.configurable() {
return self.raise_type_error("cannot redefine non-configurable property");
}
}
}
obj.set_prop_at(pos, val);
obj.set_data_meta(pos, attributes);
obj.bump_generation();
Ok(())
}
pub(crate) fn define_accessor_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, get: JsValue, set: JsValue, attributes: PropAttributes,
) -> Result<(), String> {
let pos = if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
pos
} 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(JsValue::undefined())
};
if let Some(current) = obj.prop_meta_at(pos) {
if !current.attributes.configurable()
&& (!current.is_accessor
|| current.attributes.enumerable() != attributes.enumerable()
|| current.attributes.configurable() != attributes.configurable()
|| current.get != get
|| current.set != set)
{
return self.raise_type_error("cannot redefine non-configurable property");
}
}
obj.set_prop_at(pos, JsValue::undefined());
obj.set_accessor_meta(pos, get, set, attributes);
obj.bump_generation();
Ok(())
}
pub(crate) fn write_ic_back(&mut self, shape_id: u32, slot_index: u32) {
debug_assert!(self.pc >= 3, "IC write-back requires 3 extension words before pc");
self.bytecode[self.pc - 3] = shape_id & 0x00FF_FFFF;
self.bytecode[self.pc - 2] = slot_index;
self.bytecode[self.pc - 1] = 0;
}
pub(crate) fn dispatch(&mut self) -> Result<JsValue, String> {
let mut steps: u64 = 0;
loop {
+303
View File
@@ -0,0 +1,303 @@
use crate::coercion;
use crate::vm::{FrameContinuation, Vm};
use oxide_types::object::{JsObject, PropAttributes, PropMetaEntry};
use oxide_types::value::JsValue;
impl Vm {
pub(crate) fn ordinary_get(
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue,
) -> Result<JsValue, String> {
self.ordinary_get_inner(obj, prop_name_si, receiver, None)
}
pub(crate) fn ordinary_get_with_target(
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, target_reg: u8,
) -> Result<JsValue, String> {
self.ordinary_get_inner(obj, prop_name_si, receiver, Some(target_reg))
}
fn ordinary_get_inner(
&mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, target_reg: Option<u8>,
) -> 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 if let Some(tr) = target_reg {
let getter = meta.get;
let pushed = self.push_bytecode_getter_frame(getter, receiver, tr)?;
if pushed {
return Ok(JsValue::undefined());
}
Ok(self.regs[tr as usize])
} 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_inner(proto_obj, prop_name_si, receiver, target_reg);
}
Ok(JsValue::undefined())
}
fn push_bytecode_getter_frame(
&mut self, getter: JsValue, receiver: JsValue, target_reg: u8,
) -> Result<bool, String> {
if !getter.is_object() {
return Err("TypeError: getter is not callable".into());
}
let getter_obj = unsafe { &*getter.as_js_object_ptr() };
if !getter_obj.is_function() {
return Err("TypeError: getter is not callable".into());
}
if getter_obj.native_fn().is_some() {
let result = self.call_function_sync(getter, receiver, &[])?;
self.regs[target_reg as usize] = result;
return Ok(false);
}
self.push_bytecode_frame(
getter,
receiver,
&[],
None,
None,
JsValue::undefined(),
FrameContinuation::AccessorGet { target_reg },
)?;
self.accessor_frame_target_reg = Some(target_reg);
Ok(true)
}
fn inherited_property_meta(&self, obj: &JsObject, prop_name_si: u32) -> Option<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> {
self.ordinary_set_inner(obj, prop_name_si, val, receiver, false)
}
pub(crate) fn ordinary_set_dispatch(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue,
) -> Result<(), String> {
self.ordinary_set_inner(obj, prop_name_si, val, receiver, true)
}
fn ordinary_set_inner(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, receiver: JsValue, use_frame_push: bool,
) -> 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");
}
return self.call_or_push_setter(meta.set, receiver, val, use_frame_push);
}
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");
}
return self.call_or_push_setter(meta.set, receiver, val, use_frame_push);
}
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(())
}
fn call_or_push_setter(
&mut self, setter: JsValue, receiver: JsValue, val: JsValue, use_frame_push: bool,
) -> Result<(), String> {
if !use_frame_push {
self.call_function_sync(setter, receiver, &[val])?;
return Ok(());
}
if !setter.is_object() {
return self.raise_type_error("setter is not callable");
}
let setter_obj = unsafe { &*setter.as_js_object_ptr() };
if !setter_obj.is_function() {
return self.raise_type_error("setter is not callable");
}
if setter_obj.native_fn().is_some() {
self.call_function_sync(setter, receiver, &[val])?;
return Ok(());
}
self.push_bytecode_frame(
setter,
receiver,
&[val],
None,
None,
JsValue::undefined(),
FrameContinuation::AccessorSet,
)?;
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_slot = ext1;
let val =
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_slot < obj.prop_vec_len() as u32 {
obj.get_prop_at(cached_slot)
} else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) {
if template.prop_name != prop_name_si {
self.ordinary_get(obj, prop_name_si, receiver)?
} else if template.position < obj.prop_vec_len() as u32 {
self.write_ic_back(obj.shape_id(), template.position);
obj.get_prop_at(template.position)
} else {
self.ordinary_get(obj, prop_name_si, receiver)?
}
} 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 !obj.is_accessor_meta(pos) {
self.write_ic_back(obj.shape_id(), pos);
}
}
resolved
};
Ok(val)
}
pub(crate) fn set_member_prop(
&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);
self.write_ic_back(obj.shape_id(), pos);
} else {
self.ordinary_set(obj, prop_name_si, val, receiver)?;
}
Ok(())
}
pub(crate) fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_at(pos, val);
} 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(val);
obj.bump_generation();
}
}
pub(crate) fn define_data_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue, attributes: PropAttributes,
) -> Result<(), String> {
let pos = if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
pos
} 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(JsValue::undefined())
};
if let Some(current) = obj.prop_meta_at(pos) {
if !current.attributes.configurable() {
if current.is_accessor {
return self.raise_type_error("cannot redefine non-configurable property");
}
if current.attributes.enumerable() != attributes.enumerable() {
return self.raise_type_error("cannot redefine non-configurable property");
}
if !current.attributes.writable()
&& (attributes.writable() || !coercion::same_value(obj.get_prop_at(pos), val))
{
return self.raise_type_error("cannot redefine non-configurable property");
}
if current.attributes.configurable() != attributes.configurable() {
return self.raise_type_error("cannot redefine non-configurable property");
}
}
}
obj.set_prop_at(pos, val);
obj.set_data_meta(pos, attributes);
obj.bump_generation();
Ok(())
}
pub(crate) fn define_accessor_property(
&mut self, obj: &mut JsObject, prop_name_si: u32, get: JsValue, set: JsValue, attributes: PropAttributes,
) -> Result<(), String> {
let pos = if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
pos
} 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(JsValue::undefined())
};
if let Some(current) = obj.prop_meta_at(pos) {
if !current.attributes.configurable()
&& (!current.is_accessor
|| current.attributes.enumerable() != attributes.enumerable()
|| current.attributes.configurable() != attributes.configurable()
|| current.get != get
|| current.set != set)
{
return self.raise_type_error("cannot redefine non-configurable property");
}
}
obj.set_prop_at(pos, JsValue::undefined());
obj.set_accessor_meta(pos, get, set, attributes);
obj.bump_generation();
Ok(())
}
pub(crate) fn write_ic_back(&mut self, shape_id: u32, slot_index: u32) {
debug_assert!(self.pc >= 3, "IC write-back requires 3 extension words before pc");
self.bytecode[self.pc - 3] = shape_id & 0x00FF_FFFF;
self.bytecode[self.pc - 2] = slot_index;
self.bytecode[self.pc - 1] = 0;
}
}