refactor: 字符串族下沉对象模块 string 子模块

This commit is contained in:
2026-09-15 18:19:23 +00:00
parent 875f6dac60
commit 81059f49c8
4 changed files with 557 additions and 542 deletions
+2 -453
View File
@@ -7,461 +7,10 @@
//! 闭包共享 cell (`Cell`)。
use crate::value::JsValue;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::OnceLock;
/// 堆分配的 JS 字符串值。
///
/// 字符串*值*以 48 位指针(指向 `JsString`)NaN-box(见 `JsValue::string`)。
///
/// 三种载荷形态(`tag` 区分,载荷并位 @0):
/// - `TAG_FLAT`:整块 UTF-8 文本(`String`,保留容量)。字面量、方法结果与
/// CONCAT_N 产物保持此形态;内容无孤立 surrogate(UTF-8 合法性天然保证);
/// - `TAG_FLAT_U16`:UTF-16 单元载荷(`Vec<u16>`,ptr/len/cap 与 String 同形),
/// 承载含孤立 surrogate 的任意单元序列——`String` 在结构上不可表示该内容;
/// - `TAG_CONS`(rope):指向 [`ConsNode`] 载荷(左右子裸指针 + O(1) 单元长 +
/// 惰性扁平化缓存)。二元 `+`/`+=` 拼接 O(1) 链接不拷贝文本,单元序列在
/// 首次消费时扁平化并原子发布。
///
/// 布局恒为 32B:载荷 24B @0、`utf16_len` @24(4B)、`tag` @28(1B,原 NLL
/// enum 的尾部填充位,显式 repr(C) 钉死)、尾部 3B 填充。`String` 原样存储
/// (零 realloc 收缩);Cons 专属状态(子节点/产物缓存)独立分配在 [`ConsNode`],
/// 仅大链持有。尺寸锚见下方 size_of/offset_of 断言。
///
/// 生命周期约定:
/// - `Cons` 子节点各自独立登记 session 字符串表(或为 perm 串),由 GC 的
/// mark 传播闭包保证随父存活;扁平化产物只挂在 `ConsNode::flat_cache`、不进
/// session 主表,随本节点连带释放。
/// - 地址稳定不搬移(Box 堆分配),GC 无需 forwarding / rewrite。
#[repr(C)]
pub struct JsString {
/// 载荷 24B,按 `tag` 解读:Flat = `String`(data/len/cap)、FlatU16 =
/// `Vec<u16>`(ptr/len/cap,与 String 同形)、Cons = 节点指针(首字,
/// 余 16B 未定义填充)。
payload: [u64; 3],
/// JS 语义的字符串长度(UTF-16 code unit 数)。
///
/// Flat 为懒缓存:构造是热路径(拼接/格式化),长度查询少见,不预付 O(n)
/// 扫描,首次 `utf16_len()` 访问时计算一次(sentinel + `AtomicU32`:perm
/// 串可被多线程 VM 共享读,计算幂等,relaxed 序即可)。FlatU16/Cons 构造时
/// 预写(单元数 / 子节点归纳,各 O(1))。
utf16_len: AtomicU32,
tag: u8,
}
mod string;
// 布局锚:32B = 载荷 24B(三形态同形 ptr/len/cap 或节点指针)+ utf16_len 4B
// + tag 1B + 尾部填充 3B。三变体 NLL enum 需独立 tag 字(撑到 40B、utf16_len@32)
// 故以 repr(C) + 尾部 tag 字节钉死本布局;换载荷形状/字段类型先同步结构体头
// 注释再改本断言。
const _: () = assert!(
std::mem::size_of::<JsString>() == 32
&& std::mem::offset_of!(JsString, utf16_len) == 24
&& std::mem::offset_of!(JsString, tag) == 28,
"JsString 布局漂移:锚定布局见结构体头注释(32B,utf16_len@24,tag@28)"
);
/// 载荷形态标签(`JsString::tag` 取值)。
const TAG_FLAT: u8 = 0;
const TAG_FLAT_U16: u8 = 1;
const TAG_CONS: u8 = 2;
impl std::fmt::Debug for JsString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.tag {
TAG_FLAT => write!(f, "JsString::Flat({:?})", self.flat_ref()),
TAG_FLAT_U16 => write!(f, "JsString::FlatU16({:?})", self.flat_u16_ref()),
TAG_CONS => write!(f, "JsString::Cons(len={})", self.utf16_len()),
_ => write!(f, "JsString::Invalid"),
}
}
}
impl Drop for JsString {
fn drop(&mut self) {
// 按 tag 恰好释放激活载荷:Flat/FlatU16 释放载荷自身(内部 heap 缓冲),
// Cons 载荷节点由 drop_cons_node 单独释放(见其文档)。
//
// # Safety
// tag 与载荷内容恒由构造点同步写入;drop 期载荷仍为激活形态。
unsafe {
match self.tag {
TAG_FLAT => std::ptr::drop_in_place(self.payload.as_mut_ptr() as *mut String),
TAG_FLAT_U16 => std::ptr::drop_in_place(self.payload.as_mut_ptr() as *mut Vec<u16>),
TAG_CONS => {}
_ => unreachable!(),
}
}
}
}
/// Cons(rope)节点的载荷:左右子节点指针 + O(1) 拼接单元长 + 扁平化产物缓存。
///
/// 独立于 `JsString` 分配,使 Flat 路径的 `JsString` 保持 32B 结构(tag 并入
/// 尾部填充位,见其尺寸断言);仅超过阈值的拼接(`Vm::new_cons_string`)才
/// 创建本节点。
///
/// 生命周期约定:节点由 `Box::into_raw` 分配,只经 [`JsString::drop_cons_node`]
/// 释放(连带扁平化产物);`left`/`right` 由 GC 传播闭包保证随父存活。
pub struct ConsNode {
left: *const JsString,
right: *const JsString,
/// 拼接 UTF-16 单元长:构造时 = left.utf16_len() + right.utf16_len()(各 O(1)),
/// 免递归求长、不触发扁平化。
unit_len: u32,
/// 扁平化产物缓存:首次单元消费时分配整块 FlatU16 产物并原子发布(OnceLock)。
/// 产物不进 session 主表,生命周期挂本节点——GC 传播保证其随父存活,
/// 释放节点时连带释放。
flat_cache: OnceLock<*const JsString>,
}
/// `utf16_len` 未计算的哨兵值(有效长度不可能为 u32::MAX;仅 Flat 形态使用)。
const UTF16_LEN_UNSET: u32 = u32::MAX;
impl ConsNode {
/// 扁平化产物指针(未扁平化返回 null)。
pub fn flat_cache_ptr(&self) -> *const JsString {
self.flat_cache.get().copied().unwrap_or(std::ptr::null())
}
}
impl JsString {
/// Flat 载荷借用(载荷即原始 `String`)。
///
/// # Safety
/// 仅当 `tag == TAG_FLAT` 时调用(debug_assert 已覆盖常规路径)。
#[inline(always)]
fn flat_ref(&self) -> &String {
debug_assert!(self.tag == TAG_FLAT, "flat_ref 仅限 Flat 形态");
// SAFETY: 形态检查通过后载荷即 String(布局与 [u64;3] 逐位一致)。
unsafe { &*(self.payload.as_ptr() as *const String) }
}
/// FlatU16 载荷借用(载荷即原始 `Vec<u16>`)。
///
/// # Safety
/// 仅当 `tag == TAG_FLAT_U16` 时调用(debug_assert 已覆盖常规路径)。
#[inline(always)]
fn flat_u16_ref(&self) -> &Vec<u16> {
debug_assert!(self.tag == TAG_FLAT_U16, "flat_u16_ref 仅限 FlatU16 形态");
// SAFETY: 形态检查通过后载荷即 Vec<u16>(布局与 [u64;3] 逐位一致)。
unsafe { &*(self.payload.as_ptr() as *const Vec<u16>) }
}
/// Cons 节点指针(载荷首字即节点裸指针)。
///
/// # Safety
/// 仅当 `tag == TAG_CONS` 时调用(debug_assert 已覆盖常规路径)。
#[inline(always)]
fn cons_ptr(&self) -> *const ConsNode {
debug_assert!(self.tag == TAG_CONS, "cons_ptr 仅限 Cons 形态");
// SAFETY: 形态检查通过后载荷首字即节点指针。
unsafe { *(self.payload.as_ptr() as *const *const ConsNode) }
}
/// 用 UTF-8 数据构造 Flat 字符串(内容恒 well-formed,无孤立 surrogate)。
pub fn new(data: String) -> Self {
let mut payload = [0u64; 3];
// SAFETY: String 与 [u64;3] 布局一致(24B ptr/len/cap);write 不读目的位。
unsafe { std::ptr::write(payload.as_mut_ptr() as *mut String, data) };
Self {
payload,
utf16_len: AtomicU32::new(UTF16_LEN_UNSET),
tag: TAG_FLAT,
}
}
/// 直接以单元序列构造 FlatU16 字符串(不做 well-formed 判定)。
///
/// 供明确知道内容形态的构造点使用(迭代器源物化、单元解码):smart
/// 路由场景一律走 [`Self::from_units`](well-formed 结果落 Flat)。
pub fn new_flat_u16(units: Vec<u16>) -> Self {
let len = units.len() as u32;
let mut payload = [0u64; 3];
// SAFETY: Vec<u16> 与 [u64;3] 布局一致(24B ptr/len/cap);write 不读目的位。
unsafe { std::ptr::write(payload.as_mut_ptr() as *mut Vec<u16>, units) };
Self {
payload,
utf16_len: AtomicU32::new(len),
tag: TAG_FLAT_U16,
}
}
/// 智能路由构造:单元序列含孤立 surrogate 落 FlatU16,全 well-formed 落
/// Flat(内容等价、载荷最小)。
pub fn from_units(units: Vec<u16>) -> Self {
if units_have_lone_surrogate(&units) {
Self::new_flat_u16(units)
} else {
// 已判定无孤立 surrogate,from_utf16 必成功;Err 臂仅为防御
// (单元判定与 from_utf16 同语义,不可能分歧)。
match String::from_utf16(&units) {
Ok(s) => Self::new(s),
Err(_) => Self::new_flat_u16(units),
}
}
}
/// 以左右子节点构造 Cons(rope)节点,O(1) 链接不拷贝文本。
///
/// # Safety
/// `left`/`right` 必须指向存活的 `JsString`(session 或 perm),且由 GC
/// 传播保证随本节点存活——调用方(`Vm::new_cons_string`)负责登记。
pub unsafe fn new_cons(left: *const JsString, right: *const JsString) -> Self {
// 子节点单元长各 O(1)(Flat 懒扫描首取后缓存 / FlatU16 直读 / Cons 归纳):
// 链接成本 = 子节点首取扫描,非本链 O(n)。
let unit_len = (*left).utf16_len() + (*right).utf16_len();
let node = Box::into_raw(Box::new(ConsNode {
left,
right,
unit_len,
flat_cache: OnceLock::new(),
}));
let mut payload = [0u64; 3];
// SAFETY: Cons 载荷首字为节点指针,余 16B 未定义填充;指针按位写入
// (目标小端,指针→u64 为逐位转换)。
unsafe { *payload.as_mut_ptr() = node as u64 };
Self {
payload,
utf16_len: AtomicU32::new(unit_len),
tag: TAG_CONS,
}
}
/// Cons 载荷节点指针(非 Cons 返回 null)。仅供释放路径使用。
pub fn cons_node_ptr(&self) -> *mut ConsNode {
if self.tag != TAG_CONS {
return std::ptr::null_mut();
}
// SAFETY: tag 为 CONS 时载荷首字即节点指针,由 new_cons 创建且存活。
self.cons_ptr() as *mut ConsNode
}
/// 释放 Cons 载荷节点(连带扁平化产物)。须在所属 `JsString` 的
/// `Box::from_raw` 之前调用且恰好一次;非 Cons 串传 null 无操作。
///
/// # Safety
/// `node` 必须是 [`JsString::new_cons`] 经 `Box::into_raw` 产生的指针,且
/// 尚未被释放。
pub unsafe fn drop_cons_node(node: *mut ConsNode) {
if node.is_null() {
return;
}
let flat = (*node).flat_cache_ptr();
if !flat.is_null() {
// SAFETY: 产物由本节点 Box::into_raw 创建,且只随本节点释放一次。
drop(Box::from_raw(flat as *mut JsString));
}
drop(Box::from_raw(node));
}
/// JS 字符串长度(UTF-16 code unit 数)。Flat 首次访问扫描并缓存,
/// 此后恒 O(1);FlatU16/Cons 构造时已预写。
pub fn utf16_len(&self) -> u32 {
if self.tag != TAG_FLAT {
return self.utf16_len.load(Ordering::Relaxed);
}
let cached = self.utf16_len.load(Ordering::Relaxed);
if cached != UTF16_LEN_UNSET {
return cached;
}
let s = self.flat_ref();
let len = if s.is_ascii() { s.len() as u32 } else { s.encode_utf16().count() as u32 };
self.utf16_len.store(len, Ordering::Relaxed);
len
}
/// 是否为空字符串。
pub fn is_empty(&self) -> bool {
self.utf16_len() == 0
}
/// 整块 UTF-8 文本的借用。**仅 Flat 形态**:FlatU16/Cons 无 `&str` 视图
/// (内容含孤立 surrogate 或需扁平化),一律改走 [`Self::units`] /
/// [`Self::as_lossy_str`]。
///
/// # Panics
/// 非 Flat 形态 panic(调用契约违反,非运行时正常路径)。
pub fn as_str(&self) -> &str {
if self.tag != TAG_FLAT {
panic!("JsString::as_str 仅限 Flat 形态");
}
self.flat_ref()
}
/// FlatU16 单元载荷的零拷贝借用;仅 FlatU16 形态返回 Some
/// (Flat 需编码、Cons 需扁平化,热路径经本方法免判形态)。
pub fn units_borrowed(&self) -> Option<&[u16]> {
(self.tag == TAG_FLAT_U16).then(|| self.flat_u16_ref()).map(|v| &**v)
}
/// 整块单元序列视图:FlatU16 零拷贝;Flat 即时编码(ASCII 走 u8→u16);
/// Cons 未扁平化时迭代展开(显式栈中序,防深链爆栈)发布 FlatU16 产物缓存
/// 后返回其零拷贝借用(缓存随本节点存活)。
pub fn units(&self) -> std::borrow::Cow<'_, [u16]> {
match self.tag {
TAG_FLAT => {
let s = self.flat_ref();
if s.is_ascii() {
std::borrow::Cow::Owned(s.as_bytes().iter().map(|&b| b as u16).collect())
} else {
std::borrow::Cow::Owned(s.encode_utf16().collect())
}
}
TAG_FLAT_U16 => std::borrow::Cow::Borrowed(self.flat_u16_ref()),
TAG_CONS => {
let node = unsafe { &*self.cons_ptr() };
if let Some(cached) = node.flat_cache.get() {
// SAFETY: 产物由本节点 Box::into_raw 创建且只随本节点释放,
// 存活期覆盖本次 &self 借用;扁平化产物恒为 FlatU16 形态。
let cached = unsafe { &**cached };
return std::borrow::Cow::Borrowed(cached.flat_u16_ref());
}
let units = self.flatten_units();
let product = Box::into_raw(Box::new(JsString::new_flat_u16(units)));
if node.flat_cache.set(product).is_err() {
// 并发首用竞态:另一线程已发布,本线程产物未暴露给任何调用方,
// 恰好释放一次。
// SAFETY: product 来自本线程的 Box::into_raw,无外部引用。
unsafe { drop(Box::from_raw(product)) };
}
// 上述 set 后缓存必已发布(本线程或他线程),产物恒 FlatU16。
let cached = unsafe { &**node.flat_cache.get().unwrap() };
std::borrow::Cow::Borrowed(cached.flat_u16_ref())
}
_ => unreachable!(),
}
}
/// 整块文本的 lossy 借用:Flat 直读(内容 well-formed,无损);其余形态按
/// 单元→char 映射(孤立 surrogate 替 U+FFFD)——供展示/解析/错误消息,
/// 语义消费一律走 [`Self::units`]。
pub fn as_lossy_str(&self) -> std::borrow::Cow<'_, str> {
if self.tag == TAG_FLAT {
return std::borrow::Cow::Borrowed(self.flat_ref());
}
let units = self.units();
std::borrow::Cow::Owned(match String::from_utf16(&units) {
Ok(s) => s,
Err(_) => units.iter().map(|&u| char::from_u32(u as u32).unwrap_or('\u{FFFD}')).collect(),
})
}
/// 整块文本的 lossy owned 副本(低频路径:错误消息 / 格式化 / 源码桥接)。
pub fn to_owned_string(&self) -> String {
self.as_lossy_str().into_owned()
}
/// 是否含孤立 surrogate 单元(well-formed 判定的取反)。
///
/// Flat 恒 false(不变式:UTF-8 载荷结构上不可承载);FlatU16 恒 true
/// (保守口径:直构造路径可能持有 well-formed 单元,单元消费路径对
/// well-formed 内容结果仍正确,仅多走单元通道);Cons 扁平化后扫描。
pub fn has_lone_surrogate(&self) -> bool {
match self.tag {
TAG_FLAT => false,
TAG_FLAT_U16 => true,
TAG_CONS => {
let units = self.units();
units_have_lone_surrogate(&units)
}
_ => unreachable!(),
}
}
/// 载荷的记账字节数(GC 账目口径):Flat = UTF-8 内容字节;FlatU16 /
/// Cons = 单元数 × 2。
pub fn payload_bytes(&self) -> usize {
match self.tag {
TAG_FLAT => self.flat_ref().len(),
TAG_FLAT_U16 => self.flat_u16_ref().len() * 2,
TAG_CONS => (unsafe { (*self.cons_ptr()).unit_len as usize }) * 2,
_ => unreachable!(),
}
}
/// 是否为 Flat(UTF-8 文本)形态。
pub fn is_flat(&self) -> bool {
self.tag == TAG_FLAT
}
/// 是否为 FlatU16(单元载荷)形态。
pub fn is_flat_u16(&self) -> bool {
self.tag == TAG_FLAT_U16
}
/// 是否为 Cons(rope)节点。
pub fn is_cons(&self) -> bool {
self.tag == TAG_CONS
}
/// Cons 左右子节点指针对(非 Cons 返回双 null)。仅供 GC 传播闭包使用。
pub fn cons_children(&self) -> [*const JsString; 2] {
if self.tag != TAG_CONS {
return [std::ptr::null(), std::ptr::null()];
}
let node = unsafe { &*self.cons_ptr() };
[node.left, node.right]
}
/// 扁平化产物指针(未扁平化返回 null)。仅供 GC 传播与释放路径使用。
pub fn flat_cache_ptr(&self) -> *const JsString {
if self.tag != TAG_CONS {
return std::ptr::null();
}
unsafe { (*self.cons_ptr()).flat_cache_ptr() }
}
/// 迭代展开 Cons 子树为整块单元序列(显式栈中序收集,防深链递归爆栈)。
fn flatten_units(&self) -> Vec<u16> {
let mut stack = Vec::with_capacity(8);
stack.push(self as *const JsString);
let mut buf: Vec<u16> = Vec::with_capacity(self.utf16_len() as usize);
while let Some(ptr) = stack.pop() {
// SAFETY: 树内节点由 GC 传播保证随本节点存活;展开期子树无并发修改。
let node = unsafe { &*ptr };
match node.tag {
TAG_FLAT => buf.extend(node.flat_ref().encode_utf16()),
TAG_FLAT_U16 => buf.extend_from_slice(node.flat_u16_ref()),
TAG_CONS => {
// 右子先入栈、左子后入,保证左子先出(中序顺序)。
// SAFETY: child 由 new_cons 创建且随父存活。
let child = unsafe { &*node.cons_ptr() };
stack.push(child.right);
stack.push(child.left);
}
_ => unreachable!(),
}
}
buf
}
}
/// 扫描单元序列是否含孤立 surrogate(high 后须紧跟 low 成对;low 不得独立出现)。
fn units_have_lone_surrogate(units: &[u16]) -> bool {
let mut i = 0;
while i < units.len() {
let u = units[i];
if (0xD800..=0xDBFF).contains(&u) {
if i + 1 < units.len() && (0xDC00..=0xDFFF).contains(&units[i + 1]) {
i += 2;
} else {
return true;
}
} else if (0xDC00..=0xDFFF).contains(&u) {
return true;
} else {
i += 1;
}
}
false
}
// SAFETY: 与 `StringPtr`(string_forge.rs)同款论证——Cons 节点只存在于单线程
// session VM 内;跨线程共享的 perm 串载荷为 Flat/FlatU16(无 ConsNode 裸指针
// 写路径),flat_cache 永不写入(perm 串只读共享,无任何可变路径)。裸指针
// 字段不引入跨线程数据竞争。
unsafe impl Send for JsString {}
unsafe impl Sync for JsString {}
pub use string::{ConsNode, JsString};
/// 原生函数指针的类型安全不透明包装。
///
+465
View File
@@ -0,0 +1,465 @@
//! `JsString` 三形态载荷(Flat UTF-8 文本 / FlatU16 单元 / Cons rope)与 rope 节点 `ConsNode`。
//!
//! - 布局恒为 32B:载荷 24B @0、`utf16_len` @24、`tag` @28,由文件内 size_of/
//! offset_of 断言锚定;
//! - `Cons` 子节点各自独立登记 session 字符串表(或为 perm 串),扁平化产物只挂
//! `ConsNode::flat_cache`、随节点连带释放;
//! - 地址稳定不搬移(Box 堆分配),GC 无需 forwarding / rewrite。
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::OnceLock;
/// 堆分配的 JS 字符串值。
///
/// 字符串*值*以 48 位指针(指向 `JsString`)NaN-box(见 `JsValue::string`)。
///
/// 三种载荷形态(`tag` 区分,载荷并位 @0):
/// - `TAG_FLAT`:整块 UTF-8 文本(`String`,保留容量)。字面量、方法结果与
/// CONCAT_N 产物保持此形态;内容无孤立 surrogate(UTF-8 合法性天然保证);
/// - `TAG_FLAT_U16`:UTF-16 单元载荷(`Vec<u16>`,ptr/len/cap 与 String 同形),
/// 承载含孤立 surrogate 的任意单元序列——`String` 在结构上不可表示该内容;
/// - `TAG_CONS`(rope):指向 [`ConsNode`] 载荷(左右子裸指针 + O(1) 单元长 +
/// 惰性扁平化缓存)。二元 `+`/`+=` 拼接 O(1) 链接不拷贝文本,单元序列在
/// 首次消费时扁平化并原子发布。
///
/// 布局恒为 32B:载荷 24B @0、`utf16_len` @24(4B)、`tag` @28(1B,原 NLL
/// enum 的尾部填充位,显式 repr(C) 钉死)、尾部 3B 填充。`String` 原样存储
/// (零 realloc 收缩);Cons 专属状态(子节点/产物缓存)独立分配在 [`ConsNode`],
/// 仅大链持有。尺寸锚见下方 size_of/offset_of 断言。
///
/// 生命周期约定:
/// - `Cons` 子节点各自独立登记 session 字符串表(或为 perm 串),由 GC 的
/// mark 传播闭包保证随父存活;扁平化产物只挂在 `ConsNode::flat_cache`、不进
/// session 主表,随本节点连带释放。
/// - 地址稳定不搬移(Box 堆分配),GC 无需 forwarding / rewrite。
#[repr(C)]
pub struct JsString {
/// 载荷 24B,按 `tag` 解读:Flat = `String`(data/len/cap)、FlatU16 =
/// `Vec<u16>`(ptr/len/cap,与 String 同形)、Cons = 节点指针(首字,
/// 余 16B 未定义填充)。
payload: [u64; 3],
/// JS 语义的字符串长度(UTF-16 code unit 数)。
///
/// Flat 为懒缓存:构造是热路径(拼接/格式化),长度查询少见,不预付 O(n)
/// 扫描,首次 `utf16_len()` 访问时计算一次(sentinel + `AtomicU32`:perm
/// 串可被多线程 VM 共享读,计算幂等,relaxed 序即可)。FlatU16/Cons 构造时
/// 预写(单元数 / 子节点归纳,各 O(1))。
utf16_len: AtomicU32,
tag: u8,
}
// 布局锚:32B = 载荷 24B(三形态同形 ptr/len/cap 或节点指针)+ utf16_len 4B
// + tag 1B + 尾部填充 3B。三变体 NLL enum 需独立 tag 字(撑到 40B、utf16_len@32)
// 故以 repr(C) + 尾部 tag 字节钉死本布局;换载荷形状/字段类型先同步结构体头
// 注释再改本断言。
const _: () = assert!(
std::mem::size_of::<JsString>() == 32
&& std::mem::offset_of!(JsString, utf16_len) == 24
&& std::mem::offset_of!(JsString, tag) == 28,
"JsString 布局漂移:锚定布局见结构体头注释(32B,utf16_len@24,tag@28)"
);
/// 载荷形态标签(`JsString::tag` 取值)。
const TAG_FLAT: u8 = 0;
const TAG_FLAT_U16: u8 = 1;
const TAG_CONS: u8 = 2;
impl std::fmt::Debug for JsString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.tag {
TAG_FLAT => write!(f, "JsString::Flat({:?})", self.flat_ref()),
TAG_FLAT_U16 => write!(f, "JsString::FlatU16({:?})", self.flat_u16_ref()),
TAG_CONS => write!(f, "JsString::Cons(len={})", self.utf16_len()),
_ => write!(f, "JsString::Invalid"),
}
}
}
impl Drop for JsString {
fn drop(&mut self) {
// 按 tag 恰好释放激活载荷:Flat/FlatU16 释放载荷自身(内部 heap 缓冲),
// Cons 载荷节点由 drop_cons_node 单独释放(见其文档)。
//
// # Safety
// tag 与载荷内容恒由构造点同步写入;drop 期载荷仍为激活形态。
unsafe {
match self.tag {
TAG_FLAT => std::ptr::drop_in_place(self.payload.as_mut_ptr() as *mut String),
TAG_FLAT_U16 => std::ptr::drop_in_place(self.payload.as_mut_ptr() as *mut Vec<u16>),
TAG_CONS => {}
_ => unreachable!(),
}
}
}
}
/// Cons(rope)节点的载荷:左右子节点指针 + O(1) 拼接单元长 + 扁平化产物缓存。
///
/// 独立于 `JsString` 分配,使 Flat 路径的 `JsString` 保持 32B 结构(tag 并入
/// 尾部填充位,见其尺寸断言);仅超过阈值的拼接(`Vm::new_cons_string`)才
/// 创建本节点。
///
/// 生命周期约定:节点由 `Box::into_raw` 分配,只经 [`JsString::drop_cons_node`]
/// 释放(连带扁平化产物);`left`/`right` 由 GC 传播闭包保证随父存活。
pub struct ConsNode {
left: *const JsString,
right: *const JsString,
/// 拼接 UTF-16 单元长:构造时 = left.utf16_len() + right.utf16_len()(各 O(1)),
/// 免递归求长、不触发扁平化。
unit_len: u32,
/// 扁平化产物缓存:首次单元消费时分配整块 FlatU16 产物并原子发布(OnceLock)。
/// 产物不进 session 主表,生命周期挂本节点——GC 传播保证其随父存活,
/// 释放节点时连带释放。
flat_cache: OnceLock<*const JsString>,
}
/// `utf16_len` 未计算的哨兵值(有效长度不可能为 u32::MAX;仅 Flat 形态使用)。
const UTF16_LEN_UNSET: u32 = u32::MAX;
impl ConsNode {
/// 扁平化产物指针(未扁平化返回 null)。
pub fn flat_cache_ptr(&self) -> *const JsString {
self.flat_cache.get().copied().unwrap_or(std::ptr::null())
}
}
impl JsString {
/// Flat 载荷借用(载荷即原始 `String`)。
///
/// # Safety
/// 仅当 `tag == TAG_FLAT` 时调用(debug_assert 已覆盖常规路径)。
#[inline(always)]
fn flat_ref(&self) -> &String {
debug_assert!(self.tag == TAG_FLAT, "flat_ref 仅限 Flat 形态");
// SAFETY: 形态检查通过后载荷即 String(布局与 [u64;3] 逐位一致)。
unsafe { &*(self.payload.as_ptr() as *const String) }
}
/// FlatU16 载荷借用(载荷即原始 `Vec<u16>`)。
///
/// # Safety
/// 仅当 `tag == TAG_FLAT_U16` 时调用(debug_assert 已覆盖常规路径)。
#[inline(always)]
fn flat_u16_ref(&self) -> &Vec<u16> {
debug_assert!(self.tag == TAG_FLAT_U16, "flat_u16_ref 仅限 FlatU16 形态");
// SAFETY: 形态检查通过后载荷即 Vec<u16>(布局与 [u64;3] 逐位一致)。
unsafe { &*(self.payload.as_ptr() as *const Vec<u16>) }
}
/// Cons 节点指针(载荷首字即节点裸指针)。
///
/// # Safety
/// 仅当 `tag == TAG_CONS` 时调用(debug_assert 已覆盖常规路径)。
#[inline(always)]
fn cons_ptr(&self) -> *const ConsNode {
debug_assert!(self.tag == TAG_CONS, "cons_ptr 仅限 Cons 形态");
// SAFETY: 形态检查通过后载荷首字即节点指针。
unsafe { *(self.payload.as_ptr() as *const *const ConsNode) }
}
/// 用 UTF-8 数据构造 Flat 字符串(内容恒 well-formed,无孤立 surrogate)。
pub fn new(data: String) -> Self {
let mut payload = [0u64; 3];
// SAFETY: String 与 [u64;3] 布局一致(24B ptr/len/cap);write 不读目的位。
unsafe { std::ptr::write(payload.as_mut_ptr() as *mut String, data) };
Self {
payload,
utf16_len: AtomicU32::new(UTF16_LEN_UNSET),
tag: TAG_FLAT,
}
}
/// 直接以单元序列构造 FlatU16 字符串(不做 well-formed 判定)。
///
/// 供明确知道内容形态的构造点使用(迭代器源物化、单元解码):smart
/// 路由场景一律走 [`Self::from_units`](well-formed 结果落 Flat)。
pub fn new_flat_u16(units: Vec<u16>) -> Self {
let len = units.len() as u32;
let mut payload = [0u64; 3];
// SAFETY: Vec<u16> 与 [u64;3] 布局一致(24B ptr/len/cap);write 不读目的位。
unsafe { std::ptr::write(payload.as_mut_ptr() as *mut Vec<u16>, units) };
Self {
payload,
utf16_len: AtomicU32::new(len),
tag: TAG_FLAT_U16,
}
}
/// 智能路由构造:单元序列含孤立 surrogate 落 FlatU16,全 well-formed 落
/// Flat(内容等价、载荷最小)。
pub fn from_units(units: Vec<u16>) -> Self {
if units_have_lone_surrogate(&units) {
Self::new_flat_u16(units)
} else {
// 已判定无孤立 surrogate,from_utf16 必成功;Err 臂仅为防御
// (单元判定与 from_utf16 同语义,不可能分歧)。
match String::from_utf16(&units) {
Ok(s) => Self::new(s),
Err(_) => Self::new_flat_u16(units),
}
}
}
/// 以左右子节点构造 Cons(rope)节点,O(1) 链接不拷贝文本。
///
/// # Safety
/// `left`/`right` 必须指向存活的 `JsString`(session 或 perm),且由 GC
/// 传播保证随本节点存活——调用方(`Vm::new_cons_string`)负责登记。
pub unsafe fn new_cons(left: *const JsString, right: *const JsString) -> Self {
// 子节点单元长各 O(1)(Flat 懒扫描首取后缓存 / FlatU16 直读 / Cons 归纳):
// 链接成本 = 子节点首取扫描,非本链 O(n)。
let unit_len = (*left).utf16_len() + (*right).utf16_len();
let node = Box::into_raw(Box::new(ConsNode {
left,
right,
unit_len,
flat_cache: OnceLock::new(),
}));
let mut payload = [0u64; 3];
// SAFETY: Cons 载荷首字为节点指针,余 16B 未定义填充;指针按位写入
// (目标小端,指针→u64 为逐位转换)。
unsafe { *payload.as_mut_ptr() = node as u64 };
Self {
payload,
utf16_len: AtomicU32::new(unit_len),
tag: TAG_CONS,
}
}
/// Cons 载荷节点指针(非 Cons 返回 null)。仅供释放路径使用。
pub fn cons_node_ptr(&self) -> *mut ConsNode {
if self.tag != TAG_CONS {
return std::ptr::null_mut();
}
// SAFETY: tag 为 CONS 时载荷首字即节点指针,由 new_cons 创建且存活。
self.cons_ptr() as *mut ConsNode
}
/// 释放 Cons 载荷节点(连带扁平化产物)。须在所属 `JsString` 的
/// `Box::from_raw` 之前调用且恰好一次;非 Cons 串传 null 无操作。
///
/// # Safety
/// `node` 必须是 [`JsString::new_cons`] 经 `Box::into_raw` 产生的指针,且
/// 尚未被释放。
pub unsafe fn drop_cons_node(node: *mut ConsNode) {
if node.is_null() {
return;
}
let flat = (*node).flat_cache_ptr();
if !flat.is_null() {
// SAFETY: 产物由本节点 Box::into_raw 创建,且只随本节点释放一次。
drop(Box::from_raw(flat as *mut JsString));
}
drop(Box::from_raw(node));
}
/// JS 字符串长度(UTF-16 code unit 数)。Flat 首次访问扫描并缓存,
/// 此后恒 O(1);FlatU16/Cons 构造时已预写。
pub fn utf16_len(&self) -> u32 {
if self.tag != TAG_FLAT {
return self.utf16_len.load(Ordering::Relaxed);
}
let cached = self.utf16_len.load(Ordering::Relaxed);
if cached != UTF16_LEN_UNSET {
return cached;
}
let s = self.flat_ref();
let len = if s.is_ascii() { s.len() as u32 } else { s.encode_utf16().count() as u32 };
self.utf16_len.store(len, Ordering::Relaxed);
len
}
/// 是否为空字符串。
pub fn is_empty(&self) -> bool {
self.utf16_len() == 0
}
/// 整块 UTF-8 文本的借用。**仅 Flat 形态**:FlatU16/Cons 无 `&str` 视图
/// (内容含孤立 surrogate 或需扁平化),一律改走 [`Self::units`] /
/// [`Self::as_lossy_str`]。
///
/// # Panics
/// 非 Flat 形态 panic(调用契约违反,非运行时正常路径)。
pub fn as_str(&self) -> &str {
if self.tag != TAG_FLAT {
panic!("JsString::as_str 仅限 Flat 形态");
}
self.flat_ref()
}
/// FlatU16 单元载荷的零拷贝借用;仅 FlatU16 形态返回 Some
/// (Flat 需编码、Cons 需扁平化,热路径经本方法免判形态)。
pub fn units_borrowed(&self) -> Option<&[u16]> {
(self.tag == TAG_FLAT_U16).then(|| self.flat_u16_ref()).map(|v| &**v)
}
/// 整块单元序列视图:FlatU16 零拷贝;Flat 即时编码(ASCII 走 u8→u16);
/// Cons 未扁平化时迭代展开(显式栈中序,防深链爆栈)发布 FlatU16 产物缓存
/// 后返回其零拷贝借用(缓存随本节点存活)。
pub fn units(&self) -> std::borrow::Cow<'_, [u16]> {
match self.tag {
TAG_FLAT => {
let s = self.flat_ref();
if s.is_ascii() {
std::borrow::Cow::Owned(s.as_bytes().iter().map(|&b| b as u16).collect())
} else {
std::borrow::Cow::Owned(s.encode_utf16().collect())
}
}
TAG_FLAT_U16 => std::borrow::Cow::Borrowed(self.flat_u16_ref()),
TAG_CONS => {
let node = unsafe { &*self.cons_ptr() };
if let Some(cached) = node.flat_cache.get() {
// SAFETY: 产物由本节点 Box::into_raw 创建且只随本节点释放,
// 存活期覆盖本次 &self 借用;扁平化产物恒为 FlatU16 形态。
let cached = unsafe { &**cached };
return std::borrow::Cow::Borrowed(cached.flat_u16_ref());
}
let units = self.flatten_units();
let product = Box::into_raw(Box::new(JsString::new_flat_u16(units)));
if node.flat_cache.set(product).is_err() {
// 并发首用竞态:另一线程已发布,本线程产物未暴露给任何调用方,
// 恰好释放一次。
// SAFETY: product 来自本线程的 Box::into_raw,无外部引用。
unsafe { drop(Box::from_raw(product)) };
}
// 上述 set 后缓存必已发布(本线程或他线程),产物恒 FlatU16。
let cached = unsafe { &**node.flat_cache.get().unwrap() };
std::borrow::Cow::Borrowed(cached.flat_u16_ref())
}
_ => unreachable!(),
}
}
/// 整块文本的 lossy 借用:Flat 直读(内容 well-formed,无损);其余形态按
/// 单元→char 映射(孤立 surrogate 替 U+FFFD)——供展示/解析/错误消息,
/// 语义消费一律走 [`Self::units`]。
pub fn as_lossy_str(&self) -> std::borrow::Cow<'_, str> {
if self.tag == TAG_FLAT {
return std::borrow::Cow::Borrowed(self.flat_ref());
}
let units = self.units();
std::borrow::Cow::Owned(match String::from_utf16(&units) {
Ok(s) => s,
Err(_) => units.iter().map(|&u| char::from_u32(u as u32).unwrap_or('\u{FFFD}')).collect(),
})
}
/// 整块文本的 lossy owned 副本(低频路径:错误消息 / 格式化 / 源码桥接)。
pub fn to_owned_string(&self) -> String {
self.as_lossy_str().into_owned()
}
/// 是否含孤立 surrogate 单元(well-formed 判定的取反)。
///
/// Flat 恒 false(不变式:UTF-8 载荷结构上不可承载);FlatU16 恒 true
/// (保守口径:直构造路径可能持有 well-formed 单元,单元消费路径对
/// well-formed 内容结果仍正确,仅多走单元通道);Cons 扁平化后扫描。
pub fn has_lone_surrogate(&self) -> bool {
match self.tag {
TAG_FLAT => false,
TAG_FLAT_U16 => true,
TAG_CONS => {
let units = self.units();
units_have_lone_surrogate(&units)
}
_ => unreachable!(),
}
}
/// 载荷的记账字节数(GC 账目口径):Flat = UTF-8 内容字节;FlatU16 /
/// Cons = 单元数 × 2。
pub fn payload_bytes(&self) -> usize {
match self.tag {
TAG_FLAT => self.flat_ref().len(),
TAG_FLAT_U16 => self.flat_u16_ref().len() * 2,
TAG_CONS => (unsafe { (*self.cons_ptr()).unit_len as usize }) * 2,
_ => unreachable!(),
}
}
/// 是否为 Flat(UTF-8 文本)形态。
pub fn is_flat(&self) -> bool {
self.tag == TAG_FLAT
}
/// 是否为 FlatU16(单元载荷)形态。
pub fn is_flat_u16(&self) -> bool {
self.tag == TAG_FLAT_U16
}
/// 是否为 Cons(rope)节点。
pub fn is_cons(&self) -> bool {
self.tag == TAG_CONS
}
/// Cons 左右子节点指针对(非 Cons 返回双 null)。仅供 GC 传播闭包使用。
pub fn cons_children(&self) -> [*const JsString; 2] {
if self.tag != TAG_CONS {
return [std::ptr::null(), std::ptr::null()];
}
let node = unsafe { &*self.cons_ptr() };
[node.left, node.right]
}
/// 扁平化产物指针(未扁平化返回 null)。仅供 GC 传播与释放路径使用。
pub fn flat_cache_ptr(&self) -> *const JsString {
if self.tag != TAG_CONS {
return std::ptr::null();
}
unsafe { (*self.cons_ptr()).flat_cache_ptr() }
}
/// 迭代展开 Cons 子树为整块单元序列(显式栈中序收集,防深链递归爆栈)。
fn flatten_units(&self) -> Vec<u16> {
let mut stack = Vec::with_capacity(8);
stack.push(self as *const JsString);
let mut buf: Vec<u16> = Vec::with_capacity(self.utf16_len() as usize);
while let Some(ptr) = stack.pop() {
// SAFETY: 树内节点由 GC 传播保证随本节点存活;展开期子树无并发修改。
let node = unsafe { &*ptr };
match node.tag {
TAG_FLAT => buf.extend(node.flat_ref().encode_utf16()),
TAG_FLAT_U16 => buf.extend_from_slice(node.flat_u16_ref()),
TAG_CONS => {
// 右子先入栈、左子后入,保证左子先出(中序顺序)。
// SAFETY: child 由 new_cons 创建且随父存活。
let child = unsafe { &*node.cons_ptr() };
stack.push(child.right);
stack.push(child.left);
}
_ => unreachable!(),
}
}
buf
}
}
/// 扫描单元序列是否含孤立 surrogate(high 后须紧跟 low 成对;low 不得独立出现)。
fn units_have_lone_surrogate(units: &[u16]) -> bool {
let mut i = 0;
while i < units.len() {
let u = units[i];
if (0xD800..=0xDBFF).contains(&u) {
if i + 1 < units.len() && (0xDC00..=0xDFFF).contains(&units[i + 1]) {
i += 2;
} else {
return true;
}
} else if (0xDC00..=0xDFFF).contains(&u) {
return true;
} else {
i += 1;
}
}
false
}
// SAFETY: 与 `StringPtr`(string_forge.rs)同款论证——Cons 节点只存在于单线程
// session VM 内;跨线程共享的 perm 串载荷为 Flat/FlatU16(无 ConsNode 裸指针
// 写路径),flat_cache 永不写入(perm 串只读共享,无任何可变路径)。裸指针
// 字段不引入跨线程数据竞争。
unsafe impl Send for JsString {}
unsafe impl Sync for JsString {}
#[cfg(test)]
mod tests;
@@ -0,0 +1,90 @@
use super::*;
#[test]
fn js_string_cons_basic() {
// 左右子 Flat 构造 Cons:utf16_len/is_empty/文本视图全部按拼接语义。
let left = Box::new(JsString::new("ab".to_string()));
let right = Box::new(JsString::new("cd".to_string()));
let cons = Box::new(unsafe { JsString::new_cons(&*left, &*right) });
assert_eq!(cons.utf16_len(), 4);
assert!(!cons.is_empty());
assert_eq!(cons.as_lossy_str(), "abcd");
assert_eq!(cons.to_owned_string(), "abcd");
assert!(cons.is_cons());
// 扁平化产物缓存命中:内容仍一致(units 视图同样走缓存)。
assert_eq!(cons.as_lossy_str(), "abcd");
assert_eq!(cons.units().as_ref(), &['a' as u16, 'b' as u16, 'c' as u16, 'd' as u16]);
}
#[test]
fn js_string_layout_anchor_and_tags() {
// 32B 锚 + 三形态 tag/utf16_len 预写口径(布局变更先同步头注释再改断言)。
let flat = JsString::new("ab".to_string());
assert_eq!(std::mem::size_of::<JsString>(), 32);
assert!(flat.is_flat() && !flat.is_cons() && !flat.is_flat_u16());
assert_eq!(flat.tag, TAG_FLAT);
assert_eq!(flat.utf16_len(), 2);
assert_eq!(flat.payload_bytes(), 2);
let u16 = JsString::new_flat_u16(vec![0xD800, 0xDC00]);
assert!(u16.is_flat_u16());
assert_eq!(u16.tag, TAG_FLAT_U16);
assert_eq!(u16.utf16_len(), 2);
assert_eq!(u16.payload_bytes(), 4);
assert_eq!(u16.units_borrowed(), Some(&[0xD800, 0xDC00][..]));
// 代理对 well-formed:smart 路由落 Flat 且内容等价。
let smart = JsString::from_units(vec![0xD800, 0xDC00]);
assert!(smart.is_flat());
assert_eq!(smart.as_str(), "\u{10000}");
// 孤立 surrogate:smart 路由落 FlatU16。
let lone = JsString::from_units(vec![0xD800]);
assert!(lone.is_flat_u16());
assert!(lone.has_lone_surrogate());
assert_eq!(lone.units().as_ref(), &[0xD800]);
assert_eq!(lone.as_lossy_str(), "\u{FFFD}");
let left = Box::new(JsString::new("ab".to_string()));
let right = Box::new(JsString::new("cd".to_string()));
let cons = Box::new(unsafe { JsString::new_cons(&*left, &*right) });
assert!(cons.is_cons());
assert_eq!(cons.tag, TAG_CONS);
assert_eq!(cons.utf16_len(), 4, "Cons 单元长构造期 eager 归纳");
assert_eq!(cons.payload_bytes(), 8);
assert!(!cons.has_lone_surrogate());
// Cons 扁平化产物恒 FlatU16:units 零拷贝借用来自缓存。
let u16_node = cons.units().into_owned();
assert_eq!(u16_node, vec![0x61, 0x62, 0x63, 0x64]);
assert!(!cons.flat_cache_ptr().is_null());
unsafe { JsString::drop_cons_node(cons.cons_node_ptr()) };
}
#[test]
fn js_string_cons_deep_chain_flattens_iteratively() {
// 2000 层左倾链:显式栈展开不爆栈,产物与逐段拼接一致。
let mut nodes: Vec<Box<JsString>> = Vec::new();
nodes.push(Box::new(JsString::new("x".to_string())));
for _ in 0..2000 {
nodes.push(Box::new(JsString::new("y".to_string())));
}
let mut chain_ptr = nodes[0].as_ref() as *const JsString;
for i in 1..nodes.len() {
let cons = Box::new(unsafe { JsString::new_cons(chain_ptr, nodes[i].as_ref() as *const JsString) });
chain_ptr = cons.as_ref() as *const JsString;
nodes.push(cons);
}
let text = nodes.last().unwrap().as_lossy_str();
assert_eq!(text.len(), 1 + 2000);
assert!(text.starts_with('x'));
assert!(text.ends_with('y'));
}
#[test]
fn js_string_cons_empty_parts() {
// 双空串 Cons:空判定与长度均为 0,扁平化为空文本。
let left = Box::new(JsString::new(String::new()));
let right = Box::new(JsString::new(String::new()));
let cons = Box::new(unsafe { JsString::new_cons(&*left, &*right) });
assert!(cons.is_empty());
assert_eq!(cons.utf16_len(), 0);
assert_eq!(cons.as_lossy_str(), "");
}
-89
View File
@@ -7,95 +7,6 @@ fn object_size_bounds() {
assert!(sz <= 256, "JsObject grew unexpectedly: {sz}B");
}
#[test]
fn js_string_cons_basic() {
// 左右子 Flat 构造 Cons:utf16_len/is_empty/文本视图全部按拼接语义。
let left = Box::new(JsString::new("ab".to_string()));
let right = Box::new(JsString::new("cd".to_string()));
let cons = Box::new(unsafe { JsString::new_cons(&*left, &*right) });
assert_eq!(cons.utf16_len(), 4);
assert!(!cons.is_empty());
assert_eq!(cons.as_lossy_str(), "abcd");
assert_eq!(cons.to_owned_string(), "abcd");
assert!(cons.is_cons());
// 扁平化产物缓存命中:内容仍一致(units 视图同样走缓存)。
assert_eq!(cons.as_lossy_str(), "abcd");
assert_eq!(cons.units().as_ref(), &['a' as u16, 'b' as u16, 'c' as u16, 'd' as u16]);
}
#[test]
fn js_string_layout_anchor_and_tags() {
// 32B 锚 + 三形态 tag/utf16_len 预写口径(布局变更先同步头注释再改断言)。
let flat = JsString::new("ab".to_string());
assert_eq!(std::mem::size_of::<JsString>(), 32);
assert!(flat.is_flat() && !flat.is_cons() && !flat.is_flat_u16());
assert_eq!(flat.tag, TAG_FLAT);
assert_eq!(flat.utf16_len(), 2);
assert_eq!(flat.payload_bytes(), 2);
let u16 = JsString::new_flat_u16(vec![0xD800, 0xDC00]);
assert!(u16.is_flat_u16());
assert_eq!(u16.tag, TAG_FLAT_U16);
assert_eq!(u16.utf16_len(), 2);
assert_eq!(u16.payload_bytes(), 4);
assert_eq!(u16.units_borrowed(), Some(&[0xD800, 0xDC00][..]));
// 代理对 well-formed:smart 路由落 Flat 且内容等价。
let smart = JsString::from_units(vec![0xD800, 0xDC00]);
assert!(smart.is_flat());
assert_eq!(smart.as_str(), "\u{10000}");
// 孤立 surrogate:smart 路由落 FlatU16。
let lone = JsString::from_units(vec![0xD800]);
assert!(lone.is_flat_u16());
assert!(lone.has_lone_surrogate());
assert_eq!(lone.units().as_ref(), &[0xD800]);
assert_eq!(lone.as_lossy_str(), "\u{FFFD}");
let left = Box::new(JsString::new("ab".to_string()));
let right = Box::new(JsString::new("cd".to_string()));
let cons = Box::new(unsafe { JsString::new_cons(&*left, &*right) });
assert!(cons.is_cons());
assert_eq!(cons.tag, TAG_CONS);
assert_eq!(cons.utf16_len(), 4, "Cons 单元长构造期 eager 归纳");
assert_eq!(cons.payload_bytes(), 8);
assert!(!cons.has_lone_surrogate());
// Cons 扁平化产物恒 FlatU16:units 零拷贝借用来自缓存。
let u16_node = cons.units().into_owned();
assert_eq!(u16_node, vec![0x61, 0x62, 0x63, 0x64]);
assert!(!cons.flat_cache_ptr().is_null());
unsafe { JsString::drop_cons_node(cons.cons_node_ptr()) };
}
#[test]
fn js_string_cons_deep_chain_flattens_iteratively() {
// 2000 层左倾链:显式栈展开不爆栈,产物与逐段拼接一致。
let mut nodes: Vec<Box<JsString>> = Vec::new();
nodes.push(Box::new(JsString::new("x".to_string())));
for _ in 0..2000 {
nodes.push(Box::new(JsString::new("y".to_string())));
}
let mut chain_ptr = nodes[0].as_ref() as *const JsString;
for i in 1..nodes.len() {
let cons = Box::new(unsafe { JsString::new_cons(chain_ptr, nodes[i].as_ref() as *const JsString) });
chain_ptr = cons.as_ref() as *const JsString;
nodes.push(cons);
}
let text = nodes.last().unwrap().as_lossy_str();
assert_eq!(text.len(), 1 + 2000);
assert!(text.starts_with('x'));
assert!(text.ends_with('y'));
}
#[test]
fn js_string_cons_empty_parts() {
// 双空串 Cons:空判定与长度均为 0,扁平化为空文本。
let left = Box::new(JsString::new(String::new()));
let right = Box::new(JsString::new(String::new()));
let cons = Box::new(unsafe { JsString::new_cons(&*left, &*right) });
assert!(cons.is_empty());
assert_eq!(cons.utf16_len(), 0);
assert_eq!(cons.as_lossy_str(), "");
}
#[test]
fn new_empty_defaults() {
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());