perf: 常量字符串键计算成员折叠走 IC 静态路径

This commit is contained in:
2026-08-14 10:20:32 +08:00
parent 7f7a8c3b66
commit 1a62af5485
7 changed files with 390 additions and 80 deletions
@@ -668,6 +668,98 @@ fn compile_dynamic_member_ops() {
); );
} }
/// 收集字节码指令序列,跳过 IC 扩展字(3 字)与其它扩展字,逐指令定位。
fn scan_opcodes(module: &CompiledModule) -> Vec<OpCode> {
let mut ops = Vec::new();
let mut i = 0;
while i < module.bytecode.len() {
let op = opcode::opcode(module.bytecode[i]);
ops.push(op);
i += 1 + if op.has_ic_ext_words() {
3
} else {
match op {
OpCode::SPILL
| OpCode::UNSPILL
| OpCode::CALL
| OpCode::CALL_NATIVE
| OpCode::NEW_EXPRESSION
| OpCode::SUPER_CALL
| OpCode::DEFINE_ACCESSOR
| OpCode::DEFINE_ACCESSOR_DYNAMIC
| OpCode::DEFINE_PROP_ATTRS
| OpCode::DELETE_PROP_STATIC
| OpCode::REST_OBJECT
| OpCode::INIT_PRIVATE => 1,
OpCode::DEFINE_ACCESSOR_ATTRS
| OpCode::GET_PRIVATE
| OpCode::SET_PRIVATE
| OpCode::PRIVATE_BRAND_IN => 2,
_ => 0,
}
};
}
ops
}
#[test]
fn compile_computed_member_const_string_key_folds_to_ic() {
let module = compile_source("let obj = { a: 1 }; obj[\"a\"]; obj[\"a\"] = 2;");
let ops = scan_opcodes(&module);
assert!(ops.contains(&OpCode::IC_GET_PROP), "obj[\"a\"] 应折叠为 IC_GET_PROP");
assert!(ops.contains(&OpCode::IC_SET_PROP), "obj[\"a\"] = 应折叠为 IC_SET_PROP");
assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量字符串键读不应发 GET_PROP_DYNAMIC");
assert!(!ops.contains(&OpCode::SET_PROP_DYNAMIC), "常量字符串键写不应发 SET_PROP_DYNAMIC");
}
#[test]
fn compile_computed_member_template_key_folds_to_ic() {
let module = compile_source("let obj = { a: 1 }; obj[`a`];");
let ops = scan_opcodes(&module);
assert!(ops.contains(&OpCode::IC_GET_PROP), "无插值模板键应折叠为 IC_GET_PROP");
assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "无插值模板键不应发 GET_PROP_DYNAMIC");
// 含插值模板保持 DYNAMIC(运行期键非常量)
let interp = compile_source("let obj = {}, k = 'a'; obj[`${k}`];");
assert!(
scan_opcodes(&interp).contains(&OpCode::GET_PROP_DYNAMIC),
"含插值模板键应保持 GET_PROP_DYNAMIC"
);
}
#[test]
fn compile_computed_member_numeric_and_index_keys_stay_dynamic() {
// 数字键(obj[0] / arr[\"0\"])不折叠:数组元素区 IC 永不命中。
let module = compile_source("let obj = {}, arr = [1]; obj[0]; arr[\"0\"];");
let ops = scan_opcodes(&module);
assert!(ops.contains(&OpCode::GET_PROP_DYNAMIC), "数字/数组下标键应保持 GET_PROP_DYNAMIC");
assert!(!ops.contains(&OpCode::IC_GET_PROP), "数字/数组下标键不应折叠为 IC_GET_PROP");
}
#[test]
fn compile_computed_member_compound_and_update_fold_to_ic() {
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::MEMBER_INC), "obj[\"b\"]++ 应折叠为 MEMBER_INC");
assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量键复合赋值不应发 GET_PROP_DYNAMIC");
}
#[test]
fn compile_computed_member_call_receiver_folds_to_ic() {
let module = compile_source("let obj = { m() { return 1 } }; obj[\"m\"]();");
let ops = scan_opcodes(&module);
assert!(ops.contains(&OpCode::IC_GET_PROP), "obj[\"m\"]() 接收者应折叠为 IC_GET_PROP");
assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量键方法调用不应发 GET_PROP_DYNAMIC");
}
#[test]
fn compile_computed_member_chain_folds_to_ic() {
let module = compile_source("let obj = { a: 1 }; obj?.[\"a\"];");
let ops = scan_opcodes(&module);
assert!(ops.contains(&OpCode::IC_GET_PROP), "可选链 obj?.[\"a\"] 应折叠为 IC_GET_PROP");
assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量键可选链不应发 GET_PROP_DYNAMIC");
}
#[test] #[test]
fn compile_delete_new_instanceof_in() { fn compile_delete_new_instanceof_in() {
let module = let module =
+148 -71
View File
@@ -136,80 +136,68 @@ impl Emitter {
Ok(val_reg) Ok(val_reg)
} }
} else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left { } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left {
if let Some(logical_op) = assign.operator.to_logical_operator() { // 常量字符串键折叠为 IC 静态路径,与 StaticMemberExpression 分支同构。
let store_label = ctx.next_label_id(); if let Some(key) = crate::expr::member::computed_const_key(&member.expression) {
let end_label = ctx.next_label_id(); if let Some(logical_op) = assign.operator.to_logical_operator() {
let store_label = ctx.next_label_id();
let end_label = ctx.next_label_id();
let obj_reg = self.emit_expression(&member.object, 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));
let result_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(result_reg),
Operand::Reg(obj_reg),
Operand::None,
));
ctx.inst(Inst::ic_get(Operand::Reg(result_reg), Operand::Reg(key_reg)));
self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?;
ctx.labels.set_label_pos(store_label, ctx.insts.len());
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)));
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(result_reg),
Operand::Reg(val_reg),
Operand::None,
));
ctx.labels.set_label_pos(end_label, ctx.insts.len());
return Ok(result_reg);
}
let obj_reg = self.emit_expression(&member.object, ctx)?; let obj_reg = self.emit_expression(&member.object, ctx)?;
let key_reg = self.emit_expression(&member.expression, ctx)?;
let result_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(result_reg),
));
self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?;
ctx.labels.set_label_pos(store_label, ctx.insts.len());
let val_reg = self.emit_expression(&assign.right, ctx)?; let val_reg = self.emit_expression(&assign.right, ctx)?;
ctx.inst(Inst::new( let idx = ctx.add_constant(Constant::String(key));
OpCode::SET_PROP_DYNAMIC, let key_reg = ctx.alloc_reg();
Operand::Reg(obj_reg), ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
Operand::Reg(key_reg), if assign.operator != AssignmentOperator::Assign {
Operand::Reg(val_reg), let obj = Operand::Reg(obj_reg);
)); let val = Operand::Reg(val_reg);
ctx.inst(Inst::new( let key = Operand::Reg(key_reg);
OpCode::LOAD_VAR, match assign.operator {
Operand::Reg(result_reg), AssignmentOperator::Addition => ctx.inst(Inst::compound_member_add(obj, val, key)),
Operand::Reg(val_reg), AssignmentOperator::Subtraction => ctx.inst(Inst::compound_member_sub(obj, val, key)),
Operand::None, AssignmentOperator::Multiplication => ctx.inst(Inst::compound_member_mul(obj, val, key)),
)); AssignmentOperator::Division => ctx.inst(Inst::compound_member_div(obj, val, key)),
ctx.labels.set_label_pos(end_label, ctx.insts.len()); AssignmentOperator::Remainder => ctx.inst(Inst::compound_member_mod(obj, val, key)),
return Ok(result_reg); 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)),
_ => return Err(format!("compound assignment operator {:?} not supported", assign.operator)),
}
Ok(val_reg)
} else {
ctx.inst(Inst::ic_set(Operand::Reg(obj_reg), Operand::Reg(val_reg), Operand::Reg(key_reg)));
Ok(val_reg)
}
} else {
self.emit_computed_member_dynamic(assign, member, ctx)
} }
let obj_reg = self.emit_expression(&member.object, ctx)?;
let key_reg = self.emit_expression(&member.expression, ctx)?;
if assign.operator != AssignmentOperator::Assign {
// 复合赋值:读属性 → 运算 → 写回。val_reg 同时承载旧值与新值。
let val_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(val_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)),
};
ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(val_reg), Operand::Reg(rhs)));
ctx.inst(Inst::new(
OpCode::SET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(val_reg),
));
return Ok(val_reg);
}
let val_reg = self.emit_expression(&assign.right, ctx)?;
ctx.inst(Inst::new(
OpCode::SET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(val_reg),
));
Ok(val_reg)
} else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left { } else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left {
if assign.operator != AssignmentOperator::Assign { if assign.operator != AssignmentOperator::Assign {
return Err("compound assignment to private fields not supported".into()); return Err("compound assignment to private fields not supported".into());
@@ -421,4 +409,93 @@ impl Emitter {
Err("assignment target not supported".into()) Err("assignment target not supported".into())
} }
} }
/// 计算成员赋值的动态键路径:键运行期求值(非常量键)时经 DYNAMIC 指令读写。
///
/// # 步骤
/// 1. 求值对象与键表达式到寄存器。
/// 2. 逻辑赋值:读属性 → 短路测试 → 条件写回;复合赋值:读 → 运算 → 写回;
/// 普通赋值直接写回。
///
/// # 注意事项
/// - 键表达式有 ToPropertyKey 副作用或非常量时才调用(常量字符串键走 IC 折叠路径)。
fn emit_computed_member_dynamic(
&self, assign: &oxide_parser::AssignmentExpression, member: &oxide_parser::ComputedMemberExpression,
ctx: &mut CompileCtx,
) -> Result<u32, String> {
if let Some(logical_op) = assign.operator.to_logical_operator() {
let store_label = ctx.next_label_id();
let end_label = ctx.next_label_id();
let obj_reg = self.emit_expression(&member.object, ctx)?;
let key_reg = self.emit_expression(&member.expression, ctx)?;
let result_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(result_reg),
));
self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?;
ctx.labels.set_label_pos(store_label, ctx.insts.len());
let val_reg = self.emit_expression(&assign.right, ctx)?;
ctx.inst(Inst::new(
OpCode::SET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(val_reg),
));
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(result_reg),
Operand::Reg(val_reg),
Operand::None,
));
ctx.labels.set_label_pos(end_label, ctx.insts.len());
return Ok(result_reg);
}
let obj_reg = self.emit_expression(&member.object, ctx)?;
let key_reg = self.emit_expression(&member.expression, ctx)?;
if assign.operator != AssignmentOperator::Assign {
// 复合赋值:读属性 → 运算 → 写回。val_reg 同时承载旧值与新值。
let val_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(val_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)),
};
ctx.inst(Inst::new(op, Operand::Reg(val_reg), Operand::Reg(val_reg), Operand::Reg(rhs)));
ctx.inst(Inst::new(
OpCode::SET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(val_reg),
));
return Ok(val_reg);
}
let val_reg = self.emit_expression(&assign.right, ctx)?;
ctx.inst(Inst::new(
OpCode::SET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(val_reg),
));
Ok(val_reg)
}
} }
+25 -9
View File
@@ -89,15 +89,31 @@ impl Emitter {
} }
Expression::ComputedMemberExpression(member) => { Expression::ComputedMemberExpression(member) => {
let obj_reg = self.emit_expression(&member.object, ctx)?; let obj_reg = self.emit_expression(&member.object, ctx)?;
let key_reg = self.emit_expression(&member.expression, ctx)?; // 常量字符串键折叠为 IC 静态路径:callee 寄存器独立于对象,this 保留对象。
let callee_reg = ctx.alloc_reg(); if let Some(key) = crate::expr::member::computed_const_key(&member.expression) {
ctx.inst(Inst::new( let idx = ctx.add_constant(Constant::String(key));
OpCode::GET_PROP_DYNAMIC, let key_reg = ctx.alloc_reg();
Operand::Reg(obj_reg), ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
Operand::Reg(key_reg), let callee_reg = ctx.alloc_reg();
Operand::Reg(callee_reg), ctx.inst(Inst::new(
)); OpCode::LOAD_VAR,
(callee_reg, obj_reg) Operand::Reg(callee_reg),
Operand::Reg(obj_reg),
Operand::None,
));
ctx.inst(Inst::ic_get(Operand::Reg(callee_reg), Operand::Reg(key_reg)));
(callee_reg, obj_reg)
} else {
let key_reg = self.emit_expression(&member.expression, ctx)?;
let callee_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(callee_reg),
));
(callee_reg, obj_reg)
}
} }
_ => { _ => {
let callee_reg = self.emit_expression(&call.callee, ctx)?; let callee_reg = self.emit_expression(&call.callee, ctx)?;
+40
View File
@@ -44,6 +44,15 @@ impl Emitter {
fn emit_computed_member_expression( fn emit_computed_member_expression(
&self, member: &oxide_parser::ComputedMemberExpression, ctx: &mut CompileCtx, &self, member: &oxide_parser::ComputedMemberExpression, ctx: &mut CompileCtx,
) -> Result<u32, String> { ) -> Result<u32, String> {
// 常量字符串键折叠为 IC 静态路径:免去运行期键 interning 与慢路径 ordinary_get。
if let Some(key) = computed_const_key(&member.expression) {
let obj_reg = self.emit_expression(&member.object, 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));
ctx.inst(Inst::ic_get(Operand::Reg(obj_reg), Operand::Reg(key_reg)));
return Ok(obj_reg);
}
let obj_reg = self.emit_expression(&member.object, ctx)?; let obj_reg = self.emit_expression(&member.object, ctx)?;
let key_reg = self.emit_expression(&member.expression, ctx)?; let key_reg = self.emit_expression(&member.expression, ctx)?;
let r = ctx.alloc_reg(); let r = ctx.alloc_reg();
@@ -105,3 +114,34 @@ impl Emitter {
} }
} }
} }
/// 判断字符串是否为规范数组下标键(无前导零的纯数字串,可解析为 u32)。
///
/// 数组元素区独立于 shape 槽:此类键经 `set_or_create_prop_value` 写入元素区,
/// 不进 shape 链,IC 判定 `slot < prop_vec_len` 永不命中——折叠零收益且多付探试开销,
/// 故排除。规则与 VM `array_index_from_property_key` 一致。
pub(crate) fn is_array_index_str(s: &str) -> bool {
if s.is_empty() || (s.len() > 1 && s.starts_with('0')) {
return false;
}
s.parse::<u32>().is_ok()
}
/// 提取计算成员表达式的常量字符串键(可折叠为 IC 静态路径)。
///
/// 只接受字符串字面量与无插值模板(单段、无表达式);数字字面量、含插值模板
/// (运行期非常量 / 键求值有副作用)与规范数组下标串返回 `None`,维持 DYNAMIC 路径。
pub(crate) fn computed_const_key(expr: &Expression) -> Option<String> {
let key = match expr {
Expression::StringLiteral(s) => s.value.to_string(),
Expression::TemplateLiteral(tl) if tl.expressions.is_empty() && tl.quasis.len() == 1 => {
tl.quasis[0].value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default()
}
_ => return None,
};
if is_array_index_str(&key) {
None
} else {
Some(key)
}
}
@@ -400,6 +400,35 @@ impl Emitter {
} }
SimpleAssignmentTarget::ComputedMemberExpression(member) => { SimpleAssignmentTarget::ComputedMemberExpression(member) => {
let obj_reg = self.emit_expression(&member.object, ctx)?; let obj_reg = self.emit_expression(&member.object, ctx)?;
// 常量字符串键折叠为 IC 静态路径:MEMBER_INC/DEC 携带 IC 扩展字。
if let Some(key) = crate::expr::member::computed_const_key(&member.expression) {
let idx = ctx.add_constant(Constant::String(key));
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
let val_reg = ctx.alloc_reg();
let op = match update.operator {
UpdateOperator::Increment => OpCode::MEMBER_INC,
UpdateOperator::Decrement => OpCode::MEMBER_DEC,
};
match op {
OpCode::MEMBER_INC => {
ctx.inst(Inst::member_inc(
Operand::Reg(obj_reg),
Operand::Reg(val_reg),
Operand::Reg(key_reg),
));
}
OpCode::MEMBER_DEC => {
ctx.inst(Inst::member_dec(
Operand::Reg(obj_reg),
Operand::Reg(val_reg),
Operand::Reg(key_reg),
));
}
_ => unreachable!(),
}
return Ok(val_reg);
}
let key_reg = self.emit_expression(&member.expression, ctx)?; let key_reg = self.emit_expression(&member.expression, ctx)?;
let val_reg = ctx.alloc_reg(); let val_reg = ctx.alloc_reg();
let op = match update.operator { let op = match update.operator {
+22
View File
@@ -2,6 +2,7 @@
//! 函数:`emit_chainable_expression`、`emit_optional_guard`、`emit_chain_call` 等。 //! 函数:`emit_chainable_expression`、`emit_optional_guard`、`emit_chain_call` 等。
use crate::expr::call::pack_arg_regs; use crate::expr::call::pack_arg_regs;
use crate::expr::member::is_array_index_str;
use crate::{CompileCtx, Emitter}; use crate::{CompileCtx, Emitter};
use oxide_bytecode::module::Constant; use oxide_bytecode::module::Constant;
use oxide_bytecode::opcode::OpCode; use oxide_bytecode::opcode::OpCode;
@@ -44,6 +45,16 @@ impl Emitter {
self.emit_optional_guard(obj_reg, label, ctx)?; self.emit_optional_guard(obj_reg, label, ctx)?;
} }
} }
// 常量字符串键折叠为 IC 静态路径:value 独立寄存器,base 保留供 this 绑定。
if let Some(key) = crate::expr::member::computed_const_key(&member.expression) {
let idx = ctx.add_constant(Constant::String(key));
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
let value_reg = ctx.alloc_reg();
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(value_reg), Operand::Reg(obj_reg), Operand::None));
ctx.inst(Inst::ic_get(Operand::Reg(value_reg), Operand::Reg(key_reg)));
return Ok((value_reg, obj_reg));
}
let key_reg = self.emit_expression(&member.expression, ctx)?; let key_reg = self.emit_expression(&member.expression, ctx)?;
let value_reg = ctx.alloc_reg(); let value_reg = ctx.alloc_reg();
ctx.inst(Inst::new( ctx.inst(Inst::new(
@@ -289,6 +300,17 @@ impl Emitter {
} }
let prop_reg = ctx.alloc_reg(); let prop_reg = ctx.alloc_reg();
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(prop_reg), Operand::Reg(src_reg), Operand::None)); ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(prop_reg), Operand::Reg(src_reg), Operand::None));
// 字符串字面量计算键折叠为 IC 静态路径;标识符/数字键走 DYNAMIC。
if let PropertyKey::StringLiteral(s) = key {
let key_str = s.value.to_string();
if !is_array_index_str(&key_str) {
let key_idx = ctx.add_constant(Constant::String(key_str));
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), key_idx));
ctx.inst(Inst::ic_get(Operand::Reg(prop_reg), Operand::Reg(key_reg)));
return Ok((prop_reg, None, Some(key_reg)));
}
}
let key_reg = self.emit_property_key_expression(key, ctx)?; let key_reg = self.emit_property_key_expression(key, ctx)?;
let val_reg = ctx.alloc_reg(); let val_reg = ctx.alloc_reg();
ctx.inst(Inst::new( ctx.inst(Inst::new(
+34
View File
@@ -43,6 +43,40 @@ fn eval_object_missing_property() {
assert_eq!(eval("({a:1}).b"), "undefined"); assert_eq!(eval("({a:1}).b"), "undefined");
} }
#[test]
fn eval_computed_const_string_key_folds_to_ic() {
// 常量字符串键折叠为 IC 静态路径:读写/复合/模板键/链式访问语义不变。
assert_eq!(eval("var o={a:1}; o[\"a\"]"), "1");
assert_eq!(eval("var o={a:1}; o[\"a\"]=2, o.a"), "2");
assert_eq!(eval("var o={a:1}; o[\"a\"] += 1, o.a"), "2");
assert_eq!(eval("var o={a:1}; o[`a`]"), "1");
assert_eq!(eval("var o={a:{b:4}}; o[\"a\"][\"b\"]"), "4");
assert_eq!(eval("var o={a:2}; o?.[\"a\"]"), "2");
assert_eq!(eval("var o={a:1}; o[\"a\"]++, o.a"), "2");
assert_eq!(eval("var o={a:1}; o[\"a\"] &&= 5, o.a"), "5");
assert_eq!(eval("var p={a:null}; p[\"a\"] ??= 5, p.a"), "5");
}
#[test]
fn eval_computed_const_key_dynamic_keys_stay_dynamic() {
// 动态变量键 / 数字键维持 DYNAMIC 路径,语义一致。
assert_eq!(eval("var s=\"a\", o={a:9}; o[s]"), "9");
assert_eq!(eval("var arr=[1,2]; arr[\"0\"]"), "1");
assert_eq!(eval("var arr=[1,2]; arr[0]=7, arr[0]"), "7");
}
#[test]
fn eval_computed_const_key_method_call_keeps_this() {
// 方法调用接收者折叠:this 绑定保留。
assert_eq!(eval("var o={m(){return this.x}, x:5}; o[\"m\"]()"), "5");
}
#[test]
fn eval_computed_const_key_proto_write() {
// 写 __proto__ 键走拦截路径,原型语义不变。
assert_eq!(eval("var o={}; o[\"__proto__\"]={b:3}, o.b"), "3");
}
#[test] #[test]
fn eval_member_inc_post() { fn eval_member_inc_post() {
assert_eq!(eval("var obj={x:1}; obj.x++; obj.x"), "2"); assert_eq!(eval("var obj={x:1}; obj.x++; obj.x"), "2");