perf: 不可变常量改 OnceLock 一次性缓存,删 Constant 死变体并移出 GC 根
This commit is contained in:
@@ -4,8 +4,9 @@ impl Vm {
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_load_const(&mut self, rd: usize, instr: u32) -> Result<(), String> {
|
||||
let idx = (instr >> 16) as usize;
|
||||
if idx < self.constants.len() {
|
||||
self.regs[rd] = self.constants[idx];
|
||||
let imm = self.immutables();
|
||||
if idx < imm.len() {
|
||||
self.regs[rd] = imm[idx];
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("constant index {idx} out of bounds"))
|
||||
|
||||
@@ -475,19 +475,7 @@ fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObje
|
||||
*value = rewrite_forwarded_value(*value, forwarding);
|
||||
}
|
||||
vm.last_for_of_result = rewrite_forwarded_value(vm.last_for_of_result, forwarding);
|
||||
for value in &mut vm.constants {
|
||||
*value = rewrite_forwarded_value(*value, forwarding);
|
||||
}
|
||||
for values in &mut vm.sub_module_constants {
|
||||
for value in values {
|
||||
*value = rewrite_forwarded_value(*value, forwarding);
|
||||
}
|
||||
}
|
||||
for values in &mut vm.saved_constants_stack {
|
||||
for value in values {
|
||||
*value = rewrite_forwarded_value(*value, forwarding);
|
||||
}
|
||||
}
|
||||
// Converted immutables are non-session (scalars + perm-strings) — not rewritten.
|
||||
for iter in &mut vm.for_in_iters {
|
||||
if iter.is_null() {
|
||||
continue;
|
||||
@@ -570,8 +558,6 @@ mod tests {
|
||||
vm.pending_exception = Some(JsValue::from_js_object(child_session));
|
||||
vm.for_of_iters.push(JsValue::from_js_object(child_session));
|
||||
vm.last_for_of_result = JsValue::from_js_object(root_session);
|
||||
vm.constants.push(JsValue::from_js_object(frame_session));
|
||||
vm.sub_module_constants = vec![vec![JsValue::from_js_object(child_session)]];
|
||||
|
||||
let mut roots = Vec::new();
|
||||
vm.for_each_root(|v| roots.push(v));
|
||||
@@ -580,7 +566,7 @@ mod tests {
|
||||
assert!(has_ptr(&roots, this_session));
|
||||
assert!(has_ptr(&roots, child_session));
|
||||
assert!(has_ptr(&roots, vm.session.global_object().as_ptr() as *mut JsObject));
|
||||
assert!(roots.len() >= 1);
|
||||
assert!(!roots.is_empty());
|
||||
assert!(roots.contains(&JsValue::from_js_object(root_session)));
|
||||
assert_eq!(vm.exception_value, Some(JsValue::from_js_object(root_session)));
|
||||
}
|
||||
|
||||
+50
-76
@@ -1,9 +1,9 @@
|
||||
#![allow(clippy::arc_with_non_send_sync)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_bytecode::module::{CompiledModule, Constant};
|
||||
use oxide_bytecode::opcode::{self, OpCode};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@@ -127,7 +127,7 @@ pub(crate) struct InlineSyncState {
|
||||
pub(crate) regs: Box<[JsValue; 256]>,
|
||||
pub(crate) pc: usize,
|
||||
pub(crate) bytecode: Vec<opcode::Instr>,
|
||||
pub(crate) constants: Vec<JsValue>,
|
||||
pub(crate) active_immutables: *const [JsValue],
|
||||
pub(crate) active_reg_limit: u8,
|
||||
pub(crate) root_reg_limit: u8,
|
||||
pub(crate) try_stack: Vec<TryHandler>,
|
||||
@@ -139,7 +139,7 @@ pub(crate) struct InlineSyncState {
|
||||
pub(crate) for_of_iters: Vec<JsValue>,
|
||||
pub(crate) last_for_of_result: JsValue,
|
||||
pub(crate) saved_bytecode_stack: Vec<Vec<opcode::Instr>>,
|
||||
pub(crate) saved_constants_stack: Vec<Vec<JsValue>>,
|
||||
pub(crate) saved_immutables_stack: Vec<*const [JsValue]>,
|
||||
pub(crate) save_stack: Vec<JsValue>,
|
||||
}
|
||||
|
||||
@@ -147,7 +147,13 @@ pub struct Vm {
|
||||
pub(crate) regs: [JsValue; 256],
|
||||
pub(crate) pc: usize,
|
||||
pub(crate) bytecode: Vec<opcode::Instr>,
|
||||
pub(crate) constants: Vec<JsValue>,
|
||||
/// Per-run convert-once immutables cache. Index 0 = top module, sub_idx+1 = sub_modules[sub_idx].
|
||||
/// Each `OnceLock` holds that module's constants converted to `JsValue`s exactly once this run.
|
||||
/// Rebuilt every `run()`. Immutables are scalars + perm-strings — read-only, never GC roots.
|
||||
pub(crate) immutables_cache: Vec<OnceLock<Vec<JsValue>>>,
|
||||
/// Read-only view into the currently-active module's converted immutables (inside immutables_cache).
|
||||
/// A fat `*const` because the cache Vec is VM-owned and run-stable (OnceLock filled once).
|
||||
pub(crate) active_immutables: *const [JsValue],
|
||||
pub(crate) frames: SmallVec<[CallFrame; 16]>,
|
||||
pub for_in_iters: Vec<*mut ForInIter<'static>>,
|
||||
pub(crate) kernel_core: Arc<KernelCore>,
|
||||
@@ -167,9 +173,8 @@ pub struct Vm {
|
||||
pub object_prototype: P<JsObject>,
|
||||
pub math_rng_state: u64,
|
||||
pub(crate) sub_modules: Arc<Vec<CompiledModule>>,
|
||||
pub(crate) sub_module_constants: Vec<Vec<JsValue>>,
|
||||
pub(crate) saved_bytecode_stack: Vec<Vec<opcode::Instr>>,
|
||||
pub(crate) saved_constants_stack: Vec<Vec<JsValue>>,
|
||||
pub(crate) saved_immutables_stack: Vec<*const [JsValue]>,
|
||||
/// Shared register save-stack. Each active `CallFrame` saved its caller's live
|
||||
/// registers (`regs[..caller_reg_limit]`) here at `saved_reg_offset`; restore copies
|
||||
/// them back and truncates. Capacity is retained across calls — zero per-call heap alloc.
|
||||
@@ -300,6 +305,30 @@ impl Vm {
|
||||
ptr
|
||||
}
|
||||
|
||||
/// Read-only view into the active module's converted immutables. Empty before any `run()`.
|
||||
#[inline(always)]
|
||||
pub(crate) fn immutables(&self) -> &[JsValue] {
|
||||
if self.active_immutables.is_null() {
|
||||
&[]
|
||||
} else {
|
||||
// SAFETY: active_immutables points into a OnceLock<Vec<JsValue>> inside immutables_cache,
|
||||
// which the VM owns; the Vec is filled once and never reallocated for the run's lifetime.
|
||||
unsafe { &*self.active_immutables }
|
||||
}
|
||||
}
|
||||
|
||||
/// Activate module `cache_idx`'s immutables (0 = top, sub_idx+1 = sub_modules[sub_idx]),
|
||||
/// converting them once into `immutables_cache[cache_idx]` and pointing `active_immutables` at
|
||||
/// the cached Vec. `constants` is passed by the caller (it already holds `&module.constants`).
|
||||
pub(crate) fn activate_immutables(&mut self, cache_idx: usize, constants: &[Constant]) {
|
||||
// Raw-ptr the cache slot so `get_or_init` (which borrows immutables_cache) and the &self
|
||||
// convert_immutables closure don't conflict with the subsequent self.active_immutables write.
|
||||
// Sound: immutables_cache is VM-owned, read-only, and run-stable.
|
||||
let slot: *const OnceLock<Vec<JsValue>> = &self.immutables_cache[cache_idx];
|
||||
let vec = unsafe { &*slot }.get_or_init(|| self.convert_immutables(constants));
|
||||
self.active_immutables = vec.as_slice() as *const [JsValue];
|
||||
}
|
||||
|
||||
pub(crate) fn for_each_root(&self, mut f: impl FnMut(JsValue)) {
|
||||
for value in &self.regs {
|
||||
if value.is_object() || value.is_string() {
|
||||
@@ -322,19 +351,7 @@ impl Vm {
|
||||
f(v);
|
||||
}
|
||||
f(self.last_for_of_result);
|
||||
for &v in &self.constants {
|
||||
f(v);
|
||||
}
|
||||
for sub_module in &self.sub_module_constants {
|
||||
for &v in sub_module {
|
||||
f(v);
|
||||
}
|
||||
}
|
||||
for saved_constants in &self.saved_constants_stack {
|
||||
for &v in saved_constants {
|
||||
f(v);
|
||||
}
|
||||
}
|
||||
// Converted immutables (scalars + perm-strings) are NOT session GC roots — not scanned.
|
||||
for iter in &self.for_in_iters {
|
||||
if iter.is_null() {
|
||||
continue;
|
||||
@@ -651,7 +668,6 @@ impl Vm {
|
||||
let sub_bytecode = self.sub_modules[sub_idx].bytecode.clone();
|
||||
let sub_n_args = self.sub_modules[sub_idx].n_args as usize;
|
||||
let sub_n_registers = self.sub_modules[sub_idx].n_registers;
|
||||
let sub_constants = self.sub_modules[sub_idx].constants.clone();
|
||||
let sub_param_base = self.sub_modules[sub_idx].param_base as usize;
|
||||
let sub_is_arrow = self.sub_modules[sub_idx].is_arrow;
|
||||
let caller_reg_limit = self.active_reg_limit.max(1);
|
||||
@@ -666,9 +682,8 @@ impl Vm {
|
||||
self.regs[254] = if sub_is_arrow { obj.captured_this() } else { this_value };
|
||||
self.regs[255] = new_target;
|
||||
|
||||
let converted_sub_constants = self.convert_constants(&sub_constants).map_err(String::from)?;
|
||||
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
||||
self.saved_constants_stack.push(std::mem::take(&mut self.constants));
|
||||
self.saved_immutables_stack.push(self.active_immutables);
|
||||
|
||||
let function_name = self.sub_modules[sub_idx]
|
||||
.function_name
|
||||
@@ -691,7 +706,8 @@ impl Vm {
|
||||
});
|
||||
|
||||
self.bytecode = sub_bytecode;
|
||||
self.constants = converted_sub_constants;
|
||||
let subs = Arc::clone(&self.sub_modules);
|
||||
self.activate_immutables(sub_idx + 1, &subs[sub_idx].constants);
|
||||
for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map.clone() {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
let global = self.session.global_object();
|
||||
@@ -755,8 +771,7 @@ impl Vm {
|
||||
OpCode::CREATE_REGEXP => {
|
||||
let pat_val = self.regs[a];
|
||||
let flags_val = self.regs[b];
|
||||
let ctor_ptr =
|
||||
self.session.builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||||
let ctor_ptr = self.session.builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||||
let ctor = unsafe { &*ctor_ptr };
|
||||
let Some(native_fn) = ctor.native_fn() else {
|
||||
self.raise_error_kind("TypeError", "RegExp constructor unavailable")?;
|
||||
@@ -1019,7 +1034,6 @@ impl Vm {
|
||||
let sub_bytecode = self.sub_modules[sub_idx].bytecode.clone();
|
||||
let sub_n_args = self.sub_modules[sub_idx].n_args as usize;
|
||||
let sub_n_registers = self.sub_modules[sub_idx].n_registers;
|
||||
let sub_constants = self.sub_modules[sub_idx].constants.clone();
|
||||
let sub_param_base = self.sub_modules[sub_idx].param_base as usize;
|
||||
let caller_reg_limit = self.active_reg_limit.max(1);
|
||||
let saved_reg_offset = self.save_stack.len() as u32;
|
||||
@@ -1034,9 +1048,8 @@ impl Vm {
|
||||
self.regs[254] = derived_this;
|
||||
self.regs[255] = new_target;
|
||||
|
||||
let converted_sub_constants = self.convert_constants(&sub_constants)?;
|
||||
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
||||
self.saved_constants_stack.push(std::mem::take(&mut self.constants));
|
||||
self.saved_immutables_stack.push(self.active_immutables);
|
||||
self.frames.push(CallFrame {
|
||||
return_addr: self.pc,
|
||||
function_name: self.sub_modules[sub_idx]
|
||||
@@ -1056,7 +1069,8 @@ impl Vm {
|
||||
});
|
||||
|
||||
self.bytecode = sub_bytecode;
|
||||
self.constants = converted_sub_constants;
|
||||
let subs = Arc::clone(&self.sub_modules);
|
||||
self.activate_immutables(sub_idx + 1, &subs[sub_idx].constants);
|
||||
for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
let global = self.session.global_object();
|
||||
@@ -1234,7 +1248,7 @@ impl Vm {
|
||||
OpCode::DELETE_PROP_STATIC => {
|
||||
let prop_idx = self.bytecode[self.pc] as usize;
|
||||
self.pc += 1;
|
||||
let key_val = self.constants.get(prop_idx).copied().unwrap_or_else(JsValue::undefined);
|
||||
let key_val = self.immutables().get(prop_idx).copied().unwrap_or_else(JsValue::undefined);
|
||||
let prop_name_si = self.property_key_si(key_val);
|
||||
let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "delete on non-object")? else {
|
||||
continue;
|
||||
@@ -1399,7 +1413,7 @@ impl Default for Vm {
|
||||
mod tests {
|
||||
use super::{opcode, JsValue, TryHandler, Vm};
|
||||
use crate::native::NativeResult;
|
||||
use oxide_bytecode::module::{CompiledModule, Constant};
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_types::object::NativeFnPtr;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
|
||||
@@ -1492,7 +1506,8 @@ mod tests {
|
||||
vm.for_of_iters.push(JsValue::undefined());
|
||||
vm.saved_bytecode_stack
|
||||
.push(vec![opcode::encode(opcode::OpCode::HALT, 0, 0, 0)]);
|
||||
vm.saved_constants_stack.push(vec![JsValue::int(1)]);
|
||||
vm.saved_immutables_stack
|
||||
.push(std::ptr::slice_from_raw_parts(std::ptr::null(), 0));
|
||||
vm.try_stack.push(TryHandler {
|
||||
catch_pc: Some(1),
|
||||
finally_pc: None,
|
||||
@@ -1510,13 +1525,13 @@ mod tests {
|
||||
assert!(vm.for_in_iters.is_empty());
|
||||
assert!(vm.for_of_iters.is_empty());
|
||||
assert!(vm.saved_bytecode_stack.is_empty());
|
||||
assert!(vm.saved_constants_stack.is_empty());
|
||||
assert!(vm.saved_immutables_stack.is_empty());
|
||||
assert!(vm.try_stack.is_empty());
|
||||
assert!(vm.exception_value.is_none());
|
||||
assert!(vm.pending_exception.is_none());
|
||||
assert!(vm.pending_error_kind.is_none());
|
||||
assert!(vm.bytecode.is_empty());
|
||||
assert!(vm.constants.is_empty());
|
||||
assert!(vm.immutables().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1551,47 +1566,6 @@ mod tests {
|
||||
assert!(vm.for_of_iters.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_regexp_constant_fails_explicitly() {
|
||||
let module = CompiledModule {
|
||||
constants: vec![Constant::RegExp("[".into(), "".into())],
|
||||
bytecode: vec![opcode::encode(opcode::OpCode::HALT, 0, 0, 0)],
|
||||
n_registers: 1,
|
||||
..CompiledModule::new()
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
let err = vm.run(&module).expect_err("invalid RegExp constant should fail explicitly");
|
||||
assert!(err.contains("SyntaxError: Invalid regular expression"), "unexpected error: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_regexp_constant_in_submodule_fails_explicitly() {
|
||||
let submodule = CompiledModule {
|
||||
constants: vec![Constant::RegExp("[".into(), "".into())],
|
||||
bytecode: vec![opcode::encode(opcode::OpCode::RETURN, 0, 0, 0)],
|
||||
n_registers: 1,
|
||||
..CompiledModule::new()
|
||||
};
|
||||
let module = CompiledModule {
|
||||
constants: vec![Constant::BytecodeFunc(1), Constant::Undefined],
|
||||
bytecode: vec![
|
||||
opcode::encode(opcode::OpCode::LOAD_CONST, 0, 0, 0),
|
||||
opcode::encode(opcode::OpCode::LOAD_CONST, 1, 1, 0),
|
||||
opcode::encode(opcode::OpCode::CALL, 0, 1, 0),
|
||||
0,
|
||||
opcode::encode(opcode::OpCode::HALT, 0, 0, 0),
|
||||
],
|
||||
n_registers: 2,
|
||||
sub_modules: vec![submodule],
|
||||
..CompiledModule::new()
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
let err = vm
|
||||
.run(&module)
|
||||
.expect_err("invalid submodule RegExp constant should fail explicitly");
|
||||
assert!(err.contains("SyntaxError: Invalid regular expression"), "unexpected error: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_ic_back_updates_three_extension_words() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
@@ -84,14 +84,15 @@ impl Vm {
|
||||
pub(crate) fn dispatch_define_accessor(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
let prop_idx = self.bytecode[self.pc] as usize;
|
||||
self.pc += 1;
|
||||
if prop_idx >= self.constants.len() {
|
||||
if prop_idx >= self.immutables().len() {
|
||||
return self.raise_type_error("DEFINE_ACCESSOR constant index out of bounds");
|
||||
}
|
||||
let obj_val = self.regs[rd];
|
||||
if !obj_val.is_object() {
|
||||
return self.raise_type_error("DEFINE_ACCESSOR target is not object");
|
||||
}
|
||||
let prop_name_si = self.property_key_si(self.constants[prop_idx]);
|
||||
let key_val = self.immutables()[prop_idx];
|
||||
let prop_name_si = self.property_key_si(key_val);
|
||||
let getter = self.regs[a];
|
||||
let setter = self.regs[b];
|
||||
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::native::{NativeFn, NativeResult};
|
||||
use crate::vm::{native_fn_ptr_to_fn, CallFrame, ForInIter, FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH};
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
@@ -96,7 +98,6 @@ impl Vm {
|
||||
let sub_bytecode = self.sub_modules[sub_idx].bytecode.clone();
|
||||
let sub_n_args = self.sub_modules[sub_idx].n_args as usize;
|
||||
let sub_n_registers = self.sub_modules[sub_idx].n_registers;
|
||||
let sub_constants = self.sub_modules[sub_idx].constants.clone();
|
||||
let sub_param_base = self.sub_modules[sub_idx].param_base as usize;
|
||||
let caller_reg_limit = self.active_reg_limit.max(1);
|
||||
let saved_reg_offset = self.save_stack.len() as u32;
|
||||
@@ -115,9 +116,8 @@ impl Vm {
|
||||
};
|
||||
self.regs[255] = constructor;
|
||||
|
||||
let converted_sub_constants = self.convert_constants(&sub_constants)?;
|
||||
self.saved_bytecode_stack.push(std::mem::take(&mut self.bytecode));
|
||||
self.saved_constants_stack.push(std::mem::take(&mut self.constants));
|
||||
self.saved_immutables_stack.push(self.active_immutables);
|
||||
|
||||
self.frames.push(CallFrame {
|
||||
return_addr: self.pc,
|
||||
@@ -138,7 +138,8 @@ impl Vm {
|
||||
});
|
||||
|
||||
self.bytecode = sub_bytecode;
|
||||
self.constants = converted_sub_constants;
|
||||
let subs = Arc::clone(&self.sub_modules);
|
||||
self.activate_immutables(sub_idx + 1, &subs[sub_idx].constants);
|
||||
|
||||
for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map {
|
||||
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
|
||||
@@ -182,8 +183,9 @@ impl Vm {
|
||||
result.push_str(&s);
|
||||
} else {
|
||||
let const_idx = (seg & 0x7FFF_FFFF) as usize;
|
||||
if const_idx < self.constants.len() {
|
||||
let val = self.constants[const_idx];
|
||||
let imm = self.immutables();
|
||||
if const_idx < imm.len() {
|
||||
let val = imm[const_idx];
|
||||
if val.is_string() {
|
||||
// SAFETY: val is a string value.
|
||||
let s = unsafe { (*val.as_string_ptr()).data.clone() };
|
||||
@@ -419,7 +421,7 @@ impl Vm {
|
||||
let excluded_idx = self.bytecode[self.pc] as usize;
|
||||
self.pc += 1;
|
||||
let excluded = self
|
||||
.constants
|
||||
.immutables()
|
||||
.get(excluded_idx)
|
||||
.and_then(|v| {
|
||||
if v.is_string() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_bytecode::opcode;
|
||||
@@ -34,13 +34,12 @@ impl Vm {
|
||||
|
||||
let subs = Arc::clone(&self.sub_modules);
|
||||
let sub = &subs[sub_idx];
|
||||
let converted_constants = self.convert_constants(&sub.constants).map_err(String::from)?;
|
||||
|
||||
let saved = Box::new(InlineSyncState {
|
||||
regs: Box::new(self.regs),
|
||||
pc: self.pc,
|
||||
bytecode: std::mem::take(&mut self.bytecode),
|
||||
constants: std::mem::take(&mut self.constants),
|
||||
active_immutables: self.active_immutables,
|
||||
active_reg_limit: self.active_reg_limit,
|
||||
root_reg_limit: self.root_reg_limit,
|
||||
try_stack: std::mem::take(&mut self.try_stack),
|
||||
@@ -52,14 +51,14 @@ impl Vm {
|
||||
for_of_iters: std::mem::take(&mut self.for_of_iters),
|
||||
last_for_of_result: self.last_for_of_result,
|
||||
saved_bytecode_stack: std::mem::take(&mut self.saved_bytecode_stack),
|
||||
saved_constants_stack: std::mem::take(&mut self.saved_constants_stack),
|
||||
saved_immutables_stack: std::mem::take(&mut self.saved_immutables_stack),
|
||||
save_stack: std::mem::take(&mut self.save_stack),
|
||||
});
|
||||
|
||||
self.regs = [JsValue::undefined(); 256];
|
||||
self.pc = 0;
|
||||
self.bytecode = sub.bytecode.clone();
|
||||
self.constants = converted_constants;
|
||||
self.activate_immutables(sub_idx + 1, &sub.constants);
|
||||
self.active_reg_limit = sub.n_registers.max(1);
|
||||
self.root_reg_limit = self.active_reg_limit;
|
||||
for i in 0..sub.n_args as usize {
|
||||
@@ -82,7 +81,7 @@ impl Vm {
|
||||
self.regs = *saved.regs;
|
||||
self.pc = saved.pc;
|
||||
self.bytecode = saved.bytecode;
|
||||
self.constants = saved.constants;
|
||||
self.active_immutables = saved.active_immutables;
|
||||
self.active_reg_limit = saved.active_reg_limit;
|
||||
self.root_reg_limit = saved.root_reg_limit;
|
||||
self.try_stack = saved.try_stack;
|
||||
@@ -94,7 +93,7 @@ impl Vm {
|
||||
self.for_of_iters = saved.for_of_iters;
|
||||
self.last_for_of_result = saved.last_for_of_result;
|
||||
self.saved_bytecode_stack = saved.saved_bytecode_stack;
|
||||
self.saved_constants_stack = saved.saved_constants_stack;
|
||||
self.saved_immutables_stack = saved.saved_immutables_stack;
|
||||
self.save_stack = saved.save_stack;
|
||||
|
||||
result
|
||||
@@ -104,8 +103,8 @@ impl Vm {
|
||||
if let Some(saved_bc) = self.saved_bytecode_stack.pop() {
|
||||
self.bytecode = saved_bc;
|
||||
}
|
||||
if let Some(saved_consts) = self.saved_constants_stack.pop() {
|
||||
self.constants = saved_consts;
|
||||
if let Some(saved_imm) = self.saved_immutables_stack.pop() {
|
||||
self.active_immutables = saved_imm;
|
||||
}
|
||||
let offset = frame.saved_reg_offset as usize;
|
||||
let len = frame.caller_reg_limit as usize;
|
||||
@@ -144,9 +143,10 @@ impl Vm {
|
||||
pub fn run(&mut self, module: &CompiledModule) -> Result<JsValue, String> {
|
||||
self.clear_execution_state();
|
||||
self.sub_modules = Arc::new(module.sub_modules.clone());
|
||||
self.constants = self.convert_constants(&module.constants).map_err(String::from)?;
|
||||
self.sub_module_constants = vec![Vec::new(); self.sub_modules.len()];
|
||||
// Per-run convert-once cache: slot 0 = top module, slot sub_idx+1 = sub_modules[sub_idx].
|
||||
self.immutables_cache = (0..=self.sub_modules.len()).map(|_| OnceLock::new()).collect();
|
||||
self.bytecode = module.bytecode.clone();
|
||||
self.activate_immutables(0, &module.constants);
|
||||
self.root_reg_limit = module.n_registers.max(1);
|
||||
self.active_reg_limit = self.root_reg_limit;
|
||||
|
||||
|
||||
@@ -5,10 +5,9 @@ use std::sync::Arc;
|
||||
use oxide_bytecode::module::Constant;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::vm::{native_fn_ptr_to_fn, Vm};
|
||||
use crate::vm::Vm;
|
||||
use oxide_kernel::kernel::{KernelConfig, KernelCore, KernelSession};
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::error::JsError;
|
||||
use oxide_types::mem::{Epoch, P};
|
||||
use oxide_types::object::{JsObject, JsString, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
@@ -23,7 +22,8 @@ impl Vm {
|
||||
regs: [JsValue::undefined(); 256],
|
||||
pc: 0,
|
||||
bytecode: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
immutables_cache: Vec::new(),
|
||||
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
|
||||
frames: smallvec::SmallVec::new(),
|
||||
for_in_iters: Vec::new(),
|
||||
kernel_core: core,
|
||||
@@ -39,9 +39,8 @@ impl Vm {
|
||||
object_prototype: obj_proto,
|
||||
math_rng_state: 0,
|
||||
sub_modules: Arc::new(Vec::new()),
|
||||
sub_module_constants: Vec::new(),
|
||||
saved_bytecode_stack: Vec::new(),
|
||||
saved_constants_stack: Vec::new(),
|
||||
saved_immutables_stack: Vec::new(),
|
||||
save_stack: Vec::new(),
|
||||
try_stack: Vec::new(),
|
||||
exception_value: None,
|
||||
@@ -70,7 +69,8 @@ impl Vm {
|
||||
regs: [JsValue::undefined(); 256],
|
||||
pc: 0,
|
||||
bytecode: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
immutables_cache: Vec::new(),
|
||||
active_immutables: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
|
||||
frames: smallvec::SmallVec::new(),
|
||||
for_in_iters: Vec::new(),
|
||||
kernel_core: core,
|
||||
@@ -86,9 +86,8 @@ impl Vm {
|
||||
object_prototype: obj_proto,
|
||||
math_rng_state: 0,
|
||||
sub_modules: Arc::new(Vec::new()),
|
||||
sub_module_constants: Vec::new(),
|
||||
saved_bytecode_stack: Vec::new(),
|
||||
saved_constants_stack: Vec::new(),
|
||||
saved_immutables_stack: Vec::new(),
|
||||
save_stack: Vec::new(),
|
||||
try_stack: Vec::new(),
|
||||
exception_value: None,
|
||||
@@ -135,7 +134,8 @@ impl Vm {
|
||||
fn clear_full_reset_state(&mut self) {
|
||||
self.clear_execution_state();
|
||||
self.bytecode.clear();
|
||||
self.constants.clear();
|
||||
self.immutables_cache.clear();
|
||||
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
|
||||
self.free_epoch_object_heap_data();
|
||||
self.epoch.reset();
|
||||
self.epoch_object_ptrs.clear();
|
||||
@@ -175,7 +175,7 @@ impl Vm {
|
||||
self.for_of_iters.clear();
|
||||
self.last_for_of_result = JsValue::undefined();
|
||||
self.saved_bytecode_stack.clear();
|
||||
self.saved_constants_stack.clear();
|
||||
self.saved_immutables_stack.clear();
|
||||
self.save_stack.clear();
|
||||
self.try_stack.clear();
|
||||
self.exception_value = None;
|
||||
@@ -188,7 +188,8 @@ impl Vm {
|
||||
self.clear_execution_state();
|
||||
self.maybe_collect_session_gc();
|
||||
self.bytecode.clear();
|
||||
self.constants.clear();
|
||||
self.immutables_cache.clear();
|
||||
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
|
||||
self.free_epoch_object_heap_data();
|
||||
self.epoch.reset();
|
||||
self.epoch_object_ptrs.clear();
|
||||
@@ -302,68 +303,22 @@ impl Vm {
|
||||
format!("{val}")
|
||||
}
|
||||
|
||||
fn convert_constant(&mut self, constant: &Constant) -> Result<JsValue, JsError> {
|
||||
fn convert_constant(&self, constant: &Constant) -> JsValue {
|
||||
match constant {
|
||||
Constant::Number(v) => Ok(JsValue::float(*v)),
|
||||
Constant::Int(v) => Ok(JsValue::int(*v)),
|
||||
Constant::String(s) => Ok(self.perm_string(s)),
|
||||
Constant::Boolean(b) => Ok(JsValue::bool(*b)),
|
||||
Constant::Null => Ok(JsValue::null()),
|
||||
Constant::Undefined => Ok(JsValue::undefined()),
|
||||
Constant::BytecodeFunc(idx) => {
|
||||
let sub_idx = *idx as usize;
|
||||
let (is_arrow, is_class_constructor, is_derived_constructor, needs_home_object) =
|
||||
if sub_idx > 0 && sub_idx <= self.sub_modules.len() {
|
||||
let sub_module = &self.sub_modules[sub_idx - 1];
|
||||
(
|
||||
sub_module.is_arrow,
|
||||
sub_module.is_class_constructor,
|
||||
sub_module.is_derived_constructor,
|
||||
sub_module.needs_home_object,
|
||||
)
|
||||
} else {
|
||||
(false, false, false, false)
|
||||
};
|
||||
Ok(self.create_function_object(
|
||||
*idx,
|
||||
is_arrow,
|
||||
is_class_constructor,
|
||||
is_derived_constructor,
|
||||
needs_home_object,
|
||||
))
|
||||
}
|
||||
Constant::RegExp(pattern, flags) => {
|
||||
let pat_val = self.perm_string(pattern);
|
||||
let flags_val = self.perm_string(flags);
|
||||
|
||||
let ctor_ptr = self.session.builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||||
let ctor = unsafe { &*ctor_ptr };
|
||||
let Some(native_fn) = ctor.native_fn() else {
|
||||
return Err(JsError::syntax_error("RegExp constructor unavailable"));
|
||||
};
|
||||
|
||||
let saved_0 = self.regs[0];
|
||||
let saved_1 = self.regs[1];
|
||||
let saved_2 = self.regs[2];
|
||||
self.regs[0] = JsValue::undefined();
|
||||
self.regs[1] = pat_val;
|
||||
self.regs[2] = flags_val;
|
||||
let func = unsafe { native_fn_ptr_to_fn(native_fn) };
|
||||
let result = func(self, &[0, 1, 2]);
|
||||
self.regs[0] = saved_0;
|
||||
self.regs[1] = saved_1;
|
||||
self.regs[2] = saved_2;
|
||||
result.map_err(|err| JsError::syntax_error(self.error_text(err)))
|
||||
}
|
||||
Constant::Number(v) => JsValue::float(*v),
|
||||
Constant::Int(v) => JsValue::int(*v),
|
||||
Constant::String(s) => self.perm_string(s),
|
||||
Constant::Boolean(b) => JsValue::bool(*b),
|
||||
Constant::Null => JsValue::null(),
|
||||
Constant::Undefined => JsValue::undefined(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn convert_constants(&mut self, constants: &[Constant]) -> Result<Vec<JsValue>, JsError> {
|
||||
let mut values = Vec::with_capacity(constants.len());
|
||||
for constant in constants {
|
||||
values.push(self.convert_constant(constant)?);
|
||||
}
|
||||
Ok(values)
|
||||
/// Convert a module's immutable constant pool to `JsValue`s. Infallible — the pool holds only
|
||||
/// scalars + permanent-interner string values after CreateClosure/CreateRegExp pulled functions
|
||||
/// and regexes out of the pool. `&self` so it can run inside `OnceLock::get_or_init`.
|
||||
pub(crate) fn convert_immutables(&self, constants: &[Constant]) -> Vec<JsValue> {
|
||||
constants.iter().map(|c| self.convert_constant(c)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,6 +438,18 @@ mod tests {
|
||||
assert!(unsafe { *session_ptr } == 123);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn immutables_cache_filled_once_per_module() {
|
||||
let mut vm = Vm::new();
|
||||
// `f` recurses (same sub-module entered 4x). Its immutables convert once via OnceLock.
|
||||
let result = run_source(&mut vm, "function f(n){ if(n<=0){ return 'done'; } return f(n-1); } f(3)");
|
||||
assert!(result.is_string());
|
||||
assert_eq!(vm.lookup_str(result).as_deref(), Some("done"));
|
||||
// Cache = top module + 1 sub-module (f); the sub-module slot was initialized by the calls.
|
||||
assert_eq!(vm.immutables_cache.len(), 2);
|
||||
assert!(vm.immutables_cache[1].get().is_some(), "f's immutables should be cached after its calls");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_epoch_reset_is_only_in_full_reset_state_clear() {
|
||||
let src = include_str!("vm_support.rs");
|
||||
|
||||
Reference in New Issue
Block a user