mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
refactor: 字符串族下沉对象模块 string 子模块
This commit is contained in:
@@ -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};
|
||||
|
||||
/// 原生函数指针的类型安全不透明包装。
|
||||
///
|
||||
|
||||
@@ -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(), "");
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user