mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
perf: String() 有限 double 转串加 16 槽 last-value 缓存
This commit is contained in:
Generated
+18
-18
@@ -1095,7 +1095,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1120,7 +1120,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1128,7 +1128,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1139,7 +1139,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
@@ -1159,7 +1159,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1168,7 +1168,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1187,7 +1187,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1201,7 +1201,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1216,7 +1216,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1225,7 +1225,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1240,7 +1240,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1252,7 +1252,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1261,7 +1261,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1276,7 +1276,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1292,7 +1292,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1304,7 +1304,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1321,7 +1321,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"hashbrown 0.14.5",
|
||||
@@ -1333,7 +1333,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -164,8 +164,9 @@ pub fn string_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
} 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))
|
||||
// 有限 double 走十六槽 last-value 缓存:命中复用已登记串,
|
||||
// miss 走原数值转串链,行为与转换链一致。
|
||||
vm.number_to_string_cached(d)
|
||||
}
|
||||
} else {
|
||||
// BigInt/对象参数走完整转换链(对象可经 valueOf/toString 抛错);
|
||||
|
||||
@@ -90,3 +90,62 @@ fn string_constructor_fallback_paths() {
|
||||
let (_vm, len) = eval("new String(\"ab\").length").unwrap();
|
||||
assert_eq!(len.as_int(), 2);
|
||||
}
|
||||
|
||||
// 位模式低 4 位互异的十六个有限 double(槽位互不冲突的测试前提)。
|
||||
const CACHE_TEST_VALUES: [f64; 16] = [
|
||||
0.119, 0.103, 0.087, 0.071, 0.063, 0.207, 0.175, 0.143, 0.015, 0.007, 0.003, 0.005, 0.001, 0.205, 0.173, 0.141,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn number_to_string_cache_slot_correctness() {
|
||||
// 十六个槽位互异的值两轮各调一次:命中须键全等才返回,
|
||||
// 钉住槽位碰撞不得错返。
|
||||
let mut vm = Vm::new();
|
||||
let mut seen = [false; 16];
|
||||
for &d in &CACHE_TEST_VALUES {
|
||||
let slot = (d.to_bits() as usize) & 15;
|
||||
assert!(!seen[slot], "槽位冲突:{d}");
|
||||
seen[slot] = true;
|
||||
}
|
||||
for _round in 0..2 {
|
||||
for &d in &CACHE_TEST_VALUES {
|
||||
let v = vm.number_to_string_cached(d);
|
||||
assert!(v.is_string());
|
||||
let expected = oxide_runtime_api::js_number_to_string(d);
|
||||
assert_eq!(unsafe { &*v.as_string_ptr() }.as_str(), expected.as_str(), "值 {d} 应与原转换链一致");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_to_string_cache_survives_session_gc() {
|
||||
// 填满十六槽后强制完整 session 收集:缓存串已登记为 GC 根,
|
||||
// 收集后读回仍为原内容(漏根登记此测必挂)。
|
||||
let mut vm = Vm::new();
|
||||
for &d in &CACHE_TEST_VALUES {
|
||||
vm.number_to_string_cached(d);
|
||||
}
|
||||
vm.collect_session_gc();
|
||||
for &d in &CACHE_TEST_VALUES {
|
||||
let v = vm.number_to_string_cached(d);
|
||||
assert!(v.is_string());
|
||||
let expected = oxide_runtime_api::js_number_to_string(d);
|
||||
assert_eq!(unsafe { &*v.as_string_ptr() }.as_str(), expected.as_str(), "收集后值 {d} 应可读回");
|
||||
}
|
||||
// 2.5 挤占槽 0 后写新串:新串同样须存活后续收集。
|
||||
let v = vm.number_to_string_cached(2.5);
|
||||
vm.collect_session_gc();
|
||||
let v2 = vm.number_to_string_cached(2.5);
|
||||
assert_eq!(unsafe { &*v2.as_string_ptr() }.as_str(), "2.5");
|
||||
assert_eq!(v.as_string_ptr(), v2.as_string_ptr());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_to_string_cache_identity() {
|
||||
// 同值两次调用命中同槽:返回指针相等(命中复用 session 串)。
|
||||
let mut vm = Vm::new();
|
||||
let a = vm.number_to_string_cached(2.5);
|
||||
let b = vm.number_to_string_cached(2.5);
|
||||
assert!(a.is_string() && b.is_string());
|
||||
assert_eq!(a.as_string_ptr(), b.as_string_ptr());
|
||||
}
|
||||
|
||||
@@ -58,6 +58,14 @@ pub trait VmHost {
|
||||
fn new_string(&mut self, s: &str) -> JsValue;
|
||||
/// move 接收 `String` 创建会话字符串,避免一次整串克隆。
|
||||
fn new_string_owned(&mut self, s: String) -> JsValue;
|
||||
/// 有限 double 转串,带十六槽 last-value 缓存:命中返回已登记为 GC 根的
|
||||
/// session 串,miss 走原数值转串链并写槽。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 非有限值(NaN/±Infinity)走永久常量表,不进槽。
|
||||
/// - `full_reset` 在 session 串释放前清空槽;轻量 `reset` 保留槽
|
||||
/// (session 串跨 eval 存活,缓存串仍有效)。
|
||||
fn number_to_string_cached(&mut self, d: f64) -> JsValue;
|
||||
/// 取 ASCII 单字符的永久字符串值:命中返回共享 perm 串(零分配、可指针
|
||||
/// 短路比较),非 ASCII 返回 `None` 由调用方回落普通字符串创建。
|
||||
///
|
||||
|
||||
@@ -76,6 +76,12 @@ impl Vm {
|
||||
for &cached in self.template_objects.values() {
|
||||
f(cached);
|
||||
}
|
||||
// f64→string 缓存槽值是 session 串,是 GC 根(漏根 → 串清扫释放 → 命中悬垂)。
|
||||
for &v in &self.number_to_string_cache_vals {
|
||||
if v.is_string() {
|
||||
f(v);
|
||||
}
|
||||
}
|
||||
for &entry in &self.iters.for_of_iters {
|
||||
f(entry.iterator);
|
||||
f(entry.last_result);
|
||||
|
||||
@@ -183,6 +183,12 @@ pub struct Vm {
|
||||
/// (发射层字符串常量统一经 perm_string 物化),指针相等即键为 "length",
|
||||
/// 入口快判只比指针免键解析。PermInterner 追加式、指针唯一,永不失效。
|
||||
pub(crate) length_perm_ptr: *const JsString,
|
||||
/// f64→string 十六槽 last-value 缓存的键(double 位模式)。空槽判定用
|
||||
/// 对应值非字符串:键 0 是 +0.0 的合法位模式,不得作空标记。
|
||||
pub(crate) number_to_string_cache_keys: [u64; 16],
|
||||
/// f64→string 十六槽 last-value 缓存的值(session 串,经 `for_each_value`
|
||||
/// 登记为 GC 根;`full_reset` 在 session 串释放前清空)。
|
||||
pub(crate) number_to_string_cache_vals: [JsValue; 16],
|
||||
pub object_prototype: P<JsObject>,
|
||||
/// `%GeneratorPrototype%`:生成器实例的原型(next/return/throw 方法挂此)。
|
||||
pub generator_proto: P<JsObject>,
|
||||
|
||||
@@ -33,6 +33,9 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
fn new_string_owned(&mut self, s: String) -> JsValue {
|
||||
Vm::new_string_owned(self, s)
|
||||
}
|
||||
fn number_to_string_cached(&mut self, d: f64) -> JsValue {
|
||||
Vm::number_to_string_cached(self, d)
|
||||
}
|
||||
fn new_bigint(&mut self, v: num_bigint::BigInt) -> JsValue {
|
||||
Vm::new_bigint(self, v)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ impl Vm {
|
||||
session,
|
||||
length_si,
|
||||
length_perm_ptr,
|
||||
number_to_string_cache_keys: [0u64; 16],
|
||||
number_to_string_cache_vals: [JsValue::undefined(); 16],
|
||||
object_prototype: obj_proto,
|
||||
generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
||||
generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
||||
@@ -198,6 +200,8 @@ impl Vm {
|
||||
session,
|
||||
length_si,
|
||||
length_perm_ptr,
|
||||
number_to_string_cache_keys: [0u64; 16],
|
||||
number_to_string_cache_vals: [JsValue::undefined(); 16],
|
||||
object_prototype: obj_proto,
|
||||
generator_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
||||
generator_function_proto: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
||||
@@ -409,6 +413,10 @@ impl Vm {
|
||||
}),
|
||||
);
|
||||
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
|
||||
// f64→string 缓存槽指向即将释放的 session 串:须在 teardown 之前清空,
|
||||
// 否则下次命中返回悬垂指针。
|
||||
self.number_to_string_cache_keys = [0u64; 16];
|
||||
self.number_to_string_cache_vals = [JsValue::undefined(); 16];
|
||||
self.teardown_session_heap_data();
|
||||
self.gc_state.session_bytes_allocated = 0;
|
||||
self.gc_state.session_bytes_peak = 0;
|
||||
@@ -595,6 +603,39 @@ impl Vm {
|
||||
self.register_session_string(ptr, len)
|
||||
}
|
||||
|
||||
/// 有限 double 转串,带十六槽 last-value 缓存:命中返回已登记的 session 串,
|
||||
/// miss 走原数值转串链并写槽。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 非有限值(NaN/±Infinity)返回对应永久常量串,不进槽。
|
||||
/// 2. 槽位取位模式低 4 位;槽值为字符串且键全等时返回槽值。
|
||||
/// 3. miss 走原数值转串链新建 session 串,写键与值后返回。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 槽值经 `for_each_value` 登记为 GC 根,strings-only 收集不释放;
|
||||
/// `full_reset` 在 session 串释放前清空槽。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 空槽判定用值非字符串:+0.0 的位模式是 0,键不得作空标记。
|
||||
/// - direct-mapped last-value:异值撞槽即覆盖,返回前键须 64 位全等。
|
||||
pub fn number_to_string_cached(&mut self, d: f64) -> JsValue {
|
||||
if d.is_nan() {
|
||||
return JsValue::string(oxide_kernel::string_forge::const_string_ptr(4));
|
||||
}
|
||||
if d.is_infinite() {
|
||||
return JsValue::string(oxide_kernel::string_forge::const_string_ptr(if d > 0.0 { 5 } else { 6 }));
|
||||
}
|
||||
let bits = d.to_bits();
|
||||
let slot = (bits as usize) & 15;
|
||||
if self.number_to_string_cache_vals[slot].is_string() && self.number_to_string_cache_keys[slot] == bits {
|
||||
return self.number_to_string_cache_vals[slot];
|
||||
}
|
||||
let v = self.new_string_owned(oxide_runtime_api::js_number_to_string(d));
|
||||
self.number_to_string_cache_keys[slot] = bits;
|
||||
self.number_to_string_cache_vals[slot] = v;
|
||||
v
|
||||
}
|
||||
|
||||
/// 登记一个 session 字符串并记账(`new_string_owned` 与 `new_cons_string`
|
||||
/// 共用),返回字符串值。
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user