mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 11:41:13 +08:00
types/kernel/vm/builtins: JsString UTF-16 载荷层(FlatU16 变体 + 单元入口)——原 UTF-8 载荷只存 well-formed 串、孤立 surrogate lossy 转 U+FFFD 回写,FromCodePoint/属性转义与字符串面码点语义皆破;现载荷直存 [u64;3](Flat well-formed / FlatU16 单元序列 / Cons 拼接),utf16_len 独立 32 位字,32B 布局锚保持;from_units 智能路由(含孤立→FlatU16),as_str 仅限 Flat,units/units_borrowed 单元借用,as_lossy_str 宽容读;string_forge 编解码 surrogate 感知(孤立 surrogate 逃逸 hex4 编码、解码展开 astral 代理对、string_ptr 由单元物化);VmHost 单元物化/分配/查询入口 + runtime_api 单元 API;字符串内置全法单元化(charAt/slice/substr/split/replace/search/match/matchAll 单元定位;空模式边界:search→0、match→[""]、replace 插 0 位、matchAll 游标耗尽守卫;接收者 ToString 错误原样传播);正则内置单元化(exec/test/search/symbol_* 单元文本,替换串 \u{...} 与孤立 surrogate 文本处理,lastIndex 与 matchAll 游标单元化);字符串迭代器码点化(高代理+后续低代理成对产出,astral 值回真实码点);String(Symbol) 抛 TypeError;回归钉更新五处(charAt/split/slice/substr 单元精确语义 + dispose 钉改 TypeError 断言);四 crate 全绿(oxide_vm 98 套件 2403 测)+ clippy/fmt 零新增
This commit is contained in:
@@ -99,11 +99,11 @@ impl Vm {
|
||||
}
|
||||
|
||||
/// 字符串拼接接线点(二元 `+`/`+=` 共用):O(1) 链接为 Cons(rope)节点,
|
||||
/// 消除逐次整串重拷贝。文本在消费时(`.length`/`==`/string_ref 等)惰性扁平化。
|
||||
/// 消除逐次整串重拷贝。单元序列在消费时(`.length`/`==`/单元读取等)惰性扁平化。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 非字符串操作数先转成叶子字符串(`push_to_string` 同文本语义;小整数走
|
||||
/// 永久缓存零分配,其余 `to_string` + owned)。
|
||||
/// 1. 非字符串操作数先转成叶子字符串(`to_string` 同文本语义;小整数走
|
||||
/// 永久缓存零分配,其余 owned)。
|
||||
/// 2. 两个字符串经 `new_cons_string` 链接(每链接 1 次节点分配,对比原 O(n)
|
||||
/// 拷贝 + 结果串分配)。
|
||||
///
|
||||
@@ -112,18 +112,26 @@ impl Vm {
|
||||
/// dispatch 指令边界(结果已写寄存器后检查),链接期子节点天然安全,
|
||||
/// 无需在途保护。
|
||||
fn concat_strings(&mut self, lhs: JsValue, rhs: JsValue) -> JsValue {
|
||||
// 小链急切扁平(与 rope 前同款实现):总字节长 ≤ 阈值时单次预分配
|
||||
// 写齐,零叶子转换 / Cons 节点 / 惰性扁平化开销——拼接即消费的小串
|
||||
// 场景与基线逐字节等价(该场景 rope 的 O(1) 链接收益低于其固定开销)。
|
||||
let lbytes = if lhs.is_string() { unsafe { (*lhs.as_string_ptr()).len() } } else { 0 };
|
||||
let rbytes = if rhs.is_string() { unsafe { (*rhs.as_string_ptr()).len() } } else { 0 };
|
||||
if lbytes + rbytes <= Self::CONS_FLATTEN_BYTES {
|
||||
let mut buf = String::with_capacity(lbytes + rbytes + 32);
|
||||
coercion::push_to_string(lhs, &mut buf);
|
||||
coercion::push_to_string(rhs, &mut buf);
|
||||
return self.new_string_owned(buf);
|
||||
// 小链急切扁平:总单元长 ≤ 阈值时单次预分配写齐,零叶子转换 / Cons 节点 /
|
||||
// 惰性扁平化开销——良形小串场景载荷与旧字节路径逐位一致(该场景 rope 的
|
||||
// O(1) 链接收益低于其固定开销)。
|
||||
let lu = if lhs.is_string() {
|
||||
unsafe { (*lhs.as_string_ptr()).utf16_len() as usize }
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let ru = if rhs.is_string() {
|
||||
unsafe { (*rhs.as_string_ptr()).utf16_len() as usize }
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if lu + ru <= Self::CONS_FLATTEN_UNITS {
|
||||
let mut units = Vec::with_capacity(lu + ru);
|
||||
coercion::push_units_to(lhs, &mut units);
|
||||
coercion::push_units_to(rhs, &mut units);
|
||||
return self.new_string_units_owned(units);
|
||||
}
|
||||
// 大链走 Cons rope:非字符串操作数先转叶子,再 O(1) 链接(文本惰性扁平化)。
|
||||
// 大链走 Cons rope:非字符串操作数先转叶子,再 O(1) 链接(单元惰性扁平化)。
|
||||
let lv = if lhs.is_string() { lhs } else { self.string_leaf(lhs) };
|
||||
let rv = if rhs.is_string() { rhs } else { self.string_leaf(rhs) };
|
||||
self.new_cons_string(lv, rv)
|
||||
@@ -227,9 +235,9 @@ impl Vm {
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 剩余操作数按序 coerce(对象 ToPrimitive 副作用顺序与左结合一致,抛错点先于分配)。
|
||||
/// 2. 精确总长:字符串操作数取字节长,非字符串给 32 字节余量(int/double/bool/null/
|
||||
/// undefined 的十进制文本上界;BigInt 超长时 String 自动扩容兜底)。
|
||||
/// 3. 单趟 push_to_string + new_string_owned(零二次拷贝)。
|
||||
/// 2. 精确总长:字符串操作数取单元长,非字符串给 32 单元余量(int/double/bool/null/
|
||||
/// undefined 的十进制文本上界;BigInt 超长时 Vec 自动扩容兜底)。
|
||||
/// 3. 单趟单元展开 + 智能路由创建(零二次拷贝;良形内容落 Flat 与旧路径逐位一致)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// 新建一个会话字符串。
|
||||
@@ -245,16 +253,16 @@ impl Vm {
|
||||
let cap = parts.iter().fold(0usize, |acc, p| {
|
||||
acc + if p.is_string() {
|
||||
// SAFETY: p 是字符串值。
|
||||
unsafe { (*p.as_string_ptr()).len() }
|
||||
unsafe { (*p.as_string_ptr()).utf16_len() as usize }
|
||||
} else {
|
||||
32
|
||||
}
|
||||
});
|
||||
let mut buf = String::with_capacity(cap);
|
||||
let mut units = Vec::with_capacity(cap);
|
||||
for p in &parts {
|
||||
coercion::push_to_string(*p, &mut buf);
|
||||
coercion::push_units_to(*p, &mut units);
|
||||
}
|
||||
Ok(self.new_string_owned(buf))
|
||||
Ok(self.new_string_units_owned(units))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
@@ -44,8 +44,9 @@ impl Vm {
|
||||
self.regs[rd] = val;
|
||||
Ok(false)
|
||||
} else {
|
||||
// SAFETY: key_val 已校验为字符串值,as_str 桥接 JsString 内容。
|
||||
let name = unsafe { (*key_val.as_string_ptr()).as_str() };
|
||||
// SAFETY: key_val 已校验为字符串值;错误消息走 lossy 文本(不可解析名
|
||||
// 含孤立 surrogate 时映射为 FFFD,仅为展示用途)。
|
||||
let name = unsafe { (*key_val.as_string_ptr()).as_lossy_str() };
|
||||
self.raise_error_kind("ReferenceError", &format!("{name} is not defined"))?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -541,7 +541,7 @@ impl SessionGc {
|
||||
/// `ptr` 必须是 `Box::into_raw(Box::new(JsString))` 产生的非空指针,仍存在
|
||||
/// 于 `session_string_ptrs`,且恰好释放一次。
|
||||
unsafe fn drop_dead_session_string(ptr: *mut JsString) -> u64 {
|
||||
let bytes = (size_of::<JsString>() + (*ptr).len()) as u64;
|
||||
let bytes = (size_of::<JsString>() + (*ptr).payload_bytes()) as u64;
|
||||
Self::drop_session_string_box(ptr);
|
||||
bytes
|
||||
}
|
||||
@@ -701,7 +701,7 @@ impl SessionGc {
|
||||
if self.live_strings.contains(&ptr) {
|
||||
// 存活——地址不变,无需重写。
|
||||
// SAFETY: ptr 是仍归 VM 所有的存活 session 字符串 box。
|
||||
live_bytes += unsafe { size_of::<JsString>() + (*ptr).len() };
|
||||
live_bytes += unsafe { size_of::<JsString>() + (*ptr).payload_bytes() };
|
||||
live.push(ptr);
|
||||
} else {
|
||||
// SAFETY: ptr 在 session_string_ptrs 中但不可达,恰好释放一次。
|
||||
@@ -1032,7 +1032,7 @@ impl SessionGc {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptr 在字符串表登记,收尾前有效。
|
||||
string_bytes += size_of::<JsString>() + unsafe { (*ptr).len() };
|
||||
string_bytes += size_of::<JsString>() + unsafe { (*ptr).payload_bytes() };
|
||||
}
|
||||
let bigint_bytes = vm.gc_state.session_bigint_ptrs.borrow().len() * size_of::<num_bigint::BigInt>();
|
||||
vm.gc_state.session_bytes_allocated = object_bytes + string_bytes + bigint_bytes;
|
||||
@@ -2311,7 +2311,7 @@ mod tests {
|
||||
vm.maybe_collect_session_strings();
|
||||
|
||||
// 账目 = 对象头 + 存活串(size_of::<JsString>() + len),死串不再计入。
|
||||
let expected = size_of::<JsObject>() + (size_of::<JsString>() + unsafe { (*live_ptr).len() });
|
||||
let expected = size_of::<JsObject>() + (size_of::<JsString>() + unsafe { (*live_ptr).payload_bytes() });
|
||||
assert_eq!(vm.gc_state.session_bytes_allocated, expected);
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&dead.as_string_ptr_mut()));
|
||||
}
|
||||
@@ -2349,7 +2349,7 @@ mod tests {
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&parent_ptr));
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&l_ptr));
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&r_ptr));
|
||||
assert_eq!(unsafe { (*parent_ptr).as_str() }, "left-partright-part");
|
||||
assert_eq!(unsafe { (*parent_ptr).as_lossy_str() }, "left-partright-part");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2373,7 +2373,7 @@ mod tests {
|
||||
let (parent, _, _) = make_cons_pair(&mut vm, "left-part", "right-part");
|
||||
let parent_ptr = parent.as_string_ptr_mut();
|
||||
// 触发扁平化:产物发布到 flat_cache。
|
||||
assert_eq!(unsafe { (*parent_ptr).flat_str() }, "left-partright-part");
|
||||
assert_eq!(unsafe { (*parent_ptr).as_lossy_str() }, "left-partright-part");
|
||||
let flat_ptr = unsafe { (*parent_ptr).flat_cache_ptr() };
|
||||
assert!(!flat_ptr.is_null());
|
||||
|
||||
@@ -2400,7 +2400,7 @@ mod tests {
|
||||
collect(&mut vm);
|
||||
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&chain_ptr));
|
||||
assert_eq!(unsafe { (*chain_ptr).as_str() }, format!("root{}", "x".repeat(1024)));
|
||||
assert_eq!(unsafe { (*chain_ptr).as_lossy_str() }, format!("root{}", "x".repeat(1024)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2418,7 +2418,7 @@ mod tests {
|
||||
// perm 子节点永不释放(不在 session 表);session 子节点随父存活。
|
||||
assert!(!vm.gc_state.session_string_ptrs.contains(&perm_ptr));
|
||||
assert!(vm.gc_state.session_string_ptrs.contains(&session_ptr));
|
||||
assert_eq!(unsafe { (*parent.as_string_ptr_mut()).as_str() }, "perm-leafsession-leaf");
|
||||
assert_eq!(unsafe { (*parent.as_string_ptr_mut()).as_lossy_str() }, "perm-leafsession-leaf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2426,7 +2426,7 @@ mod tests {
|
||||
let mut vm = Vm::new();
|
||||
let (parent, _, _) = make_cons_pair(&mut vm, "left-part", "right-part");
|
||||
// 触发扁平化(产物挂在父上)。
|
||||
assert_eq!(unsafe { (*parent.as_string_ptr_mut()).flat_str() }, "left-partright-part");
|
||||
assert_eq!(unsafe { (*parent.as_string_ptr_mut()).as_lossy_str() }, "left-partright-part");
|
||||
vm.regs[0] = parent;
|
||||
|
||||
// full_reset 清空全部 session 字符串(连带产物):无泄漏、无 double-free。
|
||||
|
||||
+68
-17
@@ -53,6 +53,33 @@ fn canonical_index_of(s: &str) -> Option<u32> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 从单元序列判定字符串是否为规范数组下标(无前导零的纯 ASCII 数字串),
|
||||
/// 并反解其值。口径与 [`canonical_index_of`] 一致,单元口径覆盖非 Flat
|
||||
/// 载荷的键推导。只覆盖 `[0, INT_KEY_COUNT)`,更大的数字串走普通字符串键。
|
||||
fn canonical_index_units(units: &[u16]) -> Option<u32> {
|
||||
if units.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if !matches!(units[0], 0x30..=0x39) {
|
||||
return None;
|
||||
}
|
||||
if units.len() > 1 && units[0] == 0x30 {
|
||||
return None;
|
||||
}
|
||||
if units.len() > 10 {
|
||||
return None;
|
||||
}
|
||||
let mut v: u32 = 0;
|
||||
for &u in units.iter() {
|
||||
let d = match u {
|
||||
0x30..=0x39 => u - 0x30,
|
||||
_ => return None,
|
||||
};
|
||||
v = v.checked_mul(10)? + d as u32;
|
||||
}
|
||||
(v < INT_KEY_COUNT).then_some(v)
|
||||
}
|
||||
|
||||
/// 将 [`NativeFnPtr`] 转换为可调用的 [`NativeFn`]。
|
||||
///
|
||||
/// # Safety
|
||||
@@ -1169,7 +1196,7 @@ impl Vm {
|
||||
}
|
||||
for &ptr in &self.gc_state.session_string_ptrs {
|
||||
// SAFETY: ptr 来自字符串表登记,收尾前始终有效。
|
||||
bytes += (std::mem::size_of::<oxide_types::object::JsString>() + unsafe { (*ptr).len() }) as u64;
|
||||
bytes += (std::mem::size_of::<oxide_types::object::JsString>() + unsafe { (*ptr).payload_bytes() }) as u64;
|
||||
}
|
||||
bytes += (self.gc_state.session_bigint_ptrs.borrow().len() * std::mem::size_of::<num_bigint::BigInt>()) as u64;
|
||||
bytes += (self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::<Cell>()) as u64;
|
||||
@@ -1362,12 +1389,18 @@ impl Vm {
|
||||
return Ok(make_int_key(d as u32));
|
||||
}
|
||||
} else if val.is_string() {
|
||||
// SAFETY: val 是字符串值,把其内容桥接为永久 key id(rope 经惰性扁平化)。
|
||||
let s = unsafe { (*val.as_string_ptr()).as_str() };
|
||||
if let Some(i) = canonical_index_of(s) {
|
||||
return Ok(make_int_key(i));
|
||||
// SAFETY: val 是字符串值,按载荷形态桥接为永久 key id。
|
||||
let s = unsafe { &*val.as_string_ptr() };
|
||||
if s.is_flat() {
|
||||
// Flat:良形 UTF-8 文本,键推导与旧路径逐位一致。
|
||||
let text = s.as_str();
|
||||
if let Some(i) = canonical_index_of(text) {
|
||||
return Ok(make_int_key(i));
|
||||
}
|
||||
return Ok(self.kernel_core.perm_interner().intern(text).0);
|
||||
}
|
||||
return Ok(self.kernel_core.perm_interner().intern(s).0);
|
||||
// 单元载荷:键推导同规范(见 string_key_units)。
|
||||
return Ok(self.string_key_units(&s.units()));
|
||||
}
|
||||
// Symbol 值直接编码为 Symbol 键(不进字符串 interner,键相互独立)。
|
||||
if val.is_symbol() {
|
||||
@@ -1380,18 +1413,30 @@ impl Vm {
|
||||
return Ok(make_well_known_symbol_key(id));
|
||||
}
|
||||
// ToPropertyKey:对象经 ToPrimitive(string hint),结果为 Symbol 时直接作键;
|
||||
// 其余字符串经规范化(规范数字串映射整数键)与字符串分支统一口径。
|
||||
// 其余字符串按单元序列推导键(避免 lossy 文本桥接破坏孤立 surrogate 键)。
|
||||
let prim = coercion::to_primitive(val, coercion::ToPrimitiveHint::String, self)?;
|
||||
if prim.is_symbol() {
|
||||
return Ok(make_symbol_key(prim.as_symbol_index()));
|
||||
}
|
||||
let key = coercion::to_string(prim);
|
||||
return Ok(oxide_runtime_api::VmHost::string_key_si(self, &key));
|
||||
let units = oxide_runtime_api::to_units_full(prim, self)?;
|
||||
return Ok(self.string_key_units(&units));
|
||||
}
|
||||
// 其它原始值(BigInt 等):ToPropertyKey 一律转字符串并走规范化,避免与
|
||||
// 数字键区间分裂(`o[5n]` 与 `o["5"]`/`o[5]` 必须同键)。
|
||||
let key = coercion::to_string(val);
|
||||
Ok(oxide_runtime_api::VmHost::string_key_si(self, &key))
|
||||
let units = oxide_runtime_api::to_units_full(val, self)?;
|
||||
Ok(self.string_key_units(&units))
|
||||
}
|
||||
|
||||
/// 从单元序列推导属性键 si(`property_key_si` 字符串分支的口径抽取):
|
||||
/// 规范数组下标 → 整数键,其余以 `encode_key` 形态入键空间(孤立 surrogate
|
||||
/// 以转义形态区分,不与良形键碰撞)。
|
||||
pub(crate) fn string_key_units(&self, units: &[u16]) -> u32 {
|
||||
if let Some(i) = canonical_index_units(units) {
|
||||
make_int_key(i)
|
||||
} else {
|
||||
let key = oxide_kernel::string_forge::encode_key(units);
|
||||
self.kernel_core.perm_interner().intern(&key).0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn array_index_from_property_key(&self, prop_name_si: u32) -> Option<u32> {
|
||||
@@ -2496,12 +2541,6 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
fn new_string_owned(&mut self, s: String) -> JsValue {
|
||||
Vm::new_string_owned(self, s)
|
||||
}
|
||||
fn string_ref(&self, val: JsValue) -> &str {
|
||||
// SAFETY: 调用方保证 val 为字符串值。perm 串由内核持有永不释放;session
|
||||
// 串仅经 &mut self 路径(new_string/new_string_owned/GC)释放,此处 &self
|
||||
// 借用期间编译器强制不存在 &mut 存续,字符串不会在借用期内回收。
|
||||
unsafe { (*val.as_string_ptr()).as_str() }
|
||||
}
|
||||
fn new_bigint(&mut self, v: num_bigint::BigInt) -> JsValue {
|
||||
Vm::new_bigint(self, v)
|
||||
}
|
||||
@@ -2542,6 +2581,18 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
self.kernel_core.perm_interner().intern(s).0
|
||||
}
|
||||
}
|
||||
fn string_units(&self, val: JsValue) -> std::borrow::Cow<'_, [u16]> {
|
||||
// SAFETY: 调用方保证 val 为字符串值。perm 串由内核持有永不释放;session
|
||||
// 串仅经 &mut self 路径(new_string/GC)释放,&self 借用期间编译器强制
|
||||
// 不存在 &mut 存续,字符串不会在借用期内回收。
|
||||
unsafe { (*val.as_string_ptr()).units() }
|
||||
}
|
||||
fn new_string_units(&mut self, units: &[u16]) -> JsValue {
|
||||
Vm::new_string_units(self, units)
|
||||
}
|
||||
fn new_string_units_owned(&mut self, units: Vec<u16>) -> JsValue {
|
||||
Vm::new_string_units_owned(self, units)
|
||||
}
|
||||
fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option<JsValue> {
|
||||
self.resolve_property(obj, prop_name_si)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::vm::{ForInIter, FrameArgs, FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH};
|
||||
use crate::vm_trace;
|
||||
use oxide_runtime_api::{push_to_string, to_boolean, to_string_full, VmHost};
|
||||
use oxide_runtime_api::{push_units_to, to_boolean, to_units_full};
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::private_key::{int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key};
|
||||
use oxide_types::value::JsValue;
|
||||
@@ -267,7 +267,9 @@ impl Vm {
|
||||
let segment_count = (header >> 16) as usize;
|
||||
let len_hint = (header & 0xFFFF) as usize;
|
||||
|
||||
let mut result = String::with_capacity(len_hint.max(16));
|
||||
// 单元缓冲:字符串操作数按单元序列展开(lone surrogate 保真),
|
||||
// len_hint 为单元数口径(emit 侧同口径)。
|
||||
let mut units: Vec<u16> = Vec::with_capacity(len_hint.max(16));
|
||||
for _ in 0..segment_count {
|
||||
let seg = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
@@ -275,17 +277,16 @@ impl Vm {
|
||||
let reg = (seg & 0x7FFF_FFFF) as usize;
|
||||
let val = self.regs[reg];
|
||||
if val.is_string() {
|
||||
// SAFETY: val 是字符串值;借用仅在本次 push 内消费,不跨分配点。
|
||||
let s = unsafe { (*val.as_string_ptr()).as_str() };
|
||||
result.push_str(s);
|
||||
// SAFETY: val 是字符串值;借用仅在本次展开内消费,不跨分配点。
|
||||
let s = unsafe { &*val.as_string_ptr() };
|
||||
units.extend_from_slice(s.units().as_ref());
|
||||
} else if val.is_object() || val.is_symbol() {
|
||||
// 对象走完整 ToString(ToPrimitive 副作用顺序);Symbol 由
|
||||
// to_string_full 抛 TypeError(push_to_string 无 symbol 分支)。
|
||||
let s = to_string_full(val, self)?;
|
||||
result.push_str(&s);
|
||||
// 对象走完整 ToString(ToPrimitive 副作用顺序,结果保单元);
|
||||
// Symbol 抛 TypeError(to_units_full 无 symbol 直写分支)。
|
||||
units.extend(to_units_full(val, self)?);
|
||||
} else {
|
||||
// 原始值直写结果缓冲,免中间 String。
|
||||
push_to_string(val, &mut result);
|
||||
push_units_to(val, &mut units);
|
||||
}
|
||||
} else {
|
||||
let const_idx = (seg & 0x7FFF_FFFF) as usize;
|
||||
@@ -293,14 +294,14 @@ impl Vm {
|
||||
if const_idx < imm.len() {
|
||||
let val = imm[const_idx];
|
||||
if val.is_string() {
|
||||
// SAFETY: val 是字符串值;借用仅在本次 push 内消费,不跨分配点。
|
||||
let s = unsafe { (*val.as_string_ptr()).as_str() };
|
||||
result.push_str(s);
|
||||
// SAFETY: val 是字符串值;借用仅在本次展开内消费,不跨分配点。
|
||||
let s = unsafe { &*val.as_string_ptr() };
|
||||
units.extend_from_slice(s.units().as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.regs[rd] = self.new_string_owned(result);
|
||||
self.regs[rd] = self.new_string_units_owned(units);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -825,16 +826,11 @@ impl Vm {
|
||||
JsValue::from_js_object(proto_ptr),
|
||||
));
|
||||
if src.is_string() {
|
||||
let code_units: Vec<u16> = unsafe { (*src.as_string_ptr()).as_str().encode_utf16().collect() };
|
||||
// SAFETY: src 是字符串值;单元序列展开(lone surrogate 保真)。
|
||||
let code_units: Vec<u16> = unsafe { (*src.as_string_ptr()).units().into_owned() };
|
||||
for (i, unit) in code_units.iter().enumerate() {
|
||||
let si = make_int_key(i as u32);
|
||||
let ch_val = match char::from_u32(*unit as u32) {
|
||||
Some(c) => match self.single_char(c) {
|
||||
Some(v) => v,
|
||||
None => self.new_string(&c.to_string()),
|
||||
},
|
||||
None => self.new_string(""),
|
||||
};
|
||||
let ch_val = self.unit_char_value(*unit);
|
||||
let rest = unsafe { &mut *rest_ptr };
|
||||
self.set_or_create_prop_value(rest, si, ch_val);
|
||||
}
|
||||
@@ -842,23 +838,34 @@ impl Vm {
|
||||
self.regs[rd] = JsValue::from_js_object(rest_ptr);
|
||||
return Ok(());
|
||||
}
|
||||
// 排除名单常量按单元序列展开(lone surrogate 名保真),0x0000 分隔。
|
||||
let excluded_const = self
|
||||
.immutables()
|
||||
.get(excluded_idx)
|
||||
.and_then(|v| {
|
||||
if v.is_string() {
|
||||
// SAFETY: v 是字符串常量值。
|
||||
Some(unsafe { (*v.as_string_ptr()).to_owned_string() })
|
||||
Some(unsafe { (*v.as_string_ptr()).units().into_owned() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let mut excluded: std::collections::HashSet<u32> = excluded_const
|
||||
.split('\0')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| self.string_key_si(s))
|
||||
.collect();
|
||||
let mut excluded: std::collections::HashSet<u32> = std::collections::HashSet::new();
|
||||
let mut seg: Vec<u16> = Vec::new();
|
||||
for &u in excluded_const.iter() {
|
||||
if u == 0x0000 {
|
||||
if !seg.is_empty() {
|
||||
excluded.insert(self.string_key_units(&seg));
|
||||
seg.clear();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
seg.push(u);
|
||||
}
|
||||
if !seg.is_empty() {
|
||||
excluded.insert(self.string_key_units(&seg));
|
||||
}
|
||||
// 运行时 excluded:b 槽数组(computed key 求值结果)的元素 ToPropertyKey 后排除。
|
||||
if b != 0 {
|
||||
let arr_val = self.regs[b];
|
||||
@@ -883,20 +890,14 @@ impl Vm {
|
||||
// 字符串包装对象:索引字符是可枚举自有属性(ToObject("str") 的 0..len-1)。
|
||||
if src_obj.type_tag == JsObject::OBJ_TYPE_STRING_OBJ {
|
||||
let raw = src_obj.get_prop_at(0);
|
||||
let s = unsafe { (*raw.as_string_ptr()).to_owned_string() };
|
||||
let code_units: Vec<u16> = s.encode_utf16().collect();
|
||||
// SAFETY: 包装对象槽 0 恒为字符串值;单元序列展开(lone surrogate 保真)。
|
||||
let code_units: Vec<u16> = unsafe { (*raw.as_string_ptr()).units().into_owned() };
|
||||
for (i, unit) in code_units.iter().enumerate() {
|
||||
let si = make_int_key(i as u32);
|
||||
if excluded.contains(&si) {
|
||||
continue;
|
||||
}
|
||||
let ch_val = match char::from_u32(*unit as u32) {
|
||||
Some(c) => match self.single_char(c) {
|
||||
Some(v) => v,
|
||||
None => self.new_string(&c.to_string()),
|
||||
},
|
||||
None => self.new_string(""),
|
||||
};
|
||||
let ch_val = self.unit_char_value(*unit);
|
||||
assignments.push((si, ch_val));
|
||||
}
|
||||
}
|
||||
@@ -964,19 +965,14 @@ impl Vm {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 字符串源:按索引复制字符(可枚举索引属性)。
|
||||
// 字符串源:按单元下标复制(可枚举索引属性,lone surrogate 保真)。
|
||||
if src.is_string() {
|
||||
let target = unsafe { &mut *target_val.as_js_object_ptr() };
|
||||
let code_units: Vec<u16> = unsafe { (*src.as_string_ptr()).as_str().encode_utf16().collect() };
|
||||
// SAFETY: src 是字符串值。
|
||||
let code_units: Vec<u16> = unsafe { (*src.as_string_ptr()).units().into_owned() };
|
||||
for (i, unit) in code_units.iter().enumerate() {
|
||||
let si = make_int_key(i as u32);
|
||||
let ch_val = match char::from_u32(*unit as u32) {
|
||||
Some(c) => match self.single_char(c) {
|
||||
Some(v) => v,
|
||||
None => self.new_string(&c.to_string()),
|
||||
},
|
||||
None => self.new_string(""),
|
||||
};
|
||||
let ch_val = self.unit_char_value(*unit);
|
||||
self.set_or_create_prop_value(target, si, ch_val);
|
||||
}
|
||||
return Ok(());
|
||||
|
||||
@@ -16,10 +16,11 @@ use oxide_types::object::{JsObject, JsString, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
impl Vm {
|
||||
/// Cons(rope)节点保留的字节阈值:拼接总长 ≤ 该值时急切扁平为 Flat。
|
||||
/// 小链的"链接 + 首次消费扁平化"双重分配高于直接拷贝,直接扁平更优;
|
||||
/// 超过阈值后 O(n²) 拷贝成本超过节点开销,Cons 的 O(1) 链接才值得。
|
||||
pub(crate) const CONS_FLATTEN_BYTES: usize = 128;
|
||||
/// Cons(rope)节点保留的单元阈值:拼接总长(UTF-16 单元数)≤ 该值时
|
||||
/// 急切扁平。小链的"链接 + 首次消费扁平化"双重分配高于直接拷贝,直接
|
||||
/// 扁平更优;超过阈值后 O(n²) 拷贝成本超过节点开销,Cons 的 O(1) 链接
|
||||
/// 才值得。
|
||||
pub(crate) const CONS_FLATTEN_UNITS: usize = 128;
|
||||
|
||||
/// 替换本 VM 独占的内建 P 对象:先恰好释放旧副本的属性区一次,
|
||||
/// 再让旧 Arc 归零(旧副本无 Drop 口径兜底,full_reset 重初始化
|
||||
@@ -640,24 +641,47 @@ impl Vm {
|
||||
/// 接线点调用(CONCAT_N 保持急切扁平,不经此路径)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 登记节点到 session 字符串表(账目 = `size_of::<JsString>() + byte_len`)。
|
||||
/// - 登记节点到 session 字符串表(账目 = `size_of::<JsString>() + payload_bytes`)。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 调用方须保证 `left`/`right` 在调用期间存活:子节点(coerce 新鲜字符串 /
|
||||
/// 新建叶子)要么已是执行根,要么在写入根前不被任何回收点触达——当前
|
||||
/// 回收点仅在指令边界,链接结果写寄存器先于下一次检查,天然满足。
|
||||
/// - 调用方(`concat_strings`)已按字节阈值过滤:仅大链(总长 >
|
||||
/// [`Self::CONS_FLATTEN_BYTES`])进入本方法,小链由其急切扁平。
|
||||
/// - 调用方(`concat_strings`)已按单元阈值过滤:仅大链(总长 >
|
||||
/// [`Self::CONS_FLATTEN_UNITS`])进入本方法,小链由其急切扁平。
|
||||
#[inline]
|
||||
pub fn new_cons_string(&mut self, left: JsValue, right: JsValue) -> JsValue {
|
||||
debug_assert!(left.is_string() && right.is_string(), "new_cons_string 只接收字符串操作数");
|
||||
let left_ptr = left.as_string_ptr_mut();
|
||||
let right_ptr = right.as_string_ptr_mut();
|
||||
// SAFETY: 两指针指向存活的 JsString(调用方保证),len() 各为 O(1)。
|
||||
let byte_len = unsafe { (*left_ptr).len() + (*right_ptr).len() };
|
||||
// SAFETY: new_cons 的调用方(本函数)负责保证子节点随节点存活。
|
||||
let ptr = Box::into_raw(Box::new(unsafe { JsString::new_cons(left_ptr, right_ptr) }));
|
||||
self.register_session_string(ptr, byte_len)
|
||||
// SAFETY: ptr 指向刚创建的存活节点,账目口径 = 单元数 × 2。
|
||||
let bytes = unsafe { (*ptr).payload_bytes() };
|
||||
self.register_session_string(ptr, bytes)
|
||||
}
|
||||
|
||||
/// 以单元序列分配可被 session GC 回收的字符串值:智能路由(含孤立
|
||||
/// surrogate 落 FlatU16,否则 Flat),语义与副作用同 `new_string_owned`。
|
||||
pub fn new_string_units_owned(&mut self, units: Vec<u16>) -> JsValue {
|
||||
let ptr = Box::into_raw(Box::new(JsString::from_units(units)));
|
||||
// SAFETY: ptr 指向刚创建的存活 JsString。
|
||||
let bytes = unsafe { (*ptr).payload_bytes() };
|
||||
self.register_session_string(ptr, bytes)
|
||||
}
|
||||
|
||||
/// 同 `new_string_units_owned`,以借用单元序列接收。
|
||||
pub fn new_string_units(&mut self, units: &[u16]) -> JsValue {
|
||||
self.new_string_units_owned(units.to_vec())
|
||||
}
|
||||
|
||||
/// 单单元的属性值:ASCII 单元命中共享 perm 串(零分配、可指针短路),
|
||||
/// 非 ASCII(含孤立 surrogate)物化为 1 单元会话串。
|
||||
pub(crate) fn unit_char_value(&mut self, u: u16) -> JsValue {
|
||||
if let Some(v) = oxide_runtime_api::VmHost::single_unit(self, u) {
|
||||
return v;
|
||||
}
|
||||
self.new_string_units(&[u])
|
||||
}
|
||||
|
||||
/// 把字符串 intern 为永久 key id(属性名/方法名),进程生命周期内稳定。
|
||||
|
||||
@@ -528,9 +528,9 @@ fn symbol_dispose_and_async_dispose_registered() {
|
||||
let result = eval(&mut vm, src).unwrap();
|
||||
assert_eq!(result.as_bool(), expected, "for {}", src);
|
||||
}
|
||||
// 描述性名称经 String 构造器反射(well_known_symbol_name 表)。
|
||||
let desc = eval(&mut vm, "String(Symbol.dispose)").unwrap();
|
||||
assert_eq!(to_str(&vm, desc), "Symbol(Symbol.dispose)");
|
||||
// ToString(Symbol) 按规格抛 TypeError(String 构造器无描述性名称通道)。
|
||||
let err = eval(&mut vm, "try { String(Symbol.dispose); 'no-throw' } catch (e) { e.name }").unwrap();
|
||||
assert_eq!(to_str(&vm, err), "TypeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -940,25 +940,45 @@ fn string_methods_null_receiver_throw_type_error() {
|
||||
#[test]
|
||||
fn string_slice_astral_character_indices() {
|
||||
// UTF-16 单元索引语义:astral 字符占 2 单元,切片边界落在代理对中间时
|
||||
// 保留所在 astral 字符(孤立代理无法在 UTF-8 表示,见架构限制)。
|
||||
// 单元原样保留(单元载荷可承载孤立 surrogate,规格口径)。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'\\u{1F600}ab'.slice(1, 3)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "\u{1F600}a");
|
||||
let s = eval(&mut vm, "'\\u{1F600}ab'.slice(0, 1)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "\u{1F600}");
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var s = '\\u{1F600}ab'.slice(1, 3); s.length === 2 && s.charCodeAt(0) === 0xDE00 && s.charCodeAt(1) === 0x61",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var s = '\\u{1F600}ab'.slice(0, 1); s.length === 1 && s.charCodeAt(0) === 0xD83D",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
let s = eval(&mut vm, "'\\u{1F600}'.padStart(3, 'x')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "xx\u{1F600}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_char_at_ascii_and_astral() {
|
||||
// charAt 产出:ASCII 走单字符缓存,astral 按 UTF-16 单元定位(落在代理对
|
||||
// 中间时返回所在 astral 字符,架构限制),越界空串,缺省参数取首字符。
|
||||
// charAt 按码元定位:单单元子串(代理对各出 1 单元,规格口径——
|
||||
// 规格返回单元子串而非 Unicode 标量),越界空串,缺省参数取首字符。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'abc'.charAt(1)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "b");
|
||||
let s = eval(&mut vm, "'\\u{1F600}ab'.charAt(0)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "\u{1F600}");
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"'\\u{1F600}ab'.charAt(0).length === 1 && '\\u{1F600}ab'.charAt(0).charCodeAt(0) === 0xD83D",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"'\\u{1F600}ab'.charAt(1).length === 1 && '\\u{1F600}ab'.charAt(1).charCodeAt(0) === 0xDE00",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(2) === 'a'").unwrap();
|
||||
assert!(r.as_bool());
|
||||
let s = eval(&mut vm, "'ab'.charAt(5)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "");
|
||||
let s = eval(&mut vm, "'xy'.charAt()").unwrap();
|
||||
@@ -975,13 +995,16 @@ fn string_char_at_eq_perm_string() {
|
||||
|
||||
#[test]
|
||||
fn string_split_empty_separator_chars() {
|
||||
// 空分隔 split 逐字符产出:ASCII 走单字符缓存,混合 astral 按标量切分。
|
||||
// 空分隔 split 逐码元产出:ASCII 走单字符缓存,代理对各出 1 单元元素
|
||||
// (规格口径:空分隔按单元切分,孤立 surrogate 各为独立元素)。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'abc'.split('').join('-')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "a-b-c");
|
||||
let result = eval(&mut vm, "'\\u{1F600}a'.split('').length").unwrap();
|
||||
assert_eq!(result.as_int(), 2);
|
||||
let result = eval(&mut vm, "'\\u{1F600}a'.split('')[1]").unwrap();
|
||||
assert_eq!(result.as_int(), 3);
|
||||
let r = eval(&mut vm, "'\\u{1F600}a'.split('')[1].charCodeAt(0) === 0xDE00").unwrap();
|
||||
assert!(r.as_bool());
|
||||
let result = eval(&mut vm, "'\\u{1F600}a'.split('')[2]").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "a");
|
||||
}
|
||||
|
||||
@@ -1016,10 +1039,17 @@ fn string_utf16_length_char_at_code_at() {
|
||||
assert_eq!(r.as_int(), 0xDE00);
|
||||
let r = eval(&mut vm, "'\\u{1F600}a'.charCodeAt(2)").unwrap();
|
||||
assert_eq!(r.as_int(), 0x61);
|
||||
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(2)").unwrap();
|
||||
assert_eq!(to_str(&vm, r), "a");
|
||||
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(1)").unwrap();
|
||||
assert_eq!(to_str(&vm, r), "\u{1F600}");
|
||||
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(2) === 'a'").unwrap();
|
||||
assert!(r.as_bool());
|
||||
// charAt 出单单元子串:代理对位置各出 1 单元(高/低 surrogate)。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"'\\u{1F600}ab'.charAt(0).length === 1 && '\\u{1F600}ab'.charAt(0).charCodeAt(0) === 0xD83D",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(1).charCodeAt(0) === 0xDE00").unwrap();
|
||||
assert!(r.as_bool());
|
||||
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(4)").unwrap();
|
||||
assert_eq!(to_str(&vm, r), "");
|
||||
}
|
||||
@@ -1040,7 +1070,8 @@ fn string_utf16_at() {
|
||||
|
||||
#[test]
|
||||
fn string_utf16_slice_substring_substr() {
|
||||
// 切片族按 UTF-16 单元计数:边界落在代理对中间时保留所在 astral 字符。
|
||||
// 切片族按 UTF-16 单元计数:边界落在代理对中间时单元原样保留(孤立
|
||||
// surrogate 由单元载荷承载,规格口径)。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'\\u{1F600}ab'.slice(2)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "ab");
|
||||
@@ -1054,8 +1085,12 @@ fn string_utf16_slice_substring_substr() {
|
||||
assert_eq!(to_str(&vm, s), "\u{1F600}");
|
||||
let s = eval(&mut vm, "'\\u{1F600}ab'.substr(2, 2)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "ab");
|
||||
let s = eval(&mut vm, "'\\u{1F600}ab'.substr(1, 2)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "\u{1F600}a");
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var s = '\\u{1F600}ab'.substr(1, 2); s.length === 2 && s.charCodeAt(0) === 0xDE00 && s.charCodeAt(1) === 0x61",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user