feat: 实现 JsObject 紧凑对象布局
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
pub mod coercion;
|
pub mod coercion;
|
||||||
pub mod mem;
|
pub mod mem;
|
||||||
|
pub mod object;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
pub mod vm;
|
pub mod vm;
|
||||||
pub use value::JsValue;
|
pub use value::JsValue;
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
use crate::value::JsValue;
|
||||||
|
|
||||||
|
pub type ShapeId = u32;
|
||||||
|
|
||||||
|
pub const EMPTY_SHAPE_ID: ShapeId = 0;
|
||||||
|
|
||||||
|
/// Layout:
|
||||||
|
/// header: u32 bits
|
||||||
|
/// [0:23] shape_id
|
||||||
|
/// [24:28] prop_count (5 bits, 0-31)
|
||||||
|
/// [29] is_array
|
||||||
|
/// [30] is_extensible
|
||||||
|
/// [31] is_function
|
||||||
|
/// inline[0..=3]: 4 x JsValue (32 bytes)
|
||||||
|
/// overflow: *mut JsValue (8 bytes, null if ≤4 properties)
|
||||||
|
/// proto: JsValue (8 bytes)
|
||||||
|
/// generation: u32 (4 bytes)
|
||||||
|
/// _pad: u32 (4 bytes, 64B total = one cache line)
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct JsObject {
|
||||||
|
header: u32,
|
||||||
|
inline: [JsValue; 4],
|
||||||
|
overflow: *mut JsValue,
|
||||||
|
proto: JsValue,
|
||||||
|
generation: u32,
|
||||||
|
_pad: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsObject {
|
||||||
|
pub fn new_empty(shape_id: ShapeId, proto: JsValue) -> Self {
|
||||||
|
Self {
|
||||||
|
header: (shape_id & 0x00FF_FFFF) | (1 << 30),
|
||||||
|
inline: [JsValue::undefined(); 4],
|
||||||
|
overflow: std::ptr::null_mut(),
|
||||||
|
proto,
|
||||||
|
generation: 1,
|
||||||
|
_pad: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_array(shape_id: ShapeId, proto: JsValue, n_elements: usize) -> Self {
|
||||||
|
assert!(
|
||||||
|
n_elements <= 4,
|
||||||
|
"arrays with >4 inline elements not yet supported"
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
header: (shape_id & 0x00FF_FFFF) | (1 << 30) | (1 << 29),
|
||||||
|
inline: [JsValue::undefined(); 4],
|
||||||
|
overflow: std::ptr::null_mut(),
|
||||||
|
proto,
|
||||||
|
generation: 1,
|
||||||
|
_pad: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shape_id(&self) -> ShapeId {
|
||||||
|
self.header & 0x00FF_FFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_shape_id(&mut self, id: ShapeId) {
|
||||||
|
self.header = (self.header & !0x00FF_FFFF) | (id & 0x00FF_FFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prop_count(&self) -> u8 {
|
||||||
|
((self.header >> 24) & 0x1F) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_prop_count(&mut self, count: u8) {
|
||||||
|
let count = count.min(31);
|
||||||
|
self.header = (self.header & !(0x1F << 24)) | ((count as u32) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_array(&self) -> bool {
|
||||||
|
(self.header >> 29) & 1 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_extensible(&self) -> bool {
|
||||||
|
(self.header >> 30) & 1 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_extensible(&mut self, ext: bool) {
|
||||||
|
if ext {
|
||||||
|
self.header |= 1 << 30;
|
||||||
|
} else {
|
||||||
|
self.header &= !(1 << 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_function(&self) -> bool {
|
||||||
|
(self.header >> 31) & 1 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_function(&mut self, f: bool) {
|
||||||
|
if f {
|
||||||
|
self.header |= 1 << 31;
|
||||||
|
} else {
|
||||||
|
self.header &= !(1 << 31);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_inline_prop(&self, offset: u8) -> JsValue {
|
||||||
|
debug_assert!(offset < 4, "inline offset out of range");
|
||||||
|
self.inline[offset as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_inline_prop(&mut self, offset: u8, val: JsValue) {
|
||||||
|
debug_assert!(offset < 4, "inline offset out of range");
|
||||||
|
self.inline[offset as usize] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_overflow_prop(&self, offset: u8) -> JsValue {
|
||||||
|
debug_assert!(offset >= 4);
|
||||||
|
if self.overflow.is_null() {
|
||||||
|
return JsValue::undefined();
|
||||||
|
}
|
||||||
|
unsafe { *self.overflow.add((offset - 4) as usize) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_overflow_prop(&mut self, offset: u8, val: JsValue) {
|
||||||
|
debug_assert!(offset >= 4);
|
||||||
|
debug_assert!(!self.overflow.is_null(), "overflow buffer not allocated");
|
||||||
|
unsafe {
|
||||||
|
*self.overflow.add((offset - 4) as usize) = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_prop(&self, offset: u8) -> JsValue {
|
||||||
|
if offset < 4 {
|
||||||
|
self.get_inline_prop(offset)
|
||||||
|
} else {
|
||||||
|
self.get_overflow_prop(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_prop(&mut self, offset: u8, val: JsValue) {
|
||||||
|
if offset < 4 {
|
||||||
|
self.set_inline_prop(offset, val);
|
||||||
|
} else {
|
||||||
|
self.set_overflow_prop(offset, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn proto(&self) -> JsValue {
|
||||||
|
self.proto
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_proto(&mut self, proto: JsValue) {
|
||||||
|
self.proto = proto;
|
||||||
|
self.generation = self.generation.wrapping_add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generation(&self) -> u32 {
|
||||||
|
self.generation
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bump_generation(&mut self) {
|
||||||
|
self.generation = self.generation.wrapping_add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alloc_overflow(&mut self, _bump: &bumpalo::Bump, new_count: usize) -> *mut JsValue {
|
||||||
|
use std::alloc::Layout;
|
||||||
|
debug_assert!(new_count > 4);
|
||||||
|
debug_assert!(new_count <= 32, "overflow limited to 32 slots max");
|
||||||
|
let n = new_count - 4;
|
||||||
|
let layout = Layout::array::<JsValue>(n).unwrap();
|
||||||
|
let ptr: *mut JsValue;
|
||||||
|
unsafe {
|
||||||
|
ptr = _bump.alloc_layout(layout).as_ptr() as *mut JsValue;
|
||||||
|
for i in 0..n {
|
||||||
|
*ptr.add(i) = JsValue::undefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.overflow = ptr;
|
||||||
|
ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_size_64_bytes() {
|
||||||
|
assert_eq!(
|
||||||
|
std::mem::size_of::<JsObject>(),
|
||||||
|
64,
|
||||||
|
"JsObject layout mismatch — expected 64B (one cache line)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_empty_defaults() {
|
||||||
|
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||||
|
assert_eq!(obj.shape_id(), EMPTY_SHAPE_ID);
|
||||||
|
assert_eq!(obj.prop_count(), 0);
|
||||||
|
assert!(obj.is_extensible());
|
||||||
|
assert!(!obj.is_array());
|
||||||
|
assert!(!obj.is_function());
|
||||||
|
assert_eq!(obj.generation(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shape_id_roundtrip() {
|
||||||
|
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||||
|
obj.set_shape_id(0x00AB_CDEF);
|
||||||
|
assert_eq!(obj.shape_id(), 0x00AB_CDEF);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prop_count_roundtrip() {
|
||||||
|
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||||
|
obj.set_prop_count(17);
|
||||||
|
assert_eq!(obj.prop_count(), 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flags_individual() {
|
||||||
|
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||||
|
assert!(obj.is_extensible());
|
||||||
|
obj.set_extensible(false);
|
||||||
|
assert!(!obj.is_extensible());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inline_prop_read_write() {
|
||||||
|
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||||
|
obj.set_inline_prop(0, JsValue::int(42));
|
||||||
|
assert_eq!(obj.get_inline_prop(0), JsValue::int(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_array_flags() {
|
||||||
|
let obj = JsObject::new_array(5, JsValue::null(), 3);
|
||||||
|
assert!(obj.is_array());
|
||||||
|
assert_eq!(obj.shape_id(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generation_bump() {
|
||||||
|
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||||
|
assert_eq!(obj.generation(), 1);
|
||||||
|
obj.bump_generation();
|
||||||
|
assert_eq!(obj.generation(), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user