refactor: 扁平化对象属性存储并优化常量池与 IC 热路径

This commit is contained in:
爱特每羊队
2026-06-13 23:59:47 +08:00
parent dcc7db2575
commit bf74adf7f5
26 changed files with 307 additions and 87 deletions
+5 -5
View File
@@ -26,19 +26,19 @@ fn bind_error_subtype_constructor(
let ctor_shape1 = sh.make_shape(EMPTY_SHAPE_ID, si_prototype);
let ctor_shape2 = sh.make_shape(ctor_shape1, si_name);
ctor.set_shape_id(ctor_shape2);
ctor.ensure_hash_props().push(Box::new(JsValue::from_js_object(proto_ptr)));
ctor.ensure_hash_props().push(Box::new(JsValue::string(name_si, 0)));
ctor.ensure_hash_props().push(JsValue::from_js_object(proto_ptr));
ctor.ensure_hash_props().push(JsValue::string(name_si, 0));
let ctor_ptr = Box::into_raw(ctor);
let proto = unsafe { &mut *proto_ptr };
let proto_ctor_shape = sh.make_shape(proto.shape_id(), sf.intern("constructor").0);
proto.set_shape_id(proto_ctor_shape);
proto.ensure_hash_props().push(Box::new(JsValue::from_js_object(ctor_ptr)));
proto.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr));
let global_shape = sh.make_shape(global.shape_id(), name_si);
global.set_shape_id(global_shape);
global.ensure_hash_props().push(Box::new(JsValue::from_js_object(ctor_ptr)));
global.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr));
global.bump_generation();
}
@@ -64,7 +64,7 @@ pub fn bind_error(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
let err_shape = kernel.shape_forge().make_shape(global.shape_id(), si_err);
let err_val = JsValue::from_js_object(kernel.builtin_world().error_constructor.as_ptr() as *mut JsObject);
global.set_shape_id(err_shape);
global.ensure_hash_props().push(Box::new(err_val));
global.ensure_hash_props().push(err_val);
global.bump_generation();
bind_error_subtype_constructor(
+2 -2
View File
@@ -68,13 +68,13 @@ pub fn bind_math(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
let si = kernel.string_forge().as_ref().intern(name).0;
let sh_c = kernel.shape_forge().as_ref().make_shape(math.shape_id(), si);
math.set_shape_id(sh_c);
math.ensure_hash_props().push(Box::new(JsValue::float(val)));
math.ensure_hash_props().push(JsValue::float(val));
}
let si_m = kernel.string_forge().intern("Math").0;
let m_shape = kernel.shape_forge().make_shape(global.shape_id(), si_m);
let m_val = JsValue::from_js_object(kernel.builtin_world().math_object.as_ptr() as *mut JsObject);
global.set_shape_id(m_shape);
global.ensure_hash_props().push(Box::new(m_val));
global.ensure_hash_props().push(m_val);
global.bump_generation();
}
+2 -2
View File
@@ -30,7 +30,7 @@ pub fn bind_number(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
let num_shape = kernel.shape_forge().make_shape(global.shape_id(), si_num);
let num_val = JsValue::from_js_object(kernel.builtin_world().number_constructor.as_ptr() as *mut JsObject);
global.set_shape_id(num_shape);
global.ensure_hash_props().push(Box::new(num_val));
global.ensure_hash_props().push(num_val);
global.bump_generation();
{
@@ -52,7 +52,7 @@ pub fn bind_number(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
("NEGATIVE_INFINITY", JsValue::float(f64::NEG_INFINITY)),
("POSITIVE_INFINITY", JsValue::float(f64::INFINITY)),
] {
num_ctor.ensure_hash_props().push(Box::new(value));
num_ctor.ensure_hash_props().push(value);
let prop_si = kernel.string_forge().intern(name).0;
let next_shape = kernel.shape_forge().make_shape(num_ctor.shape_id(), prop_si);
num_ctor.set_shape_id(next_shape);
+1 -1
View File
@@ -44,6 +44,6 @@ pub fn bind_string(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
let str_shape = kernel.shape_forge().make_shape(global.shape_id(), si_str);
let str_val = JsValue::from_js_object(kernel.builtin_world().string_constructor.as_ptr() as *mut JsObject);
global.set_shape_id(str_shape);
global.ensure_hash_props().push(Box::new(str_val));
global.ensure_hash_props().push(str_val);
global.bump_generation();
}
+1 -1
View File
@@ -54,6 +54,6 @@ fn bind_well_known_symbol(kernel: &Arc<OxideKernel>, ctor: &mut JsObject, name:
let si = kernel.string_forge().intern(name).0;
let shape_id = kernel.shape_forge().make_shape(ctor.shape_id(), si);
ctor.set_shape_id(shape_id);
ctor.ensure_hash_props().push(Box::new(val));
ctor.ensure_hash_props().push(val);
ctor.bump_generation();
}
+2 -2
View File
@@ -42,7 +42,7 @@ macro_rules! bind_constructor_hash {
let shape = $kernel.shape_forge().make_shape($global.shape_id(), si);
let val = $crate::JsValue::from_js_object($ctor_ptr);
$global.set_shape_id(shape);
$global.ensure_hash_props().push(Box::new(val));
$global.ensure_hash_props().push(val);
$global.bump_generation();
let ctor = unsafe { &mut *$ctor_ptr };
// SAFETY: $ctor_fn is a fn-item expression; casting to *const () preserves validity.
@@ -73,7 +73,7 @@ pub(crate) fn bind_global_value(kernel: &Arc<OxideKernel>, global: &mut JsObject
let si = kernel.string_forge().intern(name).0;
let shape = kernel.shape_forge().make_shape(global.shape_id(), si);
global.set_shape_id(shape);
global.ensure_hash_props().push(Box::new(value));
global.ensure_hash_props().push(value);
global.bump_generation();
}
+2 -2
View File
@@ -118,12 +118,12 @@ pub fn error_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
let name_val = obj
.hash_props_vec()
.and_then(|v| v.first())
.map(|b| **b)
.copied()
.unwrap_or(JsValue::undefined());
let msg_val = obj
.hash_props_vec()
.and_then(|v| v.get(1))
.map(|b| **b)
.copied()
.unwrap_or(JsValue::undefined());
(
vm.lookup_str(name_val).unwrap_or_else(|| "Error".to_string()),
+5 -5
View File
@@ -19,11 +19,11 @@ fn bind_dispatcher(vm: &mut Vm, args: &[u8]) -> NativeResult {
let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() };
let bound_target = wrapper
.hash_props_vec()
.and_then(|v| v.first().map(|b| **b))
.and_then(|v| v.first().copied())
.unwrap_or(JsValue::undefined());
let bound_this = wrapper
.hash_props_vec()
.and_then(|v| v.get(1).map(|b| **b))
.and_then(|v| v.get(1).copied())
.unwrap_or(JsValue::undefined());
// Forward bound call arguments (skip args[0], the bound-wrapper receiver).
@@ -63,7 +63,7 @@ pub fn function_apply(vm: &mut Vm, args: &[u8]) -> NativeResult {
for i in 0..n {
let vec = arr.hash_props_vec();
if let Some(v) = vec.and_then(|v| v.get(i)) {
vm.set_reg(base + i as u8, **v);
vm.set_reg(base + i as u8, *v);
}
}
} else {
@@ -97,8 +97,8 @@ pub fn function_bind(vm: &mut Vm, args: &[u8]) -> NativeResult {
// SAFETY: bind_dispatcher is a NativeFn fn-item; valid to store as NativeFnPtr.
(*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher as *const ())));
(*wrapper).set_native_arg_count(0);
(*wrapper).ensure_hash_props().push(Box::new(target_val));
(*wrapper).ensure_hash_props().push(Box::new(bound_this));
(*wrapper).ensure_hash_props().push(target_val);
(*wrapper).ensure_hash_props().push(bound_this);
}
NativeResult::Ok(JsValue::from_js_object(wrapper))
+1 -1
View File
@@ -70,7 +70,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue {
let jsv = value_to_jsvalue(vm, val);
let new_shape = vm.kernel().shape_forge().make_shape(obj.shape_id(), si);
obj.set_shape_id(new_shape);
obj.ensure_hash_props().push(Box::new(jsv));
obj.ensure_hash_props().push(jsv);
}
let obj_ptr = vm.epoch().alloc(obj);
JsValue::from_js_object(obj_ptr)
+6 -6
View File
@@ -43,13 +43,13 @@ fn set_prop(obj: &mut JsObject, name: &str, val: JsValue, vm: &Vm) {
let si = vm.kernel().string_forge().intern(name).0;
let shape_id = vm.kernel().shape_forge().make_shape(obj.shape_id(), si);
obj.set_shape_id(shape_id);
obj.ensure_hash_props().push(Box::new(val));
obj.ensure_hash_props().push(val);
}
fn get_prop(obj: &JsObject, idx: usize) -> JsValue {
obj.hash_props_vec()
.and_then(|v| v.get(idx))
.map(|b| **b)
.copied()
.unwrap_or(JsValue::undefined())
}
@@ -57,9 +57,9 @@ fn set_prop_at(obj: *mut JsObject, idx: usize, val: JsValue) {
unsafe {
let re = &mut *obj;
if let Some(vec) = re.hash_props_vec() {
let ptr = vec.as_ptr() as *mut Box<JsValue>;
let ptr = vec.as_ptr() as *mut JsValue;
let v = &mut *ptr.add(idx);
**v = val;
*v = val;
}
}
}
@@ -201,14 +201,14 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult {
let full_match = &haystack[m.start()..m.end()];
let full_si = vm.kernel().string_forge().intern(full_match).0;
match_obj.ensure_hash_props().push(Box::new(JsValue::string(full_si, 0)));
match_obj.ensure_hash_props().push(JsValue::string(full_si, 0));
let captures = regex.captures(&haystack[m.start()..m.end()]);
if let Some(caps) = &captures {
for i in 1..caps.len() {
let cap_str = caps.get(i).map(|cm| cm.as_str()).unwrap_or("");
let si = vm.kernel().string_forge().intern(cap_str).0;
match_obj.ensure_hash_props().push(Box::new(JsValue::string(si, 0)));
match_obj.ensure_hash_props().push(JsValue::string(si, 0));
}
}
+1 -1
View File
@@ -306,7 +306,7 @@ pub fn to_object(val: JsValue, vm: &mut Vm) -> Result<JsValue, &'static str> {
let obj = vm.epoch.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
let obj_val = JsValue::from_js_object(obj);
let obj_ref = unsafe { &mut *obj };
obj_ref.ensure_hash_props().push(Box::new(val));
obj_ref.ensure_hash_props().push(val);
obj_ref.set_prop_count(1);
Ok(obj_val)
}
+30 -4
View File
@@ -2,9 +2,32 @@ use oxide_compiler::opcode::OpCode;
use oxide_kernel::prop_forge::PropTemplate;
use oxide_kernel::{ic_debug, ic_trace};
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::vm::Vm;
#[inline(always)]
fn ic_get_hit(obj: &JsObject, shape_id: u32, slot_index: u32) -> Option<JsValue> {
if shape_id != 0 && obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
Some(obj.get_prop_at(slot_index))
} else {
None
}
}
#[inline(always)]
fn ic_set_hit(obj: &mut JsObject, shape_id: u32, slot_index: u32, value: JsValue) -> bool {
if shape_id != 0 && obj.shape_id() == shape_id && slot_index < obj.prop_vec_len() as u32 {
obj.set_prop_at(slot_index, value);
true
} else {
false
}
}
#[cold]
fn prop_cache_miss() {}
impl Vm {
pub(crate) fn dispatch_property_op(&mut self, op: OpCode, rd: usize, a: usize, b: usize) -> Result<(), String> {
match op {
@@ -70,10 +93,11 @@ impl Vm {
let cached_shape_id = ext0 & 0x00FF_FFFF;
let cached_slot = ext1;
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_slot < obj.prop_vec_len() as u32 {
self.regs[a] = obj.get_prop_at(cached_slot);
if let Some(value) = ic_get_hit(obj, cached_shape_id, cached_slot) {
self.regs[a] = value;
ic_trace!("IC_GET hit shape={} slot={}", cached_shape_id, cached_slot);
} else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) {
prop_cache_miss();
if template.prop_name == prop_name_si {
if template.position < obj.prop_vec_len() as u32 {
self.write_ic_back(obj.shape_id(), template.position);
@@ -93,6 +117,7 @@ impl Vm {
self.regs[a] = self.ordinary_get(obj, prop_name_si, val)?;
}
} else {
prop_cache_miss();
ic_debug!("IC_GET miss shape={} prop={}", obj.shape_id(), prop_name_si);
let resolved = self.ordinary_get(obj, prop_name_si, val)?;
if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
@@ -136,14 +161,15 @@ impl Vm {
let cached_shape_id = ext0 & 0x00FF_FFFF;
let cached_slot = ext1;
if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_slot < obj.prop_vec_len() as u32 {
obj.set_prop_at(cached_slot, value);
if ic_set_hit(obj, cached_shape_id, cached_slot, value) {
ic_trace!("IC_SET hit shape={} slot={}", cached_shape_id, cached_slot);
} else if let Some(pos) = self.kernel.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
prop_cache_miss();
obj.set_prop_at(pos, value);
self.write_ic_back(obj.shape_id(), pos);
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
} else {
prop_cache_miss();
let old_shape = obj.shape_id();
self.ordinary_set_dispatch(obj, prop_name_si, value, receiver)?;
if old_shape != obj.shape_id() {
+52 -16
View File
@@ -5,6 +5,7 @@ use std::sync::Arc;
use oxide_compiler::compiler::Constant;
use oxide_compiler::module::CompiledModule;
use oxide_compiler::opcode::{self, OpCode};
use smallvec::SmallVec;
use crate::bindings;
pub use crate::bindings::init_kernel_builtins;
@@ -13,6 +14,7 @@ use crate::native::{NativeFn, NativeResult};
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_kernel::{vm_debug, vm_trace};
use oxide_types::error::{JsError, JsErrorKind};
use oxide_types::mem::{Epoch, P};
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
use oxide_types::value::JsValue;
@@ -27,6 +29,30 @@ pub(crate) unsafe fn native_fn_ptr_to_fn(ptr: NativeFnPtr) -> NativeFn {
std::mem::transmute::<*const (), NativeFn>(ptr.as_ptr())
}
fn js_error_kind(kind: &'static str) -> JsErrorKind {
match kind {
"TypeError" => JsErrorKind::TypeError,
"RangeError" => JsErrorKind::RangeError,
"ReferenceError" => JsErrorKind::ReferenceError,
"SyntaxError" => JsErrorKind::SyntaxError,
"URIError" => JsErrorKind::URIError,
"EvalError" => JsErrorKind::EvalError,
_ => JsErrorKind::Error,
}
}
fn js_error_kind_name(kind: JsErrorKind) -> &'static str {
match kind {
JsErrorKind::TypeError => "TypeError",
JsErrorKind::RangeError => "RangeError",
JsErrorKind::ReferenceError => "ReferenceError",
JsErrorKind::SyntaxError => "SyntaxError",
JsErrorKind::Error => "Error",
JsErrorKind::URIError => "URIError",
JsErrorKind::EvalError => "EvalError",
}
}
#[allow(unused_macros)]
macro_rules! throw_err {
($self:ident, $kind:ident, $msg:expr) => {{
@@ -96,7 +122,7 @@ struct InlineSyncState {
active_reg_limit: u8,
root_reg_limit: u8,
try_stack: Vec<TryHandler>,
frames: Vec<CallFrame>,
frames: SmallVec<[CallFrame; 16]>,
exception_value: Option<JsValue>,
pending_exception: Option<JsValue>,
pending_error_kind: Option<&'static str>,
@@ -110,7 +136,7 @@ pub struct Vm {
pub(crate) pc: usize,
pub(crate) bytecode: Vec<opcode::Instr>,
pub(crate) constants: Vec<JsValue>,
pub(crate) frames: Vec<CallFrame>,
pub(crate) frames: SmallVec<[CallFrame; 16]>,
pub for_in_iters: Vec<*mut ForInIter<'static>>,
pub(crate) kernel: Arc<OxideKernel>,
pub(crate) interned_strings: Vec<u32>,
@@ -156,11 +182,16 @@ impl Vm {
}
pub(crate) fn raise_error_kind(&mut self, kind: &'static str, msg: &str) -> Result<(), String> {
self.raise_js_error(JsError::new(js_error_kind(kind), msg))
}
pub(crate) fn raise_js_error(&mut self, err: JsError) -> Result<(), String> {
let kind = js_error_kind_name(err.kind);
let error = match kind {
"TypeError" => crate::builtins::error::create_type_error(self, msg),
"ReferenceError" => crate::builtins::error::create_reference_error(self, msg),
"SyntaxError" => crate::builtins::error::create_syntax_error(self, msg),
_ => crate::builtins::error::create_error(self, msg),
"TypeError" => crate::builtins::error::create_type_error(self, &err.message),
"ReferenceError" => crate::builtins::error::create_reference_error(self, &err.message),
"SyntaxError" => crate::builtins::error::create_syntax_error(self, &err.message),
_ => crate::builtins::error::create_error(self, &err.message),
};
self.exception_value = Some(error);
self.pending_error_kind = Some(kind);
@@ -180,7 +211,7 @@ impl Vm {
pc: 0,
bytecode: Vec::new(),
constants: Vec::new(),
frames: Vec::with_capacity(128),
frames: SmallVec::new(),
for_in_iters: Vec::new(),
kernel,
interned_strings: Vec::new(),
@@ -212,7 +243,7 @@ impl Vm {
pc: 0,
bytecode: Vec::new(),
constants: Vec::new(),
frames: Vec::with_capacity(128),
frames: SmallVec::new(),
for_in_iters: Vec::new(),
kernel,
interned_strings: Vec::new(),
@@ -284,6 +315,11 @@ impl Vm {
}
fn clear_execution_state(&mut self) {
// Reset contract:
// - Clears register file, pc, frame/iterator stacks, saved execution stacks,
// try handlers, pending exceptions, and native call depth.
// - Leaves kernel-owned shared state intact.
// - `reset()` additionally clears bytecode/constants and resets epoch ownership.
self.regs = [JsValue::undefined(); 256];
self.pc = 0;
self.frames.clear();
@@ -356,7 +392,7 @@ impl Vm {
let func = unsafe { &mut *obj_ptr };
let prototype_shape = self.kernel.shape_forge().make_shape(func.shape_id(), prototype_si);
func.set_shape_id(prototype_shape);
func.ensure_hash_props().push(Box::new(prototype_val));
func.ensure_hash_props().push(prototype_val);
func.bump_generation();
}
@@ -384,7 +420,7 @@ impl Vm {
format!("{val}")
}
fn convert_constant(&mut self, constant: &Constant) -> Result<JsValue, String> {
fn convert_constant(&mut self, constant: &Constant) -> Result<JsValue, JsError> {
match constant {
Constant::Number(v) => Ok(JsValue::float(*v)),
Constant::Int(v) => Ok(JsValue::int(*v)),
@@ -423,7 +459,7 @@ impl Vm {
let ctor_ptr = self.kernel.builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
let ctor = unsafe { &*ctor_ptr };
let Some(native_fn) = ctor.native_fn() else {
return Err("SyntaxError: RegExp constructor unavailable".into());
return Err(JsError::syntax_error("RegExp constructor unavailable"));
};
let saved_0 = self.regs[0];
@@ -439,13 +475,13 @@ impl Vm {
self.regs[0] = saved_0;
self.regs[1] = saved_1;
self.regs[2] = saved_2;
result.map_err(|err| self.error_text(err))
result.map_err(|err| JsError::syntax_error(self.error_text(err)))
}
}
}
/// Convert a module constant pool into runtime values.
fn convert_constants(&mut self, constants: &[Constant]) -> Result<Vec<JsValue>, String> {
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)?);
@@ -609,7 +645,7 @@ impl Vm {
}
let sub = self.sub_modules[sub_idx].clone();
let converted_constants = self.convert_constants(&sub.constants)?;
let converted_constants = self.convert_constants(&sub.constants).map_err(String::from)?;
// Heap-allocate the saved state so this function's Rust stack frame stays small.
let saved = Box::new(InlineSyncState {
@@ -712,7 +748,7 @@ 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)?;
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));
@@ -1114,7 +1150,7 @@ impl Vm {
pub fn run(&mut self, module: &CompiledModule) -> Result<JsValue, String> {
self.clear_execution_state();
self.sub_modules = module.sub_modules.clone();
self.constants = self.convert_constants(&module.constants)?;
self.constants = self.convert_constants(&module.constants).map_err(String::from)?;
self.sub_module_constants = vec![Vec::new(); self.sub_modules.len()];
self.bytecode = module.bytecode.clone();
self.root_reg_limit = module.n_registers.max(1);