perf: typeof 改 js_type 单次提取并复用进程级静态字符串消除每次分配

This commit is contained in:
2026-08-15 16:09:19 +08:00
parent 31296ed16d
commit 3d1ece321b
8 changed files with 128 additions and 57 deletions
+39
View File
@@ -202,6 +202,34 @@ pub fn small_int_ptr(n: u32) -> Option<*const JsString> {
}
}
/// typeof 结果文本表(下标见 [`typeof_string_ptr`]),进程生命周期内不释放。
const TYPEOF_TEXTS: [&str; 8] = ["undefined", "object", "boolean", "number", "string", "symbol", "bigint", "function"];
/// typeof 结果永久 `JsString` 指针表:typeof 是高频分支产出(typeof_ops 每迭代 4 次),
/// 复用静态表免每次 session 分配与 interner 锁查。泄漏面严格有界:8 条目 ≈ 数百字节。
static TYPEOF_TABLE: [OnceLock<StringPtr>; 8] = [const { OnceLock::new() }; 8];
/// 取 typeof 结果串的永久 `JsString` 指针,惰性物化一次后恒返回同一地址。
///
/// 下标约定:0=undefined、1=object、2=boolean、3=number、4=string、
/// 5=symbol、6=bigint、7=function。调用方按 `JsValue::js_type()` 映射。
pub fn typeof_string_ptr(kind: u8) -> *const JsString {
let slot = &TYPEOF_TABLE[kind as usize];
if let Some(ptr) = slot.get() {
return ptr.0;
}
let ptr = Box::into_raw(Box::new(JsString::new(TYPEOF_TEXTS[kind as usize].to_string())));
match slot.set(StringPtr(ptr)) {
Ok(()) => ptr,
Err(existing) => {
// 并发首用竞态:与 single_char_ptr 同款处置,本线程产物恰好释放一次。
// SAFETY: ptr 来自本线程的 Box::into_raw,无任何外部引用。
unsafe { drop(Box::from_raw(ptr)) };
existing.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -283,4 +311,15 @@ mod tests {
assert_eq!(unsafe { (*ptr).as_str() }, (b as char).to_string());
}
}
#[test]
fn typeof_table_content_and_stability() {
// 8 个 typeof 结果串与下标约定一一对应,且二次调用返回同一稳定指针。
let texts = ["undefined", "object", "boolean", "number", "string", "symbol", "bigint", "function"];
for (i, t) in texts.iter().enumerate() {
let ptr = typeof_string_ptr(i as u8);
assert_eq!(unsafe { (*ptr).as_str() }, *t);
assert_eq!(typeof_string_ptr(i as u8), ptr, "下标 {i} 二次调用应返回同一指针");
}
}
}
+12 -25
View File
@@ -524,32 +524,19 @@ pub fn to_string(val: JsValue) -> String {
/// ToBoolean(ECMA-262 §7.1.2):falsy 值仅限 undefined/null/false/±0/NaN/空串,其余为 true。
pub fn to_boolean(val: JsValue) -> bool {
if val.is_undefined() || val.is_null() {
return false;
match val.js_type() {
JsType::Undefined | JsType::Null => false,
JsType::Bool => val.as_bool(),
JsType::Int => val.as_int() != 0,
JsType::Double => {
let d = val.as_double();
// ±0 与 NaN 均为 falsy(IEEE 中 0.0 == -0.0,符号位无需单独判断)。
!(d == 0.0 || d.is_nan())
}
JsType::String => !unsafe { (*val.as_string_ptr()).is_empty() },
JsType::BigInt => !unsafe { bigint_data(val) }.is_zero(),
JsType::Object | JsType::Symbol => true,
}
if val.is_bool() {
return val.as_bool();
}
if val.is_int() {
return val.as_int() != 0;
}
if val.is_double() {
let d = val.as_double();
return !(d == 0.0 || d == -0.0 || d.is_nan());
}
if val.is_string() {
return !unsafe { (*val.as_string_ptr()).is_empty() };
}
if val.is_bigint() {
return !unsafe { bigint_data(val) }.is_zero();
}
if val.is_object() {
return true;
}
if val.is_symbol() {
return true;
}
false
}
/// IsLooselyEqual(x, y) — ECMA-262 §7.2.15(`==`)。
+20 -24
View File
@@ -4,7 +4,7 @@ use oxide_bytecode::opcode;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::make_int_key;
use oxide_types::value::JsValue;
use oxide_types::value::{JsType, JsValue};
impl Vm {
#[inline(always)]
@@ -25,31 +25,27 @@ impl Vm {
pub(crate) fn dispatch_typeof(&mut self, rd: usize, a: usize) {
vm_trace!("TYPEOF rd={} r{}={:?}", rd, a, self.regs[a]);
let val = self.regs[a];
let result = if val.is_undefined() {
"undefined"
} else if val.is_null() {
"object"
} else if val.is_bool() {
"boolean"
} else if val.is_int() || val.is_double() {
"number"
} else if val.is_string() {
"string"
} else if val.is_symbol() {
"symbol"
} else if val.is_bigint() {
"bigint"
} else if val.is_object() {
let obj = unsafe { &*val.as_js_object_ptr() };
if obj.is_function() {
"function"
} else {
"object"
let kind = match val.js_type() {
JsType::Undefined => 0,
JsType::Null => 1,
JsType::Bool => 2,
JsType::Int | JsType::Double => 3,
JsType::String => 4,
JsType::Symbol => 5,
JsType::BigInt => 6,
JsType::Object => {
// 函数对象归为 "function"(语言类型是 object,typeof 有专属分支)。
let obj = unsafe { &*val.as_js_object_ptr() };
if obj.is_function() {
7
} else {
1
}
}
} else {
"undefined"
};
self.regs[rd] = self.new_string(result);
// 结果字符串复用进程级静态表(零分配、无锁、跨 VM 共享),
// 避免每次 typeof 新建 session 字符串。
self.regs[rd] = JsValue::string(oxide_kernel::string_forge::typeof_string_ptr(kind));
}
/// ToObject:rd 原地转换。对象直接返回;null/undefined 抛 TypeError;
@@ -343,8 +343,12 @@ fn format_error_message_empty_name() {
#[test]
fn error_stack_is_string() {
let result = eval("typeof new Error().stack()").unwrap();
let vm = make_vm();
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, "typeof new Error().stack()").unwrap();
let module = Compiler::new().compile(&program).unwrap();
let mut vm = make_vm();
let result = vm.run(&module).unwrap();
// typeof 结果复用进程级静态串,同 VM 上读取(跨 VM 指针存活由实现保证,不做假设)。
let s = vm.lookup_str(result);
assert_eq!(s, Some("string".to_string()));
}
+34
View File
@@ -252,6 +252,40 @@ fn eval_signed_zero_object_is_and_array_methods() {
assert_eq!(eval("[NaN].includes(NaN)"), "true");
}
// typeof 结果断言需要真实字符串内容,单独用 (Vm, JsValue) 形态的辅助。
fn eval_ty(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(&module)?;
Ok((vm, result))
}
// typeof:js_type 分派 + perm_string 的结果与语言类型一一对应。
#[test]
fn eval_typeof_all_kinds() {
let cases: &[(&str, &str)] = &[
("typeof 1", "number"),
("typeof 1.5", "number"),
("typeof NaN", "number"),
("typeof 's'", "string"),
("typeof true", "boolean"),
("typeof null", "object"),
("typeof undefined", "undefined"),
("typeof function(){}", "function"),
("typeof Symbol('x')", "symbol"),
("typeof 1n", "bigint"),
("typeof {}", "object"),
("typeof []", "object"),
];
for (src, expected) in cases {
let (vm, result) = eval_ty(src).unwrap_or_else(|e| panic!("{src} failed: {e}"));
let actual = vm.lookup_str(result).unwrap_or_default();
assert_eq!(actual, *expected, "{src} 的 typeof 结果不符");
}
}
// 算术 +:对象操作数经 valueOf 强转。
#[test]
fn add_object_valueof_coercion() {
@@ -20,9 +20,14 @@ fn run(module: &CompiledModule) -> JsValue {
}
fn eval_str(source: &str) -> String {
let v = eval(source);
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
let module = Compiler::new().compile(&program).expect("compile failed");
let mut vm = Vm::new();
let v = vm.run(&module).expect("vm run failed");
// 字符串结果须在同一 VM 上读:perm 串指向 VM 私有内核,VM drop 后指针悬垂。
if v.is_string() {
oxide_runtime_api::to_string(v)
vm.lookup_str(v).unwrap_or_default()
} else {
format!("{v}")
}
@@ -16,9 +16,10 @@ fn eval(source: &str) -> Result<(Vm, JsValue), String> {
}
fn eval_str(source: &str) -> Result<String, String> {
let (_vm, v) = eval(source)?;
let (vm, v) = eval(source)?;
// 字符串结果须在同一 VM 上读:perm 串指向 VM 私有内核,VM drop 后指针悬垂。
if v.is_string() {
Ok(oxide_runtime_api::to_string(v))
Ok(vm.lookup_str(v).unwrap_or_default())
} else {
Ok(format!("{v}"))
}
+7 -2
View File
@@ -26,9 +26,14 @@ fn eval_int(source: &str) -> i32 {
}
fn eval_str(source: &str) -> String {
let v = eval(source);
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
let module = Compiler::new().compile(&program).expect("compile failed");
let mut vm = Vm::new();
let v = vm.run(&module).expect("vm run failed");
// 字符串结果须在同一 VM 上读:perm 串指向 VM 私有内核,VM drop 后指针悬垂。
if v.is_string() {
oxide_runtime_api::to_string(v)
vm.lookup_str(v).unwrap_or_default()
} else {
format!("{v}")
}