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:
@@ -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