Files
OxideJS-New/crates/oxide_vm/src/ic_helper.rs
T

447 lines
18 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.
//! inline cache(IC)字节码流辅助函数。
//!
//! 汇聚全部 IC 读写/清零逻辑——属性分发与成员更新 handler 都经由此处,IC 格式
//! 变更(如未来的 side-table 迁移)只需改这一个文件。
//!
//! IC 项格式(操作码后 [`IC_EXT_WORDS`] 个扩展字 = [`IC_SLOTS`] 组二元组,槽 0 最前):
//! ext[i*2+0] = shape_id(低 24 位)| proto_depth(高 8 位,0 = 接收者自身属性)
//! ext[i*2+1] = slot_index(32 位)
//!
//! 槽位约定:
//! - `shape_id == 0` 恒为空槽标记(合法 shape_id 从 [`EMPTY_SHAPE_ID`] = 1 起)。
//! - miss 写回 FIFO 滚动:新条目进槽 0、原槽 0..2 顺移到槽 1..3、槽 3 丢弃,
//! 空槽恒在尾部连续——命中遍历遇空槽即可 break。
//! - 命中路径零写:只读扩展字 + Cell 计数,不触发 bytecode COW。
//! - `slot_index == [`VIRTUAL_LENGTH_SLOT`]` 为数组虚拟长度标记,不对应任何实际槽,
//! 命中判定必须带 `is_array()` 守卫(`EMPTY_SHAPE_ID` 跨对象类型共享)。
use crate::vm_trace;
use oxide_bytecode::opcode::{self, ext_word_count, Instr, IC_EXT_WORDS, IC_SLOTS};
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
/// 数组虚拟长度标记槽:与真实槽空间不相交(`prop_vec_len` 远小于 `u32::MAX`),
/// 供 `arr.length` 读快路径使用——Array.prototype 无 own length 槽,写回恒落空,
/// miss 时直接学入本标记,命中时读逻辑长度。
pub(crate) const VIRTUAL_LENGTH_SLOT: u32 = u32::MAX;
/// 读取 `pc` 处 IC 指令槽 0 的三元组并把 `pc` 推进越过全部扩展字(单态快路径用)。
/// 槽编码:[shape_id(24)|proto_depth(8)] [slot(32)]。
pub(crate) fn read_ic_slot0(bytecode: &[Instr], pc: &mut usize) -> (u32, u32, u8) {
let ext0 = bytecode[*pc];
let ext1 = bytecode[*pc + 1];
*pc += IC_EXT_WORDS;
(ext0 & 0x00FF_FFFF, ext1, (ext0 >> 24) as u8)
}
/// 把解析出的 `(shape_id, slot, proto_depth)` 以 FIFO 滚动写回 `pc - IC_EXT_WORDS`
/// 处的 IC 扩展字(位于当前指令之前):新条目进槽 0,原槽 0..2 顺移到槽 1..3,
/// 最老的槽 3 丢弃。IC 未命中并完成属性解析后调用。
pub(crate) fn write_ic_back(bytecode: &mut [Instr], pc: usize, shape_id: u32, slot_index: u32, proto_depth: u8) {
debug_assert!(pc >= IC_EXT_WORDS, "IC write-back requires {IC_EXT_WORDS} extension words before pc");
vm_trace!("write_ic_back: pc={} shape_id={} slot={} depth={}", pc, shape_id, slot_index, proto_depth);
let base = pc - IC_EXT_WORDS;
// 读旧字后 FIFO 滚动:新条目写槽 0,原槽 i 顺移到槽 i+1,最老槽丢弃。
let old = &bytecode[base..pc];
let mut new_words = [0u32; IC_EXT_WORDS];
new_words[0] = (shape_id & 0x00FF_FFFF) | ((proto_depth as u32) << 24);
new_words[1] = slot_index;
for i in 1..IC_SLOTS {
new_words[i * 2..(i + 1) * 2].copy_from_slice(&old[(i - 1) * 2..i * 2]);
}
bytecode[base..pc].copy_from_slice(&new_words);
}
/// 把流中所有 IC 扩展字清零,使全部缓存 shape 失效。
///
/// 逐指令按 opcode 的 ext 字数推进,避开变长 ext opcode(CALL_SPREAD/TEMPLATE_STR/
/// NEW_OBJECT 键表)导致的字节错位——错位会把非 IC 指令误当 IC 扩展字清零。
pub(crate) fn clear_ic_caches(bytecode: &mut [Instr]) {
let mut i = 0;
while i < bytecode.len() {
let op = opcode::opcode(bytecode[i]);
if op.has_ic_ext_words() && i + IC_EXT_WORDS < bytecode.len() {
for word in &mut bytecode[i + 1..=i + IC_EXT_WORDS] {
*word = 0;
}
}
i += 1 + ext_word_count(bytecode, i);
}
}
/// 沿原型链上溯 `proto_depth` 步,返回目标对象的裸指针(链不足时返回 null)。
/// `proto_depth` 与 IC 写回语义一致:1 = 接收者的直接原型(`obj.proto()`)。
#[inline(always)]
fn resolve_proto_target_raw(obj: *const JsObject, proto_depth: u8) -> *const JsObject {
if proto_depth == 0 {
return obj;
}
let obj_ref = unsafe { &*obj };
let mut cursor = obj_ref.proto();
for _ in 1..proto_depth {
if !cursor.is_object() {
return std::ptr::null();
}
let proto_ptr = cursor.as_js_object_ptr();
if proto_ptr.is_null() {
return std::ptr::null();
}
cursor = unsafe { (*proto_ptr).proto() };
}
if cursor.is_object() {
let ptr = cursor.as_js_object_ptr();
if !ptr.is_null() {
return ptr as *const JsObject;
}
}
std::ptr::null()
}
#[inline(always)]
pub(crate) fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32, proto_depth: u8) -> Option<JsValue> {
// 代理接收者恒 miss:命中路径直读代理自身属性槽会绕过 get trap。
if obj.is_proxy_obj() {
return None;
}
if shape_id == 0 {
return None;
}
if proto_depth == 0 {
// 虚拟长度标记:EMPTY_SHAPE_ID 跨对象类型共享,守卫必须带 is_array(),
// 防无属性普通对象(shape 1)误命中返回 0。
if obj.is_array() && obj.shape_id() == shape_id && slot_index == VIRTUAL_LENGTH_SLOT {
return Some(obj.logical_len_value());
}
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
let v = obj.get_prop_shape(slot_index);
return Some(v);
}
return None;
}
let target = resolve_proto_target_raw(obj as *const JsObject, proto_depth);
if target.is_null() {
return None;
}
let target_ref = unsafe { &*target };
// 链上目标为代理:直读自身槽绕过 get trap,恒 miss,落穿慢路径路由 trap。
if target_ref.is_proxy_obj() {
return None;
}
if target_ref.shape_id() == shape_id && slot_index < target_ref.prop_vec_len() as u32 {
Some(target_ref.get_prop_shape(slot_index))
} else {
None
}
}
/// 多态遍历判定:调用方槽 0 单态判定失败后,顺序检查槽 1..3。
/// 空槽(shape_id==0)恒在尾部(FIFO 滚动保证),遇空即停。
#[inline(never)]
pub(crate) fn ic_get_hit_poly(obj: &JsObject, bytecode: &[Instr], ext_pc: usize) -> Option<JsValue> {
// 代理接收者恒 miss:命中路径直读代理自身属性槽会绕过 get trap。
if obj.is_proxy_obj() {
return None;
}
for i in 1..IC_SLOTS {
let base = ext_pc + i * 2;
let shape_id = bytecode[base] & 0x00FF_FFFF;
if shape_id == 0 {
break;
}
let slot_index = bytecode[base + 1];
let proto_depth = (bytecode[base] >> 24) as u8;
if proto_depth == 0 {
if obj.is_array() && obj.shape_id() == shape_id && slot_index == VIRTUAL_LENGTH_SLOT {
return Some(obj.logical_len_value());
}
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
let v = obj.get_prop_shape(slot_index);
return Some(v);
}
continue;
}
let target = resolve_proto_target_raw(obj as *const JsObject, proto_depth);
if target.is_null() {
continue;
}
let target_ref = unsafe { &*target };
// 链上目标为代理:恒 miss,落穿慢路径路由 get trap。
if target_ref.is_proxy_obj() {
continue;
}
if target_ref.shape_id() == shape_id && slot_index < target_ref.prop_vec_len() as u32 {
return Some(target_ref.get_prop_shape(slot_index));
}
}
None
}
#[inline(always)]
pub(crate) fn ic_set_hit(obj: &mut JsObject, shape_id: u32, slot_index: u32, proto_depth: u8, value: JsValue) -> bool {
// 代理接收者恒 miss:命中路径直写代理自身属性槽会绕过 set trap。
if obj.is_proxy_obj() {
return false;
}
if shape_id == 0 {
return false;
}
if proto_depth == 0 {
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
obj.set_prop_shape(slot_index, value);
return true;
}
return false;
}
let target = resolve_proto_target_raw(obj as *const JsObject, proto_depth);
if target.is_null() {
return false;
}
let target_ref = unsafe { &*target };
// 链上目标为代理:直写自身槽绕过 set trap,恒 miss,落穿慢路径路由 trap。
if target_ref.is_proxy_obj() {
return false;
}
if target_ref.shape_id() == shape_id && slot_index < target_ref.prop_vec_len() as u32 {
unsafe { &mut *(target as *mut JsObject) }.set_prop_shape(slot_index, value);
true
} else {
false
}
}
/// 多态遍历判定:调用方槽 0 单态判定失败后,顺序检查槽 1..3。
/// 空槽(shape_id==0)恒在尾部(FIFO 滚动保证),遇空即停。
#[inline(never)]
pub(crate) fn ic_set_hit_poly(obj: &mut JsObject, bytecode: &[Instr], ext_pc: usize, value: JsValue) -> bool {
// 代理接收者恒 miss:命中路径直写代理自身属性槽会绕过 set trap。
if obj.is_proxy_obj() {
return false;
}
for i in 1..IC_SLOTS {
let base = ext_pc + i * 2;
let shape_id = bytecode[base] & 0x00FF_FFFF;
if shape_id == 0 {
break;
}
let slot_index = bytecode[base + 1];
let proto_depth = (bytecode[base] >> 24) as u8;
if proto_depth == 0 {
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
obj.set_prop_shape(slot_index, value);
return true;
}
continue;
}
let target = resolve_proto_target_raw(obj as *const JsObject, proto_depth);
if target.is_null() {
continue;
}
let target_ref = unsafe { &*target };
// 链上目标为代理:恒 miss,落穿慢路径路由 set trap。
if target_ref.is_proxy_obj() {
continue;
}
if target_ref.shape_id() == shape_id && slot_index < target_ref.prop_vec_len() as u32 {
unsafe { &mut *(target as *mut JsObject) }.set_prop_shape(slot_index, value);
return true;
}
}
false
}
/// member 复合写(读改写共用同一扩展字组)的写侧命中判定:顺序遍历 4 槽,只接受
/// depth==0 的数据槽并直写。
///
/// 读侧会把继承属性解析结果以 depth>0 条目写回同一扩展字组;写侧若命中这些条目
/// 会把新值直写原型对象(原型污染,规范要求 shadow 到接收者 own 属性)——因此
/// 原型条目一律跳过,落到慢路径走完整 [[Set]] 语义。
///
/// `pc` 为越过该 IC 指令全部扩展字后的位置(与 [`write_ic_back`] 同一约定),
/// 命中遍历从 `pc - IC_EXT_WORDS` 起的扩展字开始。
#[inline(always)]
pub(crate) fn ic_set_hit_own(obj: &mut JsObject, bytecode: &[Instr], pc: usize, value: JsValue) -> bool {
// 代理接收者恒 miss:命中路径直写代理自身属性槽会绕过 set trap。
if obj.is_proxy_obj() {
return false;
}
let base = pc - IC_EXT_WORDS;
let shape_id = bytecode[base] & 0x00FF_FFFF;
if shape_id == 0 {
return false;
}
let proto_depth = (bytecode[base] >> 24) as u8;
let slot_index = bytecode[base + 1];
if proto_depth == 0 && obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
obj.set_prop_shape(slot_index, value);
return true;
}
ic_set_hit_own_poly(obj, bytecode, base, value)
}
/// 写侧多态槽(1..3)遍历,depth==0 only;`inline(never)` 防主循环代码膨胀。
/// `ext_pc` 为扩展字起始位置(首个扩展字处)。
#[inline(never)]
fn ic_set_hit_own_poly(obj: &mut JsObject, bytecode: &[Instr], ext_pc: usize, value: JsValue) -> bool {
// 代理接收者恒 miss:命中路径直写代理自身属性槽会绕过 set trap。
if obj.is_proxy_obj() {
return false;
}
for i in 1..IC_SLOTS {
let base = ext_pc + i * 2;
let shape_id = bytecode[base] & 0x00FF_FFFF;
if shape_id == 0 {
break;
}
let proto_depth = (bytecode[base] >> 24) as u8;
if proto_depth != 0 {
continue;
}
let slot_index = bytecode[base + 1];
if obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
obj.set_prop_shape(slot_index, value);
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_bytecode::opcode::OpCode;
use oxide_types::shape::EMPTY_SHAPE_ID;
#[test]
fn concat_n_ext_word_count_advances_by_n() {
// CONCAT_N rd=1, a=2, ext=[3, 5, 9]:ext[0]=n=3,总 ext 字数 = 3
let mut bytecode = vec![opcode::encode(OpCode::CONCAT_N, 1, 2, 0), 3, 5, 9];
assert_eq!(ext_word_count(&bytecode, 0), 3, "ext 字数 = n");
// clear_ic_caches 跳过 CONCAT_N 扩展字,不误当 IC 字清零
bytecode.push(opcode::encode(OpCode::NOP, 0, 0, 0));
clear_ic_caches(&mut bytecode);
assert_eq!(bytecode[1..4], [3, 5, 9], "CONCAT_N ext 字保持原值");
assert_eq!(bytecode.len(), 5, "CONCAT_N 指令 + 3 ext + NOP");
}
#[test]
fn break_ret_ext_word_count_is_two() {
// BREAK/CONTINUE/RETURN 恒带 2 个逃出计数 ext 字:第一字
// pack_escape_counts(迭代器层数打包),第二字 dispose_count(资源条数)。
let bytecode = vec![
opcode::encode(OpCode::BREAK, 0, 0, 0),
0x0000_0040,
0x0000_0002,
opcode::encode(OpCode::RETURN, 1, 0, 0),
0x1234_5678,
0x0000_0003,
];
assert_eq!(ext_word_count(&bytecode, 0), 2, "BREAK ext 字数 = 2");
assert_eq!(ext_word_count(&bytecode, 3), 2, "RETURN ext 字数 = 2");
}
#[test]
fn get_template_object_ext_word_count_is_2_plus_2n() {
// GET_TEMPLATE_OBJECT:ext=[n, cooked, raw, ..., site_no] 共 2+2n 个扩展字
// (n=1 时 4 个字)。未登记时 clear_ic_caches 会把 ext 字当指令逐字扫描,
// 多跳/误清零导致后续 IC 扩展字漏清或误清,本用例可抓住该错位。
let mut bytecode = vec![
opcode::encode(OpCode::GET_TEMPLATE_OBJECT, 1, 0, 0),
1,
0x8000_0000,
0x0000_0005,
0x0000_0007,
opcode::encode(OpCode::IC_GET_PROP, 1, 2, 3),
0xAAAA_AAAA,
0xBBBB_BBBB,
0xCCCC_CCCC,
0xDDDD_DDDD,
0xEEEE_EEEE,
0xFFFF_FFFF,
0x1111_1111,
0x2222_2222,
];
assert_eq!(ext_word_count(&bytecode, 0), 4, "GET_TEMPLATE_OBJECT ext 字数 = 2+2n");
clear_ic_caches(&mut bytecode);
assert_eq!(
&bytecode[1..=4],
&[1, 0x8000_0000, 0x0000_0005, 0x0000_0007],
"GET_TEMPLATE_OBJECT ext 字保持原值"
);
assert_eq!(&bytecode[6..=13], &[0; 8], "后续 IC 扩展字被清零");
}
#[test]
fn clear_ic_caches_skips_break_ext_word() {
// BREAK 的逃出计数 ext 字(2 个:迭代器打包字 + dispose_count 字)必须原样
// 保留;第一字低 8 位 = for_of_count,取 0x40(CALL 指令号,带 ext)——未
// 登记时扫描会把它当 CALL 指令解析,越过后续 IC 指令导致扩展字漏清,本用例
// 可抓住该错位。
let mut bytecode = vec![
opcode::encode(OpCode::BREAK, 0, 0, 0),
0x0000_0040,
0x0000_0002,
opcode::encode(OpCode::IC_GET_PROP, 1, 2, 3),
0xAAAA_AAAA,
0xBBBB_BBBB,
0xCCCC_CCCC,
0xDDDD_DDDD,
0xEEEE_EEEE,
0xFFFF_FFFF,
0x1111_1111,
0x2222_2222,
];
clear_ic_caches(&mut bytecode);
assert_eq!(bytecode[1], 0x0000_0040, "BREAK 第一 ext 字保持原值");
assert_eq!(bytecode[2], 0x0000_0002, "BREAK 第二 ext 字保持原值");
assert_eq!(&bytecode[4..=11], &[0; 8], "后续 IC 扩展字被清零");
}
#[test]
fn write_ic_back_updates_slot_zero_ext_words() {
let mut bc = vec![0u32; IC_EXT_WORDS];
write_ic_back(bc.as_mut_slice(), IC_EXT_WORDS, 0x1234_5678, 7, 0);
assert_eq!(bc[0], 0x0034_5678);
assert_eq!(bc[1], 7);
}
#[test]
fn write_ic_back_rolls_fifo_and_drops_oldest_slot() {
// 预置 8 字四槽(2 字/槽):槽 0=(0xA1,1,0)、槽 1=(0xA2,2,1)、槽 2=(0xA3,3,0)、槽 3=(0xA4,4,0)。
let mut bc = vec![0u32; IC_EXT_WORDS];
bc[0] = 0xA1;
bc[1] = 1;
bc[2] = 0xA2 | (1 << 24);
bc[3] = 2;
bc[4] = 0xA3;
bc[5] = 3;
bc[6] = 0xA4;
bc[7] = 4;
write_ic_back(bc.as_mut_slice(), IC_EXT_WORDS, 0xB0, 9, 2);
// 新条目进槽 0;原槽 0..2 顺移到槽 1..3;最老槽 3 丢弃。
assert_eq!(bc[0], 0xB0 | (2 << 24));
assert_eq!(bc[1], 9);
assert_eq!(bc[2], 0xA1);
assert_eq!(bc[3], 1);
assert_eq!(bc[4], 0xA2 | (1 << 24));
assert_eq!(bc[5], 2);
assert_eq!(bc[6], 0xA3);
assert_eq!(bc[7], 3);
}
#[test]
fn ic_get_hit_virtual_length_marker_hits_array() {
// shape 1(空形状)数组经虚拟长度标记命中:返回逻辑长度。
let arr = JsObject::new_array(EMPTY_SHAPE_ID, JsValue::null(), 3);
assert_eq!(ic_get_hit(&arr, EMPTY_SHAPE_ID, VIRTUAL_LENGTH_SLOT, 0), Some(JsValue::int(3)));
}
#[test]
fn ic_get_hit_virtual_length_marker_rejects_non_array() {
// 同 shape 1 的普通对象(EMPTY_SHAPE_ID 跨对象类型共享)不得误命中虚拟长度标记。
let plain = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
assert_eq!(ic_get_hit(&plain, EMPTY_SHAPE_ID, VIRTUAL_LENGTH_SLOT, 0), None);
}
}