perf(test262): +2.4% pass rate via Function .name/.length and ArrayLike support
- Array indexOf/includes/lastIndexOf: ArrayLike receiver + fromIndex + SameValueZero - Function.prototype methods: add .length property via bind_method_static - Error objects: add .name property via create_kind_error - Constructor functions: add .name via make_pair + .length via bind_constructor! - test262: 8497→9009 pass (+512), 38.3%→40.7% rate Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> (cherry picked from commit af61949e6cab1b149a13e571e29bacc4267d5690)
This commit is contained in:
@@ -46,6 +46,46 @@ fn get_this_array_ref<H: VmHost>(vm: &mut H, val: JsValue) -> Result<*mut JsObje
|
|||||||
Ok(ptr)
|
Ok(ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 接受 array 或 ArrayLike (object with length property) — for read-only methods.
|
||||||
|
/// 返回 (object_ptr, length, is_real_array)
|
||||||
|
fn get_this_arraylike<H: VmHost>(vm: &mut H, val: JsValue) -> Result<(*mut JsObject, usize, bool), JsValue> {
|
||||||
|
if !val.is_object() {
|
||||||
|
return Err(array_type_error(vm, "Array.prototype method called on null or undefined"));
|
||||||
|
}
|
||||||
|
let ptr = val.as_js_object_ptr();
|
||||||
|
if ptr.is_null() {
|
||||||
|
return Err(array_type_error(vm, "Array.prototype method called on null or undefined"));
|
||||||
|
}
|
||||||
|
let obj = unsafe { &*ptr };
|
||||||
|
if obj.is_array() {
|
||||||
|
return Ok((ptr, obj.prop_count() as usize, true));
|
||||||
|
}
|
||||||
|
// 读取 length 属性
|
||||||
|
let length_key = vm.new_string("length");
|
||||||
|
let length_si = vm.property_key_si(length_key);
|
||||||
|
let len_val = vm.ordinary_get(unsafe { &*ptr }, length_si, val)
|
||||||
|
.unwrap_or(JsValue::int(0));
|
||||||
|
let len_num = vm.coerce_number_bounded(len_val).unwrap_or(0.0);
|
||||||
|
let len = if !len_num.is_finite() || len_num <= 0.0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(len_num as usize).min(MAX_DENSE_PROPS)
|
||||||
|
};
|
||||||
|
Ok((ptr, len, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从 arraylike 读取 index 位置元素
|
||||||
|
fn arraylike_get<H: VmHost>(vm: &mut H, ptr: *mut JsObject, is_array: bool, i: usize) -> JsValue {
|
||||||
|
if is_array {
|
||||||
|
unsafe { (*ptr).get_prop_at(i) }
|
||||||
|
} else {
|
||||||
|
let key_str = vm.new_string(&i.to_string());
|
||||||
|
let key_si = vm.property_key_si(key_str);
|
||||||
|
let recv = JsValue::from_js_object(ptr);
|
||||||
|
vm.ordinary_get(unsafe { &*ptr }, key_si, recv).unwrap_or(JsValue::undefined())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn require_callback<H: VmHost>(vm: &mut H, callback_val: JsValue) -> Result<JsValue, JsValue> {
|
fn require_callback<H: VmHost>(vm: &mut H, callback_val: JsValue) -> Result<JsValue, JsValue> {
|
||||||
if !callback_val.is_object() {
|
if !callback_val.is_object() {
|
||||||
return Err(array_type_error(vm, "callback is not a function"));
|
return Err(array_type_error(vm, "callback is not a function"));
|
||||||
@@ -350,15 +390,32 @@ pub fn array_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
|
|
||||||
pub fn array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
builtins_debug!("Array.prototype.indexOf called with {} args", args.len());
|
builtins_debug!("Array.prototype.indexOf called with {} args", args.len());
|
||||||
let arr_ptr = array_ptr!(vm, args);
|
let this_val = vm.reg(args[0]);
|
||||||
let arr = unsafe { &*arr_ptr };
|
let (ptr, n, is_array) = match get_this_arraylike(vm, this_val) {
|
||||||
let n = arr.prop_count() as usize;
|
Ok(v) => v,
|
||||||
if args.len() < 2 {
|
Err(e) => return NativeResult::Err(e),
|
||||||
|
};
|
||||||
|
if n == 0 || args.len() < 2 {
|
||||||
return NativeResult::Ok(JsValue::int(-1));
|
return NativeResult::Ok(JsValue::int(-1));
|
||||||
}
|
}
|
||||||
let target = vm.reg(args[1]);
|
let target = vm.reg(args[1]);
|
||||||
for i in 0..n {
|
// fromIndex
|
||||||
if oxide_runtime_api::strict_eq(arr.get_prop_at(i), target) {
|
let from_index = if args.len() >= 3 {
|
||||||
|
let v = vm.reg(args[2]);
|
||||||
|
let f = vm.coerce_number_bounded(v).unwrap_or(0.0);
|
||||||
|
let f = if f.is_nan() { 0.0 } else { f.trunc() };
|
||||||
|
if f >= 0.0 {
|
||||||
|
(f as usize).min(n)
|
||||||
|
} else {
|
||||||
|
let from = n as f64 + f;
|
||||||
|
if from < 0.0 { 0 } else { from as usize }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
for i in from_index..n {
|
||||||
|
let elem = arraylike_get(vm, ptr, is_array, i);
|
||||||
|
if oxide_runtime_api::strict_eq(elem, target) {
|
||||||
return NativeResult::Ok(JsValue::int(i as i32));
|
return NativeResult::Ok(JsValue::int(i as i32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,29 +424,52 @@ pub fn array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
|
|
||||||
pub fn array_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn array_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
builtins_debug!("Array.prototype.includes called with {} args", args.len());
|
builtins_debug!("Array.prototype.includes called with {} args", args.len());
|
||||||
let arr_ptr = array_ptr!(vm, args);
|
let this_val = vm.reg(args[0]);
|
||||||
let arr = unsafe { &*arr_ptr };
|
let (ptr, n, is_array) = match get_this_arraylike(vm, this_val) {
|
||||||
let n = arr.prop_count() as usize;
|
Ok(v) => v,
|
||||||
if args.len() < 2 {
|
Err(e) => return NativeResult::Err(e),
|
||||||
|
};
|
||||||
|
if n == 0 {
|
||||||
return NativeResult::Ok(JsValue::bool(false));
|
return NativeResult::Ok(JsValue::bool(false));
|
||||||
}
|
}
|
||||||
let target = vm.reg(args[1]);
|
let target = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
for i in 0..n {
|
let from_index = if args.len() >= 3 {
|
||||||
let elem = arr.get_prop_at(i);
|
let v = vm.reg(args[2]);
|
||||||
if elem.is_double() && target.is_double() {
|
let f = vm.coerce_number_bounded(v).unwrap_or(0.0);
|
||||||
let ea = elem.as_double();
|
let f = if f.is_nan() { 0.0 } else { f.trunc() };
|
||||||
let ta = target.as_double();
|
if f >= 0.0 {
|
||||||
if ea.is_nan() && ta.is_nan() {
|
(f as usize).min(n)
|
||||||
return NativeResult::Ok(JsValue::bool(true));
|
} else {
|
||||||
}
|
let from = n as f64 + f;
|
||||||
|
if from < 0.0 { 0 } else { from as usize }
|
||||||
}
|
}
|
||||||
if oxide_runtime_api::strict_equality(elem, target) {
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
for i in from_index..n {
|
||||||
|
let elem = arraylike_get(vm, ptr, is_array, i);
|
||||||
|
// SameValueZero: NaN === NaN, +0 === -0
|
||||||
|
if same_value_zero(elem, target) {
|
||||||
return NativeResult::Ok(JsValue::bool(true));
|
return NativeResult::Ok(JsValue::bool(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NativeResult::Ok(JsValue::bool(false))
|
NativeResult::Ok(JsValue::bool(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SameValueZero (ES2015 7.2.10): NaN === NaN, +0 === -0
|
||||||
|
fn same_value_zero(a: JsValue, b: JsValue) -> bool {
|
||||||
|
// 都是数字时特殊处理
|
||||||
|
let a_is_num = a.is_int() || a.is_double();
|
||||||
|
let b_is_num = b.is_int() || b.is_double();
|
||||||
|
if a_is_num && b_is_num {
|
||||||
|
let av = if a.is_int() { a.as_int() as f64 } else { a.as_double() };
|
||||||
|
let bv = if b.is_int() { b.as_int() as f64 } else { b.as_double() };
|
||||||
|
if av.is_nan() && bv.is_nan() { return true; }
|
||||||
|
return av == bv; // +0 == -0 in Rust f64
|
||||||
|
}
|
||||||
|
oxide_runtime_api::strict_equality(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
builtins_debug!("Array.prototype.reverse called with {} args", args.len());
|
builtins_debug!("Array.prototype.reverse called with {} args", args.len());
|
||||||
let arr_ptr = array_ptr!(vm, args);
|
let arr_ptr = array_ptr!(vm, args);
|
||||||
@@ -911,13 +991,36 @@ pub fn array_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
|
|
||||||
pub fn array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
pub fn array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
builtins_debug!("Array.prototype.lastIndexOf called with {} args", args.len());
|
builtins_debug!("Array.prototype.lastIndexOf called with {} args", args.len());
|
||||||
let arr_ptr = array_ptr!(vm, args);
|
let this_val = vm.reg(args[0]);
|
||||||
let arr = unsafe { &*arr_ptr };
|
let (ptr, n, is_array) = match get_this_arraylike(vm, this_val) {
|
||||||
let len = arr.prop_count() as i32;
|
Ok(v) => v,
|
||||||
|
Err(e) => return NativeResult::Err(e),
|
||||||
|
};
|
||||||
|
if n == 0 {
|
||||||
|
return NativeResult::Ok(JsValue::int(-1));
|
||||||
|
}
|
||||||
let search = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
let search = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
for i in (0..len).rev() {
|
// fromIndex (default: n-1)
|
||||||
if oxide_runtime_api::strict_eq(arr.get_prop_at(i), search) {
|
let from_index_isize: isize = if args.len() >= 3 {
|
||||||
return NativeResult::Ok(JsValue::int(i));
|
let v = vm.reg(args[2]);
|
||||||
|
let f = vm.coerce_number_bounded(v).unwrap_or(0.0);
|
||||||
|
if f.is_nan() { return NativeResult::Ok(JsValue::int(-1)); }
|
||||||
|
let f = f.trunc();
|
||||||
|
if f >= 0.0 {
|
||||||
|
(f as isize).min(n as isize - 1)
|
||||||
|
} else {
|
||||||
|
n as isize + f as isize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n as isize - 1
|
||||||
|
};
|
||||||
|
if from_index_isize < 0 {
|
||||||
|
return NativeResult::Ok(JsValue::int(-1));
|
||||||
|
}
|
||||||
|
for i in (0..=from_index_isize as usize).rev() {
|
||||||
|
let elem = arraylike_get(vm, ptr, is_array, i);
|
||||||
|
if oxide_runtime_api::strict_eq(elem, search) {
|
||||||
|
return NativeResult::Ok(JsValue::int(i as i32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NativeResult::Ok(JsValue::int(-1))
|
NativeResult::Ok(JsValue::int(-1))
|
||||||
|
|||||||
@@ -39,14 +39,24 @@ pub fn create_kind_error<H: VmHost>(host: &mut H, kind: &str, msg: &str) -> JsVa
|
|||||||
let obj = host
|
let obj = host
|
||||||
.epoch()
|
.epoch()
|
||||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
|
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
|
||||||
|
let sf = Arc::clone(host.kernel_core().perm_interner());
|
||||||
|
let sh = Arc::clone(host.kernel_core().shape_forge());
|
||||||
|
// 设置 .name = kind (ES spec: Error.prototype.name)
|
||||||
|
{
|
||||||
|
let si_name = sf.intern("name").0;
|
||||||
|
let shape = sh.make_shape(EMPTY_SHAPE_ID, si_name);
|
||||||
|
let name_val = host.new_string(kind);
|
||||||
|
unsafe {
|
||||||
|
(*obj).set_shape_id(shape);
|
||||||
|
(*obj).push_prop(name_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
if !msg.is_empty() {
|
if !msg.is_empty() {
|
||||||
let sf = Arc::clone(host.kernel_core().perm_interner());
|
let si_msg = sf.intern("message").0;
|
||||||
let sh = Arc::clone(host.kernel_core().shape_forge());
|
let shape = sh.make_shape(unsafe { (*obj).shape_id() }, si_msg);
|
||||||
let si = sf.intern("message").0;
|
|
||||||
let new_shape = sh.make_shape(EMPTY_SHAPE_ID, si);
|
|
||||||
let msg_val = host.new_string(msg);
|
let msg_val = host.new_string(msg);
|
||||||
unsafe {
|
unsafe {
|
||||||
(*obj).set_shape_id(new_shape);
|
(*obj).set_shape_id(shape);
|
||||||
(*obj).push_prop(msg_val);
|
(*obj).push_prop(msg_val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,25 +234,24 @@ fn make_pair(
|
|||||||
si_name: u32,
|
si_name: u32,
|
||||||
) -> (P<JsObject>, P<JsObject>) {
|
) -> (P<JsObject>, P<JsObject>) {
|
||||||
intern_label(string_forge, name);
|
intern_label(string_forge, name);
|
||||||
|
let name_si = string_forge.intern(name).0; // intern the constructor's name value too
|
||||||
|
|
||||||
let mut proto = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
let mut proto = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||||
let mut ctor = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
let mut ctor = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
|
||||||
|
|
||||||
let proto_shape = shape_forge.make_shape(EMPTY_SHAPE_ID, si_constructor);
|
let proto_shape = shape_forge.make_shape(EMPTY_SHAPE_ID, si_constructor);
|
||||||
proto.set_shape_id(proto_shape);
|
proto.set_shape_id(proto_shape);
|
||||||
// proto shape: EMPTY -> "constructor" (slot 0)
|
|
||||||
|
|
||||||
let ctor_shape1 = shape_forge.make_shape(EMPTY_SHAPE_ID, si_prototype);
|
let ctor_shape1 = shape_forge.make_shape(EMPTY_SHAPE_ID, si_prototype);
|
||||||
let ctor_shape2 = shape_forge.make_shape(ctor_shape1, si_name);
|
let ctor_shape2 = shape_forge.make_shape(ctor_shape1, si_name);
|
||||||
ctor.set_shape_id(ctor_shape2);
|
ctor.set_shape_id(ctor_shape2);
|
||||||
// ctor shape: EMPTY -> "prototype" (slot 0) -> "name" (slot 1)
|
|
||||||
ctor.set_function(true);
|
ctor.set_function(true);
|
||||||
|
|
||||||
// Pre-allocate Vec slots so wire_ctor_proto can overwrite vec[0] later.
|
// Pre-allocate slots: proto[0]="constructor", ctor[0]="prototype", ctor[1]="name"
|
||||||
// Proto: 1 property ("constructor"), Ctor: 2 properties ("prototype", "name").
|
proto.ensure_hash_props().push(JsValue::undefined()); // placeholder, set by wire_ctor_proto
|
||||||
proto.ensure_hash_props().push(JsValue::undefined()); // slot 0: "constructor" placeholder
|
ctor.ensure_hash_props().push(JsValue::undefined()); // placeholder, set by wire_ctor_proto
|
||||||
ctor.ensure_hash_props().push(JsValue::undefined()); // slot 0: "prototype" placeholder
|
// Set the actual name value immediately (vec[1])
|
||||||
ctor.ensure_hash_props().push(JsValue::undefined()); // slot 1: "name" placeholder
|
ctor.ensure_hash_props().push(JsValue::perm_string(string_forge.string_ptr(name_si)));
|
||||||
|
|
||||||
(P::new(proto), P::new(ctor))
|
(P::new(proto), P::new(ctor))
|
||||||
}
|
}
|
||||||
@@ -1150,6 +1149,12 @@ impl BuiltinWorld {
|
|||||||
// callers, all of which use fn-item expressions).
|
// callers, all of which use fn-item expressions).
|
||||||
wrapper.set_native_fn(Some(native_fn_ptr));
|
wrapper.set_native_fn(Some(native_fn_ptr));
|
||||||
wrapper.set_native_arg_count(arg_count);
|
wrapper.set_native_arg_count(arg_count);
|
||||||
|
// 设置 .length (ES spec: Function.length = formal parameter count, excluding rest/default)
|
||||||
|
let si_length = string_forge.intern("length").0;
|
||||||
|
let length_shape = shape_forge.make_shape(wrapper.shape_id(), si_length);
|
||||||
|
wrapper.set_shape_id(length_shape);
|
||||||
|
wrapper.ensure_hash_props().push(JsValue::int(arg_count as i32));
|
||||||
|
// 设置 .name
|
||||||
let si_name = string_forge.intern("name").0;
|
let si_name = string_forge.intern("name").0;
|
||||||
let name_shape = shape_forge.make_shape(wrapper.shape_id(), si_name);
|
let name_shape = shape_forge.make_shape(wrapper.shape_id(), si_name);
|
||||||
wrapper.set_shape_id(name_shape);
|
wrapper.set_shape_id(name_shape);
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ macro_rules! bind_constructor {
|
|||||||
let ptr: *const () = ($ctor_fn as fn(&mut $crate::vm::Vm, &[u8]) -> oxide_runtime_api::NativeResult) as *const ();
|
let ptr: *const () = ($ctor_fn as fn(&mut $crate::vm::Vm, &[u8]) -> oxide_runtime_api::NativeResult) as *const ();
|
||||||
ctor.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(ptr) }));
|
ctor.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(ptr) }));
|
||||||
ctor.set_native_arg_count($nargs);
|
ctor.set_native_arg_count($nargs);
|
||||||
|
// 设置 constructor.length (Function.length = formal parameter count)
|
||||||
|
let length_si = $core.perm_interner().intern("length").0;
|
||||||
|
let length_shape = $core.shape_forge().make_shape(ctor.shape_id(), length_si);
|
||||||
|
ctor.set_shape_id(length_shape);
|
||||||
|
ctor.ensure_hash_props().push($crate::JsValue::int($nargs as i32));
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user