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:
2026-08-15 09:08:05 +08:00
parent aa4c28cdf0
commit 38a4f20743
10 changed files with 423 additions and 0 deletions
+115
View File
@@ -2,6 +2,7 @@ use crate::vm::Vm;
use crate::vm_trace;
use oxide_runtime_api as coercion;
use oxide_types::value::JsValue;
use smallvec::SmallVec;
/// 判断 JsValue 是否为 BigInt 且值为 0(num_bigint 零值判断)。
fn bigint_is_zero(v: &num_bigint::BigInt) -> bool {
@@ -95,6 +96,120 @@ impl Vm {
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 &reg 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)]
pub(crate) fn dispatch_neg(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("NEG rd={} r{}={:?}", rd, a, self.regs[a]);