fix: 计算成员复合赋值保规范求值序
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 绑定保留。
|
||||
|
||||
Reference in New Issue
Block a user