fix: 计算成员复合赋值保规范求值序

This commit is contained in:
2026-08-14 11:41:50 +08:00
parent c2aea3f8e9
commit 02dabbb845
3 changed files with 85 additions and 18 deletions
@@ -737,9 +737,13 @@ fn compile_computed_member_numeric_and_index_keys_stay_dynamic() {
#[test]
fn compile_computed_member_compound_and_update_fold_to_ic() {
// 复合赋值折叠为 ic_get → op → ic_set 三指令序列(保规范求值序),不再用单指令 RMW。
let module = compile_source("let obj = { a: 1 }; obj[\"a\"] += 1; obj[\"b\"]++;");
let ops = scan_opcodes(&module);
assert!(ops.contains(&OpCode::COMPOUND_MEMBER_ADD), "obj[\"a\"] += 应折叠为 COMPOUND_MEMBER_ADD");
assert!(ops.contains(&OpCode::IC_GET_PROP), "obj[\"a\"] += 应先 ic_get 读属性");
assert!(ops.contains(&OpCode::IC_SET_PROP), "obj[\"a\"] += 应 ic_set 写回");
assert!(ops.contains(&OpCode::ADD), "obj[\"a\"] += 应发 ADD 运算");
assert!(!ops.contains(&OpCode::COMPOUND_MEMBER_ADD), "复合赋值不应发 COMPOUND_MEMBER_ADD 单指令");
assert!(ops.contains(&OpCode::MEMBER_INC), "obj[\"b\"]++ 应折叠为 MEMBER_INC");
assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量键复合赋值不应发 GET_PROP_DYNAMIC");
}
+32 -17
View File
@@ -167,31 +167,46 @@ impl Emitter {
return Ok(result_reg);
}
let obj_reg = self.emit_expression(&member.object, ctx)?;
let val_reg = self.emit_expression(&assign.right, ctx)?;
let idx = ctx.add_constant(Constant::String(key));
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
if assign.operator != AssignmentOperator::Assign {
let obj = Operand::Reg(obj_reg);
let val = Operand::Reg(val_reg);
let key = Operand::Reg(key_reg);
match assign.operator {
AssignmentOperator::Addition => ctx.inst(Inst::compound_member_add(obj, val, key)),
AssignmentOperator::Subtraction => ctx.inst(Inst::compound_member_sub(obj, val, key)),
AssignmentOperator::Multiplication => ctx.inst(Inst::compound_member_mul(obj, val, key)),
AssignmentOperator::Division => ctx.inst(Inst::compound_member_div(obj, val, key)),
AssignmentOperator::Remainder => ctx.inst(Inst::compound_member_mod(obj, val, key)),
AssignmentOperator::Exponential => ctx.inst(Inst::compound_member_exp(obj, val, key)),
AssignmentOperator::BitwiseAnd => ctx.inst(Inst::compound_member_bit_and(obj, val, key)),
AssignmentOperator::BitwiseOR => ctx.inst(Inst::compound_member_bit_or(obj, val, key)),
AssignmentOperator::BitwiseXOR => ctx.inst(Inst::compound_member_bit_xor(obj, val, key)),
AssignmentOperator::ShiftLeft => ctx.inst(Inst::compound_member_shl(obj, val, key)),
AssignmentOperator::ShiftRight => ctx.inst(Inst::compound_member_shr(obj, val, key)),
AssignmentOperator::ShiftRightZeroFill => ctx.inst(Inst::compound_member_ushr(obj, val, key)),
// 复合赋值保规范求值序:先读属性(ic_get)再求值 RHS——RHS 副作用
// 可能改写同一属性,后求值才能读到旧值。
let val_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(val_reg),
Operand::Reg(obj_reg),
Operand::None,
));
ctx.inst(Inst::ic_get(Operand::Reg(val_reg), Operand::Reg(key_reg)));
let rhs = self.emit_expression(&assign.right, ctx)?;
let op = match assign.operator {
AssignmentOperator::Addition => OpCode::ADD,
AssignmentOperator::Subtraction => OpCode::SUB,
AssignmentOperator::Multiplication => OpCode::MUL,
AssignmentOperator::Division => OpCode::DIV,
AssignmentOperator::Remainder => OpCode::MOD,
AssignmentOperator::Exponential => OpCode::COMPOUND_EXP,
AssignmentOperator::BitwiseAnd => OpCode::BIT_AND,
AssignmentOperator::BitwiseOR => OpCode::BIT_OR,
AssignmentOperator::BitwiseXOR => OpCode::BIT_XOR,
AssignmentOperator::ShiftLeft => OpCode::SHL,
AssignmentOperator::ShiftRight => OpCode::SHR,
AssignmentOperator::ShiftRightZeroFill => OpCode::USHR,
_ => return Err(format!("compound assignment operator {:?} not supported", assign.operator)),
};
if assign.operator == AssignmentOperator::Exponential {
// 指数无独立二元指令,COMPOUND_EXP 语义 rd=rd^a:rhs 放 a 槽。
ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(rhs), Operand::None));
} else {
ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(val_reg), Operand::Reg(rhs)));
}
ctx.inst(Inst::ic_set(Operand::Reg(obj_reg), Operand::Reg(val_reg), Operand::Reg(key_reg)));
Ok(val_reg)
} else {
let val_reg = self.emit_expression(&assign.right, ctx)?;
ctx.inst(Inst::ic_set(Operand::Reg(obj_reg), Operand::Reg(val_reg), Operand::Reg(key_reg)));
Ok(val_reg)
}
+48
View File
@@ -65,6 +65,54 @@ fn eval_computed_const_key_dynamic_keys_stay_dynamic() {
assert_eq!(eval("var arr=[1,2]; arr[0]=7, arr[0]"), "7");
}
#[test]
fn eval_computed_compound_assign_preserves_read_before_rhs() {
// 复合赋值保规范求值序:先读属性再求值 RHS,RHS 副作用改写属性不影响被加的旧值。
assert_eq!(eval("var o={a:1}; function f(){o.a=100; return 1;} o[\"a\"]+=f()"), "2");
assert_eq!(eval("var o={a:5}; o[\"a\"]-=1, o.a"), "4");
assert_eq!(eval("var o={a:5}; o[\"a\"]*=2, o.a"), "10");
assert_eq!(eval("var o={a:5}; o[\"a\"]/=2, o.a"), "2.5");
assert_eq!(eval("var o={a:5}; o[\"a\"]%=3, o.a"), "2");
assert_eq!(eval("var o={a:5}; o[\"a\"]**=2, o.a"), "25");
assert_eq!(eval("var o={a:5}; o[\"a\"]&=3, o.a"), "1");
assert_eq!(eval("var o={a:5}; o[\"a\"]|=8, o.a"), "13");
assert_eq!(eval("var o={a:5}; o[\"a\"]^=1, o.a"), "4");
assert_eq!(eval("var o={a:5}; o[\"a\"]<<=1, o.a"), "10");
assert_eq!(eval("var o={a:5}; o[\"a\"]>>=1, o.a"), "2");
assert_eq!(eval("var o={a:5}; o[\"a\"]>>>=1, o.a"), "2");
// 逻辑赋值同为读→短路→RHS→写:truthy 时写 RHS 值,falsy 短路 RHS 不求值。
assert_eq!(eval("var o={a:5}; function g(){o.a=7; return 2;} o[\"a\"]&&=g(), o.a"), "2");
assert_eq!(eval("var o={a:0}; o[\"a\"]&&=99, o.a"), "0");
assert_eq!(eval("var o={a:5}; var hit=0; o[\"a\"]||=(hit=1), hit"), "0");
assert_eq!(eval("var o={a:5}; var hit=0; o[\"a\"]??=(hit=1), hit"), "0");
assert_eq!(eval("var p={a:null}; p[\"a\"]??=5, p.a"), "5");
assert_eq!(eval("var q={a:0}; q[\"a\"]||=9, q.a"), "9");
// 静态成员复合赋值行为不变(pre-existing 求值序偏差不在本次修复范围)。
assert_eq!(eval("var o={a:5}; o.a += 1, o.a"), "6");
}
#[test]
fn eval_computed_compound_assign_getter_order() {
// getter 场景:先触发 getter 读取,再求值 RHS。
assert_eq!(eval("var g={get a(){return 2}, set a(v){}}; g[\"a\"] += 1"), "3");
// 求值序严格验证:RHS 观察到 getter 已先行(seen=1),否则 RHS 取 100。
// 结果 v*10+seen:规范序 101,RHS 先行 1001。
assert_eq!(
eval("var seen=0, v=0; var g={get a(){seen=1; return v}, set a(x){v=x}}; g[\"a\"] += (seen===0?100:10); v*10+seen"),
"101"
);
}
#[test]
fn eval_computed_compound_assign_read_error_skips_rhs() {
// 读取抛错时 RHS 不求值,其副作用不泄漏。
assert_eq!(eval("var h={get a(){throw 1}}; try{h[\"a\"]+=1}catch(e){e}"), "1");
assert_eq!(
eval("var side=0; var h={get a(){throw 1}}; try{h[\"a\"]+=(side=99)}catch(e){} side"),
"0"
);
}
#[test]
fn eval_computed_const_key_method_call_keeps_this() {
// 方法调用接收者折叠:this 绑定保留。