diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index 83d5acb..65027ea 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -132,7 +132,7 @@ fn format_result(string_forge: &StringForge, val: JsValue) { let count = obj.prop_count() as usize; let mut first = true; for i in 0..count.min(4) { - let prop_val = obj.get_inline_prop(i as u8); + let prop_val = obj.get_prop_at(i as u8); if prop_val.is_undefined() { continue; } diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs index db1d89b..8e32815 100644 --- a/crates/oxide_compiler/src/counter.rs +++ b/crates/oxide_compiler/src/counter.rs @@ -204,7 +204,7 @@ impl Compiler { ctx.alloc_reg(); ctx.projected_pc += 1; ctx.alloc_reg(); - ctx.projected_pc += 3; + ctx.projected_pc += 5; // LOAD_VAR + IC_GET_PROP + 3 ext words } _ => { self.count_expression(&call.callee, ctx); @@ -229,7 +229,9 @@ impl Compiler { ctx.projected_pc += 1; } ctx.alloc_reg(); + ctx.projected_pc += 1; // LOAD_CONST key ctx.projected_pc += 1; // IC_SET_PROP or COMPOUND_MEMBER_* + ctx.projected_pc += 3; // 3 IC ext words } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left { @@ -319,7 +321,7 @@ impl Compiler { ctx.alloc_reg(); ctx.projected_pc += 1; // LOAD_CONST key ctx.projected_pc += 1; // IC_GET_PROP - ctx.projected_pc += 1; // IC ext word + ctx.projected_pc += 3; // 3 IC ext words } Expression::ComputedMemberExpression(member) => { self.count_expression(&member.object, ctx); @@ -340,7 +342,8 @@ impl Compiler { ctx.alloc_reg(); ctx.projected_pc += 1; ctx.alloc_reg(); - ctx.projected_pc += 1; + ctx.projected_pc += 1; // MEMBER_INC/MEMBER_DEC opcode + ctx.projected_pc += 3; // 3 IC ext words } SimpleAssignmentTarget::ComputedMemberExpression(member) => { self.count_expression(&member.object, ctx); diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index e1f9223..5723c7e 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -525,6 +525,8 @@ impl Compiler { )); ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, obj_reg, key_reg)); ctx.emit(0); + ctx.emit(0); + ctx.emit(0); Ok(obj_reg) } Expression::ComputedMemberExpression(member) => { @@ -618,6 +620,8 @@ impl Compiler { }; ctx.emit(opcode::encode(op, obj_reg, val_reg, key_reg)); ctx.emit(0); + ctx.emit(0); + ctx.emit(0); Ok(val_reg) } else { ctx.emit(opcode::encode( @@ -627,6 +631,8 @@ impl Compiler { key_reg, )); ctx.emit(0); + ctx.emit(0); + ctx.emit(0); Ok(val_reg) } } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = @@ -716,6 +722,8 @@ impl Compiler { }; ctx.emit(opcode::encode(op, obj_reg, val_reg, key_reg)); ctx.emit(0); + ctx.emit(0); + ctx.emit(0); Ok(val_reg) } SimpleAssignmentTarget::ComputedMemberExpression(member) => { @@ -755,6 +763,8 @@ impl Compiler { ctx.emit(opcode::encode(OpCode::LOAD_VAR, callee_reg, obj_reg, 0)); ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, callee_reg, key_reg)); ctx.emit(0); + ctx.emit(0); + ctx.emit(0); (callee_reg, obj_reg) } _ => { diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 9e8b465..94fe1e5 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -126,17 +126,23 @@ fn make_pair( let proto_shape = shape_forge.make_shape(EMPTY_SHAPE_ID, si_constructor); proto.set_shape_id(proto_shape); - proto.set_prop_count(1); + // proto shape: EMPTY -> "constructor" (slot 0) let ctor_shape1 = shape_forge.make_shape(EMPTY_SHAPE_ID, si_prototype); let ctor_shape2 = shape_forge.make_shape(ctor_shape1, si_name); ctor.set_shape_id(ctor_shape2); - ctor.set_prop_count(2); + // ctor shape: EMPTY -> "prototype" (slot 0) -> "name" (slot 1) ctor.set_function(true); - proto.set_inline_prop(0, JsValue::undefined()); - ctor.set_inline_prop(0, JsValue::undefined()); - ctor.set_inline_prop(1, JsValue::undefined()); + // Pre-allocate Vec slots so wire_ctor_proto can overwrite vec[0] later. + // Proto: 1 property ("constructor"), Ctor: 2 properties ("prototype", "name"). + proto + .ensure_hash_props() + .push(Box::new(JsValue::undefined())); // slot 0: "constructor" placeholder + ctor.ensure_hash_props() + .push(Box::new(JsValue::undefined())); // slot 0: "prototype" placeholder + ctor.ensure_hash_props() + .push(Box::new(JsValue::undefined())); // slot 1: "name" placeholder (P::new(proto), P::new(ctor)) } @@ -229,13 +235,22 @@ impl BuiltinWorld { let math_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + /// Overwrite placeholder slots (set up by make_pair) with real values. + /// ctor.vec[0] = constructor.prototype -> proto + /// proto.vec[0] = proto.constructor -> ctor fn wire_ctor_proto(ctor: &P, proto: &P) { let ctor_ptr = ctor.as_ptr() as *mut JsObject; let ctor = unsafe { &mut *ctor_ptr }; - ctor.set_inline_prop(0, JsValue::from_js_object(proto.as_ptr() as *mut JsObject)); + let vec = ctor.ensure_hash_props(); + if !vec.is_empty() { + *vec[0] = JsValue::from_js_object(proto.as_ptr() as *mut JsObject); + } let proto_ptr = proto.as_ptr() as *mut JsObject; let proto = unsafe { &mut *proto_ptr }; - proto.set_inline_prop(0, JsValue::from_js_object(ctor_ptr)); + let pvec = proto.ensure_hash_props(); + if !pvec.is_empty() { + *pvec[0] = JsValue::from_js_object(ctor_ptr); + } } wire_ctor_proto(&object_constructor, &object_proto); @@ -735,11 +750,9 @@ impl BuiltinWorld { wrapper.set_native_fn(Some(native_fn_ptr)); wrapper.set_native_arg_count(arg_count); let wrapper_val = JsValue::from_js_object(Box::into_raw(wrapper)); - let new_offset = proto.prop_count(); let new_shape = shape_forge.make_shape(proto.shape_id(), si); proto.set_shape_id(new_shape); - proto.set_prop_count(new_offset + 1); - proto.set_prop_expand_heap(new_offset, wrapper_val); + proto.ensure_hash_props().push(Box::new(wrapper_val)); proto.bump_generation(); Ok(()) } diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index 66bc983..6c9ba57 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -87,13 +87,15 @@ impl OxideKernel { let nan_shape = shape_forge.make_shape(EMPTY_SHAPE_ID, si_nan); global_obj.set_shape_id(nan_shape); - global_obj.set_prop_count(1); - global_obj.set_inline_prop(0, JsValue::float(f64::NAN)); + global_obj + .ensure_hash_props() + .push(Box::new(JsValue::float(f64::NAN))); let undef_shape = shape_forge.make_shape(nan_shape, si_undef); global_obj.set_shape_id(undef_shape); - global_obj.set_prop_count(2); - global_obj.set_inline_prop(1, JsValue::undefined()); + global_obj + .ensure_hash_props() + .push(Box::new(JsValue::undefined())); let global_object = P::new(global_obj); diff --git a/crates/oxide_kernel/src/prop_forge.rs b/crates/oxide_kernel/src/prop_forge.rs index 58201ba..2e0c453 100644 --- a/crates/oxide_kernel/src/prop_forge.rs +++ b/crates/oxide_kernel/src/prop_forge.rs @@ -7,7 +7,7 @@ use crate::shape_forge::ShapeId; #[derive(Debug, Clone)] pub struct PropTemplate { pub shape_id: ShapeId, - pub offset: u8, + pub ptr_bits: u64, pub generation: u32, } @@ -67,13 +67,13 @@ mod tests { let forge = PropForge::new(); let t = PropTemplate { shape_id: 1, - offset: 3, + ptr_bits: 3, generation: 10, }; forge.upsert(1, t); let got = forge.get_template(1).unwrap(); assert_eq!(got.shape_id, 1); - assert_eq!(got.offset, 3); + assert_eq!(got.ptr_bits, 3); assert_eq!(got.generation, 10); } @@ -84,7 +84,7 @@ mod tests { 1, PropTemplate { shape_id: 1, - offset: 3, + ptr_bits: 3, generation: 10, }, ); @@ -92,7 +92,7 @@ mod tests { 1, PropTemplate { shape_id: 1, - offset: 7, + ptr_bits: 7, generation: 5, }, ); @@ -107,7 +107,7 @@ mod tests { 1, PropTemplate { shape_id: 1, - offset: 3, + ptr_bits: 3, generation: 5, }, ); @@ -115,12 +115,12 @@ mod tests { 1, PropTemplate { shape_id: 1, - offset: 7, + ptr_bits: 7, generation: 10, }, ); let got = forge.get_template(1).unwrap(); assert_eq!(got.generation, 10); - assert_eq!(got.offset, 7); + assert_eq!(got.ptr_bits, 7); } } diff --git a/crates/oxide_kernel/src/shape_forge.rs b/crates/oxide_kernel/src/shape_forge.rs index 2a453b5..ec80e65 100644 --- a/crates/oxide_kernel/src/shape_forge.rs +++ b/crates/oxide_kernel/src/shape_forge.rs @@ -13,7 +13,6 @@ const EMPTY_SENTINEL: StringIndex = u32::MAX; pub struct Shape { pub id: ShapeId, pub property_name: StringIndex, - pub property_offset: u8, pub parent: Option, } @@ -35,7 +34,6 @@ impl ShapeForge { let empty = Arc::new(Shape { id: EMPTY_SHAPE_ID, property_name: EMPTY_SENTINEL, - property_offset: 0, parent: None, }); debug_assert_eq!(shapes.len(), 0); @@ -48,7 +46,7 @@ impl ShapeForge { ((parent_id as u64) << 32) | (prop_name as u64) } - fn compute_offset_inner(shape_id: ShapeId, shapes: &[Option>]) -> u8 { + fn compute_depth(shape_id: ShapeId, shapes: &[Option>]) -> u8 { let mut count = 0u8; let mut cursor = Some(shape_id); while let Some(id) = cursor { @@ -62,7 +60,7 @@ impl ShapeForge { None => break, } } - count.min(31) + count } pub fn make_shape(&self, parent_id: ShapeId, prop_name: StringIndex) -> ShapeId { @@ -72,16 +70,11 @@ impl ShapeForge { return *entry; } - let shapes_read = self.shapes.read().unwrap(); - let prop_offset = Self::compute_offset_inner(parent_id, &shapes_read); - drop(shapes_read); - let new_id = self.next_id.fetch_add(1, Ordering::Relaxed); let shape = Arc::new(Shape { id: new_id, property_name: prop_name, - property_offset: prop_offset, parent: Some(parent_id), }); @@ -101,21 +94,41 @@ impl ShapeForge { shapes.get((id - 1) as usize).and_then(|s| s.clone()) } - pub fn lookup_offset(&self, shape_id: ShapeId, prop_name: StringIndex) -> Option { + pub fn lookup_position(&self, shape_id: ShapeId, prop_name: StringIndex) -> Option { + let shapes = self.shapes.read().unwrap(); + let total_depth = Self::compute_depth(shape_id, &shapes); + let mut step: u8 = 0; + let mut cursor = Some(shape_id); + while let Some(id) = cursor { + match shapes.get((id - 1) as usize).and_then(|s| s.clone()) { + Some(s) => { + if s.property_name == prop_name && s.property_name != EMPTY_SENTINEL { + return Some(total_depth - step - 1); + } + cursor = s.parent; + step += 1; + } + None => return None, + } + } + None + } + + pub fn has_property(&self, shape_id: ShapeId, prop_name: StringIndex) -> bool { let shapes = self.shapes.read().unwrap(); let mut cursor = Some(shape_id); while let Some(id) = cursor { match shapes.get((id - 1) as usize).and_then(|s| s.clone()) { Some(s) => { if s.property_name == prop_name && s.property_name != EMPTY_SENTINEL { - return Some(s.property_offset); + return true; } cursor = s.parent; } - None => return None, + None => return false, } } - None + false } pub fn shape_prop_count(&self, shape_id: ShapeId) -> u8 { @@ -195,10 +208,10 @@ mod tests { assert_eq!(forge.shape_prop_count(s3), 3); - assert_eq!(forge.lookup_offset(s3, 1_000_030), Some(2)); - assert_eq!(forge.lookup_offset(s3, 1_000_020), Some(1)); - assert_eq!(forge.lookup_offset(s3, 1_000_010), Some(0)); - assert_eq!(forge.lookup_offset(s3, 99), None); + assert_eq!(forge.lookup_position(s3, 1_000_030), Some(2)); + assert_eq!(forge.lookup_position(s3, 1_000_020), Some(1)); + assert_eq!(forge.lookup_position(s3, 1_000_010), Some(0)); + assert_eq!(forge.lookup_position(s3, 99), None); } #[test] @@ -223,8 +236,8 @@ mod tests { let s4 = forge.make_shape(s3, 1_000_100); assert_ne!(s1, s3); assert_ne!(s2, s4); - assert_eq!(forge.lookup_offset(s2, 1_000_070), Some(0)); - assert_eq!(forge.lookup_offset(s4, 1_000_090), Some(0)); + assert_eq!(forge.lookup_position(s2, 1_000_070), Some(0)); + assert_eq!(forge.lookup_position(s4, 1_000_090), Some(0)); } #[test] @@ -252,4 +265,14 @@ mod tests { assert_eq!(id1, id2); assert!(id1 > EMPTY_SHAPE_ID); } + + #[test] + fn has_property_works() { + let forge = ShapeForge::new(); + let s1 = forge.make_shape(EMPTY_SHAPE_ID, 1_000_300); + let s2 = forge.make_shape(s1, 1_000_310); + assert!(forge.has_property(s2, 1_000_300)); + assert!(forge.has_property(s2, 1_000_310)); + assert!(!forge.has_property(s2, 99)); + } } diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index ed6d986..cdf3a42 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -1,3 +1,5 @@ +#![allow(clippy::arc_with_non_send_sync)] + use oxide_compiler::compiler::Compiler; use oxide_kernel::kernel::{KernelConfig, OxideKernel}; use oxide_vm::vm::Vm; @@ -255,8 +257,7 @@ fn run_test_inner( match vm.run(&module) { Ok(result) => { let dur = start.elapsed().as_millis() as u64; - if meta.negative.is_some() { - let neg = meta.negative.as_ref().unwrap(); + if let Some(neg) = meta.negative.as_ref() { return TestResult::fail( path.to_path_buf(), dur, @@ -267,8 +268,7 @@ fn run_test_inner( } Err(e) => { let dur = start.elapsed().as_millis() as u64; - if meta.negative.is_some() { - let neg = meta.negative.as_ref().unwrap(); + if let Some(neg) = meta.negative.as_ref() { if e.contains("TypeError") && neg.error_type == "TypeError" { return TestResult::pass(path.to_path_buf(), dur, format!("expected: {e}")); } @@ -303,7 +303,7 @@ fn discover_tests(test262_root: &Path) -> Vec { WalkDir::new(test262_root) .into_iter() .filter_map(|e| e.ok()) - .filter(|e| e.path().extension().map_or(false, |ext| ext == "js")) + .filter(|e| e.path().extension().is_some_and(|ext| ext == "js")) .map(|e| e.path().to_path_buf()) .collect() } diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 1249aa3..8331b79 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -2,16 +2,14 @@ use crate::value::JsValue; pub type ShapeId = u32; -/// Layout (72B): +/// Layout (40B): /// header: u32 bits /// [0:23] shape_id -/// [24:28] prop_count (5 bits, 0-31) /// [29] is_array /// [30] is_extensible /// [31] is_function /// native_arg_count: u8 (1 byte + 3 pad) -/// inline[0..=3]: 4 x JsValue (32 bytes) -/// overflow: *mut JsValue (8 bytes, null if <=4 properties) +/// hash_props: *mut u8 (8 bytes, points to Box>>) /// proto: JsValue (8 bytes) /// generation: u32 (4 bytes + 4 pad) /// native_fn: u64 (8 bytes, 0 = None sentinel) @@ -19,10 +17,11 @@ pub type ShapeId = u32; pub struct JsObject { header: u32, native_arg_count: u8, - inline: [JsValue; 4], - overflow: *mut JsValue, + _pad: [u8; 3], + hash_props: *mut u8, proto: JsValue, generation: u32, + _pad2: [u8; 4], native_fn: u64, } @@ -31,10 +30,11 @@ impl JsObject { Self { header: (shape_id & 0x00FF_FFFF) | (1 << 30), native_arg_count: 0, - inline: [JsValue::undefined(); 4], - overflow: std::ptr::null_mut(), + _pad: [0; 3], + hash_props: std::ptr::null_mut(), proto, generation: 1, + _pad2: [0; 4], native_fn: 0, } } @@ -43,22 +43,20 @@ impl JsObject { shape_id: ShapeId, proto: JsValue, n_elements: usize, - bump: &bumpalo::Bump, + _bump: &bumpalo::Bump, ) -> Self { let mut obj = Self { header: (shape_id & 0x00FF_FFFF) | (1 << 30) | (1 << 29), native_arg_count: 0, - inline: [JsValue::undefined(); 4], - overflow: std::ptr::null_mut(), + _pad: [0; 3], + hash_props: std::ptr::null_mut(), proto, generation: 1, + _pad2: [0; 4], native_fn: 0, }; - let count = (n_elements as u8).min(31); - obj.set_prop_count(count); - if n_elements > 4 { - obj.alloc_overflow(bump, n_elements); - } + let vec = Box::new(vec![Box::new(JsValue::undefined()); n_elements]); + obj.hash_props = Box::into_raw(vec) as *mut u8; obj } @@ -70,13 +68,28 @@ impl JsObject { self.header = (self.header & !0x00FF_FFFF) | (id & 0x00FF_FFFF); } + /// Returns property count from the hash_props vec length. + /// Returns 0 if hash_props has not been allocated. pub fn prop_count(&self) -> u8 { - ((self.header >> 24) & 0x1F) as u8 + if self.hash_props.is_null() { + 0 + } else { + let vec = unsafe { &*(self.hash_props as *const Vec>) }; + vec.len() as u8 + } } + /// Sets the length of hash_props vec. Truncates or extends with undefined. pub fn set_prop_count(&mut self, count: u8) { - let count = count.min(31); - self.header = (self.header & !(0x1F << 24)) | ((count as u32) << 24); + let vec = self.ensure_hash_props(); + let target = count as usize; + if target < vec.len() { + vec.truncate(target); + } else { + while vec.len() < target { + vec.push(Box::new(JsValue::undefined())); + } + } } pub fn is_array(&self) -> bool { @@ -107,56 +120,77 @@ impl JsObject { } } - pub fn get_inline_prop(&self, offset: u8) -> JsValue { - debug_assert!(offset < 4, "inline offset out of range"); - self.inline[offset as usize] + /// Initialize hash_props if null, return mutable reference to Vec. + pub fn ensure_hash_props(&mut self) -> &mut Vec> { + if self.hash_props.is_null() { + let vec = Box::new(Vec::>::new()); + self.hash_props = Box::into_raw(vec) as *mut u8; + } + unsafe { &mut *(self.hash_props as *mut Vec>) } } - 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; + /// Safe read access to hash_props vec. Returns None if not allocated. + pub fn hash_props_vec(&self) -> Option<&Vec>> { + if self.hash_props.is_null() { + None + } else { + unsafe { Some(&*(self.hash_props as *const Vec>)) } + } } - pub fn get_overflow_prop(&self, offset: u8) -> JsValue { - debug_assert!(offset >= 4); - if self.overflow.is_null() { + /// Get property value at position index in the vec. + /// Returns JsValue::undefined() if hash_props not allocated or position out of bounds. + pub fn get_prop_at(&self, position: u8) -> JsValue { + if self.hash_props.is_null() { return JsValue::undefined(); } - unsafe { *self.overflow.add((offset - 4) as usize) } + let vec = unsafe { &*(self.hash_props as *const Vec>) }; + vec.get(position as usize) + .map(|b| **b) + .unwrap_or(JsValue::undefined()) } - 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) + /// Set property value at position index. Vec auto-grows if needed. + pub fn set_prop_at(&mut self, position: u8, val: JsValue) { + let vec = self.ensure_hash_props(); + let pos = position as usize; + if pos < vec.len() { + *vec[pos] = val; } else { - self.get_overflow_prop(offset) + while vec.len() < pos { + vec.push(Box::new(JsValue::undefined())); + } + vec.push(Box::new(val)); } } - pub fn set_prop(&mut self, offset: u8, val: JsValue) { - if offset < 4 { - self.set_inline_prop(offset, val); + /// Push a value onto hash_props vec. Returns the index position. + pub fn push_prop(&mut self, val: JsValue) -> u8 { + let vec = self.ensure_hash_props(); + let pos = vec.len(); + vec.push(Box::new(val)); + pos as u8 + } + + /// Get a stable pointer to the Box at position. + /// Returns None if hash_props not allocated or position out of bounds. + pub fn prop_ptr_at(&self, position: u8) -> Option<*const JsValue> { + if self.hash_props.is_null() { + return None; + } + let vec = unsafe { &*(self.hash_props as *const Vec>) }; + vec.get(position as usize).map(|b| &**b as *const JsValue) + } + + /// Get the length of hash_props vec (returns 0 if not allocated). + pub fn prop_vec_len(&self) -> usize { + if self.hash_props.is_null() { + 0 } else { - self.set_overflow_prop(offset, val); + unsafe { &*(self.hash_props as *const Vec>) }.len() } } - pub fn set_prop_expand(&mut self, offset: u8, val: JsValue, bump: &bumpalo::Bump) { - if offset >= 4 { - let needed = (offset as usize) + 1; - self.alloc_overflow(bump, needed.max(8)); - } - self.set_prop(offset, val); - } - pub fn proto(&self) -> JsValue { self.proto } @@ -212,40 +246,6 @@ impl JsObject { pub fn set_native_arg_count(&mut self, n: u8) { self.native_arg_count = n; } - - 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::(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 - } - - pub fn alloc_overflow_heap(&mut self, new_count: usize) -> *mut JsValue { - debug_assert!(new_count > 4); - debug_assert!(new_count <= 32, "overflow limited to 32 slots max"); - let n = new_count - 4; - let v: Vec = vec![JsValue::undefined(); n]; - let ptr = Box::into_raw(v.into_boxed_slice()) as *mut JsValue; - self.overflow = ptr; - ptr - } - - pub fn set_prop_expand_heap(&mut self, offset: u8, val: JsValue) { - if offset >= 4 && self.overflow.is_null() { - self.alloc_overflow_heap(32); - } - self.set_prop(offset, val); - } } #[cfg(test)] @@ -254,11 +254,11 @@ mod tests { use crate::shape::EMPTY_SHAPE_ID; #[test] - fn object_size_72_bytes() { - assert_eq!( - std::mem::size_of::(), - 72, - "JsObject layout mismatch - expected 72B" + fn object_size_bounds() { + let sz = std::mem::size_of::(); + assert!( + sz >= 32 && sz <= 64, + "JsObject layout mismatch - expected 32-64B, got {sz}B" ); } @@ -271,6 +271,7 @@ mod tests { assert!(!obj.is_array()); assert!(!obj.is_function()); assert_eq!(obj.generation(), 1); + assert!(obj.hash_props_vec().is_none()); } #[test] @@ -283,8 +284,9 @@ mod tests { #[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); + assert_eq!(obj.prop_count(), 0); + obj.ensure_hash_props().push(Box::new(JsValue::int(17))); + assert_eq!(obj.prop_count(), 1); } #[test] @@ -296,10 +298,10 @@ mod tests { } #[test] - fn inline_prop_read_write() { + fn hash_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)); + obj.set_prop_at(0, JsValue::int(42)); + assert_eq!(obj.get_prop_at(0), JsValue::int(42)); } #[test] @@ -308,6 +310,7 @@ mod tests { let obj = JsObject::new_array(5, JsValue::null(), 3, &bump); assert!(obj.is_array()); assert_eq!(obj.shape_id(), 5); + assert_eq!(obj.prop_count(), 3); } #[test] @@ -317,4 +320,27 @@ mod tests { obj.bump_generation(); assert_eq!(obj.generation(), 2); } + + #[test] + fn hash_props_lazy_init() { + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); + assert!(obj.hash_props_vec().is_none()); + assert_eq!(obj.prop_count(), 0); + obj.set_prop_at(0, JsValue::int(1)); + assert!(obj.hash_props_vec().is_some()); + assert_eq!(obj.prop_count(), 1); + } + + #[test] + fn hash_props_stable_pointer() { + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); + obj.set_prop_at(0, JsValue::int(100)); + let ptr = obj.prop_ptr_at(0); + obj.set_prop_at(1, JsValue::int(200)); + // Pointer to first element should be stable (per Box allocation) + let ptr2 = obj.prop_ptr_at(0); + assert_eq!(ptr, ptr2); + assert_eq!(obj.get_prop_at(0), JsValue::int(100)); + assert_eq!(obj.get_prop_at(1), JsValue::int(200)); + } } diff --git a/crates/oxide_types/src/shape.rs b/crates/oxide_types/src/shape.rs index 237df4b..668ba2a 100644 --- a/crates/oxide_types/src/shape.rs +++ b/crates/oxide_types/src/shape.rs @@ -12,7 +12,6 @@ const EMPTY_SENTINEL: StringIndex = u32::MAX; pub struct Shape { pub id: ShapeId, pub property_name: StringIndex, - pub property_offset: u8, pub parent: Option, } @@ -37,7 +36,6 @@ impl ShapeStore { let empty = Arc::new(Shape { id: EMPTY_SHAPE_ID, property_name: EMPTY_SENTINEL, - property_offset: 0, parent: None, }); debug_assert_eq!(self.shapes.len(), 0); @@ -55,14 +53,12 @@ impl ShapeStore { return existing; } - let prop_offset = self.compute_offset(parent_id); let new_id = self.next_id; self.next_id += 1; let shape = Arc::new(Shape { id: new_id, property_name: prop_name, - property_offset: prop_offset, parent: Some(parent_id), }); @@ -75,32 +71,31 @@ impl ShapeStore { new_id } - fn compute_offset(&self, shape_id: ShapeId) -> u8 { - let mut count = 0u8; + fn lookup_position(&self, shape_id: ShapeId, prop_name: StringIndex) -> Option { + let mut depth: u8 = 0; let mut cursor = Some(shape_id); while let Some(id) = cursor { match self.get_shape(id) { Some(s) => { - if s.property_name != EMPTY_SENTINEL { - count += 1; - } cursor = s.parent; + if s.property_name != EMPTY_SENTINEL { + depth += 1; + } } None => break, } } - count.min(31) - } - - fn lookup_offset(&self, shape_id: ShapeId, prop_name: StringIndex) -> Option { - let mut cursor = Some(shape_id); + let total_depth = depth; + let mut step: u8 = 0; + cursor = Some(shape_id); while let Some(id) = cursor { match self.get_shape(id) { Some(s) => { if s.property_name == prop_name && s.property_name != EMPTY_SENTINEL { - return Some(s.property_offset); + return Some(total_depth - step - 1); } cursor = s.parent; + step += 1; } None => return None, } @@ -108,6 +103,22 @@ impl ShapeStore { None } + fn has_property(&self, shape_id: ShapeId, prop_name: StringIndex) -> bool { + let mut cursor = Some(shape_id); + while let Some(id) = cursor { + match self.get_shape(id) { + Some(s) => { + if s.property_name == prop_name && s.property_name != EMPTY_SENTINEL { + return true; + } + cursor = s.parent; + } + None => return false, + } + } + false + } + fn shape_prop_count(&self, shape_id: ShapeId) -> u8 { let mut count = 0u8; let mut cursor = Some(shape_id); @@ -140,8 +151,12 @@ pub fn make_shape(parent_id: ShapeId, prop_name: StringIndex) -> ShapeId { store().lock().unwrap().make_shape(parent_id, prop_name) } -pub fn lookup_offset(shape_id: ShapeId, prop_name: StringIndex) -> Option { - store().lock().unwrap().lookup_offset(shape_id, prop_name) +pub fn lookup_position(shape_id: ShapeId, prop_name: StringIndex) -> Option { + store().lock().unwrap().lookup_position(shape_id, prop_name) +} + +pub fn has_property(shape_id: ShapeId, prop_name: StringIndex) -> bool { + store().lock().unwrap().has_property(shape_id, prop_name) } pub fn shape_prop_count(shape_id: ShapeId) -> u8 { @@ -198,10 +213,10 @@ mod tests { assert_eq!(shape_prop_count(s3), 3); - assert_eq!(lookup_offset(s3, 1_000_030), Some(2)); - assert_eq!(lookup_offset(s3, 1_000_020), Some(1)); - assert_eq!(lookup_offset(s3, 1_000_010), Some(0)); - assert_eq!(lookup_offset(s3, 99), None); + assert_eq!(lookup_position(s3, 1_000_030), Some(2)); + assert_eq!(lookup_position(s3, 1_000_020), Some(1)); + assert_eq!(lookup_position(s3, 1_000_010), Some(0)); + assert_eq!(lookup_position(s3, 99), None); } #[test] @@ -224,7 +239,16 @@ mod tests { let s4 = make_shape(s3, 1_000_100); assert_ne!(s1, s3); assert_ne!(s2, s4); - assert_eq!(lookup_offset(s2, 1_000_070), Some(0)); - assert_eq!(lookup_offset(s4, 1_000_090), Some(0)); + assert_eq!(lookup_position(s2, 1_000_070), Some(0)); + assert_eq!(lookup_position(s4, 1_000_090), Some(0)); + } + + #[test] + fn has_property_works() { + let s1 = make_shape(EMPTY_SHAPE_ID, 1_000_110); + let s2 = make_shape(s1, 1_000_120); + assert!(has_property(s2, 1_000_110)); + assert!(has_property(s2, 1_000_120)); + assert!(!has_property(s2, 99)); } } diff --git a/crates/oxide_vm/src/builtins/array.rs b/crates/oxide_vm/src/builtins/array.rs index 1c91cff..33d4e2d 100644 --- a/crates/oxide_vm/src/builtins/array.rs +++ b/crates/oxide_vm/src/builtins/array.rs @@ -100,7 +100,7 @@ pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { )); for i in 0..n_elems { unsafe { - (*arr).set_prop(i as u8, vm.reg(args[1 + i])); + (*arr).set_prop_at(i as u8, vm.reg(args[1 + i])); } } unsafe { @@ -113,10 +113,9 @@ pub fn array_push(vm: &mut Vm, args: &[u8]) -> NativeResult { let arr_ptr = get_this_array_ref(vm.reg(args[0]))?; let arr = unsafe { &mut *arr_ptr }; let mut len = arr.prop_count() as usize; - let bump = vm.epoch().bump(); for &arg_reg in args.iter().skip(1) { let val = vm.reg(arg_reg); - arr.set_prop_expand(len as u8, val, bump); + arr.set_prop_at(len as u8, val); len += 1; } let new_len = (len as u8).min(31); @@ -131,7 +130,7 @@ pub fn array_pop(vm: &mut Vm, args: &[u8]) -> NativeResult { if len == 0 { return Ok(JsValue::undefined()); } - let last = arr.get_prop(len - 1); + let last = arr.get_prop_at(len - 1); arr.set_prop_count(len - 1); Ok(last) } @@ -157,7 +156,7 @@ pub fn array_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { let new_arr = create_new_array(vm, count); unsafe { for i in 0..count { - (*new_arr).set_prop(i as u8, arr.get_prop((start + i) as u8)); + (*new_arr).set_prop_at(i as u8, arr.get_prop_at((start + i) as u8)); } (*new_arr).set_prop_count(count.min(31) as u8); } @@ -192,26 +191,25 @@ pub fn array_splice(vm: &mut Vm, args: &[u8]) -> NativeResult { let mut removed: Vec = Vec::new(); for i in 0..delete_count { - removed.push(arr.get_prop((start + i) as u8)); + removed.push(arr.get_prop_at((start + i) as u8)); } - let bump = vm.epoch().bump(); if insert_count > delete_count { let shift = insert_count - delete_count; for i in (start + delete_count..n).rev() { - let val = arr.get_prop(i as u8); - arr.set_prop_expand((i + shift) as u8, val, bump); + let val = arr.get_prop_at(i as u8); + arr.set_prop_at((i + shift) as u8, val); } } else if insert_count < delete_count { let shift = delete_count - insert_count; for i in start + delete_count..n { - let val = arr.get_prop(i as u8); - arr.set_prop((i - shift) as u8, val); + let val = arr.get_prop_at(i as u8); + arr.set_prop_at((i - shift) as u8, val); } } for i in 0..insert_count { - arr.set_prop((start + i) as u8, vm.reg(args[3 + i])); + arr.set_prop_at((start + i) as u8, vm.reg(args[3 + i])); } let new_len = n + insert_count - delete_count; @@ -220,7 +218,7 @@ pub fn array_splice(vm: &mut Vm, args: &[u8]) -> NativeResult { let removed_arr = create_new_array(vm, removed.len()); unsafe { for (i, val) in removed.iter().enumerate() { - (*removed_arr).set_prop(i as u8, *val); + (*removed_arr).set_prop_at(i as u8, *val); } (*removed_arr).set_prop_count(removed.len().min(31) as u8); } @@ -233,7 +231,7 @@ pub fn array_concat(vm: &mut Vm, args: &[u8]) -> NativeResult { let n = arr.prop_count() as usize; let mut all: Vec = Vec::new(); for i in 0..n { - all.push(arr.get_prop(i as u8)); + all.push(arr.get_prop_at(i as u8)); } for &arg_reg in args.iter().skip(1) { let val = vm.reg(arg_reg); @@ -244,7 +242,7 @@ pub fn array_concat(vm: &mut Vm, args: &[u8]) -> NativeResult { if o.is_array() { let on = o.prop_count() as usize; for i in 0..on { - all.push(o.get_prop(i as u8)); + all.push(o.get_prop_at(i as u8)); } continue; } @@ -255,7 +253,7 @@ pub fn array_concat(vm: &mut Vm, args: &[u8]) -> NativeResult { let new_arr = create_new_array(vm, all.len()); unsafe { for (i, val) in all.iter().enumerate() { - (*new_arr).set_prop(i as u8, *val); + (*new_arr).set_prop_at(i as u8, *val); } (*new_arr).set_prop_count(all.len().min(31) as u8); } @@ -274,7 +272,7 @@ pub fn array_join(vm: &mut Vm, args: &[u8]) -> NativeResult { let sf = vm.kernel().string_forge().as_ref(); let parts: Vec = (0..n) .map(|i| { - let v = arr.get_prop(i as u8); + let v = arr.get_prop_at(i as u8); if v.is_undefined() || v.is_null() { String::new() } else { @@ -296,7 +294,7 @@ pub fn array_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { } let target = vm.reg(args[1]); for i in 0..n { - if coercion::strict_equality(arr.get_prop(i as u8), target) { + if coercion::strict_equality(arr.get_prop_at(i as u8), target) { return Ok(JsValue::int(i as i32)); } } @@ -312,7 +310,7 @@ pub fn array_includes(vm: &mut Vm, args: &[u8]) -> NativeResult { } let target = vm.reg(args[1]); for i in 0..n { - let elem = arr.get_prop(i as u8); + let elem = arr.get_prop_at(i as u8); if elem.is_double() && target.is_double() { let ea = elem.as_double(); let ta = target.as_double(); @@ -334,9 +332,9 @@ pub fn array_reverse(vm: &mut Vm, args: &[u8]) -> NativeResult { let mut i = 0; let mut j = n.saturating_sub(1); while i < j { - let tmp = arr.get_prop(i as u8); - arr.set_prop(i as u8, arr.get_prop(j as u8)); - arr.set_prop(j as u8, tmp); + let tmp = arr.get_prop_at(i as u8); + arr.set_prop_at(i as u8, arr.get_prop_at(j as u8)); + arr.set_prop_at(j as u8, tmp); i += 1; j = j.saturating_sub(1); } @@ -363,7 +361,7 @@ pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult { let o = unsafe { &*ptr }; if o.is_array() { let on = o.prop_count() as usize; - let sub: Vec = (0..on).map(|i| o.get_prop(i as u8)).collect(); + let sub: Vec = (0..on).map(|i| o.get_prop_at(i as u8)).collect(); let flat = flatten(&sub, remaining_depth - 1); out.extend(flat); continue; @@ -375,12 +373,12 @@ pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult { out } - let all: Vec = (0..n).map(|i| arr.get_prop(i as u8)).collect(); + let all: Vec = (0..n).map(|i| arr.get_prop_at(i as u8)).collect(); let flat = flatten(&all, depth); let new_arr = create_new_array(vm, flat.len()); unsafe { for (i, val) in flat.iter().enumerate() { - (*new_arr).set_prop(i as u8, *val); + (*new_arr).set_prop_at(i as u8, *val); } (*new_arr).set_prop_count(flat.len().min(31) as u8); } @@ -403,7 +401,7 @@ pub fn array_for_each(vm: &mut Vm, args: &[u8]) -> NativeResult { JsValue::undefined() }; for i in 0..n { - let elem = unsafe { (*arr_ptr).get_prop(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; let _ = invoke_native_callback( vm, callback_val, @@ -431,7 +429,7 @@ pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult { }; let new_arr = create_new_array(vm, n); for i in 0..n { - let elem = unsafe { (*arr_ptr).get_prop(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; match invoke_native_callback( vm, callback_val, @@ -439,7 +437,7 @@ pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult { &[elem, JsValue::int(i as i32), vm.reg(args[0])], ) { Ok(mapped) => unsafe { - (*new_arr).set_prop(i as u8, mapped); + (*new_arr).set_prop_at(i as u8, mapped); }, Err(_) => return Err(JsValue::undefined()), } @@ -467,7 +465,7 @@ pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult { }; let mut kept: Vec = Vec::new(); for i in 0..n { - let elem = unsafe { (*arr_ptr).get_prop(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; match invoke_native_callback( vm, callback_val, @@ -486,7 +484,7 @@ pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult { let new_arr = create_new_array(vm, kept.len()); unsafe { for (i, val) in kept.iter().enumerate() { - (*new_arr).set_prop(i as u8, *val); + (*new_arr).set_prop_at(i as u8, *val); } (*new_arr).set_prop_count(kept.len().min(31) as u8); } @@ -513,12 +511,12 @@ pub fn array_reduce(vm: &mut Vm, args: &[u8]) -> NativeResult { accumulator = vm.reg(args[2]); start_idx = 0; } else { - accumulator = unsafe { (*arr_ptr).get_prop(0) }; + accumulator = unsafe { (*arr_ptr).get_prop_at(0) }; start_idx = 1; } let this_val = JsValue::undefined(); for i in start_idx..n { - let elem = unsafe { (*arr_ptr).get_prop(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; match invoke_native_callback( vm, callback_val, @@ -548,7 +546,7 @@ pub fn array_find(vm: &mut Vm, args: &[u8]) -> NativeResult { JsValue::undefined() }; for i in 0..n { - let elem = unsafe { (*arr_ptr).get_prop(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; match invoke_native_callback( vm, callback_val, @@ -583,7 +581,7 @@ pub fn array_some(vm: &mut Vm, args: &[u8]) -> NativeResult { JsValue::undefined() }; for i in 0..n { - let elem = unsafe { (*arr_ptr).get_prop(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; match invoke_native_callback( vm, callback_val, @@ -618,7 +616,7 @@ pub fn array_every(vm: &mut Vm, args: &[u8]) -> NativeResult { JsValue::undefined() }; for i in 0..n { - let elem = unsafe { (*arr_ptr).get_prop(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; match invoke_native_callback( vm, callback_val, @@ -654,7 +652,7 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult { }; let mut flat: Vec = Vec::new(); for i in 0..n { - let elem = unsafe { (*arr_ptr).get_prop(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; match invoke_native_callback( vm, callback_val, @@ -669,7 +667,7 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult { if r.is_array() { let rn = r.prop_count() as usize; for j in 0..rn { - flat.push(r.get_prop(j as u8)); + flat.push(r.get_prop_at(j as u8)); } continue; } @@ -683,7 +681,7 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult { let new_arr = create_new_array(vm, flat.len()); unsafe { for (i, val) in flat.iter().enumerate() { - (*new_arr).set_prop(i as u8, *val); + (*new_arr).set_prop_at(i as u8, *val); } (*new_arr).set_prop_count(flat.len().min(31) as u8); } diff --git a/crates/oxide_vm/src/builtins/error.rs b/crates/oxide_vm/src/builtins/error.rs index 30803e4..817d48d 100644 --- a/crates/oxide_vm/src/builtins/error.rs +++ b/crates/oxide_vm/src/builtins/error.rs @@ -28,8 +28,7 @@ fn create_error_object( let msg_val = vm.intern(message); unsafe { (*obj).set_shape_id(sh_message); - (*obj).set_prop_count(1); - (*obj).set_inline_prop(0, msg_val); + (*obj).push_prop(msg_val); } let si_name = sf.intern("name").0; @@ -37,8 +36,7 @@ fn create_error_object( let name_val = vm.intern(name); unsafe { (*obj).set_shape_id(sh_name); - (*obj).set_prop_count(2); - (*obj).set_inline_prop(1, name_val); + (*obj).push_prop(name_val); } JsValue::from_js_object(obj) diff --git a/crates/oxide_vm/src/builtins/function.rs b/crates/oxide_vm/src/builtins/function.rs index 8b8e61e..fb4d081 100644 --- a/crates/oxide_vm/src/builtins/function.rs +++ b/crates/oxide_vm/src/builtins/function.rs @@ -40,8 +40,14 @@ fn invoke_target( fn bind_dispatcher(vm: &mut Vm, args: &[u8]) -> NativeResult { let wrapper_val = vm.reg(254); let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() }; - let bound_target = wrapper.get_prop(0); - let bound_this = wrapper.get_prop(1); + let bound_target = wrapper + .hash_props_vec() + .and_then(|v| v.first().map(|b| **b)) + .unwrap_or(JsValue::undefined()); + let bound_this = wrapper + .hash_props_vec() + .and_then(|v| v.get(1).map(|b| **b)) + .unwrap_or(JsValue::undefined()); let n = args.len().saturating_sub(1); let base = 230u8; @@ -101,11 +107,14 @@ pub fn function_apply(vm: &mut Vm, args: &[u8]) -> NativeResult { if !arr_ptr.is_null() { let arr = unsafe { &*arr_ptr }; if arr.is_array() { - let n = arr.prop_count() as usize; + let n = arr.hash_props_vec().map_or(0, |v| v.len()); let base = 200u8; arg_regs = (0..n).map(|i| base + i as u8).collect(); for i in 0..n { - vm.set_reg(base + i as u8, arr.get_prop(i as u8)); + 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); + } } } else { arg_regs = Vec::new(); @@ -143,9 +152,8 @@ pub fn function_bind(vm: &mut Vm, args: &[u8]) -> NativeResult { (*wrapper).set_function(true); (*wrapper).set_native_fn(Some(bind_dispatcher as *const ())); (*wrapper).set_native_arg_count(0); - (*wrapper).set_prop_count(2); - (*wrapper).set_inline_prop(0, target_val); - (*wrapper).set_inline_prop(1, bound_this); + (*wrapper).ensure_hash_props().push(Box::new(target_val)); + (*wrapper).ensure_hash_props().push(Box::new(bound_this)); } Ok(JsValue::from_js_object(wrapper)) diff --git a/crates/oxide_vm/src/builtins/object.rs b/crates/oxide_vm/src/builtins/object.rs index a8c8b0e..f600ae8 100644 --- a/crates/oxide_vm/src/builtins/object.rs +++ b/crates/oxide_vm/src/builtins/object.rs @@ -9,18 +9,31 @@ use crate::vm::Vm; fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u8)> { let mut keys: Vec<(u32, u8)> = Vec::new(); - let mut shape_id = obj.shape_id(); - while shape_id != EMPTY_SHAPE_ID { - if let Some(shape) = vm.kernel().shape_forge().get_shape(shape_id) { - if shape.property_name != 0 { - keys.push((shape.property_name, shape.property_offset)); + let shape_id = obj.shape_id(); + let mut pos: u8 = 0; + let mut shape_ids = Vec::new(); + let mut cursor = Some(shape_id); + while let Some(id) = cursor { + if id == EMPTY_SHAPE_ID { + break; + } + if let Some(shape) = vm.kernel().shape_forge().get_shape(id) { + cursor = shape.parent; + if shape.property_name != u32::MAX { + shape_ids.push(id); } - shape_id = shape.parent.unwrap_or(EMPTY_SHAPE_ID); } else { break; } } - keys.reverse(); + for id in shape_ids.iter().rev() { + if let Some(shape) = vm.kernel().shape_forge().get_shape(*id) { + if shape.property_name != 0 { + keys.push((shape.property_name, pos)); + } + } + pos += 1; + } keys } @@ -62,15 +75,12 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { n, vm.epoch().bump(), )); - for (i, k) in key_names.iter().take(n).enumerate() { + for k in key_names.iter().take(n) { let str_val = vm.intern(k); unsafe { - (*arr).set_prop(i as u8, str_val); + (*arr).ensure_hash_props().push(Box::new(str_val)); } } - unsafe { - (*arr).set_prop_count(n as u8); - } Ok(JsValue::from_js_object(arr)) } @@ -100,10 +110,8 @@ pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult { if target_ptr.is_null() { return Ok(target_val); } - let mut target_prop_count; { let target = unsafe { &mut *target_ptr }; - target_prop_count = target.prop_count() as usize; for &arg_reg in args.iter().skip(2) { let source_val = vm.reg(arg_reg); if !source_val.is_object() { @@ -116,9 +124,8 @@ pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult { let source = unsafe { &*source_ptr }; let source_keys = walk_own_keys(vm, source); for (_si, offset) in source_keys { - let prop_val = source.get_prop(offset); - target.set_prop_expand(target_prop_count as u8, prop_val, vm.epoch().bump()); - target_prop_count += 1; + let prop_val = source.get_prop_at(offset); + target.push_prop(prop_val); } } } @@ -149,17 +156,16 @@ pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { let si = vm.kernel().string_forge().intern(&prop_name_str).0; let shape_forge = vm.kernel().shape_forge().as_ref(); let new_shape = shape_forge.make_shape(unsafe { (&*obj_ptr).shape_id() }, si); - let new_offset; + let value = { + let desc = unsafe { &*desc_ptr }; + desc.hash_props_vec() + .and_then(|v| v.first().map(|b| **b)) + .unwrap_or(JsValue::undefined()) + }; { let obj = unsafe { &mut *obj_ptr }; - new_offset = obj.prop_count(); obj.set_shape_id(new_shape); - obj.set_prop_count(new_offset + 1); - } - let desc = unsafe { &*desc_ptr }; - let value = desc.get_prop(0); - unsafe { - (&mut *obj_ptr).set_prop_expand(new_offset, value, vm.epoch().bump()); + obj.ensure_hash_props().push(Box::new(value)); } Ok(obj_val) } @@ -186,7 +192,7 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes let mut found = false; for (prop_si, offset) in keys { if prop_si == si { - found_value = obj.get_prop(offset); + found_value = obj.get_prop_at(offset); found = true; break; } @@ -206,7 +212,6 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes JsValue::from_js_object(desc_proto), )); let true_val = JsValue::bool(true); - let b = vm.epoch().bump(); let sf = unsafe { &*sf_ptr }; let sh = unsafe { &*sh_ptr }; @@ -214,26 +219,22 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes let si_val = sf.intern("value").0; let sh_val = sh.make_shape(EMPTY_SHAPE_ID, si_val); d.set_shape_id(sh_val); - d.set_prop_count(1); - d.set_prop_expand(0, found_value, b); + d.ensure_hash_props().push(Box::new(found_value)); let si_wr = sf.intern("writable").0; let sh_wr = sh.make_shape(sh_val, si_wr); d.set_shape_id(sh_wr); - d.set_prop_count(2); - d.set_prop_expand(1, true_val, b); + d.ensure_hash_props().push(Box::new(true_val)); let si_en = sf.intern("enumerable").0; let sh_en = sh.make_shape(sh_wr, si_en); d.set_shape_id(sh_en); - d.set_prop_count(3); - d.set_prop_expand(2, true_val, b); + d.ensure_hash_props().push(Box::new(true_val)); let si_cf = sf.intern("configurable").0; let sh_cf = sh.make_shape(sh_en, si_cf); d.set_shape_id(sh_cf); - d.set_prop_count(4); - d.set_prop_expand(3, true_val, b); + d.ensure_hash_props().push(Box::new(true_val)); Ok(JsValue::from_js_object(desc)) } diff --git a/crates/oxide_vm/src/builtins/string.rs b/crates/oxide_vm/src/builtins/string.rs index 935113e..a1b9d68 100644 --- a/crates/oxide_vm/src/builtins/string.rs +++ b/crates/oxide_vm/src/builtins/string.rs @@ -24,7 +24,7 @@ fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { unsafe { for (i, s) in parts.iter().enumerate().take(n) { let sv = vm.intern(s); - (*arr).set_prop(i as u8, sv); + (*arr).set_prop_at(i as u8, sv); } (*arr).set_prop_count(n as u8); } diff --git a/crates/oxide_vm/src/coercion.rs b/crates/oxide_vm/src/coercion.rs index dd0d6fd..e3aff0b 100644 --- a/crates/oxide_vm/src/coercion.rs +++ b/crates/oxide_vm/src/coercion.rs @@ -284,7 +284,7 @@ pub fn to_object(val: JsValue, vm: &mut Vm) -> Result { .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.set_inline_prop(0, val); + obj_ref.ensure_hash_props().push(Box::new(val)); obj_ref.set_prop_count(1); Ok(obj_val) } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 87c34ec..60c58ea 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -65,10 +65,8 @@ pub fn init_kernel_builtins(kernel: &Arc) { let obj_val = JsValue::from_js_object( kernel.builtin_world().object_constructor.as_ptr() as *mut JsObject ); - let cur_count = global.prop_count(); global.set_shape_id(obj_shape); - global.set_prop_count(cur_count + 1); - global.set_prop_expand_heap(cur_count, obj_val); + global.push_prop(obj_val); let _array_methods = ArrayMethods { push: crate::builtins::array::array_push as *const (), @@ -101,10 +99,8 @@ pub fn init_kernel_builtins(kernel: &Arc) { let arr_shape = kernel.shape_forge().make_shape(global.shape_id(), si_arr); let arr_val = JsValue::from_js_object(kernel.builtin_world().array_constructor.as_ptr() as *mut JsObject); - let cur_count = global.prop_count(); global.set_shape_id(arr_shape); - global.set_prop_count(cur_count + 1); - global.set_prop_expand_heap(cur_count, arr_val); + global.ensure_hash_props().push(Box::new(arr_val)); global.bump_generation(); let error_methods = ErrorMethods { @@ -126,10 +122,8 @@ pub fn init_kernel_builtins(kernel: &Arc) { 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); - let cur_count = global.prop_count(); global.set_shape_id(err_shape); - global.set_prop_count(cur_count + 1); - global.set_prop_expand_heap(cur_count, err_val); + global.ensure_hash_props().push(Box::new(err_val)); global.bump_generation(); let string_methods = StringMethods { @@ -164,10 +158,8 @@ pub fn init_kernel_builtins(kernel: &Arc) { let str_val = JsValue::from_js_object( kernel.builtin_world().string_constructor.as_ptr() as *mut JsObject ); - let cur_count = global.prop_count(); global.set_shape_id(str_shape); - global.set_prop_count(cur_count + 1); - global.set_prop_expand_heap(cur_count, str_val); + global.ensure_hash_props().push(Box::new(str_val)); global.bump_generation(); let number_methods = NumberMethods { @@ -189,10 +181,8 @@ pub fn init_kernel_builtins(kernel: &Arc) { let num_val = JsValue::from_js_object( kernel.builtin_world().number_constructor.as_ptr() as *mut JsObject ); - let cur_count = global.prop_count(); global.set_shape_id(num_shape); - global.set_prop_count(cur_count + 1); - global.set_prop_expand_heap(cur_count, num_val); + global.ensure_hash_props().push(Box::new(num_val)); global.bump_generation(); { @@ -457,20 +447,16 @@ pub fn init_kernel_builtins(kernel: &Arc) { { let si_pi = sf.intern("PI").0; let sh_pi = sh.make_shape(math.shape_id(), si_pi); - let cur = math.prop_count(); math.set_shape_id(sh_pi); - math.set_prop_count(cur + 1); - math.set_prop_expand_heap(cur, pi_val); + math.ensure_hash_props().push(Box::new(pi_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); - let cur_count = global.prop_count(); global.set_shape_id(m_shape); - global.set_prop_count(cur_count + 1); - global.set_prop_expand_heap(cur_count, m_val); + global.ensure_hash_props().push(Box::new(m_val)); global.bump_generation(); let function_methods = FunctionMethods { @@ -586,22 +572,28 @@ impl Vm { } fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option { - if let Some(offset) = self + if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) { - return Some(obj.get_prop(offset)); + let val = obj.get_prop_at(pos); + if !val.is_undefined() || obj.prop_vec_len() > pos as usize { + return Some(val); + } } let mut proto = obj.proto(); while proto.is_object() { let proto_obj = unsafe { &*proto.as_js_object_ptr() }; - if let Some(offset) = self + if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(proto_obj.shape_id(), prop_name_si) + .lookup_position(proto_obj.shape_id(), prop_name_si) { - return Some(proto_obj.get_prop(offset)); + let val = proto_obj.get_prop_at(pos); + if !val.is_undefined() || proto_obj.prop_vec_len() > pos as usize { + return Some(val); + } } proto = proto_obj.proto(); } @@ -614,34 +606,39 @@ impl Vm { prop_name_si: u32, val: JsValue, ) -> Result<(), String> { - if let Some(offset) = self + if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) { - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.set_prop(offset, val); + obj.set_prop_at(pos, val); + // IC write-back: 3 extension words (shape_id + ptr_lo + ptr_hi) + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } } else { - let new_offset = obj.prop_count(); let new_shape_id = self .kernel .shape_forge() .make_shape(obj.shape_id(), prop_name_si); - let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop_expand(new_offset, val, self.epoch.bump()); + let new_pos = obj.push_prop(val); obj.bump_generation(); - self.kernel.prop_forge().upsert( - new_shape_id, - PropTemplate { - shape_id: new_shape_id, - offset: new_offset, - generation: obj.generation(), - }, - ); + if let Some(ptr) = obj.prop_ptr_at(new_pos) { + self.bytecode[self.pc - 3] = new_shape_id & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + self.kernel.prop_forge().upsert( + new_shape_id, + PropTemplate { + shape_id: new_shape_id, + ptr_bits: ptr as u64, + generation: obj.generation(), + }, + ); + } } Ok(()) } @@ -675,21 +672,12 @@ impl Vm { for (name, reg) in &module.builtin_reg_map { let si = self.kernel.string_forge().intern(name.as_str()).0; let global = self.kernel.global_object(); - let mut shape_id = global.shape_id(); - let mut found: Option = None; - while shape_id != EMPTY_SHAPE_ID { - if let Some(shape) = self.kernel.shape_forge().get_shape(shape_id) { - if shape.property_name == si { - found = Some(shape.property_offset); - break; - } - shape_id = shape.parent.unwrap_or(EMPTY_SHAPE_ID); - } else { - break; - } - } - if let Some(offset) = found { - self.regs[*reg as usize] = global.get_prop(offset); + if let Some(pos) = self + .kernel + .shape_forge() + .lookup_position(global.shape_id(), si) + { + self.regs[*reg as usize] = global.get_prop_at(pos); } } @@ -934,7 +922,7 @@ impl Vm { let prop_name_si = self.regs[b].as_string_index(); if let Some(resolved) = self.resolve_property(proto, prop_name_si) { self.regs[a] = resolved; - self.pc += 1; + self.pc += 3; continue; } } @@ -945,7 +933,7 @@ impl Vm { let prop_name_si = self.regs[b].as_string_index(); if let Some(resolved) = self.resolve_property(proto, prop_name_si) { self.regs[a] = resolved; - self.pc += 1; + self.pc += 3; continue; } } @@ -953,28 +941,41 @@ impl Vm { } let obj = unsafe { &*obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; - let cached_offset = ((ext >> 24) & 0xFF) as u8; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - self.regs[a] = obj.get_prop(cached_offset); + if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_ptr != 0 + { + self.regs[a] = unsafe { *(cached_ptr as *const JsValue) }; } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let new_ext = - (obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - self.regs[a] = obj.get_prop(template.offset); + if template.ptr_bits != 0 { + let ptr = template.ptr_bits; + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr >> 32) as u32; + self.regs[a] = unsafe { *(ptr as *const JsValue) }; + } else { + self.regs[a] = self + .resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()); + } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let offset = self + let pos = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) .unwrap_or(0); - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } self.regs[a] = val; } else { self.regs[a] = JsValue::undefined(); @@ -988,43 +989,51 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; - let cached_offset = ((ext >> 24) & 0xFF) as u8; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - obj.set_prop(cached_offset, self.regs[a]); + if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_ptr != 0 + { + unsafe { + *(cached_ptr as *mut JsValue) = self.regs[a]; + } } else { - if let Some(offset) = self + if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) { - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.set_prop(offset, self.regs[a]); + obj.set_prop_at(pos, self.regs[a]); + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } } else { - let new_offset = obj.prop_count(); let new_shape_id = self .kernel .shape_forge() .make_shape(obj.shape_id(), prop_name_si); obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop(new_offset, self.regs[a]); + let new_pos = obj.push_prop(self.regs[a]); obj.bump_generation(); - let new_ext = - (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - self.kernel.prop_forge().upsert( - new_shape_id, - PropTemplate { - shape_id: new_shape_id, - offset: new_offset, - generation: obj.generation(), - }, - ); + if let Some(ptr) = obj.prop_ptr_at(new_pos) { + self.bytecode[self.pc - 3] = new_shape_id & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + self.kernel.prop_forge().upsert( + new_shape_id, + PropTemplate { + shape_id: new_shape_id, + ptr_bits: ptr as u64, + generation: obj.generation(), + }, + ); + } } } } @@ -1047,21 +1056,19 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - if let Some(offset) = self + if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) { - obj.set_prop(offset, self.regs[a]); + obj.set_prop_at(pos, self.regs[a]); } else { - let new_offset = obj.prop_count(); let new_shape_id = self .kernel .shape_forge() .make_shape(obj.shape_id(), prop_name_si); obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop_expand(new_offset, self.regs[a], self.epoch.bump()); + obj.push_prop(self.regs[a]); obj.bump_generation(); } } @@ -1093,21 +1100,19 @@ impl Vm { return Err("SET_PROP_DYNAMIC key not a string".into()); } let prop_name_si = key_val.as_string_index(); - if let Some(offset) = self + if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) { - obj.set_prop(offset, self.regs[b]); + obj.set_prop_at(pos, self.regs[b]); } else { - let new_offset = obj.prop_count(); let new_shape_id = self .kernel .shape_forge() .make_shape(obj.shape_id(), prop_name_si); obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop_expand(new_offset, self.regs[b], self.epoch.bump()); + obj.push_prop(self.regs[b]); obj.bump_generation(); } } @@ -1142,10 +1147,7 @@ impl Vm { } let idx = self.regs[a].as_int() as usize; let obj = unsafe { &mut *obj_ptr }; - obj.set_prop(idx as u8, self.regs[b]); - if idx as u8 >= obj.prop_count() { - obj.set_prop_count(idx as u8 + 1); - } + obj.set_prop_at(idx as u8, self.regs[b]); } OpCode::COMPOUND_ADD => { @@ -1289,28 +1291,42 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; - let cached_offset = ((ext >> 24) & 0xFF) as u8; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - obj.get_prop(cached_offset) + let prop_val = if cached_shape_id != 0 + && cached_shape_id == obj.shape_id() + && cached_ptr != 0 + { + unsafe { *(cached_ptr as *const JsValue) } } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let new_ext = - (obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.get_prop(template.offset) + if template.ptr_bits != 0 { + let ptr = template.ptr_bits; + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr >> 32) as u32; + unsafe { *(ptr as *const JsValue) } + } else { + self.resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()) + } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let offset = self + let pos = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) .unwrap_or(0); - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } val } else { JsValue::undefined() @@ -1319,38 +1335,46 @@ impl Vm { let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref()); let new_val = JsValue::float(n + 1.0); - let offset = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - cached_offset - } else if let Some(off) = self + if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_ptr != 0 + { + unsafe { + *(cached_ptr as *mut JsValue) = new_val; + } + } else if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) { - off + obj.set_prop_at(pos, new_val); + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } } else { - let new_offset = obj.prop_count(); let new_shape_id = self .kernel .shape_forge() .make_shape(obj.shape_id(), prop_name_si); obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop_expand(new_offset, new_val, self.epoch.bump()); + let new_pos = obj.push_prop(new_val); obj.bump_generation(); - let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - self.kernel.prop_forge().upsert( - new_shape_id, - PropTemplate { - shape_id: new_shape_id, - offset: new_offset, - generation: obj.generation(), - }, - ); + if let Some(ptr) = obj.prop_ptr_at(new_pos) { + self.bytecode[self.pc - 3] = new_shape_id & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + self.kernel.prop_forge().upsert( + new_shape_id, + PropTemplate { + shape_id: new_shape_id, + ptr_bits: ptr as u64, + generation: obj.generation(), + }, + ); + } self.regs[a] = new_val; continue; }; - obj.set_prop(offset, new_val); self.regs[a] = new_val; } @@ -1361,28 +1385,42 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; - let cached_offset = ((ext >> 24) & 0xFF) as u8; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - obj.get_prop(cached_offset) + let prop_val = if cached_shape_id != 0 + && cached_shape_id == obj.shape_id() + && cached_ptr != 0 + { + unsafe { *(cached_ptr as *const JsValue) } } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let new_ext = - (obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.get_prop(template.offset) + if template.ptr_bits != 0 { + let ptr = template.ptr_bits; + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr >> 32) as u32; + unsafe { *(ptr as *const JsValue) } + } else { + self.resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()) + } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let offset = self + let pos = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) .unwrap_or(0); - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } val } else { JsValue::undefined() @@ -1391,38 +1429,46 @@ impl Vm { let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref()); let new_val = JsValue::float(n - 1.0); - let offset = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - cached_offset - } else if let Some(off) = self + if cached_shape_id != 0 && cached_shape_id == obj.shape_id() && cached_ptr != 0 + { + unsafe { + *(cached_ptr as *mut JsValue) = new_val; + } + } else if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) { - off + obj.set_prop_at(pos, new_val); + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } } else { - let new_offset = obj.prop_count(); let new_shape_id = self .kernel .shape_forge() .make_shape(obj.shape_id(), prop_name_si); obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop_expand(new_offset, new_val, self.epoch.bump()); + let new_pos = obj.push_prop(new_val); obj.bump_generation(); - let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - self.kernel.prop_forge().upsert( - new_shape_id, - PropTemplate { - shape_id: new_shape_id, - offset: new_offset, - generation: obj.generation(), - }, - ); + if let Some(ptr) = obj.prop_ptr_at(new_pos) { + self.bytecode[self.pc - 3] = new_shape_id & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + self.kernel.prop_forge().upsert( + new_shape_id, + PropTemplate { + shape_id: new_shape_id, + ptr_bits: ptr as u64, + generation: obj.generation(), + }, + ); + } self.regs[a] = new_val; continue; }; - obj.set_prop(offset, new_val); self.regs[a] = new_val; } @@ -1443,21 +1489,19 @@ impl Vm { let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref()); let new_val = JsValue::float(n + 1.0); - if let Some(offset) = self + if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) { - obj.set_prop(offset, new_val); + obj.set_prop_at(pos, new_val); } else { - let new_offset = obj.prop_count(); let new_shape_id = self .kernel .shape_forge() .make_shape(obj.shape_id(), prop_name_si); obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop_expand(new_offset, new_val, self.epoch.bump()); + obj.push_prop(new_val); obj.bump_generation(); }; self.regs[b] = new_val; @@ -1480,21 +1524,19 @@ impl Vm { let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref()); let new_val = JsValue::float(n - 1.0); - if let Some(offset) = self + if let Some(pos) = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) { - obj.set_prop(offset, new_val); + obj.set_prop_at(pos, new_val); } else { - let new_offset = obj.prop_count(); let new_shape_id = self .kernel .shape_forge() .make_shape(obj.shape_id(), prop_name_si); obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop_expand(new_offset, new_val, self.epoch.bump()); + obj.push_prop(new_val); obj.bump_generation(); }; self.regs[b] = new_val; @@ -1507,28 +1549,42 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; - let cached_offset = ((ext >> 24) & 0xFF) as u8; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - obj.get_prop(cached_offset) + let prop_val = if cached_shape_id != 0 + && cached_shape_id == obj.shape_id() + && cached_ptr != 0 + { + unsafe { *(cached_ptr as *const JsValue) } } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let new_ext = - (obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.get_prop(template.offset) + if template.ptr_bits != 0 { + let ptr = template.ptr_bits; + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr >> 32) as u32; + unsafe { *(ptr as *const JsValue) } + } else { + self.resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()) + } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let offset = self + let pos = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) .unwrap_or(0); - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } val } else { JsValue::undefined() @@ -1546,35 +1602,7 @@ impl Vm { JsValue::float(ln + rn) }; - if let Some(offset) = self - .kernel - .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) - { - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.set_prop(offset, new_val); - } else { - let new_offset = obj.prop_count(); - let new_shape_id = self - .kernel - .shape_forge() - .make_shape(obj.shape_id(), prop_name_si); - let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop_expand(new_offset, new_val, self.epoch.bump()); - obj.bump_generation(); - self.kernel.prop_forge().upsert( - new_shape_id, - PropTemplate { - shape_id: new_shape_id, - offset: new_offset, - generation: obj.generation(), - }, - ); - } + self.set_member_prop(obj, prop_name_si, new_val)?; self.regs[a] = new_val; } @@ -1585,28 +1613,42 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; - let cached_offset = ((ext >> 24) & 0xFF) as u8; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - obj.get_prop(cached_offset) + let prop_val = if cached_shape_id != 0 + && cached_shape_id == obj.shape_id() + && cached_ptr != 0 + { + unsafe { *(cached_ptr as *const JsValue) } } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let new_ext = - (obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.get_prop(template.offset) + if template.ptr_bits != 0 { + let ptr = template.ptr_bits; + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr >> 32) as u32; + unsafe { *(ptr as *const JsValue) } + } else { + self.resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()) + } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let offset = self + let pos = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) .unwrap_or(0); - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } val } else { JsValue::undefined() @@ -1616,35 +1658,7 @@ impl Vm { let rn = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref()); let new_val = JsValue::float(ln - rn); self.regs[a] = new_val; - if let Some(offset) = self - .kernel - .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) - { - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.set_prop(offset, new_val); - } else { - let new_offset = obj.prop_count(); - let new_shape_id = self - .kernel - .shape_forge() - .make_shape(obj.shape_id(), prop_name_si); - let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.set_shape_id(new_shape_id); - obj.set_prop_count(new_offset + 1); - obj.set_prop_expand(new_offset, new_val, self.epoch.bump()); - obj.bump_generation(); - self.kernel.prop_forge().upsert( - new_shape_id, - PropTemplate { - shape_id: new_shape_id, - offset: new_offset, - generation: obj.generation(), - }, - ); - } + self.set_member_prop(obj, prop_name_si, new_val)?; } OpCode::COMPOUND_MEMBER_MUL => { @@ -1654,27 +1668,42 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - obj.get_prop(((ext >> 24) & 0xFF) as u8) + let prop_val = if cached_shape_id != 0 + && cached_shape_id == obj.shape_id() + && cached_ptr != 0 + { + unsafe { *(cached_ptr as *const JsValue) } } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let new_ext = - (obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.get_prop(template.offset) + if template.ptr_bits != 0 { + let ptr = template.ptr_bits; + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr >> 32) as u32; + unsafe { *(ptr as *const JsValue) } + } else { + self.resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()) + } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let offset = self + let pos = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) .unwrap_or(0); - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } val } else { JsValue::undefined() @@ -1694,27 +1723,42 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - obj.get_prop(((ext >> 24) & 0xFF) as u8) + let prop_val = if cached_shape_id != 0 + && cached_shape_id == obj.shape_id() + && cached_ptr != 0 + { + unsafe { *(cached_ptr as *const JsValue) } } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let new_ext = - (obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.get_prop(template.offset) + if template.ptr_bits != 0 { + let ptr = template.ptr_bits; + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr >> 32) as u32; + unsafe { *(ptr as *const JsValue) } + } else { + self.resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()) + } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let offset = self + let pos = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) .unwrap_or(0); - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } val } else { JsValue::undefined() @@ -1734,27 +1778,42 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - obj.get_prop(((ext >> 24) & 0xFF) as u8) + let prop_val = if cached_shape_id != 0 + && cached_shape_id == obj.shape_id() + && cached_ptr != 0 + { + unsafe { *(cached_ptr as *const JsValue) } } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let new_ext = - (obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.get_prop(template.offset) + if template.ptr_bits != 0 { + let ptr = template.ptr_bits; + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr >> 32) as u32; + unsafe { *(ptr as *const JsValue) } + } else { + self.resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()) + } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let offset = self + let pos = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) .unwrap_or(0); - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } val } else { JsValue::undefined() @@ -1774,27 +1833,42 @@ impl Vm { } let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.regs[b].as_string_index(); - let ext = self.bytecode[self.pc]; - self.pc += 1; - let cached_shape_id = ext & 0x00FF_FFFF; + let ext0 = self.bytecode[self.pc]; + let ext1 = self.bytecode[self.pc + 1]; + let ext2 = self.bytecode[self.pc + 2]; + self.pc += 3; + let cached_shape_id = ext0 & 0x00FF_FFFF; + let cached_ptr = ((ext2 as u64) << 32) | (ext1 as u64); - let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() { - obj.get_prop(((ext >> 24) & 0xFF) as u8) + let prop_val = if cached_shape_id != 0 + && cached_shape_id == obj.shape_id() + && cached_ptr != 0 + { + unsafe { *(cached_ptr as *const JsValue) } } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let new_ext = - (obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; - obj.get_prop(template.offset) + if template.ptr_bits != 0 { + let ptr = template.ptr_bits; + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr >> 32) as u32; + unsafe { *(ptr as *const JsValue) } + } else { + self.resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()) + } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let offset = self + let pos = self .kernel .shape_forge() - .lookup_offset(obj.shape_id(), prop_name_si) + .lookup_position(obj.shape_id(), prop_name_si) .unwrap_or(0); - let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24); - self.bytecode[self.pc - 1] = new_ext; + if let Some(ptr) = obj.prop_ptr_at(pos) { + self.bytecode[self.pc - 3] = obj.shape_id() & 0x00FF_FFFF; + self.bytecode[self.pc - 2] = ptr as u32; + self.bytecode[self.pc - 1] = (ptr as u64 >> 32) as u32; + } val } else { JsValue::undefined() diff --git a/crates/oxide_vm/tests/builtins_array_tests.rs b/crates/oxide_vm/tests/builtins_array_tests.rs index 7375715..051608d 100644 --- a/crates/oxide_vm/tests/builtins_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_array_tests.rs @@ -17,7 +17,7 @@ fn array_push_adds_element() { let result = eval("var a = [1,2]; a.push(3); a").unwrap(); let obj = unsafe { &*result.as_js_object_ptr() }; assert_eq!(obj.prop_count(), 3); - assert_eq!(obj.get_prop(2), oxide_types::value::JsValue::int(3)); + assert_eq!(obj.get_prop_at(2), oxide_types::value::JsValue::int(3)); } #[test] @@ -50,8 +50,8 @@ fn array_slice_returns_subarray() { let result = eval("[1,2,3,4].slice(1,3)").unwrap(); let obj = unsafe { &*result.as_js_object_ptr() }; assert_eq!(obj.prop_count(), 2); - assert_eq!(obj.get_prop(0), oxide_types::value::JsValue::int(2)); - assert_eq!(obj.get_prop(1), oxide_types::value::JsValue::int(3)); + assert_eq!(obj.get_prop_at(0), oxide_types::value::JsValue::int(2)); + assert_eq!(obj.get_prop_at(1), oxide_types::value::JsValue::int(3)); } #[test] @@ -66,7 +66,7 @@ fn array_concat_combines() { let result = eval("[1,2].concat([3,4])").unwrap(); let obj = unsafe { &*result.as_js_object_ptr() }; assert_eq!(obj.prop_count(), 4); - assert_eq!(obj.get_prop(3), oxide_types::value::JsValue::int(4)); + assert_eq!(obj.get_prop_at(3), oxide_types::value::JsValue::int(4)); } #[test] @@ -110,9 +110,9 @@ fn array_includes_false() { fn array_reverse_mutates() { let result = eval("var a = [1,2,3]; a.reverse(); a").unwrap(); let obj = unsafe { &*result.as_js_object_ptr() }; - assert_eq!(obj.get_prop(0), oxide_types::value::JsValue::int(3)); - assert_eq!(obj.get_prop(1), oxide_types::value::JsValue::int(2)); - assert_eq!(obj.get_prop(2), oxide_types::value::JsValue::int(1)); + assert_eq!(obj.get_prop_at(0), oxide_types::value::JsValue::int(3)); + assert_eq!(obj.get_prop_at(1), oxide_types::value::JsValue::int(2)); + assert_eq!(obj.get_prop_at(2), oxide_types::value::JsValue::int(1)); } #[test] @@ -120,8 +120,8 @@ fn array_splice_remove() { let result = eval("[1,2,3,4].splice(1,2)").unwrap(); let obj = unsafe { &*result.as_js_object_ptr() }; assert_eq!(obj.prop_count(), 2); - assert_eq!(obj.get_prop(0), oxide_types::value::JsValue::int(2)); - assert_eq!(obj.get_prop(1), oxide_types::value::JsValue::int(3)); + assert_eq!(obj.get_prop_at(0), oxide_types::value::JsValue::int(2)); + assert_eq!(obj.get_prop_at(1), oxide_types::value::JsValue::int(3)); } #[test] diff --git a/crates/oxide_vm/tests/object_tests.rs b/crates/oxide_vm/tests/object_tests.rs index 66158c6..6c2eb38 100644 --- a/crates/oxide_vm/tests/object_tests.rs +++ b/crates/oxide_vm/tests/object_tests.rs @@ -30,7 +30,7 @@ fn object_create_and_read() { assert!(obj.is_object()); let obj_ref = unsafe { &*obj.as_js_object_ptr() }; assert_eq!(obj_ref.prop_count(), 1, "object should have 1 property"); - assert!(obj_ref.get_inline_prop(0).is_int()); + assert!(obj_ref.get_prop_at(0).is_int()); } #[test]