From 2bebd822d071ed8e2840808951bd65355ed4e9b7 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 9 Aug 2026 12:26:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20TypedArray=20=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E6=89=B9=E8=A7=A3=E9=94=81=EF=BC=88%TypedArray%=20=E6=9E=84?= =?UTF-8?q?=E9=80=A0=E5=99=A8=E3=80=81=E5=8E=9F=E5=9E=8B=E8=AE=BF=E9=97=AE?= =?UTF-8?q?=E5=99=A8=E3=80=81of/from=E3=80=81bind=20=E7=BB=91=E5=8F=82?= =?UTF-8?q?=E3=80=81harness=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array.rs | 4 +- crates/oxide_builtins/src/function.rs | 41 +-- crates/oxide_builtins/src/object.rs | 14 +- crates/oxide_builtins/src/typed_array.rs | 260 +++++++++++++++--- crates/oxide_emit/src/emit.rs | 14 +- crates/oxide_kernel/src/builtin.rs | 59 +++- crates/oxide_test262/src/main.rs | 4 +- crates/oxide_types/src/object.rs | 17 ++ .../oxide_vm/src/bindings/bind_typed_array.rs | 103 ++++++- crates/oxide_vm/src/bindings/mod.rs | 2 +- crates/oxide_vm/src/vm.rs | 32 ++- crates/oxide_vm/src/vm_props.rs | 5 +- .../tests/builtins_typed_array_tests.rs | 2 +- 13 files changed, 475 insertions(+), 82 deletions(-) diff --git a/crates/oxide_builtins/src/array.rs b/crates/oxide_builtins/src/array.rs index 80bbbc4e..578bb6bd 100644 --- a/crates/oxide_builtins/src/array.rs +++ b/crates/oxide_builtins/src/array.rs @@ -108,7 +108,7 @@ fn arraylike_get(vm: &mut H, ptr: *mut JsObject, is_array: bool, i: u } } -fn require_callback(vm: &mut H, callback_val: JsValue) -> Result { +pub(crate) fn require_callback(vm: &mut H, callback_val: JsValue) -> Result { if !callback_val.is_object() { return Err(array_type_error(vm, "callback is not a function")); } @@ -137,7 +137,7 @@ fn array_length_arg(vm: &mut H, value: JsValue) -> Result( +pub(crate) fn invoke_native_callback( vm: &mut H, callback_val: JsValue, this_val: JsValue, cb_args: &[JsValue], ) -> NativeResult { if !callback_val.is_object() { diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index 42623cf2..da6ad763 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -57,18 +57,21 @@ fn to_string_error_value(vm: &mut H, msg: &str) -> JsValue { fn bind_dispatcher(vm: &mut H, args: &[u8]) -> NativeResult { let wrapper_val = vm.reg(254); let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() }; - let bound_target = wrapper - .hash_props_vec() - .and_then(|v| v.first().copied()) - .unwrap_or(JsValue::undefined()); - let bound_this = wrapper - .hash_props_vec() - .and_then(|v| v.get(1).copied()) - .unwrap_or(JsValue::undefined()); + let props = wrapper.hash_props_vec().cloned().unwrap_or_default(); + let bound_target = props.first().copied().unwrap_or(JsValue::undefined()); + let bound_this = props.get(1).copied().unwrap_or(JsValue::undefined()); - // 转发绑定后的调用实参(跳过 args[0] 即绑定包装器的 receiver)。 - let arg_regs: Vec = args.iter().skip(1).copied().collect(); - invoke_target(vm, bound_target, bound_this, &arg_regs) + // 拼接调用实参:绑定实参(hash_props[2..])在前,本次调用实参(跳过 args[0] + // 即绑定包装器的 receiver)在后,与规范的"绑定实参先于调用实参"一致。 + let mut call_args: Vec = props.iter().skip(2).copied().collect(); + for &r in args.iter().skip(1) { + call_args.push(vm.reg(r)); + } + NativeResult::TailCall { + callee: bound_target, + this: bound_this, + args: call_args, + } } /// `Function.prototype.call(thisArg, ...args)`:以指定 this 调用目标函数。 @@ -125,8 +128,8 @@ pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { invoke_target(vm, target_val, this_val, &arg_regs) } -/// `Function.prototype.bind(thisArg, ...args)`:返回绑定 this 的新包装函数, -/// 调用时通过 `bind_dispatcher` 转发到原目标。非函数目标抛 TypeError。 +/// `Function.prototype.bind(thisArg, ...args)`:返回绑定 this 与前置实参的新包装函数, +/// 调用时通过 `bind_dispatcher` 把绑定实参拼到调用实参前转发到原目标。非函数目标抛 TypeError。 pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { if args.is_empty() { return NativeResult::Err(crate::error::create_type_error( @@ -150,9 +153,15 @@ pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { unsafe { (*wrapper).set_function(true); (*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher:: as *const ()))); - (*wrapper).set_native_arg_count(0); - (*wrapper).ensure_hash_props().push(target_val); - (*wrapper).ensure_hash_props().push(bound_this); + // hash_props 布局:[target, thisArg, ...boundArgs];绑定实参个数记入 + // native_arg_count,与 Function.length 语义一致。 + (*wrapper).set_native_arg_count(args.len().saturating_sub(2) as u8); + let props = (*wrapper).ensure_hash_props(); + props.push(target_val); + props.push(bound_this); + for &r in &args[2..] { + props.push(vm.reg(r)); + } } NativeResult::Ok(JsValue::from_js_object(wrapper)) diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index c3586a05..1c70acd7 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -887,7 +887,8 @@ pub fn object_proto_value_of(vm: &mut H, args: &[u8]) -> NativeResult NativeResult::Ok(vm.reg(args[0])) } -/// `Object.prototype.toString`:返回 `[object Object]`(暂不区分具体类型标签)。 +/// `Object.prototype.toString`:返回 `[object Tag]`。对象路径先按内置类型判定标签, +/// 再读 `@@toStringTag`——为字符串时覆盖内置标签(TypedArray 依赖它区分具体类型)。 pub fn object_proto_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let tag = if !this_val.is_object() { @@ -908,7 +909,7 @@ pub fn object_proto_to_string(vm: &mut H, args: &[u8]) -> NativeResul "Object" } else { let obj = unsafe { &*ptr }; - if obj.is_array() { + let builtin = if obj.is_array() { "Array" } else if obj.is_function() { "Function" @@ -928,6 +929,15 @@ pub fn object_proto_to_string(vm: &mut H, args: &[u8]) -> NativeResul "ArrayBuffer" } else { "Object" + }; + // @@toStringTag 为字符串时覆盖内置标签;getter 抛错透传原异常。 + let tag_si = vm.kernel_core().perm_interner().intern("@@toStringTag").0; + match vm.ordinary_get(obj, tag_si, this_val) { + Ok(v) => match vm.lookup_str(v) { + Some(s) => return NativeResult::Ok(vm.new_string(&format!("[object {s}]"))), + None => builtin, + }, + Err(err) => return NativeResult::Err(crate::iterator::engine_error(vm, &err)), } } }; diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 8c70901e..47d23b26 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -1,5 +1,5 @@ use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; -use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes, TypedArrayKind}; +use oxide_types::object::{JsObject, NativeFnPtr, TypedArrayKind}; use oxide_types::value::JsValue; use crate::array_buffer::{array_buffer_data_ptr, new_array_buffer, MAX_ARRAY_BUFFER_LENGTH}; @@ -55,11 +55,6 @@ fn normalize_index(vm: &mut H, value: JsValue, len: usize) -> usize { } } -fn set_named_prop(vm: &mut H, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { - let si = vm.kernel_core().perm_interner().intern(name).0; - let _ = vm.define_data_property(obj, si, value, attributes); -} - fn typed_array_proto_ptr(vm: &mut H, kind: TypedArrayKind) -> *mut JsObject { let world = vm.session().builtin_world(); match kind { @@ -92,30 +87,6 @@ fn create_typed_array( // SAFETY: TypedArray 实例不可调用,native_fn 存不透明 `Box`, // 与本 VM 中 ArrayBuffer/DataView 的类型化对象存储一致。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(data as *const ()) })); - - let byte_length = length.saturating_mul(kind.bytes_per_element()); - set_named_prop(vm, &mut obj, "buffer", buffer, PropAttributes::new(false, false, false)); - set_named_prop( - vm, - &mut obj, - "byteOffset", - JsValue::int(byte_offset as i32), - PropAttributes::new(false, false, false), - ); - set_named_prop( - vm, - &mut obj, - "byteLength", - JsValue::int(byte_length as i32), - PropAttributes::new(false, false, false), - ); - set_named_prop( - vm, - &mut obj, - "length", - JsValue::int(length as i32), - PropAttributes::new(false, false, false), - ); vm.alloc_object(obj) } @@ -335,11 +306,11 @@ fn write_element(kind: TypedArrayKind, bytes: &mut [u8], offset: usize, value: f fn collect_array_like(vm: &mut H, value: JsValue) -> Result, JsValue> { if !value.is_object() { - return Err(type_error(vm, "TypedArray source must be array-like")); + return Err(type_error(vm, "TypedArray source must be array-like or iterable")); } let ptr = value.as_js_object_ptr(); if ptr.is_null() { - return Err(type_error(vm, "TypedArray source must be array-like")); + return Err(type_error(vm, "TypedArray source must be array-like or iterable")); } let obj = unsafe { &*ptr }; if obj.is_array() { @@ -354,7 +325,42 @@ fn collect_array_like(vm: &mut H, value: JsValue) -> Result usize { + if n.is_nan() || n <= 0.0 { + 0 + } else { + (n.min(9_007_199_254_740_991.0).trunc() as u64).min(oxide_types::object::MAX_DENSE_PROPS as u64) as usize + } } fn typed_array_new(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> NativeResult { @@ -559,9 +565,187 @@ pub fn typed_array_set(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } -/// `TypedArray.prototype.toString`:校验 receiver 后返回 `[object TypedArray]`。 -pub fn typed_array_to_string(vm: &mut H, args: &[u8]) -> NativeResult { - let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); - native_try!(get_typed_array_data(vm, this_val)); - NativeResult::Ok(vm.new_string("[object TypedArray]")) +/// `%TypedArray%` 抽象构造器:不可 new 也不可调用,任何调用方式都抛 TypeError。 +pub fn typed_array_abstract_constructor(vm: &mut H, _args: &[u8]) -> NativeResult { + NativeResult::Err(crate::error::create_type_error(vm, "TypedArray is not a constructor")) +} + +/// `%TypedArray%.prototype.buffer` 访问器:返回视图引用的 ArrayBuffer。 +pub fn typed_array_buffer_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let view = native_try!(get_typed_array_data(vm, this_val)); + NativeResult::Ok(view.buffer) +} + +/// `%TypedArray%.prototype.byteOffset` 访问器:返回视图相对 buffer 起始的字节偏移。 +pub fn typed_array_byte_offset_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let view = native_try!(get_typed_array_data(vm, this_val)); + NativeResult::Ok(JsValue::int(view.byte_offset as i32)) +} + +/// `%TypedArray%.prototype.byteLength` 访问器:返回视图占用的字节数。 +pub fn typed_array_byte_length_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let view = native_try!(get_typed_array_data(vm, this_val)); + NativeResult::Ok(JsValue::int((view.length * view.kind.bytes_per_element()) as i32)) +} + +/// `%TypedArray%.prototype.length` 访问器:返回元素个数。 +pub fn typed_array_length_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let view = native_try!(get_typed_array_data(vm, this_val)); + NativeResult::Ok(JsValue::int(view.length as i32)) +} + +/// `%TypedArray%.prototype[@@toStringTag]` 访问器:返回具体类型名(如 `Int16Array`), +/// 供 `Object.prototype.toString` 区分类型。 +pub fn typed_array_to_string_tag_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let view = native_try!(get_typed_array_data(vm, this_val)); + NativeResult::Ok(vm.new_string(view.kind.name())) +} + +/// 按 TypedArrayCreateWithLength 语义构造 of/from 的结果对象:调用构造器 C 分配 +/// 长度为 `len` 的 TypedArray,校验结果确为 TypedArray 且长度不小于 `len`。 +/// +/// # 边界与前提 +/// - C 非可调用函数、调用抛错、返回非 TypedArray、或返回对象长度不足时抛 TypeError。 +fn allocate_typed_array(vm: &mut H, c: JsValue, len: usize) -> Result { + if !c.is_object() || c.as_js_object_ptr().is_null() || !unsafe { &*c.as_js_object_ptr() }.is_function() { + return Err(type_error(vm, "TypedArray.of/from requires a constructor")); + } + let result = vm + .call_function_sync(c, JsValue::undefined(), &[JsValue::int(len as i32)]) + .map_err(|e| crate::iterator::engine_error(vm, &e))?; + if !result.is_object() || result.as_js_object_ptr().is_null() { + return Err(type_error(vm, "TypedArray.of/from constructor did not return a TypedArray")); + } + let result_obj = unsafe { &*result.as_js_object_ptr() }; + if !result_obj.is_typed_array_obj() { + return Err(type_error(vm, "TypedArray.of/from constructor did not return a TypedArray")); + } + let view = get_typed_array_data(vm, result)?; + if view.length < len { + return Err(type_error( + vm, + "TypedArray.of/from constructor returned a TypedArray with insufficient length", + )); + } + Ok(result) +} + +/// 按 IntegerIndexedElementSet 语义把元素写入 TypedArray:先 ToNumber(symbol 等 +/// 不可转换值抛 TypeError),越界索引静默忽略。 +fn set_typed_array_element(vm: &mut H, ta: JsValue, index: usize, value: JsValue) -> Result<(), JsValue> { + let n = oxide_runtime_api::to_number_full(value, vm).map_err(|e| crate::iterator::engine_error(vm, &e))?; + let obj = unsafe { &*ta.as_js_object_ptr() }; + typed_array_element_set(vm, obj, index as u32, JsValue::float(n)).map_err(|e| crate::iterator::engine_error(vm, &e)) +} + +/// `%TypedArray%.of(...items)`:以实参为元素构造一个以 `this`(构造器 C)为类型的 +/// TypedArray;元素逐个 ToNumber 写入,不可转换(如 Symbol)抛 TypeError。 +pub fn typed_array_of(vm: &mut H, args: &[u8]) -> NativeResult { + let c = vm.reg(args[0]); + let len = args.len().saturating_sub(1); + let new_obj = match allocate_typed_array(vm, c, len) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + for i in 0..len { + let value = vm.reg(args[1 + i]); + if let Err(e) = set_typed_array_element(vm, new_obj, i, value) { + return NativeResult::Err(e); + } + } + NativeResult::Ok(new_obj) +} + +/// `%TypedArray%.from(source, mapfn?, thisArg?)`:从可迭代对象或 array-like 构造 +/// 以 `this`(构造器 C)为类型的 TypedArray;`mapfn` 逐元素映射(`thisArg` 作回调 +/// this),元素经 ToNumber 写入。 +/// +/// # 步骤 +/// 1. `source` 为 null/undefined 抛 TypeError;`mapfn` 非 undefined 时须可调用。 +/// 2. 经 `@@iterator` 判定可迭代:迭代路径逐元素收集(异常退出先 IteratorClose), +/// array-like 路径读 `length` 逐索引取值(缺失属性取 undefined)。 +/// 3. 按长度构造结果对象,逐元素经 `mapfn` 映射后 ToNumber 写入。 +pub fn typed_array_from(vm: &mut H, args: &[u8]) -> NativeResult { + let c = vm.reg(args[0]); + let source = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + if source.is_null() || source.is_undefined() { + return NativeResult::Err(type_error(vm, "TypedArray.from requires an iterable or array-like object")); + } + let mapfn_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + let mapping = if mapfn_val.is_undefined() { + None + } else { + match crate::array::require_callback(vm, mapfn_val) { + Ok(cb) => Some(cb), + Err(e) => return NativeResult::Err(e), + } + }; + let this_arg = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() }; + + let values = match crate::iterator::peek_iterator_method(vm, source) { + Ok(true) => { + let mut values = Vec::new(); + match crate::iterator::iterate_elements(vm, source, |_vm, elem| { + values.push(elem); + Ok(()) + }) { + Ok(()) => values, + Err(e) => return NativeResult::Err(e), + } + } + Ok(false) => { + let obj_val = match oxide_runtime_api::to_object(source, vm) { + Ok(o) => o, + Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)), + }; + let obj = unsafe { &*obj_val.as_js_object_ptr() }; + let length_si = vm.kernel_core().perm_interner().intern("length").0; + let len_val = match vm.ordinary_get(obj, length_si, obj_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), + }; + let n = match oxide_runtime_api::to_number_full(len_val, vm) { + Ok(n) => n, + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), + }; + let len = to_collect_len(n); + let mut values = Vec::with_capacity(len); + for i in 0..len { + let key = vm.new_string(&i.to_string()); + let key_si = vm.property_key_si(key); + match vm.ordinary_get(obj, key_si, obj_val) { + Ok(v) => values.push(v), + Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)), + } + } + values + } + Err(e) => return NativeResult::Err(e), + }; + + let new_obj = match allocate_typed_array(vm, c, values.len()) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + for (k, elem) in values.into_iter().enumerate() { + let mapped = match mapping { + Some(cb) => match crate::array::invoke_native_callback(vm, cb, this_arg, &[elem, JsValue::int(k as i32)]) { + NativeResult::Ok(m) => m, + NativeResult::Err(e) => return NativeResult::Err(e), + NativeResult::TailCall { .. } => { + return NativeResult::Err(type_error(vm, "unexpected tail call in TypedArray.from callback")) + } + }, + None => elem, + }; + if let Err(e) = set_typed_array_element(vm, new_obj, k, mapped) { + return NativeResult::Err(e); + } + } + NativeResult::Ok(new_obj) } diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 573ac49e..7e0eab8f 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -44,6 +44,9 @@ pub fn is_int_literal(value: f64) -> bool { /// 判断表达式是否无副作用(字面量/标识符/纯二元运算等)。 /// 用于可丢弃值的优化路径。 +/// 逻辑运算符快速路径的"无副作用"判定:仅字面量/标识符/this 读取安全。 +/// 算术表达式(`1 / a` 等)不得判为无副作用——对象操作数强转(ToNumber 触发 +/// valueOf/toString/getter)可能在运行期抛错,急切求值会破坏 `||`/`&&` 短路。 pub fn is_side_effect_free(expr: &Expression) -> bool { let mut stack = vec![expr]; while let Some(expr) = stack.pop() { @@ -56,13 +59,6 @@ pub fn is_side_effect_free(expr: &Expression) -> bool { | Expression::RegExpLiteral(_) | Expression::ThisExpression(_) => {} Expression::ParenthesizedExpression(p) => stack.push(&p.expression), - Expression::BinaryExpression(bin) => { - stack.push(&bin.left); - stack.push(&bin.right); - } - Expression::UnaryExpression(un) if !matches!(un.operator, UnaryOperator::Delete) => { - stack.push(&un.argument); - } _ => return false, } } @@ -362,7 +358,9 @@ impl CompileCtx { pub(crate) fn lookup_or_builtin(&mut self, name: &str) -> Result { match self.scopes.symbols.lookup(name) { Ok(reg) => Ok(reg), - Err(err) if Self::is_known_builtin(name) && err.contains("is not defined") => { + // 未声明标识符按全局处理:`typeof X` 守卫等合法 JS 应在运行期判定 + // 未定义(读取未定义全局返回 undefined),而非编译期报错。 + Err(err) if err.contains("is not defined") => { let reg = self.alloc_reg(); self.scopes.symbols.pre_register_global(name, reg); self.scopes.builtin_reg_map.push((name.to_string(), reg)); diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 8d3713c2..ac7eb193 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -209,6 +209,7 @@ pub struct BuiltinWorld { pub data_view_constructor: P, pub data_view_proto: P, pub typed_array_proto: P, + pub typed_array_constructor: P, pub int8array_constructor: P, pub int8array_proto: P, pub uint8array_constructor: P, @@ -328,6 +329,7 @@ struct ErrorSubtypeProtos { } struct TypedArrayFamily { + typed_array_constructor: P, typed_array_proto: P, int8array_constructor: P, int8array_proto: P, @@ -353,11 +355,40 @@ struct TypedArrayFamily { biguint64array_proto: P, } +/// 建 `%TypedArray%` 抽象构造器对象:name=`TypedArray`、`prototype` 指向共享原型, +/// 实际 native 实现由绑定层配置为恒抛 TypeError(不可 new 也不可调用)。 +fn make_typed_array_abstract_ctor( + string_forge: &PermInterner, shape_forge: &ShapeForge, labels: BuiltinLabels, typed_array_proto: &P, +) -> P { + let name_si = string_forge.intern("TypedArray").0; + let mut ctor = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); + let ctor_shape1 = shape_forge.make_shape(EMPTY_SHAPE_ID, labels.prototype); + let ctor_shape2 = shape_forge.make_shape(ctor_shape1, labels.name); + ctor.set_shape_id(ctor_shape2); + ctor.set_function(true); + ctor.ensure_hash_props() + .push(JsValue::from_js_object(typed_array_proto.as_ptr() as *mut JsObject)); + ctor.ensure_hash_props() + .push(JsValue::perm_string(string_forge.string_ptr(name_si))); + // .prototype 不可写不可枚举不可配置;.name 不可写不可枚举可配置。 + ctor.set_data_meta(0, PropAttributes::new(false, false, false)); + ctor.set_data_meta(1, PropAttributes::new(false, false, true)); + P::new(ctor) +} + fn make_typed_array_family( string_forge: &PermInterner, shape_forge: &ShapeForge, labels: BuiltinLabels, object_proto: &P, ) -> TypedArrayFamily { let obj_proto_val = JsValue::from_js_object(object_proto.as_ptr() as *mut JsObject); - let typed_array_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, obj_proto_val)); + // 给共享原型开 "constructor" 槽位(占位值在 wire 时填抽象构造器)。 + let mut typed_array_proto_obj = JsObject::new_empty(EMPTY_SHAPE_ID, obj_proto_val); + let ctor_si = labels.constructor; + let proto_shape = shape_forge.make_shape(typed_array_proto_obj.shape_id(), ctor_si); + typed_array_proto_obj.set_shape_id(proto_shape); + typed_array_proto_obj.ensure_hash_props().push(JsValue::undefined()); + typed_array_proto_obj.set_data_meta(0, PropAttributes::new(true, false, true)); + let typed_array_proto = P::new(typed_array_proto_obj); + let typed_array_constructor = make_typed_array_abstract_ctor(string_forge, shape_forge, labels, &typed_array_proto); let (int8array_proto, int8array_constructor) = make_named_pair(string_forge, shape_forge, labels, "Int8Array"); let (uint8array_proto, uint8array_constructor) = make_named_pair(string_forge, shape_forge, labels, "Uint8Array"); let (uint8clampedarray_proto, uint8clampedarray_constructor) = @@ -378,6 +409,7 @@ fn make_typed_array_family( make_named_pair(string_forge, shape_forge, labels, "BigUint64Array"); TypedArrayFamily { + typed_array_constructor, typed_array_proto, int8array_constructor, int8array_proto, @@ -495,6 +527,28 @@ fn wire_builtin_world_links(world: &BuiltinWorld) { for proto in &typed_array_protos { set_proto_if_changed(proto, typed_array_proto_val); } + + // 共享原型与抽象构造器互指(prototype/constructor);11 个具体构造器的 + // [[Prototype]] 指向 `%TypedArray%`,抽象构造器自身 [[Prototype]] 为 Function.prototype。 + wire_ctor_proto(&world.typed_array_constructor, &world.typed_array_proto); + set_proto_if_changed(&world.typed_array_constructor, world.fn_proto_val()); + let typed_array_ctor_val = JsValue::from_js_object(world.typed_array_constructor.as_ptr() as *mut JsObject); + let typed_array_ctors: [&P; 11] = [ + &world.int8array_constructor, + &world.uint8array_constructor, + &world.uint8clampedarray_constructor, + &world.int16array_constructor, + &world.uint16array_constructor, + &world.int32array_constructor, + &world.uint32array_constructor, + &world.float32array_constructor, + &world.float64array_constructor, + &world.bigint64array_constructor, + &world.biguint64array_constructor, + ]; + for ctor in &typed_array_ctors { + set_proto_if_changed(ctor, typed_array_ctor_val); + } } impl BuiltinWorld { @@ -652,6 +706,7 @@ impl BuiltinWorld { data_view_constructor, data_view_proto, typed_array_proto: typed_arrays.typed_array_proto, + typed_array_constructor: typed_arrays.typed_array_constructor, int8array_constructor: typed_arrays.int8array_constructor, int8array_proto: typed_arrays.int8array_proto, uint8array_constructor: typed_arrays.uint8array_constructor, @@ -828,6 +883,7 @@ impl BuiltinWorld { } else { TypedArrayFamily { typed_array_proto: current.typed_array_proto.clone(), + typed_array_constructor: current.typed_array_constructor.clone(), int8array_constructor: current.int8array_constructor.clone(), int8array_proto: current.int8array_proto.clone(), uint8array_constructor: current.uint8array_constructor.clone(), @@ -892,6 +948,7 @@ impl BuiltinWorld { data_view_constructor, data_view_proto, typed_array_proto: typed_arrays.typed_array_proto, + typed_array_constructor: typed_arrays.typed_array_constructor, int8array_constructor: typed_arrays.int8array_constructor, int8array_proto: typed_arrays.int8array_proto, uint8array_constructor: typed_arrays.uint8array_constructor, diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index 80e50779..52a86f1d 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -212,6 +212,7 @@ impl HarnessSources { ); sources.insert("typeCoercion.js", include_str!("../../../tests/test262/harness/typeCoercion.js")); sources.insert("deepEqual.js", include_str!("../../../tests/test262/harness/deepEqual.js")); + sources.insert("testTypedArray.js", include_str!("../../../tests/test262/harness/testTypedArray.js")); Self { sources } } @@ -227,8 +228,7 @@ static HARNESS: OnceLock = OnceLock::new(); fn is_blacklisted_harness(name: &str) -> bool { matches!( name, - "testTypedArray.js" - | "testIntl.js" + "testIntl.js" | "testAtomics.js" | "atomicsHelper.js" | "proxyTrapsHelper.js" diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 783554fb..496d3546 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -108,6 +108,23 @@ impl TypedArrayKind { Self::Float64 | Self::BigInt64 | Self::BigUint64 => 8, } } + + /// 对应的具体构造器名(如 `Int16Array`),供 `@@toStringTag` getter 返回。 + pub const fn name(self) -> &'static str { + match self { + Self::Int8 => "Int8Array", + Self::Uint8 => "Uint8Array", + Self::Uint8Clamped => "Uint8ClampedArray", + Self::Int16 => "Int16Array", + Self::Uint16 => "Uint16Array", + Self::Int32 => "Int32Array", + Self::Uint32 => "Uint32Array", + Self::Float32 => "Float32Array", + Self::Float64 => "Float64Array", + Self::BigInt64 => "BigInt64Array", + Self::BigUint64 => "BigUint64Array", + } + } } /// 属性描述符标志位集合,压缩在单个 `u8` 中。 diff --git a/crates/oxide_vm/src/bindings/bind_typed_array.rs b/crates/oxide_vm/src/bindings/bind_typed_array.rs index bd88ec92..aa46ac9c 100644 --- a/crates/oxide_vm/src/bindings/bind_typed_array.rs +++ b/crates/oxide_vm/src/bindings/bind_typed_array.rs @@ -1,11 +1,60 @@ use std::sync::Arc; use crate::bind_constructor; -use crate::bindings::{apply_binding_table, configure_native_constructor}; +use crate::bindings::{apply_binding_table, bind_accessor_getter, configure_native_constructor}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; +/// 把 `%TypedArray%` 抽象构造器的 native 实现配置为恒抛 TypeError(不可 new/不可调用), +/// 设置 `length` 属性为 0,并挂载静态 `of`/`from`(具体构造器经原型链继承)。 +fn bind_typed_array_abstract_ctor(core: &Arc, session: &KernelSession) { + let ctor_ptr = session.builtin_world().typed_array_constructor.as_ptr() as *mut JsObject; + let ctor = unsafe { &mut *ctor_ptr }; + configure_native_constructor( + ctor, + oxide_builtins::typed_array::typed_array_abstract_constructor:: as *const (), + 0, + ); + 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(JsValue::int(0)); + let pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(pos, PropAttributes::new(false, false, true)); + + let sf = core.perm_interner().as_ref(); + let sh = core.shape_forge().as_ref(); + let world = session.builtin_world(); + let ctor_ptr = world.typed_array_constructor.as_ptr() as *mut JsObject; + let ctor = unsafe { &mut *ctor_ptr }; + // SAFETY: of/from 是转成 *const () 的 NativeFn 函数项指针。 + let _ = world.bind_method( + ctor, + sh, + sf, + "of", + unsafe { + oxide_types::object::NativeFnPtr::from_raw( + oxide_builtins::typed_array::typed_array_of:: as *const (), + ) + }, + 0, + ); + let _ = world.bind_method( + ctor, + sh, + sf, + "from", + unsafe { + oxide_types::object::NativeFnPtr::from_raw( + oxide_builtins::typed_array::typed_array_from:: as *const (), + ) + }, + 1, + ); +} + /// 给构造器与对应原型设置 `BYTES_PER_ELEMENT`(只读、不可枚举、不可配置)。 /// /// # 步骤 @@ -34,8 +83,11 @@ fn set_bypes_per_element(core: &Arc, ctor_ptr: *mut JsObject, proto_ macro_rules! bind_typed_array_constructor { ($core:expr, $global:expr, $name:literal, $ctor_ptr:expr, $proto_ptr:expr, $bpe:expr, $ctor_fn:path) => {{ let ctor = unsafe { &mut *$ctor_ptr }; - configure_native_constructor(ctor, ($ctor_fn as fn(&mut $crate::vm::Vm, &[u8]) -> oxide_runtime_api::NativeResult) as *const (), 1); - bind_constructor!($core, $global, $name, $ctor_ptr, $ctor_fn, 1, hash: true); + configure_native_constructor(ctor, ($ctor_fn as fn(&mut $crate::vm::Vm, &[u8]) -> oxide_runtime_api::NativeResult) as *const (), 3); + bind_constructor!($core, $global, $name, $ctor_ptr, $ctor_fn, 3, hash: true); + // Function.length 描述符:不可写、不可枚举、可配置。 + let len_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(len_pos, PropAttributes::new(false, false, true)); set_bypes_per_element($core, $ctor_ptr, $proto_ptr, $bpe); }}; } @@ -58,14 +110,49 @@ pub fn bind_typed_array(core: &Arc, session: &KernelSession, global: oxide_builtins::typed_array::typed_array_subarray:: as *const (), 2, ), - ( - "toString", - oxide_builtins::typed_array::typed_array_to_string:: as *const (), - 0, - ), ], ); + // 原型访问器:视图属性(buffer/byteOffset/byteLength/length)读内部数据槽, + // @@toStringTag 返回具体类型名,供 Object.prototype.toString 区分类型。 + bind_accessor_getter( + core, + session, + shared_proto, + "buffer", + oxide_builtins::typed_array::typed_array_buffer_getter:: as *const (), + ); + bind_accessor_getter( + core, + session, + shared_proto, + "byteOffset", + oxide_builtins::typed_array::typed_array_byte_offset_getter:: as *const (), + ); + bind_accessor_getter( + core, + session, + shared_proto, + "byteLength", + oxide_builtins::typed_array::typed_array_byte_length_getter:: as *const (), + ); + bind_accessor_getter( + core, + session, + shared_proto, + "length", + oxide_builtins::typed_array::typed_array_length_getter:: as *const (), + ); + bind_accessor_getter( + core, + session, + shared_proto, + "@@toStringTag", + oxide_builtins::typed_array::typed_array_to_string_tag_getter:: as *const (), + ); + + bind_typed_array_abstract_ctor(core, session); + bind_typed_array_constructor!( core, global, diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 07835220..75bfa473 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -502,7 +502,7 @@ pub fn bind_global_builtin_slots(core: &Arc, session: &KernelSession oxide_builtins::typed_array::biguint64array_constructor:: as *const (), ), ] { - configure_existing_ctor(ctor, native_fn, 1); + configure_existing_ctor(ctor, native_fn, 3); bind_existing_global(core, global, name, JsValue::from_js_object(ctor.as_ptr() as *mut JsObject)); } diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 5a23e480..0a6d1110 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -323,7 +323,10 @@ impl Vm { return Err(self.error_message_text("TypeError", "Symbol.toPrimitive is not a function")); } let hint_val = self.new_string(if prefer_string { "string" } else { "number" }); - let result = self.call_function_sync(exotic, value, &[hint_val])?; + let result = match self.call_function_sync(exotic, value, &[hint_val]) { + Ok(r) => r, + Err(err) => return self.raise_call_error(&err), + }; if result.is_object() { return Err(self.error_message_text("TypeError", "Cannot convert object to primitive value")); } @@ -351,7 +354,10 @@ impl Vm { return Ok(JsValue::undefined()); } - let result = self.call_function_sync(method, value, &[])?; + let result = match self.call_function_sync(method, value, &[]) { + Ok(r) => r, + Err(err) => return self.raise_call_error(&err), + }; if !result.is_object() { return Ok(result); } @@ -360,6 +366,28 @@ impl Vm { Err(self.error_message_text("TypeError", "Cannot convert object to primitive value")) } + /// 把 `call_function_sync` 返回的调用错误恢复为原始异常值并走异常展开, + /// 使外围 try/catch 可捕获(native 函数抛错时原值存于 `last_uncaught_value`)。 + /// + /// # 注意事项 + /// 仅在主 dispatch(`native_call_depth == 0`)下展开——此时 try_stack 只含当前 + /// 字节码的处理器,展开后 pc/regs[0] 不会被中途的原生调用栈覆盖。原生 builtin + /// 内部(depth > 0)必须传播错误,由其调用边界(dispatch_native_call)转换。 + pub(crate) fn raise_call_error(&mut self, err: &str) -> Result { + if self.native_call_depth == 0 { + let exc = self + .last_uncaught_value + .take() + .unwrap_or_else(|| oxide_builtins::error::create_error(self, err)); + let kind = self.thrown_error_kind(exc); + self.exception_value = Some(exc); + self.pending_error_kind = Some(kind); + self.unwind()?; + return Ok(JsValue::undefined()); + } + Err(err.to_string()) + } + pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result { let primitive = self.coerce_primitive_bounded(value, false)?; Ok(coercion::to_number(primitive)) diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index d074e92c..ed1b87ef 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -108,7 +108,10 @@ impl Vm { } if getter_obj.native_fn().is_some() { - let result = self.call_function_sync(getter, receiver, &[])?; + let result = match self.call_function_sync(getter, receiver, &[]) { + Ok(v) => v, + Err(err) => self.raise_call_error(&err)?, + }; self.regs[target_reg as usize] = result; return Ok(false); } diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index eb5e83e6..7995dfd1 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -132,7 +132,7 @@ fn typed_array_bigint_fallback_returns_float() { fn typed_array_to_string_is_identifiable() { let mut vm = Vm::new(); let result = eval(&mut vm, "var ta = new Uint8Array(1); ta.toString()").unwrap(); - assert_eq!(to_str(&vm, result), "[object TypedArray]"); + assert_eq!(to_str(&vm, result), "[object Uint8Array]"); } #[test]