fix: 顶层 var 读写直连全局对象属性单一真值——GDI 序言 + 声明处同步删除,类原型 session 直分防晋升克隆 identity 分裂,自增减 int 保持

This commit is contained in:
2026-09-12 15:48:32 +00:00
parent 4b807b5b90
commit 44c16cab8a
21 changed files with 447 additions and 74 deletions
@@ -27,6 +27,26 @@ fn int_sub(a: i32, b: i32) -> JsValue {
}
}
/// int 自增:结果落在 i32 范围则保 int,否则升 double(与 int_add 同族语义,
/// 保持与 ADD 路径一致的 int 保持行为)。
#[inline(always)]
fn int_inc(a: i32) -> JsValue {
match a.checked_add(1) {
Some(v) => JsValue::int(v),
None => JsValue::float(a as f64 + 1.0),
}
}
/// int 自减:结果落在 i32 范围则保 int,否则升 double(与 int_sub 同族语义,
/// 保持与 SUB 路径一致的 int 保持行为)。
#[inline(always)]
fn int_dec(a: i32) -> JsValue {
match a.checked_sub(1) {
Some(v) => JsValue::int(v),
None => JsValue::float(a as f64 - 1.0),
}
}
/// int+int 乘法:结果落在 i32 范围则保 int,否则升 double。
#[inline(always)]
fn int_mul(a: i32, b: i32) -> JsValue {
@@ -538,6 +558,12 @@ impl Vm {
self.regs[a] = result;
return Ok(());
}
if self.regs[rd].is_int() {
let result = int_inc(self.regs[rd].as_int());
self.regs[rd] = result;
self.regs[a] = result;
return Ok(());
}
let n = self.coerce_number_bounded(self.regs[rd])?;
let result = JsValue::float(n + 1.0);
self.regs[rd] = result;
@@ -554,6 +580,12 @@ impl Vm {
self.regs[rd] = self.new_bigint(v + 1);
return Ok(());
}
if self.regs[rd].is_int() {
let old = self.regs[rd];
self.regs[a] = old;
self.regs[rd] = int_inc(old.as_int());
return Ok(());
}
let n = self.coerce_number_bounded(self.regs[rd])?;
self.regs[a] = JsValue::float(n);
self.regs[rd] = JsValue::float(n + 1.0);
@@ -570,6 +602,12 @@ impl Vm {
self.regs[a] = result;
return Ok(());
}
if self.regs[rd].is_int() {
let result = int_dec(self.regs[rd].as_int());
self.regs[rd] = result;
self.regs[a] = result;
return Ok(());
}
let n = self.coerce_number_bounded(self.regs[rd])?;
let result = JsValue::float(n - 1.0);
self.regs[rd] = result;
@@ -586,6 +624,12 @@ impl Vm {
self.regs[rd] = self.new_bigint(v - 1);
return Ok(());
}
if self.regs[rd].is_int() {
let old = self.regs[rd];
self.regs[a] = old;
self.regs[rd] = int_dec(old.as_int());
return Ok(());
}
let n = self.coerce_number_bounded(self.regs[rd])?;
self.regs[a] = JsValue::float(n);
self.regs[rd] = JsValue::float(n - 1.0);
+21
View File
@@ -238,6 +238,27 @@ impl Vm {
Ok(())
}
/// 创建 session 直分的空普通对象([[Prototype]] = Object.prototype):与
/// `create_function_object` 的 prototype 子对象同策略——若按 epoch 分配,写入
/// 全局等逃逸根时晋升屏障会深克隆进 session,持有者(构造器 prototype 属性)
/// 与克隆体指针分裂、严格相等恒 false。
///
/// # 副作用
/// - 登记 session 对象表并计入堆账目;回收由 session GC mark/sweep 承担。
pub(crate) fn dispatch_new_session_object(&mut self, rd: usize) -> Result<(), String> {
vm_trace!("NEW_SESSION_OBJECT rd={}", rd);
let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject;
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr));
obj.set_session_epoch(true);
let obj_ptr = self.gc_state.session_epoch.alloc(obj) as *mut JsObject;
self.gc_state.session_object_ptrs.push(obj_ptr);
// 直 session 分配计入堆账目(与 promote 同式:对象头 + 对象堆数据)。
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsObject>()
+ crate::session_gc::SessionGc::object_heap_data_bytes(unsafe { &*obj_ptr }) as usize;
self.regs[rd] = JsValue::object(obj_ptr as *mut u8);
Ok(())
}
/// 创建 arguments 对象:索引属性取当前帧(或 inline 同步调用)的完整实参,
/// 附 length / callee 属性。第一版为 unmapped(非严格)语义,索引与形参不同步。
///
+1
View File
@@ -67,6 +67,7 @@ fn ext_word_count(bytecode: &[Instr], pc: usize) -> usize {
| OpCode::DEFINE_ACCESSOR_DYNAMIC
| OpCode::DEFINE_PROP_ATTRS
| OpCode::DEFINE_GLOBAL_PROP_C
| OpCode::DEFINE_GLOBAL_PROP_C_IF_ABSENT
| OpCode::DELETE_PROP_STATIC
| OpCode::REST_OBJECT
| OpCode::INIT_PRIVATE => 1,
+10 -4
View File
@@ -1826,8 +1826,9 @@ mod tests {
#[test]
fn closure_cell_survives_object_sweep() {
let mut vm = vm_with_threshold(1);
// counter 函数作用域局部:被 inc 经 cell 捕获(顶层 var 直连全局属性不走 cell)。
vm.run(&Arc::new(compile(
"var counter = 0; function inc() { return ++counter; } globalThis.inc = inc; 0",
"function outer() { var counter = 0; function inc() { return ++counter; } globalThis.inc = inc; return 0; } outer(); 0",
)))
.expect("run1");
assert!(!vm.gc_state.session_cell_ptrs.borrow().is_empty(), "run1 应分配 upvalue cell");
@@ -1880,15 +1881,20 @@ mod tests {
#[test]
fn cells_freed_by_full_reset_and_reallocatable() {
let mut vm = Vm::new();
vm.run(&Arc::new(compile("var x = 1; function f() { return x; } globalThis.f = f; f()")))
.expect("run1");
// x/y 函数作用域局部:被 f/g 经 cell 捕获(顶层 var 直连全局属性不走 cell)。
vm.run(&Arc::new(compile(
"function outer() { var x = 1; function f() { return x; } globalThis.f = f; return f(); } outer()",
)))
.expect("run1");
assert!(!vm.gc_state.session_cell_ptrs.borrow().is_empty());
vm.full_reset();
assert!(vm.gc_state.session_cell_ptrs.borrow().is_empty(), "full_reset 应释放全部 cell");
let result = vm
.run(&Arc::new(compile("var y = 2; function g() { return y; } g()")))
.run(&Arc::new(compile(
"function outer() { var y = 2; function g() { return y; } return g(); } outer()",
)))
.expect("run2");
assert_eq!(format!("{result}"), "2", "重置后新 cell 应正常分配与读取");
}
+14
View File
@@ -2040,6 +2040,16 @@ impl Vm {
self.dispatch_define_global_prop_c(a, key_idx)?;
}
OpCode::DEFINE_GLOBAL_PROP_IF_ABSENT => {
self.dispatch_define_global_prop_if_absent(rd, a, b)?;
}
OpCode::DEFINE_GLOBAL_PROP_C_IF_ABSENT => {
let key_idx = self.bytecode[self.pc] as u16;
self.pc += 1;
self.dispatch_define_global_prop_c_if_absent(a, key_idx)?;
}
OpCode::DEFINE_PROP_ATTRS => {
let attrs = self.bytecode[self.pc] as u8;
self.pc += 1;
@@ -2078,6 +2088,10 @@ impl Vm {
self.dispatch_new_object(rd, instr)?;
}
OpCode::NEW_SESSION_OBJECT => {
self.dispatch_new_session_object(rd)?;
}
OpCode::CREATE_ARGUMENTS => {
self.dispatch_create_arguments(rd)?;
}
+64
View File
@@ -365,6 +365,70 @@ impl Vm {
}
}
/// 顶层 var 声明的全局属性 define-if-absent:rd=全局对象,a=值,b=键。
///
/// CreateGlobalVarBinding 对既有属性零动作:属性已存在(数据或 accessor)直接
/// 返回——可写/不可写/配置位与值均不更新。缺失新建可写/可枚举/不可配置数据
/// 属性;全局对象不可扩展时静默 no-op(GDI 面,基线同形)。
///
/// # 边界与前提
/// - rd 非对象(非对象 this)时按 no-op 处理,不抛错。
pub(crate) fn dispatch_define_global_prop_if_absent(
&mut self, rd: usize, a: usize, b: usize,
) -> Result<(), String> {
vm_trace!("DEFINE_GLOBAL_PROP_IF_ABSENT rd={} value={} key={}", rd, a, b);
let obj_val = self.regs[rd];
if !obj_val.is_object() {
return Ok(());
}
let prop_name_si = self.property_key_si(self.regs[b])?;
let value = self.regs[a];
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
// 既有属性(数据或 accessor):CreateGlobalVarBinding 零动作。
if self
.kernel_core
.shape_forge()
.lookup_position(obj.shape_id(), prop_name_si)
.is_some()
{
return Ok(());
}
// 缺失:新建可写/可枚举/不可配置;不可扩展静默(GDI 面,基线同形)。
let _ = self.define_data_property(obj, prop_name_si, value, PropAttributes::new(true, true, false));
Ok(())
}
/// eval 脚本顶层 var 声明的全局属性 define-if-absent:ext 字 = 键常量池下标
/// (u16),a 槽 = 值寄存器。全局对象由 session 解析(不依赖 this)。
///
/// CreateGlobalVarBinding 对既有属性零动作:属性已存在直接返回。缺失新建
/// 可写/可枚举/可配置属性;全局对象不可扩展 → TypeError(两模式均抛)。
///
/// # 边界与前提
/// - 键常量必须是字符串(emit 侧保证);非字符串按错误返回。
pub(crate) fn dispatch_define_global_prop_c_if_absent(&mut self, a: usize, key_idx: u16) -> Result<(), String> {
let idx = key_idx as usize;
vm_trace!("DEFINE_GLOBAL_PROP_C_IF_ABSENT value={} idx={}", a, idx);
let key_val = self.immutables().get(idx).copied().unwrap_or(JsValue::undefined());
if !key_val.is_string() {
return Err(format!("DEFINE_GLOBAL_PROP_C_IF_ABSENT constant index {idx} is not a string key"));
}
let si = self.property_key_si(key_val)?;
let value = self.regs[a];
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
// SAFETY: 全局对象钉在 session 永久区,指针在 VM 生命周期内有效。
let obj = unsafe { &mut *global_ptr };
// 既有属性:CreateGlobalVarBinding 零动作。
if self.kernel_core.shape_forge().lookup_position(obj.shape_id(), si).is_some() {
return Ok(());
}
// 缺失:新建;唯一失败面是全局对象不可扩展(两模式均抛 TypeError)。
match self.define_data_property(obj, si, value, PropAttributes::new(true, true, true)) {
Ok(()) => Ok(()),
Err(msg) => self.raise_error_kind("TypeError", &msg),
}
}
/// break 完成:`crossed`(rd 槽)为 emit 词法算出的逃出 finally 域数。
/// 逐个穿越 finally 后跳转到目标;crossed 为 0 时直接跳转。
/// ext 字携带逃出的迭代器层数:无 finally 穿越时立即关闭后跳转,有 finally
+8 -3
View File
@@ -1068,9 +1068,10 @@ mod tests {
#[test]
fn generator_captured_upvalue_survives_sweep() {
let mut vm = vm_with_low_threshold();
// x 函数作用域局部:被生成器 g 经 cell 捕获(顶层 var 直连全局属性不走 cell)。
let _ = run_source(
&mut vm,
"var x = 0; function* g(){ x++; yield x; x++; yield x; } globalThis.it = g(); globalThis.it.next(); 0",
"function outer() { var x = 0; function* g(){ x++; yield x; x++; yield x; } globalThis.it = g(); globalThis.it.next(); return 0; } outer(); 0",
);
vm.maybe_collect_session_gc();
@@ -1085,8 +1086,12 @@ mod tests {
#[test]
fn generator_promoted_clone_owns_independent_state_box() {
let mut vm = Vm::new();
// `var it = g(); it.next(); it`:it 为 epoch 生成器对象(未逃逸不 promote)。
let it = run_source(&mut vm, "function* g(){ yield 1; yield 2; } var it = g(); it.next(); it");
// `(function(){ var it = g(); it.next(); return it; })()`:it 为函数局部(非顶层
// var,不经全局属性逃逸),保持 epoch 生成器对象(未 promote)。
let it = run_source(
&mut vm,
"function* g(){ yield 1; yield 2; } (function(){ var it = g(); it.next(); return it; })()",
);
assert!(it.is_object());
let epoch_ptr = it.as_js_object_ptr();
let epoch_box = unsafe { (*epoch_ptr).native_data() };