From 3d1ece321b7f5f24ea8c879ec85869be52739f1d Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 15 Aug 2026 16:09:19 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20typeof=20=E6=94=B9=20js=5Ftype=20?= =?UTF-8?q?=E5=8D=95=E6=AC=A1=E6=8F=90=E5=8F=96=E5=B9=B6=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E7=BA=A7=E9=9D=99=E6=80=81=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E6=B6=88=E9=99=A4=E6=AF=8F=E6=AC=A1=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_kernel/src/string_forge.rs | 39 ++++++++++++++++ crates/oxide_runtime_api/src/lib.rs | 37 +++++----------- crates/oxide_vm/src/dispatch/misc.rs | 44 +++++++++---------- crates/oxide_vm/tests/builtins_error_tests.rs | 8 +++- crates/oxide_vm/tests/coercion_tests.rs | 34 ++++++++++++++ .../oxide_vm/tests/function_length_tests.rs | 9 +++- .../tests/property_array_fastpath_tests.rs | 5 ++- crates/oxide_vm/tests/rest_params_tests.rs | 9 +++- 8 files changed, 128 insertions(+), 57 deletions(-) diff --git a/crates/oxide_kernel/src/string_forge.rs b/crates/oxide_kernel/src/string_forge.rs index ef7d469..fe3e718 100644 --- a/crates/oxide_kernel/src/string_forge.rs +++ b/crates/oxide_kernel/src/string_forge.rs @@ -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; 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} 二次调用应返回同一指针"); + } + } } diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index 67a4aa6..a6aeddd 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -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(`==`)。 diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index c1adaf8..a86549f 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -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; diff --git a/crates/oxide_vm/tests/builtins_error_tests.rs b/crates/oxide_vm/tests/builtins_error_tests.rs index 8c61f87..a6b110d 100644 --- a/crates/oxide_vm/tests/builtins_error_tests.rs +++ b/crates/oxide_vm/tests/builtins_error_tests.rs @@ -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())); } diff --git a/crates/oxide_vm/tests/coercion_tests.rs b/crates/oxide_vm/tests/coercion_tests.rs index da1ed30..0076dc1 100644 --- a/crates/oxide_vm/tests/coercion_tests.rs +++ b/crates/oxide_vm/tests/coercion_tests.rs @@ -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() { diff --git a/crates/oxide_vm/tests/function_length_tests.rs b/crates/oxide_vm/tests/function_length_tests.rs index 39215de..669ae9f 100644 --- a/crates/oxide_vm/tests/function_length_tests.rs +++ b/crates/oxide_vm/tests/function_length_tests.rs @@ -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}") } diff --git a/crates/oxide_vm/tests/property_array_fastpath_tests.rs b/crates/oxide_vm/tests/property_array_fastpath_tests.rs index fa6133a..9f1eb04 100644 --- a/crates/oxide_vm/tests/property_array_fastpath_tests.rs +++ b/crates/oxide_vm/tests/property_array_fastpath_tests.rs @@ -16,9 +16,10 @@ fn eval(source: &str) -> Result<(Vm, JsValue), String> { } fn eval_str(source: &str) -> Result { - 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}")) } diff --git a/crates/oxide_vm/tests/rest_params_tests.rs b/crates/oxide_vm/tests/rest_params_tests.rs index 26822d8..4609305 100644 --- a/crates/oxide_vm/tests/rest_params_tests.rs +++ b/crates/oxide_vm/tests/rest_params_tests.rs @@ -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}") }