mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
fix: tagged template 语义修正——cooked undefined/this绑定/site缓存/冻结 + groupBy getter 异常传播 + defineGetter setter 保留
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::arc_with_non_send_sync)]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use num_traits::Zero;
|
||||
@@ -375,6 +375,7 @@ pub(crate) struct InlineSyncState {
|
||||
pub(crate) inline_args_base: u32,
|
||||
pub(crate) inline_args_count: u16,
|
||||
pub(crate) accessor_frame_target_reg: Option<u8>,
|
||||
pub(crate) active_flat_id: u32,
|
||||
}
|
||||
|
||||
/// 基于寄存器的 JS 虚拟机:持有执行状态、寄存器文件、调用栈与 session 内存。
|
||||
@@ -522,6 +523,15 @@ pub struct Vm {
|
||||
/// 分组保存 inline cache 与指令计数器。
|
||||
pub(crate) profiling: ProfilingState,
|
||||
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
|
||||
/// 标签模板对象缓存(GetTemplateObject):键 = (模块 flat_id, site 序号)。
|
||||
/// 同一编译树同 site 恒返回同一对象;每次 `run()` 清空(flat_id 复用防误命中)。
|
||||
/// 值为 GC 根(for_each_value/rewrite_values 遍历)。
|
||||
pub(crate) template_objects: HashMap<(u32, u32), JsValue>,
|
||||
/// 当前活动字节码所属模块的 flat_id(顶层 0;帧切换时随 bytecode 换)。
|
||||
/// GET_TEMPLATE_OBJECT 据其区分不同编译树(eval 每次编译独立 site)。
|
||||
pub(crate) active_flat_id: u32,
|
||||
/// 帧切换时暂存调用方 flat_id 的栈(与 saved_bytecode_stack 同步 push/pop)。
|
||||
pub(crate) saved_flat_id_stack: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Drop for Vm {
|
||||
@@ -791,6 +801,10 @@ impl Vm {
|
||||
f(self.async_context.unwrap_or(JsValue::undefined()));
|
||||
f(self.async_gen_context.unwrap_or(JsValue::undefined()));
|
||||
f(self.inline_callee.unwrap_or(JsValue::undefined()));
|
||||
// 标签模板对象缓存:命中的模板对象是 GC 根(未根 → sweep 搬移/回收悬垂)。
|
||||
for &cached in self.template_objects.values() {
|
||||
f(cached);
|
||||
}
|
||||
for &entry in &self.iters.for_of_iters {
|
||||
f(entry.iterator);
|
||||
f(entry.last_result);
|
||||
@@ -843,6 +857,9 @@ impl Vm {
|
||||
self.exception_value = self.exception_value.map(&mut rewrite);
|
||||
self.pending_exception = self.pending_exception.map(&mut rewrite);
|
||||
self.last_uncaught_value = self.last_uncaught_value.map(&mut rewrite);
|
||||
for cached in self.template_objects.values_mut() {
|
||||
*cached = rewrite(*cached);
|
||||
}
|
||||
self.pending_completion = self.pending_completion.map(|completion| match completion {
|
||||
Completion::Return {
|
||||
value,
|
||||
@@ -1393,6 +1410,9 @@ impl Vm {
|
||||
|
||||
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
||||
self.saved_immutables_stack.push(self.active_immutables);
|
||||
// 记录调用方 flat_id,进入被调模块(标签模板 site 缓存按模块隔离)。
|
||||
self.saved_flat_id_stack.push(self.active_flat_id);
|
||||
self.active_flat_id = sub_idx as u32;
|
||||
|
||||
let function_name = self.sub_modules[sub_idx]
|
||||
.function_name
|
||||
@@ -1889,6 +1909,10 @@ impl Vm {
|
||||
self.dispatch_template_str(rd)?;
|
||||
}
|
||||
|
||||
OpCode::GET_TEMPLATE_OBJECT => {
|
||||
self.dispatch_get_template_object(rd)?;
|
||||
}
|
||||
|
||||
OpCode::DELETE_PROP_STATIC => match self.dispatch_delete_prop_static(rd) {
|
||||
Ok(true) => continue,
|
||||
Ok(false) => {}
|
||||
|
||||
@@ -1013,4 +1013,103 @@ impl Vm {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// GET_TEMPLATE_OBJECT:GetTemplateObject 语义——按 (模块 flat_id, site 序号)
|
||||
/// 查缓存,未命中则构建模板对象(cooked/raw 数组 + raw 属性)并冻结。
|
||||
///
|
||||
/// # ext 布局(紧跟指令后的扩展字)
|
||||
/// - `n`:quasis 段数;
|
||||
/// - 每段两个字:cooked 字(高位 `0x8000_0000` 标记非法转义 → undefined,
|
||||
/// 低 31 位为常量池下标)、raw 字(常量池下标);
|
||||
/// - 末尾:site 序号。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 首次构建时分配 cooked/raw 数组并写入 `template_objects` 缓存(GC 根);
|
||||
/// 缓存命中时零分配。
|
||||
pub(crate) fn dispatch_get_template_object(&mut self, rd: usize) -> Result<(), String> {
|
||||
vm_trace!("GET_TEMPLATE_OBJECT rd={}", rd);
|
||||
let n = self.bytecode[self.pc] as usize;
|
||||
self.pc += 1;
|
||||
let mut cooked_words = Vec::with_capacity(n);
|
||||
let mut raw_idxs = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
cooked_words.push(self.bytecode[self.pc]);
|
||||
self.pc += 1;
|
||||
raw_idxs.push(self.bytecode[self.pc]);
|
||||
self.pc += 1;
|
||||
}
|
||||
let site_no = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
|
||||
let key = (self.active_flat_id, site_no);
|
||||
if let Some(&cached) = self.template_objects.get(&key) {
|
||||
self.regs[rd] = cached;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let proto_ptr = self.session.builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let proto_val = JsValue::from_js_object(proto_ptr);
|
||||
// 模板对象直接分配为 session 对象:跨 epoch 写入(存进 global/数组)时
|
||||
// 免 promote 搬移——若按 epoch 分配,首次写入 session 根会把对象搬到新
|
||||
// 地址,缓存中的旧指针与新实例分叉,同一 site 两次取值将返回不同对象。
|
||||
let mut alloc_session_array = |n: usize| {
|
||||
let mut clone = JsObject::new_array(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, proto_val, n, self.epoch.bump());
|
||||
// 标记 session 归属:session_epoch 分配的对象须显式置位,GC/释放路径
|
||||
// 据 SESSION_EPOCH_BIT 判定归属(与 promote_object 的 clone 路径一致)。
|
||||
clone.set_session_epoch(true);
|
||||
let ptr = self.gc_state.session_epoch.alloc(clone) as *mut JsObject;
|
||||
self.gc_state.session_object_ptrs.push(ptr);
|
||||
ptr
|
||||
};
|
||||
let cooked = alloc_session_array(n);
|
||||
let raw = alloc_session_array(n);
|
||||
let cooked_val = JsValue::from_js_object(cooked);
|
||||
let raw_val = JsValue::from_js_object(raw);
|
||||
|
||||
// 元素:可枚举、不可写、不可配置(模板对象属性恒只读,禁止改写元素)。
|
||||
let elem_attrs = PropAttributes::new(false, true, false);
|
||||
for i in 0..n {
|
||||
let (c_val, r_val) = {
|
||||
// immutables 借用限于本块:promote 需 &mut self,先取值再放行借用。
|
||||
let imm = self.immutables();
|
||||
let c_val = if cooked_words[i] & 0x8000_0000 != 0 {
|
||||
JsValue::undefined()
|
||||
} else {
|
||||
imm.get(cooked_words[i] as usize).copied().unwrap_or(JsValue::undefined())
|
||||
};
|
||||
let r_val = imm.get(raw_idxs[i] as usize).copied().unwrap_or(JsValue::undefined());
|
||||
(c_val, r_val)
|
||||
};
|
||||
let c_promoted = self.promote_if_needed_for_write_ptr(cooked, c_val);
|
||||
let r_promoted = self.promote_if_needed_for_write_ptr(raw, r_val);
|
||||
// SAFETY: cooked/raw 为本函数刚分配的 epoch 对象,借用仅在本循环内消费。
|
||||
unsafe {
|
||||
(*cooked).set_prop_at(i, c_promoted);
|
||||
(*cooked).set_data_meta(i, elem_attrs);
|
||||
(*raw).set_prop_at(i, r_promoted);
|
||||
(*raw).set_data_meta(i, elem_attrs);
|
||||
}
|
||||
}
|
||||
|
||||
// raw 属性:不可枚举、不可写、不可配置(规范 desc 无 writable/enumerable
|
||||
// 键,DefinePropertyOrThrow 默认 false)。先于冻结定义(冻结后不可扩展,
|
||||
// 新增属性会被拒)。
|
||||
let raw_si = self.kernel_core.perm_interner().intern("raw").0;
|
||||
// SAFETY: cooked 为本函数分配的 epoch 对象,借用仅在本次 define 内消费。
|
||||
self.define_data_property(unsafe { &mut *cooked }, raw_si, raw_val, PropAttributes::new(false, false, false))?;
|
||||
|
||||
// 冻结两个数组:不可扩展 + length writable=false(is_frozen 使 length
|
||||
// 赋值与元素写入经 ordinary_set 拒绝);length {e:false,w:false,c:false}
|
||||
// 由 getOwnPropertyDescriptor 的数组 length 特判表达。
|
||||
// SAFETY: cooked/raw 为本函数分配的 session 对象。
|
||||
unsafe {
|
||||
(*cooked).set_frozen(true);
|
||||
(*cooked).set_extensible(false);
|
||||
(*raw).set_frozen(true);
|
||||
(*raw).set_extensible(false);
|
||||
}
|
||||
self.template_objects.insert(key, cooked_val);
|
||||
self.regs[rd] = cooked_val;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,9 @@ macro_rules! inline_save_field {
|
||||
($recv:ident, $window_regs:ident, accessor_frame_target_reg, copy) => {
|
||||
$recv.accessor_frame_target_reg
|
||||
};
|
||||
($recv:ident, $window_regs:ident, active_flat_id, copy) => {
|
||||
$recv.active_flat_id
|
||||
};
|
||||
}
|
||||
|
||||
/// restore 方向的字段写回语句。`regs` 只回拷窗口并把缓冲归还池。
|
||||
@@ -189,6 +192,9 @@ macro_rules! inline_restore_field {
|
||||
($recv:ident, $saved:ident, accessor_frame_target_reg, copy) => {
|
||||
$recv.accessor_frame_target_reg = $saved.accessor_frame_target_reg
|
||||
};
|
||||
($recv:ident, $saved:ident, active_flat_id, copy) => {
|
||||
$recv.active_flat_id = $saved.active_flat_id
|
||||
};
|
||||
}
|
||||
|
||||
/// 内联同步调用可搬移执行核心字段的单一登记表。save/restore 双向由本宏展开;
|
||||
@@ -225,6 +231,7 @@ macro_rules! inline_core_fields {
|
||||
(inline_args_base, copy), // M
|
||||
(inline_args_count, copy), // M
|
||||
(accessor_frame_target_reg, copy), // M
|
||||
(active_flat_id, copy), // M
|
||||
)
|
||||
};
|
||||
}
|
||||
@@ -370,6 +377,7 @@ impl Vm {
|
||||
self.pc = 0;
|
||||
self.bytecode = Arc::clone(&sub.bytecode);
|
||||
self.activate_immutables(sub_idx, &sub.constants);
|
||||
self.active_flat_id = sub_idx as u32;
|
||||
self.active_reg_limit = sub.n_registers.max(1);
|
||||
self.root_reg_limit = self.active_reg_limit;
|
||||
self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize));
|
||||
@@ -424,6 +432,9 @@ impl Vm {
|
||||
if let Some(saved_bc) = self.saved_bytecode_stack.pop() {
|
||||
self.bytecode = saved_bc;
|
||||
}
|
||||
if let Some(saved_flat) = self.saved_flat_id_stack.pop() {
|
||||
self.active_flat_id = saved_flat;
|
||||
}
|
||||
vm_debug!(
|
||||
"restore_frame: return_addr={} bc_len={} saved_stack={} fn={:?}",
|
||||
frame.return_addr,
|
||||
@@ -466,6 +477,11 @@ impl Vm {
|
||||
pub fn run(&mut self, module: &CompiledModule) -> Result<JsValue, String> {
|
||||
vm_debug!("run: starting bytecode execution, {} instructions", module.bytecode.len());
|
||||
self.clear_execution_state();
|
||||
// 模板对象缓存按 run 清空:flat_id 每次 run 从 0 重新分配,跨 run 复用会
|
||||
// 让不同编译树的 site 误命中(缓存键 = (flat_id, site_no))。
|
||||
self.template_objects.clear();
|
||||
self.saved_flat_id_stack.clear();
|
||||
self.active_flat_id = 0;
|
||||
self.cell_stack.clear();
|
||||
self.cell_stack.push(Vec::new());
|
||||
self.sub_modules = Arc::new(collect_flat_modules(module));
|
||||
|
||||
@@ -117,6 +117,9 @@ impl Vm {
|
||||
instruction_count: 0,
|
||||
},
|
||||
cell_stack: Vec::new(),
|
||||
template_objects: std::collections::HashMap::new(),
|
||||
active_flat_id: 0,
|
||||
saved_flat_id_stack: Vec::new(),
|
||||
};
|
||||
vm.init_generator_intrinsics();
|
||||
vm.init_promise_intrinsics();
|
||||
@@ -223,6 +226,9 @@ impl Vm {
|
||||
instruction_count: 0,
|
||||
},
|
||||
cell_stack: Vec::new(),
|
||||
template_objects: std::collections::HashMap::new(),
|
||||
active_flat_id: 0,
|
||||
saved_flat_id_stack: Vec::new(),
|
||||
};
|
||||
vm.init_generator_intrinsics();
|
||||
vm.init_promise_intrinsics();
|
||||
@@ -304,6 +310,11 @@ impl Vm {
|
||||
|
||||
fn clear_full_reset_state(&mut self) {
|
||||
self.clear_execution_state();
|
||||
// 模板对象缓存中的 JsValue 指向将被重建的 session 对象:全量重置后悬垂,
|
||||
// 必须随 session 一并清空。
|
||||
self.template_objects.clear();
|
||||
self.saved_flat_id_stack.clear();
|
||||
self.active_flat_id = 0;
|
||||
self.bytecode = Arc::default();
|
||||
self.immutables_cache.clear();
|
||||
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
|
||||
|
||||
Reference in New Issue
Block a user