This repository has been archived on 2026-09-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OxideJS/crates/oxide_vm/tests/concat_n_tests.rs
T
pjh123 9b2e33916a 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 _ 兜底)
2026-08-15 09:08:05 +08:00

158 lines
6.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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);
}