perf: typeof 改 js_type 单次提取并复用进程级静态字符串消除每次分配
This commit is contained in:
@@ -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} 二次调用应返回同一指针");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(`==`)。
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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}"))
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user