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
+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);
}