perf: CONCAT_N 多操作数拼接单趟精确预分配替代逐次 ADD
新增 CONCAT_N opcode(0x93):连续加号左结合链摊平为单条指令, 字符串模式单趟精确总长预分配,消除 N-2 次中间串分配与逐次整串重拷贝。 - emit 只下钻 left 链、right 括号不拆(a+(b+c) 退化 ADD),求值序与 f64 结合性保持左结合原义;摊平上限 14 防超长链寄存器压力 - dispatch 两阶段:数值前缀逐项复用 ADD 判定序列(int 溢出升 double、 BigInt 混合 TypeError、对象 ToPrimitive 副作用序),遇字符串转入 字符串模式单趟预分配拼接(concat_n_strings) - RegAlloc graph/rewrite 登记 CONCAT_N ext 操作数(漏收集即错值); ic_helper ext_word_count 登记(clear_ic_caches 字节定位) - contract golden 显式 CONCAT_N 分支(规避 canonical_inst _ 兜底)
This commit is contained in:
@@ -450,6 +450,12 @@ define_opcodes! {
|
|||||||
SET_PROP_BATCH = 0x92 => "SET_PROP_BATCH",
|
SET_PROP_BATCH = 0x92 => "SET_PROP_BATCH",
|
||||||
def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)],
|
def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)],
|
||||||
pure = false, jump = false, term = false, ic = false,
|
pure = false, jump = false, term = false, ic = false,
|
||||||
|
// 多操作数拼接(连续 `+` 左结合链摊平):rd=结果,a=首操作数,ext=[n, op2..opn],
|
||||||
|
// n=操作数总数,ext 字数 = 1+(n-1) = n;op 字无高位标记(纯 vreg,≤255),
|
||||||
|
// SpreadArgs 直接解析。与 ADD 同标纯(含 ToPrimitive 副作用仍静态可删,语义同 ADD)。
|
||||||
|
CONCAT_N = 0x93 => "CONCAT_N",
|
||||||
|
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [SlotSpec::Slot(Slot::A), SlotSpec::SpreadArgs],
|
||||||
|
pure = true, jump = false, term = false, ic = false,
|
||||||
|
|
||||||
// ── 成员更新 (0x59-0x62) ──
|
// ── 成员更新 (0x59-0x62) ──
|
||||||
MEMBER_INC = 0x59 => "MEMBER_INC", // 结果写 a 槽(val 槽原地更新)
|
MEMBER_INC = 0x59 => "MEMBER_INC", // 结果写 a 槽(val 槽原地更新)
|
||||||
@@ -783,6 +789,7 @@ mod tests {
|
|||||||
assert_eq!(OpCode::CREATE_REST_ARRAY as u8, 0x6E);
|
assert_eq!(OpCode::CREATE_REST_ARRAY as u8, 0x6E);
|
||||||
assert_eq!(OpCode::DEFINE_ACCESSOR_DYNAMIC as u8, 0x7F);
|
assert_eq!(OpCode::DEFINE_ACCESSOR_DYNAMIC as u8, 0x7F);
|
||||||
assert_eq!(OpCode::SET_PROP_BATCH as u8, 0x92);
|
assert_eq!(OpCode::SET_PROP_BATCH as u8, 0x92);
|
||||||
|
assert_eq!(OpCode::CONCAT_N as u8, 0x93);
|
||||||
assert_eq!(OpCode::ADD.to_string(), "ADD");
|
assert_eq!(OpCode::ADD.to_string(), "ADD");
|
||||||
assert_eq!(OpCode::COMPOUND_MEMBER_EXP.to_string(), "COMPOUND_MEMBER_EXP");
|
assert_eq!(OpCode::COMPOUND_MEMBER_EXP.to_string(), "COMPOUND_MEMBER_EXP");
|
||||||
assert_eq!(OpCode::MOV.to_string(), "MOV");
|
assert_eq!(OpCode::MOV.to_string(), "MOV");
|
||||||
|
|||||||
@@ -30,6 +30,25 @@ impl Emitter {
|
|||||||
fn emit_binary_expression(
|
fn emit_binary_expression(
|
||||||
&self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx,
|
&self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx,
|
||||||
) -> Result<u32, String> {
|
) -> Result<u32, String> {
|
||||||
|
// Addition 左结合链摊平:≥3 操作数合并为单条 CONCAT_N(单趟预分配拼接,
|
||||||
|
// 消除 N-2 次中间串分配)。只下钻 left 链、right 括号不拆(a+(b+c) 退化 ADD),
|
||||||
|
// 保 f64 结合性与源码求值序。
|
||||||
|
if bin.operator == BinaryOperator::Addition {
|
||||||
|
let mut operands = Vec::new();
|
||||||
|
collect_add_operands(&bin.left, &mut operands);
|
||||||
|
operands.push(&bin.right);
|
||||||
|
// 上限防寄存器压力:CONCAT_N 单点读全部操作数(须同时存活),超长链回退
|
||||||
|
// 左结合 ADD(寄存器随链复用,纯表达式寄存器占用有界)。
|
||||||
|
if operands.len() >= 3 && operands.len() <= MAX_CONCAT_N_OPERANDS {
|
||||||
|
let mut regs = Vec::with_capacity(operands.len());
|
||||||
|
for op in &operands {
|
||||||
|
regs.push(self.emit_expression(op, ctx)?);
|
||||||
|
}
|
||||||
|
let result_reg = ctx.alloc_reg();
|
||||||
|
ctx.inst(Inst::concat_n(Operand::Reg(result_reg), ®s));
|
||||||
|
return Ok(result_reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
let left = self.emit_expression(&bin.left, ctx)?;
|
let left = self.emit_expression(&bin.left, ctx)?;
|
||||||
let right = self.emit_expression(&bin.right, ctx)?;
|
let right = self.emit_expression(&bin.right, ctx)?;
|
||||||
let op = match bin.operator {
|
let op = match bin.operator {
|
||||||
@@ -554,3 +573,20 @@ impl Emitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CONCAT_N 摊平的操作数上限:CONCAT_N 单点读全部操作数(须同时存活),寄存器占用
|
||||||
|
/// = 操作数 + 结果 ≤ 15;超过上限回退左结合 ADD(超长链保持寄存器随链复用属性)。
|
||||||
|
const MAX_CONCAT_N_OPERANDS: usize = 14;
|
||||||
|
|
||||||
|
/// 收集 Addition 左结合链的操作数(源码求值序):只下钻 left 链,right 恒为单个
|
||||||
|
/// 操作数(即使自身是 Addition 也不拆——括号右结合保持 f64 结合性不引入偏差)。
|
||||||
|
fn collect_add_operands<'expr, 'r>(expr: &'r Expression<'expr>, out: &mut Vec<&'r Expression<'expr>>) {
|
||||||
|
if let Expression::BinaryExpression(bin) = expr {
|
||||||
|
if bin.operator == BinaryOperator::Addition {
|
||||||
|
collect_add_operands(&bin.left, out);
|
||||||
|
out.push(&bin.right);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push(expr);
|
||||||
|
}
|
||||||
|
|||||||
@@ -556,6 +556,7 @@ mod tests {
|
|||||||
Inst::compound_member_ushr(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3))
|
Inst::compound_member_ushr(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3))
|
||||||
}
|
}
|
||||||
OpCode::TEMPLATE_STR => Inst::template_str(Operand::Reg(1), 2, 10, &[0x1234, 0x8000_0000 | 5]),
|
OpCode::TEMPLATE_STR => Inst::template_str(Operand::Reg(1), 2, 10, &[0x1234, 0x8000_0000 | 5]),
|
||||||
|
OpCode::CONCAT_N => Inst::concat_n(Operand::Reg(1), &[2, 5, 9]),
|
||||||
OpCode::GET_PRIVATE => {
|
OpCode::GET_PRIVATE => {
|
||||||
Inst::get_private(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 7, 99)
|
Inst::get_private(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 7, 99)
|
||||||
}
|
}
|
||||||
@@ -723,6 +724,8 @@ mod tests {
|
|||||||
assert_contract(OpCode::HALT, None, &[0], false);
|
assert_contract(OpCode::HALT, None, &[0], false);
|
||||||
// 模板字符串:表达式寄存器来自 ext
|
// 模板字符串:表达式寄存器来自 ext
|
||||||
assert_contract(OpCode::TEMPLATE_STR, Some(1), &[5], true);
|
assert_contract(OpCode::TEMPLATE_STR, Some(1), &[5], true);
|
||||||
|
// 多操作数拼接:a 槽首操作数 + ext[1..] 后续操作数(SpreadArgs 解析)
|
||||||
|
assert_contract(OpCode::CONCAT_N, Some(1), &[2, 5, 9], true);
|
||||||
// 小语言特性
|
// 小语言特性
|
||||||
assert_contract(OpCode::DELETE_PROP_STATIC, None, &[1], false);
|
assert_contract(OpCode::DELETE_PROP_STATIC, None, &[1], false);
|
||||||
assert_contract(OpCode::DELETE_PROP_DYNAMIC, None, &[1, 3], false);
|
assert_contract(OpCode::DELETE_PROP_DYNAMIC, None, &[1, 3], false);
|
||||||
|
|||||||
@@ -258,6 +258,23 @@ impl Inst {
|
|||||||
Self::new(OpCode::SET_PROP_BATCH, target, value, Operand::Imm(slot))
|
Self::new(OpCode::SET_PROP_BATCH, target, value, Operand::Imm(slot))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 多操作数拼接(连续 `+` 表达式摊平):rd=结果,首操作数放 a 槽,
|
||||||
|
/// ext=[n, op2..opn](n=操作数总数,ext 字数 = 1+(n-1) = n)。
|
||||||
|
/// 操作数按源码求值序排列,emit 层保证 ≥3 个(<3 退化 ADD);op 字无高位标记。
|
||||||
|
pub fn concat_n(dst: Operand, operands: &[u32]) -> Self {
|
||||||
|
debug_assert!(operands.len() >= 3, "CONCAT_N 仅用于 ≥3 操作数摊平");
|
||||||
|
let mut ext = SmallVec::with_capacity(operands.len());
|
||||||
|
ext.push(operands.len() as u32);
|
||||||
|
ext.extend_from_slice(&operands[1..]);
|
||||||
|
Self {
|
||||||
|
op: OpCode::CONCAT_N,
|
||||||
|
rd: dst,
|
||||||
|
a: Operand::Reg(operands[0]),
|
||||||
|
b: Operand::None,
|
||||||
|
ext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// TEMPLATE_STR:变长 ext。首字打包 `(segment_count<<16) | total_len_hint`,
|
/// TEMPLATE_STR:变长 ext。首字打包 `(segment_count<<16) | total_len_hint`,
|
||||||
/// 后续每 quasi 一项 `quasi_const_idx & 0x7FFF_FFFF`,其后若跟表达式再一项 `0x8000_0000 | expr_reg`。
|
/// 后续每 quasi 一项 `quasi_const_idx & 0x7FFF_FFFF`,其后若跟表达式再一项 `0x8000_0000 | expr_reg`。
|
||||||
pub fn template_str(dst: Operand, segment_count: u32, total_len_hint: u16, parts: &[u32]) -> Self {
|
pub fn template_str(dst: Operand, segment_count: u32, total_len_hint: u16, parts: &[u32]) -> Self {
|
||||||
@@ -591,4 +608,15 @@ mod tests {
|
|||||||
assert_eq!(inst.ext[1], 0x1234);
|
assert_eq!(inst.ext[1], 0x1234);
|
||||||
assert_eq!(inst.ext[2], 0x8000_0000 | 5);
|
assert_eq!(inst.ext[2], 0x8000_0000 | 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_packs_n_header_and_operand_regs() {
|
||||||
|
// 6 操作数:a 槽=op1,ext=[6, op2..op6]
|
||||||
|
let inst = Inst::concat_n(Operand::Reg(1), &[2, 5, 9, 12, 300, 7]);
|
||||||
|
assert_eq!(inst.op, OpCode::CONCAT_N);
|
||||||
|
assert_eq!(inst.rd, Operand::Reg(1));
|
||||||
|
assert_eq!(inst.a, Operand::Reg(2));
|
||||||
|
assert_eq!(inst.b, Operand::None);
|
||||||
|
assert_eq!(inst.ext.as_slice(), &[6, 5, 9, 12, 300, 7]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,16 @@ pub(super) fn collect_real_vregs(f: &IRFunction) -> BTreeSet<u32> {
|
|||||||
real.insert(r);
|
real.insert(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// CONCAT_N:ext[1..] 每个字是完整操作数 vreg(无高位标记)——漏收集则
|
||||||
|
// RegAlloc 不分配该 vreg,物理号冲突 → 错值。
|
||||||
|
if inst.op == OpCode::CONCAT_N {
|
||||||
|
for &w in inst.ext.iter().skip(1) {
|
||||||
|
let r = w & 0x7FFF_FFFF;
|
||||||
|
if r != 0 {
|
||||||
|
real.insert(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
real
|
real
|
||||||
}
|
}
|
||||||
@@ -474,4 +484,21 @@ mod tests {
|
|||||||
assert!(g.nodes.contains_key(&254), "vreg 254 建节点");
|
assert!(g.nodes.contains_key(&254), "vreg 254 建节点");
|
||||||
assert_eq!(g.nodes[&254].pre_color, None, "254 非预着色");
|
assert_eq!(g.nodes[&254].pre_color, None, "254 非预着色");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_ext_operands_collected_as_real() {
|
||||||
|
// CONCAT_N 的 ext 操作数 vreg 必须进入真实集合(漏收集 → 未染色 → 错值)
|
||||||
|
let mut f = empty_function();
|
||||||
|
f.insts.push(Inst::concat_n(Operand::Reg(1), &[2, 5, 9, 300]));
|
||||||
|
f.insts
|
||||||
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None));
|
||||||
|
let real = collect_real_vregs(&f);
|
||||||
|
for r in [1u32, 2, 5, 9, 300] {
|
||||||
|
assert!(real.contains(&r), "vreg {r} 应被 CONCAT_N 收集");
|
||||||
|
}
|
||||||
|
let g = build_graph(&f);
|
||||||
|
for r in [1u32, 2, 5, 9, 300] {
|
||||||
|
assert!(g.nodes.contains_key(&r), "vreg {r} 建节点");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,6 +246,15 @@ fn rewrite_inst(
|
|||||||
}
|
}
|
||||||
ext
|
ext
|
||||||
}
|
}
|
||||||
|
// CONCAT_N:ext[1..] 每个字是完整操作数 vreg(无高位标记),直接重映射,
|
||||||
|
// 否则 RegAlloc 后读旧 vreg 号对应物理槽(错值)。
|
||||||
|
OpCode::CONCAT_N => {
|
||||||
|
let mut ext = inst.ext.clone();
|
||||||
|
for seg in ext.iter_mut().skip(1) {
|
||||||
|
*seg = remap_ext_reg(*seg, slot_color, map);
|
||||||
|
}
|
||||||
|
ext
|
||||||
|
}
|
||||||
_ => inst.ext.clone(),
|
_ => inst.ext.clone(),
|
||||||
};
|
};
|
||||||
// 调用点首参槽改指 arg_window_base(桥接后)
|
// 调用点首参槽改指 arg_window_base(桥接后)
|
||||||
@@ -457,6 +466,23 @@ mod tests {
|
|||||||
assert_eq!(f.insts[0].ext.as_slice(), &[3 << 16, 0, 0x8000_0000 | 200, 1]);
|
assert_eq!(f.insts[0].ext.as_slice(), &[3 << 16, 0, 0x8000_0000 | 200, 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_ext_operands_remapped() {
|
||||||
|
// CONCAT_N ext[1..] 操作数 vreg 必须重映射到物理号(含 spill use 点 fresh 色)
|
||||||
|
let inst = Inst::concat_n(Operand::Reg(1), &[2, 300, 9]);
|
||||||
|
let mut map = AllocMap::new();
|
||||||
|
map.map.insert(1, Alloc::Phys(2));
|
||||||
|
map.map.insert(2, Alloc::Phys(3));
|
||||||
|
map.map.insert(300, Alloc::Phys(200));
|
||||||
|
map.map.insert(9, Alloc::Phys(11));
|
||||||
|
let f = rewrite_with_map(vec![inst], map);
|
||||||
|
|
||||||
|
assert_eq!(f.insts[0].op, OpCode::CONCAT_N);
|
||||||
|
assert_eq!(f.insts[0].rd, Operand::Reg(2));
|
||||||
|
assert_eq!(f.insts[0].a, Operand::Reg(3));
|
||||||
|
assert_eq!(f.insts[0].ext.as_slice(), &[3, 200, 11]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scattered_args_insert_mov_bridge() {
|
fn scattered_args_insert_mov_bridge() {
|
||||||
let insts = vec![Inst::call(Operand::Reg(2), Operand::Reg(3), Operand::Reg(4), 3)];
|
let insts = vec![Inst::call(Operand::Reg(2), Operand::Reg(3), Operand::Reg(4), 3)];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::vm::Vm;
|
|||||||
use crate::vm_trace;
|
use crate::vm_trace;
|
||||||
use oxide_runtime_api as coercion;
|
use oxide_runtime_api as coercion;
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
/// 判断 JsValue 是否为 BigInt 且值为 0(num_bigint 零值判断)。
|
/// 判断 JsValue 是否为 BigInt 且值为 0(num_bigint 零值判断)。
|
||||||
fn bigint_is_zero(v: &num_bigint::BigInt) -> bool {
|
fn bigint_is_zero(v: &num_bigint::BigInt) -> bool {
|
||||||
@@ -95,6 +96,120 @@ impl Vm {
|
|||||||
self.new_string_owned(buf)
|
self.new_string_owned(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CONCAT_N:多操作数拼接(连续 `+` 左结合链摊平)的两阶段 dispatch。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. 读 ext 收集全部操作数寄存器(pc 推进约定同 TEMPLATE_STR:主循环已越过指令,
|
||||||
|
/// 此处先读 ext[0]=n 并推进,再逐操作数推进,总推进 n 个扩展字)。
|
||||||
|
/// 2. 阶段 1 数值前缀折叠:逐项复用 ADD 判定序列(int 溢出升 double / double /
|
||||||
|
/// BigInt 混合 TypeError / 对象 ToPrimitive 副作用顺序),与左结合两两 ADD
|
||||||
|
/// 逐点一致;遇字符串即转入阶段 2。
|
||||||
|
/// 3. 阶段 2 字符串模式单趟预分配:剩余操作数按序 coerce 后一次写齐,消除
|
||||||
|
/// N-2 次中间串分配。
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - emit 层保证 n ≥ 3;此处 n<2 防御退化(返回首操作数)。
|
||||||
|
/// - Symbol 操作数行为与 concat_strings 一致(push_to_string 无 symbol 分支)。
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// 写 regs[rd];字符串路径新建一个会话字符串。
|
||||||
|
#[inline(never)]
|
||||||
|
pub(crate) fn dispatch_concat_n(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||||
|
vm_trace!("CONCAT_N rd={}", rd);
|
||||||
|
let n = self.bytecode[self.pc] as usize;
|
||||||
|
self.pc += 1;
|
||||||
|
let mut ops = SmallVec::<[usize; 8]>::new();
|
||||||
|
ops.push(a);
|
||||||
|
for _ in 0..n.saturating_sub(1) {
|
||||||
|
let reg = (self.bytecode[self.pc] & 0x7FFF_FFFF) as usize;
|
||||||
|
self.pc += 1;
|
||||||
|
ops.push(reg);
|
||||||
|
}
|
||||||
|
if ops.len() < 2 {
|
||||||
|
self.regs[rd] = self.regs[ops[0]];
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// ── 阶段 1:数值前缀折叠(与左结合两两 ADD 逐点一致)──
|
||||||
|
let mut acc = self.regs[ops[0]];
|
||||||
|
let mut idx = 1;
|
||||||
|
while idx < ops.len() {
|
||||||
|
let next = self.regs[ops[idx]];
|
||||||
|
if acc.is_int() && next.is_int() {
|
||||||
|
acc = int_add(acc.as_int(), next.as_int());
|
||||||
|
idx += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if acc.is_double() && next.is_double() {
|
||||||
|
acc = JsValue::float(acc.as_double() + next.as_double());
|
||||||
|
idx += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if acc.is_bigint() && next.is_bigint() {
|
||||||
|
acc = self.new_bigint(self.bigint_value(acc) + self.bigint_value(next));
|
||||||
|
idx += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 快路径失败才 coerce:acc 已原语零副作用;next 对象 ToPrimitive 在此触发,
|
||||||
|
// 顺序与左结合 ADD 一致。
|
||||||
|
let lhs = self.coerce_primitive_bounded(acc, false)?;
|
||||||
|
let rhs = self.coerce_primitive_bounded(next, false)?;
|
||||||
|
if lhs.is_string() || rhs.is_string() {
|
||||||
|
// ── 阶段 2:字符串模式单趟预分配 ──
|
||||||
|
self.regs[rd] = self.concat_n_strings(lhs, rhs, &ops[idx + 1..])?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if lhs.is_bigint() && rhs.is_bigint() {
|
||||||
|
acc = self.new_bigint(self.bigint_value(lhs) + self.bigint_value(rhs));
|
||||||
|
idx += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if lhs.is_bigint() != rhs.is_bigint() {
|
||||||
|
// 包装对象 coerce 后暴露 BigInt:混合加法必须抛 TypeError(同 ADD 抛点)。
|
||||||
|
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
|
||||||
|
}
|
||||||
|
let ln = coercion::to_number(lhs);
|
||||||
|
let rn = coercion::to_number(rhs);
|
||||||
|
acc = JsValue::float(ln + rn);
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
self.regs[rd] = acc;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 字符串模式单趟预分配拼接:l/r 已 coerce,剩余操作数按序 coerce 后一次写齐。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. 剩余操作数按序 coerce(对象 ToPrimitive 副作用顺序与左结合一致,抛错点先于分配)。
|
||||||
|
/// 2. 精确总长:字符串操作数取字节长,非字符串给 32 字节余量(int/double/bool/null/
|
||||||
|
/// undefined 的十进制文本上界;BigInt 超长时 String 自动扩容兜底)。
|
||||||
|
/// 3. 单趟 push_to_string + new_string_owned(零二次拷贝)。
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// 新建一个会话字符串。
|
||||||
|
fn concat_n_strings(&mut self, lhs: JsValue, rhs: JsValue, rest: &[usize]) -> Result<JsValue, String> {
|
||||||
|
let mut parts = SmallVec::<[JsValue; 8]>::new();
|
||||||
|
parts.push(lhs);
|
||||||
|
parts.push(rhs);
|
||||||
|
for ® in rest {
|
||||||
|
let v = self.regs[reg];
|
||||||
|
let prim = if v.is_object() { self.coerce_primitive_bounded(v, false)? } else { v };
|
||||||
|
parts.push(prim);
|
||||||
|
}
|
||||||
|
let cap = parts.iter().fold(0usize, |acc, p| {
|
||||||
|
acc + if p.is_string() {
|
||||||
|
// SAFETY: p 是字符串值。
|
||||||
|
unsafe { (*p.as_string_ptr()).len() }
|
||||||
|
} else {
|
||||||
|
32
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let mut buf = String::with_capacity(cap);
|
||||||
|
for p in &parts {
|
||||||
|
coercion::push_to_string(*p, &mut buf);
|
||||||
|
}
|
||||||
|
Ok(self.new_string_owned(buf))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn dispatch_neg(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
pub(crate) fn dispatch_neg(&mut self, rd: usize, a: usize) -> Result<(), String> {
|
||||||
vm_trace!("NEG rd={} r{}={:?}", rd, a, self.regs[a]);
|
vm_trace!("NEG rd={} r{}={:?}", rd, a, self.regs[a]);
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ fn ext_word_count(bytecode: &[Instr], pc: usize) -> usize {
|
|||||||
let header = bytecode.get(pc + 1).copied().unwrap_or(0);
|
let header = bytecode.get(pc + 1).copied().unwrap_or(0);
|
||||||
1 + ((header >> 16) & 0xFFFF) as usize
|
1 + ((header >> 16) & 0xFFFF) as usize
|
||||||
}
|
}
|
||||||
|
// CONCAT_N:ext[0]=n=操作数总数,ext 字数 = 1+(n-1) = n。
|
||||||
|
OpCode::CONCAT_N => bytecode.get(pc + 1).copied().unwrap_or(0) as usize,
|
||||||
OpCode::NEW_OBJECT => opcode::a(bytecode[pc]) as usize,
|
OpCode::NEW_OBJECT => opcode::a(bytecode[pc]) as usize,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
@@ -287,3 +289,21 @@ fn ic_set_hit_own_poly(obj: &mut JsObject, bytecode: &[Instr], ext_pc: usize, va
|
|||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_ext_word_count_advances_by_n() {
|
||||||
|
// CONCAT_N rd=1, a=2, ext=[3, 5, 9]:ext[0]=n=3,总 ext 字数 = 3
|
||||||
|
let mut bytecode = vec![opcode::encode(OpCode::CONCAT_N, 1, 2, 0), 3, 5, 9];
|
||||||
|
assert_eq!(ext_word_count(&bytecode, 0), 3, "ext 字数 = n");
|
||||||
|
|
||||||
|
// clear_ic_caches 跳过 CONCAT_N 扩展字,不误当 IC 字清零
|
||||||
|
bytecode.push(opcode::encode(OpCode::NOP, 0, 0, 0));
|
||||||
|
clear_ic_caches(&mut bytecode);
|
||||||
|
assert_eq!(bytecode[1..4], [3, 5, 9], "CONCAT_N ext 字保持原值");
|
||||||
|
assert_eq!(bytecode.len(), 5, "CONCAT_N 指令 + 3 ext + NOP");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1342,6 +1342,10 @@ impl Vm {
|
|||||||
self.dispatch_add(rd, a, b)?;
|
self.dispatch_add(rd, a, b)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpCode::CONCAT_N => {
|
||||||
|
self.dispatch_concat_n(rd, a)?;
|
||||||
|
}
|
||||||
|
|
||||||
OpCode::SUB => {
|
OpCode::SUB => {
|
||||||
binary_arith!(self, a, b, rd, -, false);
|
binary_arith!(self, a, b, rd, -, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
//! CONCAT_N 多操作数拼接(连续 `+` 左结合链摊平)语义回归测试。
|
||||||
|
//!
|
||||||
|
//! 覆盖:三形态混合锚点(数值前缀折叠 → 字符串模式单趟拼接)、BigInt 混合抛错点、
|
||||||
|
//! 对象 ToPrimitive 副作用顺序、Symbol 行为与二元 ADD 一致、f64 结合性(右括号不拆)、
|
||||||
|
//! 求值顺序、2 操作数与 3+ 操作数等价性、长链拼接正确性。
|
||||||
|
|
||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
|
||||||
|
let allocator = oxide_parser::Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||||
|
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {e}"))?;
|
||||||
|
vm.run(&module)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_str(vm: &Vm, val: JsValue) -> String {
|
||||||
|
vm.lookup_str(val).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 按 CLI 语义格式化结果:字符串取内容(不带引号),数值/BigInt 走 Display。
|
||||||
|
fn fmt(vm: &Vm, val: JsValue) -> String {
|
||||||
|
if val.is_string() {
|
||||||
|
// SAFETY: val 已确认是字符串值。
|
||||||
|
unsafe { (*val.as_string_ptr()).data.clone() }
|
||||||
|
} else if val.is_bigint() {
|
||||||
|
format!("{}", vm.bigint_value(val))
|
||||||
|
} else {
|
||||||
|
format!("{val}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_str(source: &str) -> String {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let val = eval(&mut vm, source).expect("eval ok");
|
||||||
|
fmt(&vm, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_three_form_mixed_anchors() {
|
||||||
|
// 数值前缀折叠保证 1+2+"3" = "33"(非 "123");字符串开头整链拼接;中缀字符串同理。
|
||||||
|
assert_eq!(eval_str("1 + 2 + \"3\""), "33");
|
||||||
|
assert_eq!(eval_str("\"1\" + 2 + 3"), "123");
|
||||||
|
assert_eq!(eval_str("1 + \"2\" + 3"), "123");
|
||||||
|
assert_eq!(eval_str("\"a\" + 1 + 2"), "a12");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_pure_numeric_folds_like_left_assoc() {
|
||||||
|
// 全数值链走阶段 1 折叠:int 溢出升 double 与两两 ADD 逐点一致。
|
||||||
|
assert_eq!(eval_str("1 + 2 + 3 + 4 + 5"), "15");
|
||||||
|
assert_eq!(eval_str("2147483647 + 1 + 2147483647"), "4294967295");
|
||||||
|
assert_eq!(eval_str("1 + 2.5 + 3"), "6.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_bigint_mix_semantics() {
|
||||||
|
// 全 BigInt 链折叠;BigInt 混合在第一个混合点抛 TypeError(与 (1+2n) 同位置);
|
||||||
|
// BigInt + 字符串开头走字符串模式。
|
||||||
|
assert_eq!(eval_str("1n + 2n + 3n"), "6");
|
||||||
|
let err = eval(&mut Vm::new(), "1 + 2n + \"a\"").unwrap_err();
|
||||||
|
assert!(err.contains("TypeError"), "expected TypeError, got {err}");
|
||||||
|
assert_eq!(eval_str("1n + \"a\" + 2"), "1a2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_object_valueof_side_effect_order() {
|
||||||
|
// 操作数按源码序 coerce(左结合顺序),valueOf 副作用序保持。
|
||||||
|
assert_eq!(
|
||||||
|
eval_str(
|
||||||
|
"var log = []; var o1 = { valueOf: function () { log.push(1); return 'x' } }; \
|
||||||
|
var o2 = { valueOf: function () { log.push(2); return 'y' } }; \
|
||||||
|
(o1 + o2 + 'z') + '|' + log.join(',')"
|
||||||
|
),
|
||||||
|
"xyz|1,2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_call_side_effect_order() {
|
||||||
|
// f()+g()+h() 求值序 f→g→h(摊平后按序 emit,顺序保持)。
|
||||||
|
assert_eq!(
|
||||||
|
eval_str(
|
||||||
|
"var log = []; \
|
||||||
|
function f() { log.push('f'); return 'f' } \
|
||||||
|
function g() { log.push('g'); return 'g' } \
|
||||||
|
function h() { log.push('h'); return 'h' } \
|
||||||
|
(f() + g() + h()) + '|' + log.join(',')"
|
||||||
|
),
|
||||||
|
"fgh|f,g,h"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_symbol_matches_binary_add_behavior() {
|
||||||
|
// Symbol 在字符串模式下被静默忽略(既有 concat_strings 行为),CONCAT_N 与二元
|
||||||
|
// ADD 结果逐字节一致,不放大不修复。
|
||||||
|
let flat = eval_str("var s = Symbol(); 'a' + s + 'b'");
|
||||||
|
let binary = eval_str("var s = Symbol(); ('a' + s) + 'b'");
|
||||||
|
assert_eq!(flat, binary);
|
||||||
|
assert_eq!(flat, "ab");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_right_parentheses_not_flattened() {
|
||||||
|
// 右括号不拆:0.1+0.2+0.3 左结合(CONCAT_N)与 0.1+(0.2+0.3)(ADD 链)结果不同,
|
||||||
|
// 证明摊平只发生在 left 链、右括号保持原结合性。
|
||||||
|
assert_eq!(eval_str("0.1 + 0.2 + 0.3"), "0.6000000000000001");
|
||||||
|
assert_eq!(eval_str("0.1 + (0.2 + 0.3)"), "0.6");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_two_and_multi_operand_equivalence() {
|
||||||
|
// 同一语义表达式以 2 操作数(ADD)与 3+ 操作数(CONCAT_N)两种写法输出一致。
|
||||||
|
assert_eq!(eval_str("1 + 2 + 3"), eval_str("(1 + 2) + 3"));
|
||||||
|
assert_eq!(eval_str("'a' + 'b' + 'c'"), eval_str("('a' + 'b') + 'c'"));
|
||||||
|
assert_eq!(eval_str("1.5 + 2.5 + 3.5"), eval_str("(1.5 + 2.5) + 3.5"));
|
||||||
|
assert_eq!(eval_str("1 + 2 + 3 + 4 + 5 + 6"), "21");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_compound_rhs_chain() {
|
||||||
|
// `s += a+b+c`:rhs 摊平为单条 CONCAT_N,COMPOUND_ADD 读旧值拼接;
|
||||||
|
// 单操作数 rhs(`s += j`)不触达摊平,仍走 COMPOUND_ADD。
|
||||||
|
assert_eq!(eval_str("var s = ''; s += 'a' + 'b' + 'c'; s"), "abc");
|
||||||
|
assert_eq!(eval_str("var s = ''; for (var j = 0; j < 5; j++) { s += j } s"), "01234");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_large_string_builds_correctly() {
|
||||||
|
// 长串多段拼接:长度与逐字符位置正确(结果串单趟成型)。
|
||||||
|
assert_eq!(eval_str("('x'.repeat(1000) + 'a' + 'b' + 'c').length"), "1003");
|
||||||
|
assert_eq!(eval_str("('x'.repeat(1000) + 'a' + 'b' + 'c').charCodeAt(1000)"), "97");
|
||||||
|
assert_eq!(eval_str("('x'.repeat(1000) + 'a' + 'b' + 'c').charCodeAt(1002)"), "99");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_single_and_two_operand_degrades() {
|
||||||
|
// 1/2 操作数不触达 CONCAT_N(emit 层退化 ADD),行为不变。
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
assert_eq!(eval(&mut vm, "1").unwrap().as_int(), 1);
|
||||||
|
assert_eq!(eval(&mut vm, "1 + 2").unwrap().as_int(), 3);
|
||||||
|
let ab = eval(&mut vm, "\"a\" + \"b\"").unwrap();
|
||||||
|
assert_eq!(to_str(&vm, ab), "ab");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn concat_n_emit_registers_bounded_for_long_chain() {
|
||||||
|
// 超长纯链部分摊平:CONCAT_N 摊平上限保证单点同时存活寄存器 ≤15,纯表达式
|
||||||
|
// 链的寄存器复用属性保持(编译不因摊平而耗尽寄存器)。
|
||||||
|
let allocator = oxide_parser::Allocator::default();
|
||||||
|
let source = std::iter::repeat("1").take(40).collect::<Vec<_>>().join(" + ");
|
||||||
|
let program = oxide_parser::parse(&allocator, &source).expect("parse ok");
|
||||||
|
let module = Compiler::new().compile(&program).expect("compile ok");
|
||||||
|
assert!(module.n_registers < 16, "long chain registers {}, expected < 16", module.n_registers);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user