Files
OxideJS-New/crates/oxide_vm/src/dispatch/property.rs
T

839 lines
42 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::{ic_debug, ic_trace, vm_trace};
use oxide_bytecode::opcode::{OpCode, IC_EXT_WORDS};
use oxide_runtime_api as coercion;
use oxide_types::object::JsObject;
use oxide_types::private_key::{int_key_value, is_int_key, make_int_key, make_private_name_id};
use oxide_types::value::JsValue;
use crate::ic_helper::{self, ic_get_hit, ic_set_hit, VIRTUAL_LENGTH_SLOT};
use crate::vm::{Vm, MAX_PROTO_CHAIN_DEPTH};
use crate::vm_props::SetContract;
#[cold]
fn prop_cache_miss() {}
impl Vm {
pub(crate) fn dispatch_property_op(&mut self, op: OpCode, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("PROPERTY_OP {:?} rd={} a={} b={}", op, rd, a, b);
match op {
OpCode::IC_GET_PROP => self.dispatch_ic_get_prop(a, b),
OpCode::IC_SET_PROP => self.dispatch_ic_set_prop(rd, a, b),
OpCode::GET_PROP => self.dispatch_get_prop(rd, a, b),
OpCode::SET_PROP => self.dispatch_set_prop(rd, a, b),
OpCode::SET_PROP_BATCH => self.dispatch_set_prop_batch(rd, a, b),
OpCode::GET_PROP_DYNAMIC => self.dispatch_get_prop_dynamic(rd, a, b),
OpCode::SET_PROP_DYNAMIC => self.dispatch_set_prop_dynamic(rd, a, b),
OpCode::SET_ELEM => self.dispatch_set_elem(rd, a, b),
OpCode::GET_PRIVATE => self.dispatch_get_private(rd, a, b),
OpCode::SET_PRIVATE => self.dispatch_set_private(rd, a, b),
OpCode::INIT_PRIVATE => self.dispatch_init_private(rd, a, b),
OpCode::PRIVATE_BRAND_IN => self.dispatch_private_brand_in(rd, a, b),
_ => unreachable!("non-property opcode passed to dispatch_property_op"),
}
}
/// SET 路径的接收者解析:对象直取;非对象按严格性分流——strict 抛
/// TypeError(规范 OrdinarySetWithOwnDescriptor 的 ToObject 基语义在
/// strict 下不 auto-box),sloppy 经 ToObject 自动装箱(null/undefined
/// 抛 TypeError,其余原始值写盒后丢弃盒)。
fn set_prop_base(&mut self, receiver: JsValue) -> Result<Option<*mut JsObject>, String> {
if receiver.is_object() {
return self.checked_object_ptr(receiver, "Cannot create property on non-object");
}
if self.current_strict() {
self.raise_type_error("Cannot create property on a primitive value")?;
return Ok(None);
}
// null/undefined:ToObject 抛可捕获 TypeError(经 raise 走 JS 异常,
// 非 Rust Err 传播,try/catch 可接)。
if receiver.is_null() || receiver.is_undefined() {
self.raise_type_error("Cannot convert null or undefined to object")?;
return Ok(None);
}
let boxed = coercion::to_object(receiver, self)?;
Ok(Some(boxed.as_js_object_ptr()))
}
fn private_key_from_reg(&self, reg: usize) -> u32 {
let value = self.regs[reg];
let local_id = if value.is_int() {
value.as_int().max(0) as u32
} else if value.is_double() {
value.as_double().max(0.0) as u32
} else {
0
};
make_private_name_id(local_id)
}
fn primitive_property_get(&mut self, val: JsValue, prop_name_si: u32) -> Result<Option<JsValue>, String> {
if val.is_string() {
let length_si = self.length_si;
if prop_name_si == length_si {
// SAFETY: val 是字符串值。utf16_len 构造时缓存,O(1) 读取。
let len = unsafe { (*val.as_string_ptr()).utf16_len() };
return Ok(Some(JsValue::int(len as i32)));
}
// 整数索引键:规范 String exotic [[Get]] 界内返码元子串(码元口径非码点),
// 越界返 undefined——必须 Some(undefined) 而非 None,None 会被调用点当
// "非原始值属性"回落对象臂抛 TypeError。
if is_int_key(prop_name_si) {
// SAFETY: val 是字符串值。
let s = unsafe { &*val.as_string_ptr() };
let len = s.utf16_len();
let idx = int_key_value(prop_name_si);
if idx < len {
// 码元读取:ASCII 命中共享 perm 串零分配,非 ASCII 物化 1 单元会话串。
let units = s.units();
return Ok(Some(self.unit_char_value(units[idx as usize])));
}
return Ok(Some(JsValue::undefined()));
}
let proto_ptr = self.realm.session.borrow().builtin_world().string_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
if val.is_int() || val.is_double() {
let proto_ptr = self.realm.session.borrow().builtin_world().number_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
if val.is_bool() {
let proto_ptr = self.realm.session.borrow().builtin_world().boolean_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
if val.is_bigint() {
let proto_ptr = self.realm.session.borrow().builtin_world().bigint_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
if val.is_symbol() {
let proto_ptr = self.realm.session.borrow().builtin_world().symbol_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
Ok(None)
}
fn private_slot(&self, obj: &JsObject, private_key: u32) -> Option<u32> {
self.kernel_core.shape_forge().lookup_position(obj.shape_id(), private_key)
}
/// 沿实例→proto 链查找私有方法/访问器槽(存于类的 home=proto)。字段槽不跨原型
/// (PrivateFieldFind 只查 own)——链上遇到字段槽(无 hole meta)即拒绝,防
/// `Object.create(instance)` 穿透读取实例字段。
fn find_private_method_slot(&self, obj: &JsObject, private_key: u32) -> Option<*mut JsObject> {
let mut proto = obj.proto();
let mut depth = 0usize;
while proto.is_object() && depth < MAX_PROTO_CHAIN_DEPTH {
depth += 1;
let proto_obj = unsafe { &*proto.as_js_object_ptr() };
if let Some(pos) = self.private_slot(proto_obj, private_key) {
// 方法/访问器槽有 hole 标记(私有方法)或 is_accessor 标记(私有 getter/setter);
// 字段槽(存实例)沿链出现说明接收者并非本类实例 → 拒绝穿透。
if proto_obj.prop_meta_at(pos).is_some_and(|m| m.is_hole() || m.is_accessor) {
return Some(proto_obj as *const JsObject as *mut JsObject);
}
return None;
}
proto = proto_obj.proto();
}
None
}
/// 私有方法/访问器/静态字段访问的 brand 检查:接收者 own 的 brand 槽(构造时写入
/// 的类 brand 对象)必须与当前类 brand 对象同一。类每次求值创建新 brand,同字节码
/// 多次实例化(eval/factory)可借此区分。
fn private_brand_check(&mut self, obj: &JsObject, brand_reg: usize, brand_key: u32) -> Result<(), String> {
let Some(pos) = self.private_slot(obj, brand_key) else {
return self.raise_type_error("private field brand check failed");
};
let inst_brand = obj.get_prop_at(pos);
if !coercion::strict_equality(inst_brand, self.regs[brand_reg]) {
return self.raise_type_error("private field brand check failed");
}
Ok(())
}
fn dispatch_get_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("GET_PRIVATE rd={} a={} b={}", rd, a, b);
let obj_val = self.regs[a];
let Some(obj_ptr) = self.checked_object_ptr(obj_val, "private field access on non-object")? else {
return Ok(());
};
let private_key = self.private_key_from_reg(b);
let brand_reg = self.bytecode[self.pc] as usize;
let brand_key = make_private_name_id(self.bytecode[self.pc + 1]);
self.pc += 2;
let obj = unsafe { &*obj_ptr };
// 字段路径:own 有该私有槽(字段存实例 own)→ 直接读,不跨原型、不做 brand 值比较
// (PrivateFieldFind 只查 own;接收者带槽即构造器产物)。
if let Some(pos) = self.private_slot(obj, private_key) {
if let Some(meta) = obj.prop_meta_at(pos) {
if meta.is_accessor {
if meta.get.is_undefined() {
return self.raise_type_error("private field has no getter");
}
let getter = meta.get;
self.push_bytecode_getter_frame(getter, obj_val, rd as u8)?;
self.accessor_frame_target_reg.take();
return Ok(());
}
}
self.regs[rd] = obj.get_prop_at(pos);
return Ok(());
}
// 方法路径:own 无槽(私有方法/访问器存于 home=proto)→ 沿链找方法槽 + brand 检查。
let Some(home_ptr) = self.find_private_method_slot(obj, private_key) else {
return self.raise_type_error("private field brand check failed");
};
if brand_reg != 0 {
self.private_brand_check(obj, brand_reg, brand_key)?;
}
let home = unsafe { &*home_ptr };
let pos = self.private_slot(home, private_key).expect("slot just found");
if let Some(meta) = home.prop_meta_at(pos) {
if meta.is_accessor {
if meta.get.is_undefined() {
return self.raise_type_error("private field has no getter");
}
let getter = meta.get;
self.push_bytecode_getter_frame(getter, obj_val, rd as u8)?;
// 与 dispatch_get_prop 一致:getter 帧入栈后消费 accessor_frame_target_reg,
// 避免残留状态污染后续属性访问(getter 返回经 continuation 写 rd)。
self.accessor_frame_target_reg.take();
return Ok(());
}
}
self.regs[rd] = home.get_prop_at(pos);
Ok(())
}
fn dispatch_set_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("SET_PRIVATE rd={} a={} b={}", rd, a, b);
let obj_val = self.regs[rd];
let Some(obj_ptr) = self.checked_object_ptr(obj_val, "private field assignment on non-object")? else {
return Ok(());
};
let private_key = self.private_key_from_reg(b);
let brand_reg = self.bytecode[self.pc] as usize;
let brand_key = make_private_name_id(self.bytecode[self.pc + 1]);
self.pc += 2;
let obj = unsafe { &*obj_ptr };
// 字段路径:own 有槽(字段存实例 own)→ 直接写,不跨原型、不做 brand 值比较。
if let Some(pos) = self.private_slot(obj, private_key) {
if let Some(meta) = obj.prop_meta_at(pos) {
if meta.is_accessor {
if meta.set.is_undefined() {
return self.raise_type_error("private field has no setter");
}
let setter = meta.set;
let value = self.regs[a];
return self.call_or_push_setter(setter, obj_val, value, true);
}
// 私有方法槽不可写(init 时打标记)。
if meta.is_hole() {
return self
.raise_type_error("Cannot write private member to an object whose class did not declare it");
}
}
let value = self.regs[a];
let obj = unsafe { &mut *obj_ptr };
obj.set_prop_shape(pos, value);
return Ok(());
}
// 方法路径:own 无槽(私有方法/访问器存于 home=proto)→ 沿链找方法槽 + brand 检查。
let Some(home_ptr) = self.find_private_method_slot(obj, private_key) else {
return self.raise_type_error("private field brand check failed");
};
if brand_reg != 0 {
self.private_brand_check(obj, brand_reg, brand_key)?;
}
let home = unsafe { &*home_ptr };
let pos = self.private_slot(home, private_key).expect("slot just found");
if let Some(meta) = home.prop_meta_at(pos) {
if meta.is_accessor {
if meta.set.is_undefined() {
return self.raise_type_error("private field has no setter");
}
let setter = meta.set;
let value = self.regs[a];
return self.call_or_push_setter(setter, obj_val, value, true);
}
// 私有方法槽不可写(init 时打标记)。
if meta.is_hole() {
return self
.raise_type_error("Cannot write private member to an object whose class did not declare it");
}
}
let value = self.regs[a];
let home = unsafe { &mut *home_ptr };
home.set_prop_shape(pos, value);
Ok(())
}
fn dispatch_init_private(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("INIT_PRIVATE rd={} a={} b={}", rd, a, b);
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "private field initialization on non-object")?
else {
return Ok(());
};
let private_key = self.private_key_from_reg(b);
let is_method = self.bytecode[self.pc] != 0;
self.pc += 1;
let value = self.regs[a];
let obj = unsafe { &mut *obj_ptr };
// 规范 PrivateFieldAdd 步 1:对象不可扩展时拒加私有字段(模块命名空间
// 等 exotic 对象创建起即 non-extensible)。
if !obj.is_extensible() {
return self.raise_type_error("Cannot add private field to a non-extensible object");
}
if self
.kernel_core
.shape_forge()
.lookup_position(obj.shape_id(), private_key)
.is_some()
{
// 私有元素重复初始化(构造器返回外部对象、同一对象再次初始化):
// 规范 PrivateFieldAdd 对已存在的私有名抛 TypeError。
return self.raise_type_error("Cannot initialize private element twice");
}
self.set_or_create_prop_value(obj, private_key, value);
if is_method {
let pos = self
.kernel_core
.shape_forge()
.lookup_position(obj.shape_id(), private_key)
.expect("private slot just created");
obj.set_private_method_meta(pos);
}
Ok(())
}
fn dispatch_private_brand_in(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("PRIVATE_BRAND_IN rd={} a={} b={}", rd, a, b);
let obj_val = self.regs[a];
if !obj_val.is_object() {
// `#x in rval`:rval 非对象抛 TypeError(规范 PrivateFieldIn 步骤 4)。
return self.raise_type_error("private field `in` requires an object on the right-hand side");
}
let obj = unsafe { &*obj_val.as_js_object_ptr() };
let private_key = self.private_key_from_reg(b);
let brand_reg = self.bytecode[self.pc] as usize;
let brand_key = make_private_name_id(self.bytecode[self.pc + 1]);
self.pc += 2;
// 只查 own 私有元素(PrivateFieldIn 不跨原型链):own 有该私有槽(字段)→ true;
// 否则接收者 own 有当前类 brand 槽(方法/访问器,槽在 home)→ true;否则 false。
// brand_key 是类级私有名 id(每类独立),own 存在该槽即本类实例——存在性即可
// 判定,不做 cell 值比较(规避 upvalue cell 生命周期缺陷)。
if self.private_slot(obj, private_key).is_some() {
self.regs[rd] = JsValue::bool(true);
return Ok(());
}
let has_brand = brand_reg != 0 && self.private_slot(obj, brand_key).is_some();
self.regs[rd] = JsValue::bool(has_brand);
Ok(())
}
fn dispatch_ic_get_prop(&mut self, a: usize, b: usize) -> Result<(), String> {
let val = self.regs[a];
let obj_ptr = val.as_object_ptr() as *mut JsObject;
if obj_ptr.is_null() {
// 入口快判:键寄存器恒为 perm 串,指针相等即键为 "length";
// 字符串接收者直返码元长度,免键解析(hash64 + DashMap + RwLock)。
let key_val = self.regs[b];
if key_val.is_string() && key_val.as_string_ptr() == self.length_perm_ptr && val.is_string() {
// SAFETY: val 是字符串值。
let len = unsafe { (*val.as_string_ptr()).utf16_len() };
self.regs[a] = JsValue::int(len as i32);
self.pc += IC_EXT_WORDS;
return Ok(());
}
let prop_name_si = self.property_key_si(self.regs[b])?;
if let Some(resolved) = self.primitive_property_get(val, prop_name_si)? {
self.regs[a] = resolved;
self.pc += IC_EXT_WORDS;
return Ok(());
}
self.raise_type_error("IC_GET_PROP on non-object")?;
return Ok(());
}
let addr = obj_ptr as usize;
if addr < 0x10000 || addr % std::mem::align_of::<JsObject>() != 0 {
self.raise_type_error("IC_GET_PROP on non-object")?;
return Ok(());
}
let obj = unsafe { &*obj_ptr };
// 入口快判:键寄存器恒为 perm 串,指针相等即键为 "length";
// 数组接收者直返逻辑长度,免 IC 读与键解析(与虚拟长度槽口径一致)。
let key_val = self.regs[b];
if key_val.is_string() && key_val.as_string_ptr() == self.length_perm_ptr && obj.is_array() {
self.regs[a] = obj.logical_len_value();
self.pc += IC_EXT_WORDS;
return Ok(());
}
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_slot0(&self.bytecode, &mut self.pc);
let ic_pc = self.pc;
if obj.has_prop_meta() {
// meta 对象整对象走慢路径,此时才解析键。
let prop_name_si = self.property_key_si(self.regs[b])?;
let val = self.ordinary_get_with_target(obj, prop_name_si, val, a as u8)?;
if self.accessor_frame_target_reg.take().is_none() {
self.regs[a] = val;
}
return Ok(());
}
if let Some(value) = ic_get_hit(obj, cached_shape_id, cached_slot, cached_depth) {
// 槽 0 单态命中:只比对 shape_id,不解析键。
self.regs[a] = value;
self.profiling.record_ic_hit();
ic_trace!("IC_GET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth);
} else if let Some(value) = ic_helper::ic_get_hit_poly(obj, &self.bytecode, ic_pc - IC_EXT_WORDS) {
// 多态槽(1..3)命中。
self.regs[a] = value;
self.profiling.record_ic_hit();
ic_trace!("IC_GET poly hit pc={} shape={}", ic_pc, obj.shape_id());
} else {
// miss 分支才解析键(IC 站点键恒为编译期字符串常量,延后解析无 ToPrimitive 副作用)。
let prop_name_si = self.property_key_si(self.regs[b])?;
self.profiling.record_ic_miss();
prop_cache_miss();
// 数组 length 虚拟槽:Array.prototype 无 own length,写回恒落空;
// 直接学入标记并读逻辑长度,跳过模板查 / ordinary_get / proto 空跑。
if obj.is_array() && prop_name_si == self.length_si {
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), VIRTUAL_LENGTH_SLOT, 0);
ic_debug!("IC_GET virtual length hit shape={}", obj.shape_id());
self.regs[a] = obj.logical_len_value();
return Ok(());
}
// 模板快路径:prop_forge 只缓存每 shape 最后新增的属性,恰好覆盖该属性时直取槽位。
if let Some(template) = self.kernel_core.prop_forge().get_template(obj.shape_id()) {
if template.prop_name == prop_name_si && template.position < obj.prop_vec_len() as u32 {
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), template.position, 0);
ic_debug!(
"IC_GET propforge hit shape={} prop={} slot={}",
obj.shape_id(),
prop_name_si,
template.position
);
self.regs[a] = obj.get_prop_shape(template.position);
} else {
// 模板不覆盖该属性(读非末位属性):走通用 miss 路径并写回,
// 否则该 shape 的缓存条目永远学不到。
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
self.regs[a] = self.proto_chain_ic_get(obj, prop_name_si, val)?;
}
} else {
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
let resolved = self.ordinary_get(obj, prop_name_si, val)?;
// 先试自身属性(快路径,depth=0)。
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
if !obj.is_accessor_meta(pos) {
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), pos, 0);
ic_debug!("IC_GET write-back own shape={} slot={}", obj.shape_id(), pos);
}
} else {
// 沿原型链查找真正拥有该属性的对象。
let mut cursor = obj.proto().as_js_object_ptr();
let mut depth = 1u8;
while !cursor.is_null() {
let co_ref = unsafe { &*cursor };
if let Some(pos) =
self.kernel_core.shape_forge().lookup_position(co_ref.shape_id(), prop_name_si)
{
if !co_ref.is_accessor_meta(pos) {
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, co_ref.shape_id(), pos, depth);
ic_debug!(
"IC_GET write-back proto shape={} slot={} depth={}",
co_ref.shape_id(),
pos,
depth
);
}
break;
}
if !co_ref.proto().is_object() {
break;
}
cursor = co_ref.proto().as_js_object_ptr();
depth += 1;
}
}
self.regs[a] = resolved;
}
}
Ok(())
}
fn dispatch_ic_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
let Some(obj_ptr) = self.set_prop_base(self.regs[rd])? else {
return Ok(());
};
// read_ic_slot0 先消费全部扩展字,命中路径无需解析键。
let (cached_shape_id, cached_slot, cached_depth) = ic_helper::read_ic_slot0(&self.bytecode, &mut self.pc);
let ic_pc = self.pc;
let value = self.regs[a];
let receiver = self.regs[rd];
let obj = unsafe { &mut *obj_ptr };
// 命中路径零键零拦截:__proto__ 赋值从不写 IC,缓存槽必为数据槽。
if !obj.has_prop_meta()
&& (ic_set_hit(obj, cached_shape_id, cached_slot, cached_depth, value)
|| ic_helper::ic_set_hit_poly(obj, &self.bytecode, ic_pc - IC_EXT_WORDS, value))
{
self.profiling.record_ic_hit();
ic_trace!("IC_SET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth);
// IC 命中快路径防御:今日全局对象带 meta 不可达,防 meta 不变量
// 被未来优化破坏后镜像静默失步(键由 (shape, slot) 反查)。
self.sync_global_builtin_mirror_slot(obj, cached_shape_id, cached_slot, cached_depth, value);
return Ok(());
}
// 慢路径(has_prop_meta / miss)才解析键并拦截 __proto__ 赋值,须先于写槽,
// 防 __proto__ 落入数据槽。
let prop_name_si = self.property_key_si(self.regs[b])?;
if self.kernel_core.perm_interner().lookup(prop_name_si) == Some("__proto__") {
if self.is_object_prototype(obj_ptr) && !value.is_null() {
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
return Ok(());
}
obj.set_proto(value).map_err(|e| e.to_string())?;
return Ok(());
}
if obj.has_prop_meta() {
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver, self.current_strict())?;
return Ok(());
}
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
self.profiling.record_ic_miss();
prop_cache_miss();
obj.set_prop_shape(pos, value);
self.sync_global_builtin_mirror(obj, prop_name_si, value);
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), pos, 0);
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
} else {
self.profiling.record_ic_miss();
prop_cache_miss();
// 写新属性(shape 转换):数组 length / 整数索引键仍走完整
// ordinary_set_dispatch 语义,其余走 CreateDataProperty 快路径。
if self.named_prop_create_needs_ordinary_set(obj, prop_name_si) {
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver, self.current_strict())?;
return Ok(());
}
self.create_named_prop_fast(
obj,
prop_name_si,
value,
receiver,
ic_pc,
SetContract::dispatch(self.current_strict()),
)?;
}
Ok(())
}
fn dispatch_get_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("GET_PROP rd={} a={} b={}", rd, a, b);
let prop_name_si = self.property_key_si(self.regs[b])?;
if let Some(value) = self.primitive_property_get(self.regs[rd], prop_name_si)? {
self.regs[a] = value;
return Ok(());
}
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "GET_PROP on non-object")? else {
return Ok(());
};
let obj = unsafe { &*obj_ptr };
let val = self.ordinary_get_with_target(obj, prop_name_si, self.regs[rd], a as u8)?;
if self.accessor_frame_target_reg.take().is_none() {
self.regs[a] = val;
}
Ok(())
}
fn dispatch_set_prop(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("SET_PROP rd={} a={} b={}", rd, a, b);
let Some(obj_ptr) = self.set_prop_base(self.regs[rd])? else {
return Ok(());
};
let prop_name_si = self.property_key_si(self.regs[b])?;
if self.kernel_core.perm_interner().lookup(prop_name_si) == Some("__proto__") {
let proto_value = self.regs[a];
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
return Ok(());
}
let obj = unsafe { &mut *obj_ptr };
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
return Ok(());
}
let value = self.regs[a];
let obj = unsafe { &mut *obj_ptr };
self.ordinary_set_dispatch(obj, prop_name_si, value, self.regs[rd], self.current_strict())?;
Ok(())
}
/// 对象字面量批量构造的纯槽写:把值写入键序预建 shape 的 `slot` 槽。
/// 不做键解析、`__proto__` 拦截或任何形状变更——键与槽位由
/// NEW_OBJECT 的键常量表一次性建好。
///
/// # 边界与前提
/// - `slot` 必须 < NEW_OBJECT 预分配的槽数(emit 保证 ≤255)。
fn dispatch_set_prop_batch(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("SET_PROP_BATCH rd={} slot={}", rd, b);
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
return Ok(());
};
let value = self.regs[a];
let obj = unsafe { &mut *obj_ptr };
obj.set_prop_storage(b, value);
Ok(())
}
fn dispatch_get_prop_dynamic(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("GET_PROP_DYNAMIC rd={} a={} b={}", rd, a, b);
let prop_name_si = self.property_key_si(self.regs[a])?;
let receiver = self.regs[rd];
// 数组元素 fast path:整数键 + 真数组 + 无元素 meta(无 hole/accessor/描述符)
// + 界内 → 直读元素区,免 primitive_property_get / checked_object_ptr /
// ordinary_get 调用链。判定条件与 ordinary_get_inner 数组分支严格等价。
// 非对象 receiver(如 `"abc"[0]`)须先 is_object 快检再 checked_object_ptr,
// 否则该检查会误抛 TypeError(原路径对原始值走 primitive_property_get 不抛)。
if is_int_key(prop_name_si) && receiver.is_object() {
if let Some(obj_ptr) = self.checked_object_ptr(receiver, "GET_PROP_DYNAMIC on non-object")? {
let obj = unsafe { &*obj_ptr };
if obj.is_array() && obj.array_elements_meta_vec().is_none() {
let idx = int_key_value(prop_name_si);
if idx < obj.array_prop_count {
self.regs[b] = obj.get_prop_at(idx);
return Ok(());
}
}
}
}
if let Some(value) = self.primitive_property_get(receiver, prop_name_si)? {
self.regs[b] = value;
return Ok(());
}
let Some(obj_ptr) = self.checked_object_ptr(receiver, "GET_PROP_DYNAMIC on non-object")? else {
return Ok(());
};
let obj = unsafe { &*obj_ptr };
let val = self.ordinary_get_with_target(obj, prop_name_si, receiver, b as u8)?;
if self.accessor_frame_target_reg.take().is_none() {
self.regs[b] = val;
}
Ok(())
}
fn dispatch_set_prop_dynamic(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("SET_PROP_DYNAMIC rd={} a={} b={}", rd, a, b);
let Some(obj_ptr) = self.set_prop_base(self.regs[rd])? else {
return Ok(());
};
let prop_name_si = self.property_key_si(self.regs[a])?;
// 数组元素 fast path:整数键 + 真数组 + 无 meta + 界内 → 直写元素区,
// 免 __proto__ 查询 / ordinary_set_dispatch / get_own_property_slot 调用链。
// 与 ordinary_set_inner 数组分支等价(界内写不触发 length 扩展、无 shadow
// 同步)。越界写必须走原路径(CreateDataProperty + length 扩展),绝不可
// 直写——set_prop_storage 对越界下标会落到 hash_props 属性区。
if is_int_key(prop_name_si) {
let obj = unsafe { &*obj_ptr };
if obj.is_array() && obj.array_elements_meta_vec().is_none() {
let idx = int_key_value(prop_name_si);
if idx < obj.array_prop_count {
let value = self.regs[b];
let obj = unsafe { &mut *obj_ptr };
obj.set_prop_storage(idx as usize, value);
return Ok(());
}
}
}
// 整数键恒不可能等于 "__proto__"(字符串 intern id 远小于整数键区间),
// 短路免去每次 set 的 RwLock 查询。
if !is_int_key(prop_name_si) && self.kernel_core.perm_interner().lookup(prop_name_si) == Some("__proto__") {
let proto_value = self.regs[b];
if self.is_object_prototype(obj_ptr) && !proto_value.is_null() {
self.raise_type_error("Object.prototype.__proto__ is immutable")?;
return Ok(());
}
let obj = unsafe { &mut *obj_ptr };
obj.set_proto(proto_value).map_err(|e| e.to_string())?;
return Ok(());
}
let value = self.regs[b];
let obj = unsafe { &mut *obj_ptr };
self.ordinary_set_dispatch(obj, prop_name_si, value, self.regs[rd], self.current_strict())?;
Ok(())
}
fn dispatch_set_elem(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("SET_ELEM rd={} a={} b={}", rd, a, b);
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "Cannot create property on non-object")? else {
return Ok(());
};
let idx = if self.regs[a].is_int() {
self.regs[a].as_int().max(0) as u32
} else if self.regs[a].is_double() {
self.regs[a].as_double().max(0.0) as u32
} else {
0
};
let value = self.regs[b];
let obj = unsafe { &mut *obj_ptr };
// 有属性 meta(freeze/seal 逐属性写 meta 后恒 true,含 accessor 元素/描述符
// 元素):回落 ordinary_set 走完整 writable/accessor/extensible 检查,防止
// 直写绕过冻结语义。
if obj.has_prop_meta() {
let si = make_int_key(idx);
return self.ordinary_set_dispatch(obj, si, value, self.regs[rd], self.current_strict());
}
// 无 meta 快路径:不可扩展对象禁止新增元素(越界下标);已有元素直写不受限
// (无 meta 即默认可写)。
if !obj.is_extensible() && idx >= obj.array_prop_count {
self.raise_type_error("object is not extensible")?;
return Ok(());
}
// 越界索引写 length 不可写时失败(与 ordinary_set_inner 同序):SET_ELEM 当前
// 仅对 fresh 数组发射不可达,防未来 codegen 对非 fresh 数组发射时绕过守卫。
if idx >= obj.logical_len() && !obj.is_length_writable() {
if self.current_strict() {
self.raise_type_error("Cannot add property, array length is not writable")?;
}
return Ok(());
}
obj.set_prop_at(idx, value);
Ok(())
}
}
impl Vm {
pub(crate) fn dispatch_delete_prop_static(&mut self, rd: usize) -> Result<bool, String> {
vm_trace!("DELETE_PROP_STATIC rd={}", rd);
let prop_idx = self.bytecode[self.pc] as usize;
self.pc += 1;
let key_val = self.immutables().get(prop_idx).copied().unwrap_or_else(JsValue::undefined);
let prop_name_si = self.property_key_si(key_val)?;
if !self.regs[rd].is_object() {
// 非对象基:ToObject 基求值——null/undefined 抛 TypeError,其余原始
// 基装箱后按自身属性面判删除成败。
return self.delete_prop_non_object_base(self.regs[rd], rd, prop_name_si);
}
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
return Ok(false);
};
let obj = unsafe { &mut *obj_ptr };
// 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);严格模式分派见
// finish_member_delete。
let outcome = oxide_builtins::object::delete_own_property_outcome(self, obj, prop_name_si);
match outcome {
// deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值重抛。
Err(exc) => self.raise_captured(exc).map(|_| true),
Ok(outcome) => self.finish_member_delete(rd, outcome),
}
}
pub(crate) fn dispatch_delete_prop_dynamic(&mut self, rd: usize, b: usize) -> Result<bool, String> {
vm_trace!("DELETE_PROP_DYNAMIC rd={}", rd);
let prop_name_si = self.property_key_si(self.regs[b])?;
if !self.regs[rd].is_object() {
// 非对象基:ToObject 基求值——null/undefined 抛 TypeError,其余原始
// 基装箱后按自身属性面判删除成败。
return self.delete_prop_non_object_base(self.regs[rd], rd, prop_name_si);
}
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
return Ok(false);
};
let obj = unsafe { &mut *obj_ptr };
// 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);严格模式分派见
// finish_member_delete。
let outcome = oxide_builtins::object::delete_own_property_outcome(self, obj, prop_name_si);
match outcome {
// deferred namespace 求值触发失败(cyclic / 失败缓存重抛)原值重抛。
Err(exc) => self.raise_captured(exc).map(|_| true),
Ok(outcome) => self.finish_member_delete(rd, outcome),
}
}
/// 成员形 delete 的结果分派:不可配置属性在严格模式抛 TypeError(规范要求
/// delete 运算符对 [[Delete]] 返回 false 的引用抛错),其余情形把「是否删除
/// 成功」写入 `rd`。
///
/// # 步骤
/// 1. 不可配置且当前上下文严格:抛 TypeError,返回 `Ok(true)` 令调用方重取 pc
/// 2. 否则按三态投影布尔结果写 `rd`(`Missing`/`Deleted` 为 true)
///
/// # 边界与前提
/// - 严格性取 `current_strict()`(覆盖帧栈顶 / inline 基线 / 顶层脚本三形态)
///
/// # 副作用
/// - 抛错路径经 `unwind` 改写 pc / 异常通道;成功路径写 `regs[rd]`
fn finish_member_delete(
&mut self, rd: usize, outcome: oxide_builtins::object::DeleteOutcome,
) -> Result<bool, String> {
if outcome == oxide_builtins::object::DeleteOutcome::NonConfigurable && self.current_strict() {
self.raise_type_error("Cannot delete property")?;
return Ok(true);
}
self.regs[rd] = JsValue::bool(outcome != oxide_builtins::object::DeleteOutcome::NonConfigurable);
Ok(false)
}
/// 删除全局内置数据属性(delete 标识符):ext 字 = 键常量池下标(u16),
/// `rd` = 布尔结果槽,`slot` = 内置镜像槽寄存器(0 = 无槽)。全局对象由
/// session 解析——函数体内 delete 不依赖 this 寄存器。
///
/// # 步骤
/// 1. 键常量 → intern si → 对全局对象走共享删除逻辑(不可配置 → false 且属性
/// 保留;缺失 → true;可配置 → 真删并返回 true)
/// 2. 删除成功且 a 槽为有效镜像槽时槽写 undefined
///
/// # 副作用
/// - 删除成功时修改全局对象 shape/属性表/generation
/// - 删除成功时写镜像槽寄存器(该槽随后裸读回 undefined)
///
/// # 边界与前提
/// - 键常量必须是字符串(emit 侧保证);非字符串按错误返回
/// - 镜像槽仅在 run/帧入口重载,不清槽则删除后裸读回入口预载的旧值——
/// 清槽与真删同生共死:删除失败(c:false)属性保留,槽值不动
pub(crate) fn dispatch_delete_global_prop_c(&mut self, rd: usize, slot: usize, key_idx: u16) -> Result<(), String> {
vm_trace!("DELETE_GLOBAL_PROP_C slot={} idx={}", slot, key_idx);
let idx = key_idx as usize;
let key_val = self.immutables().get(idx).copied().unwrap_or(JsValue::undefined());
if !key_val.is_string() {
return Err(format!("DELETE_GLOBAL_PROP_C constant index {idx} is not a string key"));
}
let si = self.property_key_si(key_val)?;
// 全局词法环境绑定(顶层 let/const)不可删除:镜像命中恒 false 且属性保留。
{
let session = self.realm.session.borrow();
if self.resolve_property(session.global_lexical(), si).is_some() {
self.regs[rd] = JsValue::bool(false);
return Ok(());
}
}
let global_ptr = self.realm.session.borrow().global_object().as_ptr() as *mut JsObject;
// SAFETY: 全局对象钉在 session 永久区,指针在 VM 生命周期内有效。
let obj = unsafe { &mut *global_ptr };
// 统一走共享删除逻辑(与 Reflect.deleteProperty 同源);不可配置返回
// false 且属性保留。全局对象非 deferred namespace,触发恒 no-op。
let deleted = match oxide_builtins::object::delete_own_property(self, obj, si) {
Ok(d) => d,
Err(exc) => return self.raise_captured(exc),
};
// 仅删除成功才清镜像槽(无槽 0 不写,reg 0 是返回槽不可误写)。
if deleted && slot > 0 {
self.regs[slot] = JsValue::undefined();
}
self.regs[rd] = JsValue::bool(deleted);
Ok(())
}
}