From e57e140bef2dd4791f65a79dda9bc55d9936f536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Mon, 8 Jun 2026 22:55:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E9=95=BF=E5=BA=A6=E4=B8=8E=E6=A7=BD=E4=BD=8D=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E6=88=AA=E6=96=AD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_cli/src/main.rs | 4 +- crates/oxide_compiler/src/counter.rs | 15 +-- crates/oxide_kernel/src/prop_forge.rs | 9 +- crates/oxide_kernel/src/shape_forge.rs | 12 +- crates/oxide_types/src/object.rs | 60 +++++++-- crates/oxide_types/src/shape.rs | 14 +- crates/oxide_vm/src/builtins/array.rs | 123 +++++++++--------- crates/oxide_vm/src/builtins/json.rs | 4 +- crates/oxide_vm/src/builtins/object.rs | 43 +++--- crates/oxide_vm/src/builtins/string.rs | 8 +- crates/oxide_vm/src/vm.rs | 66 ++++++---- crates/oxide_vm/tests/builtins_array_tests.rs | 15 +++ .../oxide_vm/tests/builtins_object_tests.rs | 10 +- .../oxide_vm/tests/builtins_regexp_tests.rs | 3 + .../oxide_vm/tests/builtins_string_tests.rs | 4 + crates/oxide_vm/tests/json_tests.rs | 11 +- 16 files changed, 242 insertions(+), 159 deletions(-) diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index 755548d..fabff5f 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -167,7 +167,7 @@ fn format_object(string_forge: &StringForge, shape_forge: &ShapeForge, obj: &JsO break; } } - let mut pos: u8 = 0; + let mut pos: u32 = 0; for id in shape_ids.iter().rev() { if let Some(shape) = shape_forge.get_shape(*id) { if shape.property_name != 0 { @@ -190,7 +190,7 @@ fn format_array(string_forge: &StringForge, shape_forge: &ShapeForge, obj: &JsOb let len = obj.prop_vec_len(); let mut items = Vec::new(); for i in 0..len { - let val = obj.get_prop_at(i as u8); + let val = obj.get_prop_at(i); items.push(format_js_value(string_forge, shape_forge, val)); } format!("[{}]", items.join(", ")) diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs index a43e76e..1b27d24 100644 --- a/crates/oxide_compiler/src/counter.rs +++ b/crates/oxide_compiler/src/counter.rs @@ -318,19 +318,8 @@ impl Compiler { self.count_expression(expr, ctx); } } - // CALL_NATIVE: 1 opcode + 1 ext word; CALL(0x40): 1 opcode - let is_builtin = match &call.callee { - Expression::Identifier(ident) => ctx.is_builtin(ident.name.as_str()), - Expression::StaticMemberExpression(m) => { - if let Expression::Identifier(ident) = &m.object { - ctx.is_builtin(ident.name.as_str()) - } else { - false - } - } - _ => false, - }; - ctx.projected_pc += if is_builtin { 2 } else { 1 }; + // CALL and CALL_NATIVE both carry 1 ext word for arg_count. + ctx.projected_pc += 2; ctx.alloc_reg(); // result register ctx.projected_pc += 1; // LOAD_VAR result <- regs[0] } diff --git a/crates/oxide_kernel/src/prop_forge.rs b/crates/oxide_kernel/src/prop_forge.rs index de65a2a..c3eb96a 100644 --- a/crates/oxide_kernel/src/prop_forge.rs +++ b/crates/oxide_kernel/src/prop_forge.rs @@ -7,7 +7,8 @@ use crate::shape_forge::ShapeId; #[derive(Debug, Clone)] pub struct PropTemplate { pub shape_id: ShapeId, - pub position: u8, + pub prop_name: u32, + pub position: u32, pub generation: u32, } @@ -67,12 +68,14 @@ mod tests { let forge = PropForge::new(); let t = PropTemplate { shape_id: 1, + prop_name: 11, position: 3, generation: 10, }; forge.upsert(1, t); let got = forge.get_template(1).unwrap(); assert_eq!(got.shape_id, 1); + assert_eq!(got.prop_name, 11); assert_eq!(got.position, 3); assert_eq!(got.generation, 10); } @@ -84,6 +87,7 @@ mod tests { 1, PropTemplate { shape_id: 1, + prop_name: 11, position: 3, generation: 10, }, @@ -92,6 +96,7 @@ mod tests { 1, PropTemplate { shape_id: 1, + prop_name: 12, position: 7, generation: 5, }, @@ -107,6 +112,7 @@ mod tests { 1, PropTemplate { shape_id: 1, + prop_name: 11, position: 3, generation: 5, }, @@ -115,6 +121,7 @@ mod tests { 1, PropTemplate { shape_id: 1, + prop_name: 12, position: 7, generation: 10, }, diff --git a/crates/oxide_kernel/src/shape_forge.rs b/crates/oxide_kernel/src/shape_forge.rs index ec80e65..f614fa9 100644 --- a/crates/oxide_kernel/src/shape_forge.rs +++ b/crates/oxide_kernel/src/shape_forge.rs @@ -46,8 +46,8 @@ impl ShapeForge { ((parent_id as u64) << 32) | (prop_name as u64) } - fn compute_depth(shape_id: ShapeId, shapes: &[Option>]) -> u8 { - let mut count = 0u8; + fn compute_depth(shape_id: ShapeId, shapes: &[Option>]) -> u32 { + let mut count = 0u32; let mut cursor = Some(shape_id); while let Some(id) = cursor { match shapes.get((id - 1) as usize).and_then(|s| s.clone()) { @@ -94,10 +94,10 @@ impl ShapeForge { shapes.get((id - 1) as usize).and_then(|s| s.clone()) } - pub fn lookup_position(&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 step: u32 = 0; let mut cursor = Some(shape_id); while let Some(id) = cursor { match shapes.get((id - 1) as usize).and_then(|s| s.clone()) { @@ -131,9 +131,9 @@ impl ShapeForge { false } - pub fn shape_prop_count(&self, shape_id: ShapeId) -> u8 { + pub fn shape_prop_count(&self, shape_id: ShapeId) -> u32 { let shapes = self.shapes.read().unwrap(); - let mut count = 0u8; + let mut count = 0u32; let mut cursor = Some(shape_id); while let Some(id) = cursor { match shapes.get((id - 1) as usize).and_then(|s| s.clone()) { diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 99eb23b..6370b37 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -2,6 +2,41 @@ use crate::value::JsValue; pub type ShapeId = u32; +pub trait PropIndex { + fn to_u32(self) -> u32; +} + +impl PropIndex for u8 { + fn to_u32(self) -> u32 { + self as u32 + } +} + +impl PropIndex for u16 { + fn to_u32(self) -> u32 { + self as u32 + } +} + +impl PropIndex for u32 { + fn to_u32(self) -> u32 { + self + } +} + +impl PropIndex for usize { + fn to_u32(self) -> u32 { + self as u32 + } +} + +impl PropIndex for i32 { + fn to_u32(self) -> u32 { + debug_assert!(self >= 0, "property index must be non-negative"); + self.max(0) as u32 + } +} + /// Layout (56B): /// header: u32 bits /// [0:23] shape_id @@ -82,19 +117,19 @@ impl JsObject { /// Returns property count from the hash_props vec length. /// Returns 0 if hash_props has not been allocated. - pub fn prop_count(&self) -> u8 { + pub fn prop_count(&self) -> u32 { if self.hash_props.is_null() { 0 } else { let vec = unsafe { &*(self.hash_props as *const Vec>) }; - vec.len() as u8 + vec.len() as u32 } } /// Sets the length of hash_props vec. Truncates or extends with undefined. - pub fn set_prop_count(&mut self, count: u8) { + pub fn set_prop_count(&mut self, count: impl PropIndex) { let vec = self.ensure_hash_props(); - let target = count as usize; + let target = count.to_u32() as usize; if target < vec.len() { vec.truncate(target); } else { @@ -152,20 +187,20 @@ impl JsObject { /// 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 { + pub fn get_prop_at(&self, position: impl PropIndex) -> JsValue { if self.hash_props.is_null() { return JsValue::undefined(); } let vec = unsafe { &*(self.hash_props as *const Vec>) }; - vec.get(position as usize) + vec.get(position.to_u32() as usize) .map(|b| **b) .unwrap_or(JsValue::undefined()) } /// Set property value at position index. Vec auto-grows if needed. - pub fn set_prop_at(&mut self, position: u8, val: JsValue) { + pub fn set_prop_at(&mut self, position: impl PropIndex, val: JsValue) { let vec = self.ensure_hash_props(); - let pos = position as usize; + let pos = position.to_u32() as usize; if pos < vec.len() { *vec[pos] = val; } else { @@ -177,21 +212,22 @@ impl JsObject { } /// Push a value onto hash_props vec. Returns the index position. - pub fn push_prop(&mut self, val: JsValue) -> u8 { + pub fn push_prop(&mut self, val: JsValue) -> u32 { let vec = self.ensure_hash_props(); let pos = vec.len(); vec.push(Box::new(val)); - pos as u8 + pos as u32 } /// 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> { + pub fn prop_ptr_at(&self, position: impl PropIndex) -> 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) + vec.get(position.to_u32() as usize) + .map(|b| &**b as *const JsValue) } /// Get the length of hash_props vec (returns 0 if not allocated). diff --git a/crates/oxide_types/src/shape.rs b/crates/oxide_types/src/shape.rs index 668ba2a..99a8cd6 100644 --- a/crates/oxide_types/src/shape.rs +++ b/crates/oxide_types/src/shape.rs @@ -71,8 +71,8 @@ impl ShapeStore { new_id } - fn lookup_position(&self, shape_id: ShapeId, prop_name: StringIndex) -> Option { - let mut depth: u8 = 0; + fn lookup_position(&self, shape_id: ShapeId, prop_name: StringIndex) -> Option { + let mut depth: u32 = 0; let mut cursor = Some(shape_id); while let Some(id) = cursor { match self.get_shape(id) { @@ -86,7 +86,7 @@ impl ShapeStore { } } let total_depth = depth; - let mut step: u8 = 0; + let mut step: u32 = 0; cursor = Some(shape_id); while let Some(id) = cursor { match self.get_shape(id) { @@ -119,8 +119,8 @@ impl ShapeStore { false } - fn shape_prop_count(&self, shape_id: ShapeId) -> u8 { - let mut count = 0u8; + fn shape_prop_count(&self, shape_id: ShapeId) -> u32 { + let mut count = 0u32; let mut cursor = Some(shape_id); while let Some(id) = cursor { match self.get_shape(id) { @@ -151,7 +151,7 @@ pub fn make_shape(parent_id: ShapeId, prop_name: StringIndex) -> ShapeId { store().lock().unwrap().make_shape(parent_id, prop_name) } -pub fn lookup_position(shape_id: ShapeId, prop_name: StringIndex) -> Option { +pub fn lookup_position(shape_id: ShapeId, prop_name: StringIndex) -> Option { store().lock().unwrap().lookup_position(shape_id, prop_name) } @@ -159,7 +159,7 @@ 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 { +pub fn shape_prop_count(shape_id: ShapeId) -> u32 { store().lock().unwrap().shape_prop_count(shape_id) } diff --git a/crates/oxide_vm/src/builtins/array.rs b/crates/oxide_vm/src/builtins/array.rs index 4905dc5..aa4f37c 100644 --- a/crates/oxide_vm/src/builtins/array.rs +++ b/crates/oxide_vm/src/builtins/array.rs @@ -100,11 +100,11 @@ pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { )); for i in 0..n_elems { unsafe { - (*arr).set_prop_at(i as u8, vm.reg(args[1 + i])); + (*arr).set_prop_at(i, vm.reg(args[1 + i])); } } unsafe { - (*arr).set_prop_count(n_elems as u8); + (*arr).set_prop_count(n_elems); } Ok(JsValue::from_js_object(arr)) } @@ -112,14 +112,13 @@ pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { 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 mut len = arr.prop_count(); for &arg_reg in args.iter().skip(1) { let val = vm.reg(arg_reg); - arr.set_prop_at(len as u8, val); + arr.set_prop_at(len, val); len += 1; } - let stored_len = (len as u8).min(31); - arr.set_prop_count(stored_len); + arr.set_prop_count(len); Ok(JsValue::int(len as i32)) } @@ -156,9 +155,9 @@ 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_at(i as u8, arr.get_prop_at((start + i) as u8)); + (*new_arr).set_prop_at(i, arr.get_prop_at(start + i)); } - (*new_arr).set_prop_count(count.min(31) as u8); + (*new_arr).set_prop_count(count); } Ok(JsValue::from_js_object(new_arr)) } @@ -191,36 +190,36 @@ 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_at((start + i) as u8)); + removed.push(arr.get_prop_at(start + i)); } if insert_count > delete_count { let shift = insert_count - delete_count; for i in (start + delete_count..n).rev() { - let val = arr.get_prop_at(i as u8); - arr.set_prop_at((i + shift) as u8, val); + let val = arr.get_prop_at(i); + arr.set_prop_at(i + shift, 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_at(i as u8); - arr.set_prop_at((i - shift) as u8, val); + let val = arr.get_prop_at(i); + arr.set_prop_at(i - shift, val); } } for i in 0..insert_count { - arr.set_prop_at((start + i) as u8, vm.reg(args[3 + i])); + arr.set_prop_at(start + i, vm.reg(args[3 + i])); } let new_len = n + insert_count - delete_count; - arr.set_prop_count(new_len.min(31) as u8); + arr.set_prop_count(new_len); let removed_arr = create_new_array(vm, removed.len()); unsafe { for (i, val) in removed.iter().enumerate() { - (*removed_arr).set_prop_at(i as u8, *val); + (*removed_arr).set_prop_at(i, *val); } - (*removed_arr).set_prop_count(removed.len().min(31) as u8); + (*removed_arr).set_prop_count(removed.len()); } Ok(JsValue::from_js_object(removed_arr)) } @@ -231,7 +230,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_at(i as u8)); + all.push(arr.get_prop_at(i)); } for &arg_reg in args.iter().skip(1) { let val = vm.reg(arg_reg); @@ -242,7 +241,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_at(i as u8)); + all.push(o.get_prop_at(i)); } continue; } @@ -253,9 +252,9 @@ 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_at(i as u8, *val); + (*new_arr).set_prop_at(i, *val); } - (*new_arr).set_prop_count(all.len().min(31) as u8); + (*new_arr).set_prop_count(all.len()); } Ok(JsValue::from_js_object(new_arr)) } @@ -272,7 +271,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_at(i as u8); + let v = arr.get_prop_at(i); if v.is_undefined() || v.is_null() { String::new() } else { @@ -294,7 +293,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_at(i as u8), target) { + if coercion::strict_equality(arr.get_prop_at(i), target) { return Ok(JsValue::int(i as i32)); } } @@ -310,7 +309,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_at(i as u8); + let elem = arr.get_prop_at(i); if elem.is_double() && target.is_double() { let ea = elem.as_double(); let ta = target.as_double(); @@ -332,9 +331,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_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); + let tmp = arr.get_prop_at(i); + arr.set_prop_at(i, arr.get_prop_at(j)); + arr.set_prop_at(j, tmp); i += 1; j = j.saturating_sub(1); } @@ -361,7 +360,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_at(i as u8)).collect(); + let sub: Vec = (0..on).map(|i| o.get_prop_at(i)).collect(); let flat = flatten(&sub, remaining_depth - 1); out.extend(flat); continue; @@ -373,14 +372,14 @@ pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult { out } - let all: Vec = (0..n).map(|i| arr.get_prop_at(i as u8)).collect(); + let all: Vec = (0..n).map(|i| arr.get_prop_at(i)).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_at(i as u8, *val); + (*new_arr).set_prop_at(i, *val); } - (*new_arr).set_prop_count(flat.len().min(31) as u8); + (*new_arr).set_prop_count(flat.len()); } Ok(JsValue::from_js_object(new_arr)) } @@ -401,7 +400,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_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; let _ = invoke_native_callback( vm, callback_val, @@ -429,7 +428,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_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -437,13 +436,13 @@ 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_at(i as u8, mapped); + (*new_arr).set_prop_at(i, mapped); }, Err(_) => return Err(JsValue::undefined()), } } unsafe { - (*new_arr).set_prop_count(n.min(31) as u8); + (*new_arr).set_prop_count(n); } Ok(JsValue::from_js_object(new_arr)) } @@ -465,7 +464,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_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -484,9 +483,9 @@ 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_at(i as u8, *val); + (*new_arr).set_prop_at(i, *val); } - (*new_arr).set_prop_count(kept.len().min(31) as u8); + (*new_arr).set_prop_count(kept.len()); } Ok(JsValue::from_js_object(new_arr)) } @@ -516,7 +515,7 @@ pub fn array_reduce(vm: &mut Vm, args: &[u8]) -> NativeResult { } let this_val = JsValue::undefined(); for i in start_idx..n { - let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -546,7 +545,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_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -581,7 +580,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_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -616,7 +615,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_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -652,7 +651,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_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -667,7 +666,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_at(j as u8)); + flat.push(r.get_prop_at(j)); } continue; } @@ -681,9 +680,9 @@ 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_at(i as u8, *val); + (*new_arr).set_prop_at(i, *val); } - (*new_arr).set_prop_count(flat.len().min(31) as u8); + (*new_arr).set_prop_count(flat.len()); } Ok(JsValue::from_js_object(new_arr)) } @@ -710,14 +709,14 @@ pub fn array_unshift(vm: &mut Vm, args: &[u8]) -> NativeResult { let len = arr.prop_count(); let n_items = args.len().saturating_sub(1); for i in (0..len as usize).rev() { - let v = arr.get_prop_at(i as u8); - arr.set_prop_at(i as u8 + n_items as u8, v); + let v = arr.get_prop_at(i); + arr.set_prop_at(i + n_items, v); } for (j, &arg_reg) in args.iter().skip(1).enumerate() { - arr.set_prop_at(j as u8, vm.reg(arg_reg)); + arr.set_prop_at(j, vm.reg(arg_reg)); } - let new_len = (len as usize + n_items).min(31); - arr.set_prop_count(new_len as u8); + let new_len = len as usize + n_items; + arr.set_prop_count(new_len); Ok(JsValue::int(new_len as i32)) } @@ -743,7 +742,7 @@ pub fn array_fill(vm: &mut Vm, args: &[u8]) -> NativeResult { len }; for i in start..end { - arr.set_prop_at(i as u8, value); + arr.set_prop_at(i, value); } Ok(vm.reg(args[0])) } @@ -776,8 +775,8 @@ pub fn array_copy_within(vm: &mut Vm, args: &[u8]) -> NativeResult { if to >= len { break; } - let v = arr.get_prop_at(from as u8); - arr.set_prop_at(to as u8, v); + let v = arr.get_prop_at(from); + arr.set_prop_at(to, v); to += 1; } Ok(vm.reg(args[0])) @@ -798,7 +797,7 @@ pub fn array_at(vm: &mut Vm, args: &[u8]) -> NativeResult { if index < 0 || index >= len { return Ok(JsValue::undefined()); } - Ok(arr.get_prop_at(index as u8)) + Ok(arr.get_prop_at(index)) } pub fn array_last_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { @@ -811,7 +810,7 @@ pub fn array_last_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { JsValue::undefined() }; for i in (0..len).rev() { - if coercion::strict_equality(arr.get_prop_at(i as u8), search) { + if coercion::strict_equality(arr.get_prop_at(i), search) { return Ok(JsValue::int(i)); } } @@ -831,7 +830,7 @@ pub fn array_find_index(vm: &mut Vm, args: &[u8]) -> NativeResult { JsValue::undefined() }; for i in 0..n { - let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -862,7 +861,7 @@ pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult { JsValue::undefined() }; for i in (0..n).rev() { - let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -893,10 +892,10 @@ pub fn array_reduce_right(vm: &mut Vm, args: &[u8]) -> NativeResult { if n == 0 { return Err(JsValue::undefined()); } - (unsafe { (*arr_ptr).get_prop_at(n as u8 - 1) }, n as i32 - 2) + (unsafe { (*arr_ptr).get_prop_at(n - 1) }, n as i32 - 2) }; for i in (0..=start_idx).rev() { - let elem = unsafe { (*arr_ptr).get_prop_at(i as u8) }; + let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback( vm, callback_val, @@ -914,14 +913,14 @@ pub fn array_sort(vm: &mut Vm, _args: &[u8]) -> NativeResult { let arr_ptr = get_this_array_ref(vm.reg(_args[0]))?; let arr = unsafe { &mut *arr_ptr }; let len = arr.prop_count() as usize; - let mut vals: Vec = (0..len).map(|i| arr.get_prop_at(i as u8)).collect(); + let mut vals: Vec = (0..len).map(|i| arr.get_prop_at(i)).collect(); vals.sort_by(|a, b| { let sa = coercion::to_string(vm.kernel().string_forge().as_ref(), *a); let sb = coercion::to_string(vm.kernel().string_forge().as_ref(), *b); sa.cmp(&sb) }); for (i, &v) in vals.iter().enumerate() { - arr.set_prop_at(i as u8, v); + arr.set_prop_at(i, v); } Ok(vm.reg(_args[0])) } diff --git a/crates/oxide_vm/src/builtins/json.rs b/crates/oxide_vm/src/builtins/json.rs index 9ef4f60..12fe71b 100644 --- a/crates/oxide_vm/src/builtins/json.rs +++ b/crates/oxide_vm/src/builtins/json.rs @@ -58,7 +58,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue { for (i, v) in arr.iter().enumerate() { let jsv = value_to_jsvalue(vm, v); unsafe { - (*array_obj).set_prop_at(i as u8, jsv); + (*array_obj).set_prop_at(i, jsv); } } JsValue::from_js_object(array_obj) @@ -216,7 +216,7 @@ fn stringify_array( if i > 0 { out.push(','); } - let val = obj.get_prop_at(i as u8); + let val = obj.get_prop_at(i); let is_function = val.is_object() && { let ptr = val.as_js_object_ptr(); if ptr.is_null() { diff --git a/crates/oxide_vm/src/builtins/object.rs b/crates/oxide_vm/src/builtins/object.rs index 4810da5..4a33ce7 100644 --- a/crates/oxide_vm/src/builtins/object.rs +++ b/crates/oxide_vm/src/builtins/object.rs @@ -7,10 +7,10 @@ use crate::coercion; use crate::native::NativeResult; use crate::vm::Vm; -pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u8)> { - let mut keys: Vec<(u32, u8)> = Vec::new(); +pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u32)> { + let mut keys: Vec<(u32, u32)> = Vec::new(); let shape_id = obj.shape_id(); - let mut pos: u8 = 0; + let mut pos: u32 = 0; let mut shape_ids = Vec::new(); let mut cursor = Some(shape_id); while let Some(id) = cursor { @@ -67,7 +67,7 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { .collect(); } - let n = key_names.len().min(255); + let n = key_names.len(); let array_proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject; let arr = vm.epoch().alloc(JsObject::new_array( EMPTY_SHAPE_ID, @@ -75,12 +75,15 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { n, vm.epoch().bump(), )); - for k in key_names.iter().take(n) { + for (i, k) in key_names.iter().enumerate() { let str_val = vm.intern(k); unsafe { - (*arr).ensure_hash_props().push(Box::new(str_val)); + (*arr).set_prop_at(i, str_val); } } + unsafe { + (*arr).set_prop_count(n); + } Ok(JsValue::from_js_object(arr)) } @@ -334,7 +337,7 @@ pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult { .collect() }; - let n = key_names.len().min(255); + let n = key_names.len(); let array_proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject; let arr = vm.epoch().alloc(JsObject::new_array( EMPTY_SHAPE_ID, @@ -342,12 +345,15 @@ pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult { n, vm.epoch().bump(), )); - for k in key_names.iter().take(n) { + for (i, k) in key_names.iter().enumerate() { let str_val = vm.intern(k); unsafe { - (*arr).ensure_hash_props().push(Box::new(str_val)); + (*arr).set_prop_at(i, str_val); } } + unsafe { + (*arr).set_prop_count(n); + } Ok(JsValue::from_js_object(arr)) } @@ -399,7 +405,7 @@ pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { let obj_ptr = require_obj_arg(vm, args, "entries")?; let obj = unsafe { &*obj_ptr }; let keys = walk_own_keys(vm, obj); - let n = keys.len().min(255); + let n = keys.len(); let array_proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject; let arr = vm.epoch().alloc(JsObject::new_array( EMPTY_SHAPE_ID, @@ -407,7 +413,7 @@ pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { n, vm.epoch().bump(), )); - for (_i, (si, offset)) in keys.iter().enumerate().take(n) { + for (i, (si, offset)) in keys.iter().enumerate() { let key_str = vm.kernel().string_forge().lookup(*si).unwrap_or_default(); let key_val = vm.intern(&key_str); let val = obj.get_prop_at(*offset); @@ -421,11 +427,12 @@ pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { (*pair).set_prop_at(0, key_val); (*pair).set_prop_at(1, val); (*pair).set_prop_count(2); - (*arr) - .ensure_hash_props() - .push(Box::new(JsValue::from_js_object(pair))); + (*arr).set_prop_at(i, JsValue::from_js_object(pair)); } } + unsafe { + (*arr).set_prop_count(n); + } Ok(JsValue::from_js_object(arr)) } @@ -433,7 +440,7 @@ pub fn object_values(vm: &mut Vm, args: &[u8]) -> NativeResult { let obj_ptr = require_obj_arg(vm, args, "values")?; let obj = unsafe { &*obj_ptr }; let keys = walk_own_keys(vm, obj); - let n = keys.len().min(255); + let n = keys.len(); let array_proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject; let arr = vm.epoch().alloc(JsObject::new_array( EMPTY_SHAPE_ID, @@ -441,14 +448,14 @@ pub fn object_values(vm: &mut Vm, args: &[u8]) -> NativeResult { n, vm.epoch().bump(), )); - for (i, (_si, offset)) in keys.iter().enumerate().take(n) { + for (i, (_si, offset)) in keys.iter().enumerate() { let val = obj.get_prop_at(*offset); unsafe { - (*arr).set_prop_at(i as u8, val); + (*arr).set_prop_at(i, val); } } unsafe { - (*arr).set_prop_count(n as u8); + (*arr).set_prop_count(n); } Ok(JsValue::from_js_object(arr)) } diff --git a/crates/oxide_vm/src/builtins/string.rs b/crates/oxide_vm/src/builtins/string.rs index a5de4d3..f3cdc37 100644 --- a/crates/oxide_vm/src/builtins/string.rs +++ b/crates/oxide_vm/src/builtins/string.rs @@ -13,7 +13,7 @@ fn this_string(vm: &Vm, args: &[u8]) -> String { fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { let proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject; - let n = parts.len().min(31); + let n = parts.len(); let arr = vm.epoch().alloc(JsObject::new_array( EMPTY_SHAPE_ID, JsValue::from_js_object(proto), @@ -21,11 +21,11 @@ fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { vm.epoch().bump(), )); unsafe { - for (i, s) in parts.iter().enumerate().take(n) { + for (i, s) in parts.iter().enumerate() { let sv = vm.intern(s); - (*arr).set_prop_at(i as u8, sv); + (*arr).set_prop_at(i, sv); } - (*arr).set_prop_count(n as u8); + (*arr).set_prop_count(n); } JsValue::from_js_object(arr) } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 0883ade..60cc041 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -105,7 +105,11 @@ macro_rules! member_read_prop { 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()) { - if let Some(ptr) = $self.template_prop_ptr($obj, &template) { + if template.prop_name != $prop_name_si { + $self + .resolve_property($obj, $prop_name_si) + .unwrap_or(JsValue::undefined()) + } else if let Some(ptr) = $self.template_prop_ptr($obj, &template) { $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; @@ -116,15 +120,16 @@ macro_rules! member_read_prop { .unwrap_or(JsValue::undefined()) } } else if let Some(val) = $self.resolve_property($obj, $prop_name_si) { - let pos = $self + if let Some(pos) = $self .kernel .shape_forge() .lookup_position($obj.shape_id(), $prop_name_si) - .unwrap_or(0); - 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; + { + 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 { @@ -397,6 +402,10 @@ impl Vm { } fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option { + let length_si = self.kernel.string_forge().intern("length").0; + if obj.is_array() && prop_name_si == length_si { + return Some(JsValue::int(obj.prop_count() as i32)); + } if let Some(pos) = self .kernel .shape_forge() @@ -459,6 +468,7 @@ impl Vm { new_shape_id, PropTemplate { shape_id: new_shape_id, + prop_name: prop_name_si, position: new_pos, generation: obj.generation(), }, @@ -1023,14 +1033,20 @@ impl Vm { } else if let Some(template) = self.kernel.prop_forge().get_template(obj.shape_id()) { - let pos = template.position as usize; - if let Some(vec) = obj.hash_props_vec() { - if pos < vec.len() { - let ptr = &*vec[pos] as *const JsValue; - 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] = unsafe { *(ptr) }; + if template.prop_name == prop_name_si { + let pos = template.position as usize; + if let Some(vec) = obj.hash_props_vec() { + if pos < vec.len() { + let ptr = &*vec[pos] as *const JsValue; + 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] = unsafe { *(ptr) }; + } else { + self.regs[a] = self + .resolve_property(obj, prop_name_si) + .unwrap_or(JsValue::undefined()); + } } else { self.regs[a] = self .resolve_property(obj, prop_name_si) @@ -1042,15 +1058,16 @@ impl Vm { .unwrap_or(JsValue::undefined()); } } else if let Some(val) = self.resolve_property(obj, prop_name_si) { - let pos = self + if let Some(pos) = self .kernel .shape_forge() .lookup_position(obj.shape_id(), prop_name_si) - .unwrap_or(0); - 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; + { + 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 { @@ -1105,6 +1122,7 @@ impl Vm { new_shape_id, PropTemplate { shape_id: new_shape_id, + prop_name: prop_name_si, position: new_pos, generation: obj.generation(), }, @@ -1221,9 +1239,9 @@ impl Vm { if obj_ptr.is_null() { throw_err!(self, TypeError, "SET_ELEM on non-object"); } - let idx = self.regs[a].as_int() as usize; + let idx = self.regs[a].as_int().max(0) as u32; let obj = unsafe { &mut *obj_ptr }; - obj.set_prop_at(idx as u8, self.regs[b]); + obj.set_prop_at(idx, self.regs[b]); } OpCode::COMPOUND_ADD => { @@ -1507,6 +1525,7 @@ impl Vm { new_shape_id, PropTemplate { shape_id: new_shape_id, + prop_name: prop_name_si, position: new_pos, generation: obj.generation(), }, @@ -1557,6 +1576,7 @@ impl Vm { new_shape_id, PropTemplate { shape_id: new_shape_id, + prop_name: prop_name_si, position: new_pos, generation: obj.generation(), }, diff --git a/crates/oxide_vm/tests/builtins_array_tests.rs b/crates/oxide_vm/tests/builtins_array_tests.rs index 39dda42..e6a0783 100644 --- a/crates/oxide_vm/tests/builtins_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_array_tests.rs @@ -38,6 +38,21 @@ fn array_push_returns_true_length_beyond_31() { assert_eq!(result.as_int(), 32); } +#[test] +fn array_length_tracks_more_than_31_elements() { + let result = eval("var a=[]; for (var i=0;i<40;i=i+1) a.push(i); a.length").unwrap(); + assert_eq!(result.as_int(), 40); +} + +#[test] +fn array_push_preserves_slots_beyond_255() { + let result = eval("var a=[]; for (var i=0;i<257;i=i+1) a.push(i); a").unwrap(); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 257); + let tail = obj.get_prop_at(256); + assert_eq!(tail.as_double(), 256.0); +} + #[test] fn array_pop_returns_last() { let result = eval("[1,2,3].pop()").unwrap(); diff --git a/crates/oxide_vm/tests/builtins_object_tests.rs b/crates/oxide_vm/tests/builtins_object_tests.rs index 8fd632d..074fc22 100644 --- a/crates/oxide_vm/tests/builtins_object_tests.rs +++ b/crates/oxide_vm/tests/builtins_object_tests.rs @@ -16,11 +16,15 @@ fn eval(source: &str) -> Result { fn object_keys_empty() { let result = eval("Object.keys({})").unwrap(); assert!(result.is_object()); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 0); } #[test] fn object_keys_has_own_property() { - eval("Object.keys({a:1})").unwrap(); + let result = eval("Object.keys({a:1,b:2})").unwrap(); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 2); } #[test] @@ -68,10 +72,14 @@ fn object_has_own_true() { fn object_entries_returns_array() { let result = eval("Object.entries({a:1,b:2})").unwrap(); assert!(result.is_object()); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 2); } #[test] fn object_values_returns_array() { let result = eval("Object.values({a:1,b:2})").unwrap(); assert!(result.is_object()); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 2); } diff --git a/crates/oxide_vm/tests/builtins_regexp_tests.rs b/crates/oxide_vm/tests/builtins_regexp_tests.rs index facf7d2..ea0da4f 100644 --- a/crates/oxide_vm/tests/builtins_regexp_tests.rs +++ b/crates/oxide_vm/tests/builtins_regexp_tests.rs @@ -227,6 +227,9 @@ fn string_split_with_regexp() { let mut vm = Vm::new(); let result = eval(&mut vm, "'a,b,c'.split(/,/)").unwrap(); assert!(result.is_object()); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 3); + assert_eq!(to_str(&vm, obj.get_prop_at(1)), "b"); } // --- toString --- diff --git a/crates/oxide_vm/tests/builtins_string_tests.rs b/crates/oxide_vm/tests/builtins_string_tests.rs index 7cc4742..452ea45 100644 --- a/crates/oxide_vm/tests/builtins_string_tests.rs +++ b/crates/oxide_vm/tests/builtins_string_tests.rs @@ -143,6 +143,10 @@ fn string_split_comma() { let mut vm = Vm::new(); let result = eval(&mut vm, "'a,b,c'.split(',')").unwrap(); assert!(result.is_object()); + let obj = unsafe { &*result.as_js_object_ptr() }; + assert_eq!(obj.prop_count(), 3); + assert_eq!(to_str(&vm, obj.get_prop_at(0)), "a"); + assert_eq!(to_str(&vm, obj.get_prop_at(2)), "c"); } #[test] diff --git a/crates/oxide_vm/tests/json_tests.rs b/crates/oxide_vm/tests/json_tests.rs index d1069ce..6204c80 100644 --- a/crates/oxide_vm/tests/json_tests.rs +++ b/crates/oxide_vm/tests/json_tests.rs @@ -27,13 +27,7 @@ fn json_parse_object() { let result = eval(&mut vm, "JSON.parse('{\"a\":1,\"b\":2}')").unwrap(); assert!(result.is_object()); let obj = unsafe { &*result.as_js_object_ptr() }; - let mut count = 0; - for i in 0..obj.prop_vec_len() { - if !obj.get_prop_at(i as u8).is_undefined() { - count += 1; - } - } - assert_eq!(count, 2); + assert_eq!(obj.prop_count(), 2); } #[test] @@ -43,7 +37,8 @@ fn json_parse_array() { assert!(result.is_object()); let obj = unsafe { &*result.as_js_object_ptr() }; assert!(obj.is_array()); - assert_eq!(obj.prop_vec_len(), 3); + assert_eq!(obj.prop_count(), 3); + assert_eq!(obj.get_prop_at(2).as_double(), 3.0); } #[test]