perf: 常量字符串键计算成员折叠走 IC 静态路径
This commit is contained in:
@@ -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 =
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)?;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user