perf: String() 常量 perm 化,原始值常量走永久表免 session 分配

This commit is contained in:
2026-10-05 07:00:53 +00:00
parent bd14e332ba
commit 1dc9f41618
5 changed files with 189 additions and 32 deletions
Generated
+18 -18
View File
@@ -1095,7 +1095,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"chrono",
"fast-float",
@@ -1120,7 +1120,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1128,7 +1128,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1139,7 +1139,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1159,7 +1159,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1168,7 +1168,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1187,7 +1187,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1201,7 +1201,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1216,7 +1216,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"bumpalo",
"dashmap",
@@ -1240,7 +1240,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1252,7 +1252,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"tracing",
"tracing-appender",
@@ -1261,7 +1261,7 @@ dependencies = [
[[package]]
name = "oxide_parser"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1276,7 +1276,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1292,7 +1292,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"num-bigint",
"num-traits",
@@ -1304,7 +1304,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"bumpalo",
"hashbrown 0.14.5",
@@ -1333,7 +1333,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
]
[workspace.package]
version = "0.8.0"
version = "0.8.1"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+48 -13
View File
@@ -135,23 +135,58 @@ pub fn string_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
// 对 Symbol 抛 TypeError——与函数调用路径(3a 描述串)相异。
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"));
}
// 对象参数须经 ToPrimitive/ToString 完整转换;函数调用路径的 Symbol
// 走 SymbolDescriptiveString(规范步骤 3a,不经 ToString)。
let v = oxide_runtime_api::to_string_for_string_constructor(v, vm);
match v {
Ok(s) => vm.new_string_owned(s),
Err(msg) => {
// ToString on an object may throw via toString/valueOf; propagate the original exception.
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
// 常量快路径:字符串直通,布尔/null/undefined/NaN/±Infinity 走永久常量表,
// 0..=99 小整数走小整数永久表,均零分配、不经过转换链。
if v.is_string() {
// ToString 对字符串是恒等(规范 §21.2.1.1 步骤 3),原值返回免克隆。
v
} else if v.is_bool() {
JsValue::string(oxide_kernel::string_forge::const_string_ptr(if v.as_bool() { 0 } else { 1 }))
} else if v.is_null() {
JsValue::string(oxide_kernel::string_forge::const_string_ptr(2))
} else if v.is_undefined() {
JsValue::string(oxide_kernel::string_forge::const_string_ptr(3))
} else if v.is_int() {
let n = v.as_int();
if n >= 0 {
if let Some(ptr) = oxide_kernel::string_forge::small_int_ptr(n as u32) {
JsValue::string(ptr)
} else {
vm.new_string_owned(oxide_runtime_api::to_string(v))
}
} else {
vm.new_string_owned(oxide_runtime_api::to_string(v))
}
} else if v.is_double() {
let d = v.as_double();
if d.is_nan() {
JsValue::string(oxide_kernel::string_forge::const_string_ptr(4))
} else if d.is_infinite() {
JsValue::string(oxide_kernel::string_forge::const_string_ptr(if d > 0.0 { 5 } else { 6 }))
} else {
// 有限 double 走原数值转串链,行为与转换链一致。
vm.new_string_owned(oxide_runtime_api::js_number_to_string(d))
}
} else {
// BigInt/对象参数走完整转换链(对象可经 valueOf/toString 抛错);
// 函数调用路径的 Symbol 走 SymbolDescriptiveString(规范步骤 3a,不经 ToString)。
let v = oxide_runtime_api::to_string_for_string_constructor(v, vm);
match v {
Ok(s) => vm.new_string_owned(s),
Err(msg) => {
// ToString on an object may throw via toString/valueOf; propagate the original exception.
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
// 无在途异常时按格式化文本恢复 kind 与消息(装箱 Symbol 走 ToString
// 抛 TypeError,消息须与原始异常一致)。
return NativeResult::Err(crate::error::create_from_text(vm, &msg));
}
// 无在途异常时按格式化文本恢复 kind 与消息(装箱 Symbol 走 ToString
// 抛 TypeError,消息须与原始异常一致)。
return NativeResult::Err(crate::error::create_from_text(vm, &msg));
}
}
} else {
vm.new_string("")
// 无参返回永久空串,免每次新建 session 串。
JsValue::string(oxide_kernel::string_forge::empty_string_ptr())
};
if !is_ctor {
@@ -0,0 +1,92 @@
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
let mut vm = Vm::new();
let result = vm.run(&Arc::new(module))?;
Ok((vm, result))
}
fn string_of(source: &str) -> String {
let (_vm, result) = eval(source).unwrap();
assert!(result.is_string(), "expected a string result from {source}");
unsafe { &*result.as_string_ptr() }.as_str().to_string()
}
#[test]
fn string_constructor_primitive_content() {
// 原始值各类别的内容正确性:整数、负整数、有限 double、布尔、
// null/undefined、NaN/±Infinity、-0、大指数、字符串直通、无参。
assert_eq!(string_of("String(42)"), "42");
assert_eq!(string_of("String(-5)"), "-5");
assert_eq!(string_of("String(3.14)"), "3.14");
assert_eq!(string_of("String(true)"), "true");
assert_eq!(string_of("String(false)"), "false");
assert_eq!(string_of("String(null)"), "null");
assert_eq!(string_of("String(undefined)"), "undefined");
assert_eq!(string_of("String(NaN)"), "NaN");
assert_eq!(string_of("String(Infinity)"), "Infinity");
assert_eq!(string_of("String(-Infinity)"), "-Infinity");
assert_eq!(string_of("String(-0)"), "0");
assert_eq!(string_of("String(1e21)"), "1e+21");
assert_eq!(string_of("String(\"abc\")"), "abc");
assert_eq!(string_of("String()"), "");
}
#[test]
fn string_constructor_perm_identity() {
// 0..=99 小整数命中小整数永久表:两次调用返回同一指针,且与表项一致。
let (_vm, a) = eval("String(42)").unwrap();
let (_vm, b) = eval("String(42)").unwrap();
assert!(a.is_string() && b.is_string());
assert_eq!(a.as_string_ptr(), b.as_string_ptr());
assert_eq!(a.as_string_ptr(), oxide_kernel::string_forge::small_int_ptr(42).unwrap());
// 布尔/null/undefined/NaN/±Infinity 命中常量表:两次调用返回同一指针。
for src in [
"String(true)",
"String(false)",
"String(null)",
"String(undefined)",
"String(NaN)",
"String(Infinity)",
"String(-Infinity)",
] {
let (_vm, a) = eval(src).unwrap();
let (_vm, b) = eval(src).unwrap();
assert!(a.is_string() && b.is_string());
assert_eq!(a.as_string_ptr(), b.as_string_ptr(), "{src} 应返回同一指针");
}
}
#[test]
fn string_constructor_string_passthrough() {
// 字符串入参恒等返回:内容不变,且跨 VM 实例与永久表同源(免克隆)。
let (_vm, result) = eval("String(\"abc\")").unwrap();
assert!(result.is_string());
assert_eq!(unsafe { &*result.as_string_ptr() }.as_str(), "abc");
}
#[test]
fn string_constructor_fallback_paths() {
// 对象参数走完整转换链(valueOf/toString)。
assert_eq!(string_of("String({})"), "[object Object]");
// BigInt 参数走 BigInt 路径。
assert_eq!(string_of("String(1n)"), "1");
// 装箱 Symbol 抛 TypeError(抛错点不变)。
assert!(eval("String(Object(Symbol('s')))").is_err());
// 构造形态返回包装对象,length 为 2。
let (_vm, result) = eval("new String(42)").unwrap();
assert!(result.is_object());
let (_vm, len) = eval("new String(42).length").unwrap();
assert_eq!(len.as_int(), 2);
// 构造形态字符串入参:从原串物化盒体。
let (_vm, len) = eval("new String(\"ab\").length").unwrap();
assert_eq!(len.as_int(), 2);
}
+30
View File
@@ -479,3 +479,33 @@ pub fn typeof_string_ptr(kind: u8) -> *const JsString {
}
}
}
/// 原始值常量串文本表(下标见 [`const_string_ptr`]),进程生命周期内不释放。
const CONST_STRING_TEXTS: [&str; 7] = ["true", "false", "null", "undefined", "NaN", "Infinity", "-Infinity"];
/// 原始值常量串的永久 `JsString` 指针表:String() 构造器等高频常量产出路径,
/// 复用静态表免每次 session 分配与 interner 锁查。物化面严格有界:7 条目 ≈ 数百字节,
/// 随进程生命周期有意保留(同 `TYPEOF_TABLE` 论证)。
static CONST_STRING_TABLE: [OnceLock<StringPtr>; 7] = [const { OnceLock::new() }; 7];
/// 取原始值常量串的永久 `JsString` 指针,惰性物化一次后恒返回同一地址。
///
/// 下标约定:0=true、1=false、2=null、3=undefined、4=NaN、
/// 5=Infinity、6=-Infinity。调用方按值类型映射。
pub fn const_string_ptr(kind: u8) -> *const JsString {
let slot = &CONST_STRING_TABLE[kind as usize];
if let Some(ptr) = slot.get() {
return ptr.0;
}
let ptr = Box::into_raw(Box::new(JsString::new(CONST_STRING_TEXTS[kind as usize].to_string())));
match slot.set(StringPtr(ptr)) {
Ok(()) => ptr,
Err(_) => {
// 并发首用竞态:与 single_char_ptr 同款处置,本线程产物恰好释放一次。
let existing = slot.get().expect("set 失败时槽必已初始化").0;
// SAFETY: ptr 来自本线程的 Box::into_raw,无任何外部引用。
unsafe { drop(Box::from_raw(ptr)) };
existing
}
}
}