diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index 3f708c32..806666c7 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -668,6 +668,98 @@ fn compile_dynamic_member_ops() { ); } +/// 收集字节码指令序列,跳过 IC 扩展字(3 字)与其它扩展字,逐指令定位。 +fn scan_opcodes(module: &CompiledModule) -> Vec { + 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] fn compile_delete_new_instanceof_in() { let module = diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index cae86762..ede6734c 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -136,80 +136,68 @@ impl Emitter { Ok(val_reg) } } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left { - if let Some(logical_op) = assign.operator.to_logical_operator() { - let store_label = ctx.next_label_id(); - let end_label = ctx.next_label_id(); + // 常量字符串键折叠为 IC 静态路径,与 StaticMemberExpression 分支同构。 + if let Some(key) = crate::expr::member::computed_const_key(&member.expression) { + 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 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 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)), + _ => 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 { if assign.operator != AssignmentOperator::Assign { return Err("compound assignment to private fields not supported".into()); @@ -421,4 +409,93 @@ impl Emitter { 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 { + 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) + } } diff --git a/crates/oxide_emit/src/expr/call.rs b/crates/oxide_emit/src/expr/call.rs index 3b7e381a..40bef76f 100644 --- a/crates/oxide_emit/src/expr/call.rs +++ b/crates/oxide_emit/src/expr/call.rs @@ -89,15 +89,31 @@ impl Emitter { } Expression::ComputedMemberExpression(member) => { let obj_reg = self.emit_expression(&member.object, ctx)?; - 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) + // 常量字符串键折叠为 IC 静态路径:callee 寄存器独立于对象,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 callee_reg = ctx.alloc_reg(); + ctx.inst(Inst::new( + OpCode::LOAD_VAR, + 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)?; diff --git a/crates/oxide_emit/src/expr/member/mod.rs b/crates/oxide_emit/src/expr/member/mod.rs index 08af0c9b..3ae20426 100644 --- a/crates/oxide_emit/src/expr/member/mod.rs +++ b/crates/oxide_emit/src/expr/member/mod.rs @@ -44,6 +44,15 @@ impl Emitter { fn emit_computed_member_expression( &self, member: &oxide_parser::ComputedMemberExpression, ctx: &mut CompileCtx, ) -> Result { + // 常量字符串键折叠为 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 key_reg = self.emit_expression(&member.expression, ctx)?; 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::().is_ok() +} + +/// 提取计算成员表达式的常量字符串键(可折叠为 IC 静态路径)。 +/// +/// 只接受字符串字面量与无插值模板(单段、无表达式);数字字面量、含插值模板 +/// (运行期非常量 / 键求值有副作用)与规范数组下标串返回 `None`,维持 DYNAMIC 路径。 +pub(crate) fn computed_const_key(expr: &Expression) -> Option { + 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) + } +} diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index 3c858acb..177b107b 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -400,6 +400,35 @@ impl Emitter { } SimpleAssignmentTarget::ComputedMemberExpression(member) => { 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 val_reg = ctx.alloc_reg(); let op = match update.operator { diff --git a/crates/oxide_emit/src/shared/chain.rs b/crates/oxide_emit/src/shared/chain.rs index 55e7e80f..584fe236 100644 --- a/crates/oxide_emit/src/shared/chain.rs +++ b/crates/oxide_emit/src/shared/chain.rs @@ -2,6 +2,7 @@ //! 函数:`emit_chainable_expression`、`emit_optional_guard`、`emit_chain_call` 等。 use crate::expr::call::pack_arg_regs; +use crate::expr::member::is_array_index_str; use crate::{CompileCtx, Emitter}; use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; @@ -44,6 +45,16 @@ impl Emitter { 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 value_reg = ctx.alloc_reg(); ctx.inst(Inst::new( @@ -289,6 +300,17 @@ impl Emitter { } let prop_reg = ctx.alloc_reg(); 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 val_reg = ctx.alloc_reg(); ctx.inst(Inst::new( diff --git a/crates/oxide_vm/tests/object_tests.rs b/crates/oxide_vm/tests/object_tests.rs index c81bcee9..90f74796 100644 --- a/crates/oxide_vm/tests/object_tests.rs +++ b/crates/oxide_vm/tests/object_tests.rs @@ -43,6 +43,40 @@ fn eval_object_missing_property() { 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] fn eval_member_inc_post() { assert_eq!(eval("var obj={x:1}; obj.x++; obj.x"), "2");