From 45d299282b754597fcd126f70722676ae4b137cf Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 18 Oct 2026 13:33:14 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20IC=20=E5=91=BD=E4=B8=AD=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E5=8A=A0=E4=BB=A3=E7=90=86=E6=8E=A5=E6=94=B6=E8=80=85?= =?UTF-8?q?=E6=81=92=20miss=20=E5=AE=88=E5=8D=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 ++++----- Cargo.toml | 2 +- crates/oxide_vm/src/dispatch/member.rs | 115 +++++++++++++++++++++++++ crates/oxide_vm/src/ic_helper.rs | 24 ++++++ 4 files changed, 161 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0b68980..99e69f48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.20.15" +version = "0.20.16" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.20.15" +version = "0.20.16" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.20.15" +version = "0.20.16" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.20.15" +version = "0.20.16" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.20.15" +version = "0.20.16" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.20.15" +version = "0.20.16" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.20.15" +version = "0.20.16" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.20.15" +version = "0.20.16" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.20.15" +version = "0.20.16" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.20.15" +version = "0.20.16" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.20.15" +version = "0.20.16" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.20.15" +version = "0.20.16" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.20.15" +version = "0.20.16" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.20.15" +version = "0.20.16" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.20.15" +version = "0.20.16" [[package]] name = "oxide_parser" -version = "0.20.15" +version = "0.20.16" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.20.15" +version = "0.20.16" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.20.15" +version = "0.20.16" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.20.15" +version = "0.20.16" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.20.15" +version = "0.20.16" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.20.15" +version = "0.20.16" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 18e3ffd6..f40888c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.20.15" +version = "0.20.16" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/dispatch/member.rs b/crates/oxide_vm/src/dispatch/member.rs index 7537afd7..ee38a4cb 100644 --- a/crates/oxide_vm/src/dispatch/member.rs +++ b/crates/oxide_vm/src/dispatch/member.rs @@ -9,6 +9,15 @@ use oxide_runtime_api as coercion; impl Vm { pub(crate) fn dispatch_member_op(&mut self, op: OpCode, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("MEMBER_OP {:?} rd={} a={} b={}", op, rd, a, b); + // 代理接收者统一走全路径:ordinary_get/ordinary_set 已带代理守卫, + // 命中路径直读代理自身属性槽会绕过 get trap(member 读侧无入口守卫)。 + let receiver = self.regs[rd]; + if receiver.is_object() { + let obj_ptr = receiver.as_js_object_ptr(); + if !obj_ptr.is_null() && unsafe { (*obj_ptr).is_proxy_obj() } { + return self.dispatch_member_op_full(op, rd, a, b); + } + } match op { OpCode::MEMBER_INC => self.dispatch_member_inc(rd, a, b), OpCode::MEMBER_DEC => self.dispatch_member_dec(rd, a, b), @@ -119,6 +128,112 @@ impl Vm { Ok((obj_ptr, prop_name_si)) } + /// 代理接收者全路径读改写:ordinary_get/ordinary_set 已带代理守卫, + /// 经 get/set trap 路由,不接触 IC 命中路径。 + fn dispatch_member_op_full(&mut self, op: OpCode, rd: usize, a: usize, b: usize) -> Result<(), String> { + vm_trace!("MEMBER_OP_FULL {:?} rd={} a={} b={}", op, rd, a, b); + let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "MEMBER_OP on non-object")? else { + return Ok(()); + }; + let receiver = self.regs[rd]; + // 静态操作键在 regs[b]、结果在 regs[a];动态操作键在 regs[a]、结果在 regs[b]。 + let (key_reg, result_reg) = match op { + OpCode::DYN_MEMBER_INC | OpCode::DYN_MEMBER_DEC => (a, b), + _ => (b, a), + }; + let prop_name_si = self.property_key_si(self.regs[key_reg])?; + // 静态操作带 IC 扩展字:代理分支不写回 IC(槽恒零),先消费扩展字, + // 否则派发循环会错位把扩展字当指令。 + if op.has_ic_ext_words() { + let _ = crate::ic_helper::read_ic_slot0(&self.bytecode, &mut self.pc); + } + let obj = unsafe { &mut *obj_ptr }; + let prop_val = self.ordinary_get(obj, prop_name_si, receiver)?; + let new_val = self.member_op_new_value(op, prop_val, self.regs[a])?; + self.ordinary_set(obj, prop_name_si, new_val, receiver, self.current_strict())?; + self.regs[result_reg] = new_val; + Ok(()) + } + + /// 代理接收者全路径的新值计算:强制转换口径与既有 IC 路径各分臂一致。 + fn member_op_new_value(&mut self, op: OpCode, prop_val: JsValue, rhs: JsValue) -> Result { + match op { + OpCode::MEMBER_INC | OpCode::DYN_MEMBER_INC => { + let n = self.coerce_number_bounded(prop_val)?; + Ok(JsValue::float(n + 1.0)) + } + OpCode::MEMBER_DEC | OpCode::DYN_MEMBER_DEC => { + let n = self.coerce_number_bounded(prop_val)?; + Ok(JsValue::float(n - 1.0)) + } + OpCode::COMPOUND_MEMBER_ADD => { + let prop_val = self.coerce_primitive_bounded(prop_val, coercion::ToPrimitiveHint::Number)?; + let rhs = self.coerce_primitive_bounded(rhs, coercion::ToPrimitiveHint::Number)?; + if prop_val.is_symbol() || rhs.is_symbol() { + // 复合成员加法分流同 `+`:字符串分支走 ToString、数值分支走 ToNumber, + // 两者对 Symbol 均抛 TypeError(ECMA-262 §7.1.4 / §7.1.17)。 + let msg = if prop_val.is_string() || rhs.is_string() { + "Cannot convert a Symbol value to a string" + } else { + "Cannot convert a Symbol value to a number" + }; + self.raise_type_error(msg)?; + return Ok(JsValue::undefined()); + } + if prop_val.is_string() || rhs.is_string() { + let ls = coercion::to_string(prop_val); + let rs = coercion::to_string(rhs); + Ok(self.new_string(&format!("{ls}{rs}"))) + } else { + let ln = coercion::to_number(prop_val); + let rn = coercion::to_number(rhs); + Ok(JsValue::float(ln + rn)) + } + } + OpCode::COMPOUND_MEMBER_SUB => self.member_op_f64(prop_val, rhs, |l, r| l - r), + OpCode::COMPOUND_MEMBER_MUL => self.member_op_f64(prop_val, rhs, |l, r| l * r), + OpCode::COMPOUND_MEMBER_DIV => self.member_op_f64(prop_val, rhs, |l, r| l / r), + OpCode::COMPOUND_MEMBER_MOD => self.member_op_f64(prop_val, rhs, |l, r| l % r), + OpCode::COMPOUND_MEMBER_EXP => self.member_op_f64(prop_val, rhs, |l, r| l.powf(r)), + OpCode::COMPOUND_MEMBER_BIT_AND => self.member_op_i32(prop_val, rhs, |l, r| l & r), + OpCode::COMPOUND_MEMBER_BIT_OR => self.member_op_i32(prop_val, rhs, |l, r| l | r), + OpCode::COMPOUND_MEMBER_BIT_XOR => self.member_op_i32(prop_val, rhs, |l, r| l ^ r), + OpCode::COMPOUND_MEMBER_SHL => self.member_op_shift(prop_val, rhs, false, false), + OpCode::COMPOUND_MEMBER_SHR => self.member_op_shift(prop_val, rhs, true, false), + OpCode::COMPOUND_MEMBER_USHR => self.member_op_shift(prop_val, rhs, true, true), + _ => unreachable!("non-member opcode passed to member_op_new_value"), + } + } + + fn member_op_f64(&mut self, prop_val: JsValue, rhs: JsValue, op: impl FnOnce(f64, f64) -> f64) -> Result { + let ln = self.coerce_number_bounded(prop_val)?; + let rn = self.coerce_number_bounded(rhs)?; + Ok(JsValue::float(op(ln, rn))) + } + + fn member_op_i32(&mut self, prop_val: JsValue, rhs: JsValue, op: impl FnOnce(i32, i32) -> i32) -> Result { + let ln = self.coerce_int32_bounded(prop_val)?; + let rn = self.coerce_int32_bounded(rhs)?; + Ok(JsValue::int(op(ln, rn))) + } + + fn member_op_shift(&mut self, prop_val: JsValue, rhs: JsValue, is_right: bool, is_unsigned: bool) -> Result { + let ln = self.coerce_int32_bounded(prop_val)?; + let shift = self.coerce_uint32_bounded(rhs)? & 0x1F; + Ok(if !is_right { + JsValue::int(ln.wrapping_shl(shift)) + } else if is_unsigned { + let u = (ln as u32) >> shift; + if u <= i32::MAX as u32 { + JsValue::int(u as i32) + } else { + JsValue::float(u as f64) + } + } else { + JsValue::int(ln >> shift) + }) + } + fn dispatch_member_inc(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { vm_trace!("MEMBER_INC rd={} a={} b={}", rd, a, b); let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_INC on non-object") else { diff --git a/crates/oxide_vm/src/ic_helper.rs b/crates/oxide_vm/src/ic_helper.rs index 24f42a6a..5b786b14 100644 --- a/crates/oxide_vm/src/ic_helper.rs +++ b/crates/oxide_vm/src/ic_helper.rs @@ -99,6 +99,10 @@ fn resolve_proto_target_raw(obj: *const JsObject, proto_depth: u8) -> *const JsO #[inline(always)] pub(crate) fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32, proto_depth: u8) -> Option { + // 代理接收者恒 miss:命中路径直读代理自身属性槽会绕过 get trap。 + if obj.is_proxy_obj() { + return None; + } if shape_id == 0 { return None; } @@ -134,6 +138,10 @@ pub(crate) fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32, proto_d /// 空槽(shape_id==0)恒在尾部(FIFO 滚动保证),遇空即停。 #[inline(never)] pub(crate) fn ic_get_hit_poly(obj: &JsObject, bytecode: &[Instr], ext_pc: usize) -> Option { + // 代理接收者恒 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; @@ -170,6 +178,10 @@ pub(crate) fn ic_get_hit_poly(obj: &JsObject, bytecode: &[Instr], ext_pc: usize) #[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; } @@ -201,6 +213,10 @@ pub(crate) fn ic_set_hit(obj: &mut JsObject, shape_id: u32, slot_index: u32, pro /// 空槽(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; @@ -244,6 +260,10 @@ pub(crate) fn ic_set_hit_poly(obj: &mut JsObject, bytecode: &[Instr], ext_pc: us /// 命中遍历从 `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 { @@ -262,6 +282,10 @@ pub(crate) fn ic_set_hit_own(obj: &mut JsObject, bytecode: &[Instr], pc: usize, /// `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;