mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
perf: String() 常量 perm 化,原始值常量走永久表免 session 分配
This commit is contained in:
Generated
+18
-18
@@ -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
@@ -22,7 +22,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user