f2535ffc77
JsString 拆 Flat/Cons 变体:Cons 经独立 ConsNode 保存左右子节点 + O(1) 字节长 + 惰性扁平化产物缓存(OnceLock 原子发布,产物挂节点生命周期)。 concat_strings 接线:总长 ≤ 128B 的小链急切扁平(与 rope 前同款,拼接即 消费场景零开销),大链 O(1) 链接——大段重复拼接从 O(n²) 拷贝降为 O(n) 链接,文本在消费点(.length/==/string_ref/方法族)首次扁平化后缓存。 GC 兼容:mark 收口 mark_string_live 传播 Cons 子节点与产物闭包(显式栈 防深链爆栈);sweep/full_reset 共用 drop 辅助连带释放产物;节点字节记账 用 O(1) byte_len。rope 节点登记 session 字符串表,与执行期字符串 GC (指令边界触发)天然兼容。JsString 结构 32B 与 rope 前同 bin,小串路径 零结构回退;unsafe impl Send/Sync 论证同 StringPtr(perm 恒 Flat)。
168 lines
6.8 KiB
Rust
168 lines
6.8 KiB
Rust
//! 二元 ConsString(rope)语义回归测试。
|
||
//!
|
||
//! 覆盖:`+`/`+=` 拼接产物(rope 节点)在各消费点(`==`/`.length`/string 方法/
|
||
//! for-of/属性键/模板/JSON/展开)的惰性扁平化正确性、数字/对象叶子混合、CONCAT_N
|
||
//! 产物作 rope 叶子、低 GC 阈值下跨执行期字符串回收的存活与 mark 传播。
|
||
|
||
use oxide_compiler::compiler::Compiler;
|
||
use oxide_kernel::kernel::{KernelConfig, KernelCore};
|
||
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 eval_str(source: &str) -> String {
|
||
let mut vm = Vm::new();
|
||
let val = eval(&mut vm, source).expect("eval ok");
|
||
fmt(&vm, val)
|
||
}
|
||
|
||
fn fmt(vm: &Vm, val: JsValue) -> String {
|
||
if val.is_string() {
|
||
vm.lookup_str(val).unwrap_or_default()
|
||
} else if val.is_bigint() {
|
||
format!("{}", vm.bigint_value(val))
|
||
} else {
|
||
format!("{val}")
|
||
}
|
||
}
|
||
|
||
/// 低 GC 阈值 VM:水位=1 使指令边界检查几乎每指令触发,压 rope 的 mark 传播与
|
||
/// 跨回收存活路径。
|
||
fn vm_with_tiny_threshold() -> Vm {
|
||
let mut cfg = KernelConfig::minimal();
|
||
cfg.set_session_gc_threshold(1);
|
||
let core = KernelCore::new(cfg);
|
||
Vm::with_kernel_core(core)
|
||
}
|
||
|
||
#[test]
|
||
fn rope_binary_concat_content_and_length() {
|
||
// 二元 `+` 产物为 Cons 节点:内容与长度经惰性扁平化与缓存 O(1) 读取。
|
||
assert_eq!(eval_str("\"ab\" + \"cd\""), "abcd");
|
||
assert_eq!(eval_str("(\"ab\" + \"cd\").length"), "4");
|
||
assert_eq!(eval_str("(\"ab\" + \"cd\" + \"ef\").length"), "6");
|
||
assert_eq!(eval_str("(\"ab\" + \"cd\").charCodeAt(1)"), "98");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_equality_with_literal_and_rope() {
|
||
// `==`/`===` 走 string_value_eq 内容比较:rope 与同内容字面量/另一 rope 相等,
|
||
// 与不同内容不等。
|
||
assert_eq!(eval_str("(\"ab\" + \"cd\") === \"abcd\""), "true");
|
||
assert_eq!(eval_str("(\"ab\" + \"cd\") == \"abcd\""), "true");
|
||
assert_eq!(eval_str("(\"ab\" + \"cd\") === (\"a\" + \"bcd\")"), "true");
|
||
assert_eq!(eval_str("(\"ab\" + \"cd\") === \"abce\""), "false");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_compound_loop_builds_correctly() {
|
||
// string_build 主模式:`s += j` 循环 50 次(数字叶子 + Cons 链接),
|
||
// 末次 `.length` 扁平化结果正确(0..49 拼接:10 个单字符 + 40 个双字符 = 90)。
|
||
assert_eq!(
|
||
eval_str(
|
||
"var s = ''; for (var j = 0; j < 50; j++) { s += j } \
|
||
s + '|' + s.length"
|
||
),
|
||
"012345678910111213141516171819202122232425262728293031323334353637383940414243444546474849|90"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn rope_mixed_operand_leaf_conversion() {
|
||
// 非字符串操作数先转叶子再链接:数字/布尔/null/undefined/对象与字符串混拼。
|
||
assert_eq!(eval_str("var s = 'a'; s += 1; s += 2.5; s += true; s"), "a12.5true");
|
||
assert_eq!(eval_str("'x' + null + undefined"), "xnullundefined");
|
||
assert_eq!(eval_str("var s = 'a'; s += { toString: function () { return 'obj' } }; s"), "aobj");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_concat_n_rhs_becomes_leaf() {
|
||
// `s += a+b+c`:rhs 摊平为 CONCAT_N(急切 Flat 产物),外层 COMPOUND_ADD 把
|
||
// Flat 产物链接成 Cons 子节点——两层机制叠加内容正确。
|
||
assert_eq!(eval_str("var s = ''; s += 'a' + 'b' + 'c'; s + s"), "abcabc");
|
||
assert_eq!(eval_str("var s = 'z'; for (var i = 0; i < 5; i++) { s += 'x' + 'y' } s"), "zxyxyxyxyxy");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_as_property_key() {
|
||
// rope 作属性键:ToPropertyKey → flat 桥接 intern,键内容一致。
|
||
assert_eq!(eval_str("var s = 'a' + 'b'; var o = {}; o[s] = 42; o['ab']"), "42");
|
||
assert_eq!(eval_str("var s = '5'; var o = {}; o[s] = 7; o[5]"), "7");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_for_of_iterates_chars() {
|
||
// for-of 字符串迭代 rope:首次访问扁平化后游标推进,逐字符正确。
|
||
assert_eq!(eval_str("var s = 'ab' + 'cd'; var out = ''; for (var c of s) { out += c } out"), "abcd");
|
||
assert_eq!(eval_str("var s = 'x'; s += 'y'; s += 'z'; [...s].join('-')"), "x-y-z");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_consumed_by_string_methods() {
|
||
// string 方法族(this_string 借用 + 只读扫描)消费 rope 时惰性扁平化。
|
||
assert_eq!(eval_str("('hello' + 'world').indexOf('wo')"), "5");
|
||
assert_eq!(eval_str("('hello' + 'world').substring(2, 7)"), "llowo");
|
||
assert_eq!(eval_str("('ab' + 'cd').repeat(2)"), "abcdabcd");
|
||
assert_eq!(eval_str("('aB' + 'cD').toUpperCase()"), "ABCD");
|
||
assert_eq!(eval_str("('a,b' + ',c').split(',').length"), "3");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_in_template_and_json() {
|
||
// 模板串与 JSON.stringify 消费 rope 结果一致。
|
||
assert_eq!(eval_str("var s = 'a' + 'b'; `${s}-x`"), "ab-x");
|
||
assert_eq!(eval_str("var s = 'x' + 'y'; JSON.stringify([s])"), "[\"xy\"]");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_numeric_coercion_of_content() {
|
||
// rope 文本参与数值转换:ToNumber 走 string_data 扁平化文本。
|
||
assert_eq!(eval_str("('1' + '0') * 2"), "20");
|
||
assert_eq!(eval_str("Number('4' + '2')"), "42");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_survives_runtime_string_gc() {
|
||
// 低阈值下指令边界检查几乎每指令触发:循环拼接全程(链接结果写寄存器先于
|
||
// 下次检查)不被误回收;大量中间垃圾触发多轮回收后 rope 仍正确消费。
|
||
let mut vm = vm_with_tiny_threshold();
|
||
let val = eval(
|
||
&mut vm,
|
||
"var s = 'a'; for (var i = 0; i < 300; i++) { s += 'b' } \
|
||
var garbage = ''; for (var g = 0; g < 500; g++) { garbage += 'x' } \
|
||
s.length + '|' + s.charAt(150)",
|
||
)
|
||
.expect("eval ok");
|
||
assert_eq!(fmt(&vm, val), "301|b");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_in_object_holder_survives_gc() {
|
||
// rope 挂在全局对象属性上:mark 经对象字符串边传播 rope 闭包,子节点与
|
||
// 产物随父存活(跨多轮低阈值回收)。
|
||
let mut vm = vm_with_tiny_threshold();
|
||
let val = eval(
|
||
&mut vm,
|
||
"var o = {}; o.s = 'x'; for (var i = 0; i < 200; i++) { o.s += 'y' } \
|
||
var t = ''; for (var g = 0; g < 300; g++) { t += 'z' } \
|
||
o.s.length + '|' + o.s.charAt(200) + '|' + t.length",
|
||
)
|
||
.expect("eval ok");
|
||
assert_eq!(fmt(&vm, val), "201|y|300");
|
||
}
|
||
|
||
#[test]
|
||
fn rope_dead_after_scope_released() {
|
||
// rope 离开作用域成为垃圾:低阈值下被后续分配回收,不影响新值。
|
||
let mut vm = vm_with_tiny_threshold();
|
||
eval(&mut vm, "var t = ''; for (var i = 0; i < 200; i++) { t += 'z' }").expect("eval ok");
|
||
let val = eval(&mut vm, "'fresh' + 'value'").expect("eval ok");
|
||
assert_eq!(fmt(&vm, val), "freshvalue");
|
||
}
|