mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: TypedArray 第一批解锁(%TypedArray% 构造器、原型访问器、of/from、bind 绑参、harness)
This commit is contained in:
@@ -108,7 +108,7 @@ fn arraylike_get<H: VmHost>(vm: &mut H, ptr: *mut JsObject, is_array: bool, i: u
|
||||
}
|
||||
}
|
||||
|
||||
fn require_callback<H: VmHost>(vm: &mut H, callback_val: JsValue) -> Result<JsValue, JsValue> {
|
||||
pub(crate) fn require_callback<H: VmHost>(vm: &mut H, callback_val: JsValue) -> Result<JsValue, JsValue> {
|
||||
if !callback_val.is_object() {
|
||||
return Err(array_type_error(vm, "callback is not a function"));
|
||||
}
|
||||
@@ -137,7 +137,7 @@ fn array_length_arg<H: VmHost>(vm: &mut H, value: JsValue) -> Result<usize, JsVa
|
||||
Ok(n as usize)
|
||||
}
|
||||
|
||||
fn invoke_native_callback<H: VmHost>(
|
||||
pub(crate) fn invoke_native_callback<H: VmHost>(
|
||||
vm: &mut H, callback_val: JsValue, this_val: JsValue, cb_args: &[JsValue],
|
||||
) -> NativeResult {
|
||||
if !callback_val.is_object() {
|
||||
|
||||
@@ -57,18 +57,21 @@ fn to_string_error_value<H: VmHost>(vm: &mut H, msg: &str) -> JsValue {
|
||||
fn bind_dispatcher<H: VmHost>(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<u8> = 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<JsValue> = 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
unsafe {
|
||||
(*wrapper).set_function(true);
|
||||
(*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher::<H> 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))
|
||||
|
||||
@@ -887,7 +887,8 @@ pub fn object_proto_value_of<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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<H: VmHost>(vm: &mut H, value: JsValue, len: usize) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_named_prop<H: VmHost>(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<H: VmHost>(vm: &mut H, kind: TypedArrayKind) -> *mut JsObject {
|
||||
let world = vm.session().builtin_world();
|
||||
match kind {
|
||||
@@ -92,30 +87,6 @@ fn create_typed_array<H: VmHost>(
|
||||
// SAFETY: TypedArray 实例不可调用,native_fn 存不透明 `Box<TypedArrayData>`,
|
||||
// 与本 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<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Vec<JsValue>, 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<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Vec<JsVal
|
||||
.map(|i| read_element(view.kind, buffer, absolute_byte_offset(view, i)))
|
||||
.collect());
|
||||
}
|
||||
Err(type_error(vm, "TypedArray source must be Array, ArrayBuffer, or TypedArray"))
|
||||
|
||||
// 有 @@iterator 走迭代路径(含用户自定义迭代器),否则按 array-like 读 length 逐索引取值。
|
||||
if crate::iterator::peek_iterator_method(vm, value)? {
|
||||
let mut values = Vec::new();
|
||||
crate::iterator::iterate_elements(vm, value, |_vm, elem| {
|
||||
values.push(elem);
|
||||
Ok(())
|
||||
})?;
|
||||
return Ok(values);
|
||||
}
|
||||
|
||||
let length_si = vm.kernel_core().perm_interner().intern("length").0;
|
||||
let len_val = vm
|
||||
.ordinary_get(obj, length_si, value)
|
||||
.map_err(|e| crate::iterator::engine_error(vm, &e))?;
|
||||
let n = oxide_runtime_api::to_number_full(len_val, vm).map_err(|e| 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);
|
||||
let elem = vm
|
||||
.ordinary_get(obj, key_si, value)
|
||||
.map_err(|e| crate::iterator::engine_error(vm, &e))?;
|
||||
values.push(elem);
|
||||
}
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
/// 按 ToLength 语义把 length 数值夹到收集上限:NaN/非正取 0,超出密集上限截断。
|
||||
fn to_collect_len(n: f64) -> 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<H: VmHost>(vm: &mut H, args: &[u8], kind: TypedArrayKind) -> NativeResult {
|
||||
@@ -559,9 +565,187 @@ pub fn typed_array_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `TypedArray.prototype.toString`:校验 receiver 后返回 `[object TypedArray]`。
|
||||
pub fn typed_array_to_string<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, c: JsValue, len: usize) -> Result<JsValue, JsValue> {
|
||||
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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)
|
||||
}
|
||||
|
||||
@@ -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<u32, String> {
|
||||
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));
|
||||
|
||||
@@ -209,6 +209,7 @@ pub struct BuiltinWorld {
|
||||
pub data_view_constructor: P<JsObject>,
|
||||
pub data_view_proto: P<JsObject>,
|
||||
pub typed_array_proto: P<JsObject>,
|
||||
pub typed_array_constructor: P<JsObject>,
|
||||
pub int8array_constructor: P<JsObject>,
|
||||
pub int8array_proto: P<JsObject>,
|
||||
pub uint8array_constructor: P<JsObject>,
|
||||
@@ -328,6 +329,7 @@ struct ErrorSubtypeProtos {
|
||||
}
|
||||
|
||||
struct TypedArrayFamily {
|
||||
typed_array_constructor: P<JsObject>,
|
||||
typed_array_proto: P<JsObject>,
|
||||
int8array_constructor: P<JsObject>,
|
||||
int8array_proto: P<JsObject>,
|
||||
@@ -353,11 +355,40 @@ struct TypedArrayFamily {
|
||||
biguint64array_proto: P<JsObject>,
|
||||
}
|
||||
|
||||
/// 建 `%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<JsObject>,
|
||||
) -> P<JsObject> {
|
||||
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<JsObject>,
|
||||
) -> 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<JsObject>; 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,
|
||||
|
||||
@@ -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<HarnessSources> = OnceLock::new();
|
||||
fn is_blacklisted_harness(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"testTypedArray.js"
|
||||
| "testIntl.js"
|
||||
"testIntl.js"
|
||||
| "testAtomics.js"
|
||||
| "atomicsHelper.js"
|
||||
| "proxyTrapsHelper.js"
|
||||
|
||||
@@ -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` 中。
|
||||
|
||||
@@ -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<KernelCore>, 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::<crate::vm::Vm> 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::<crate::vm::Vm> 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::<crate::vm::Vm> as *const (),
|
||||
)
|
||||
},
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
/// 给构造器与对应原型设置 `BYTES_PER_ELEMENT`(只读、不可枚举、不可配置)。
|
||||
///
|
||||
/// # 步骤
|
||||
@@ -34,8 +83,11 @@ fn set_bypes_per_element(core: &Arc<KernelCore>, 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<KernelCore>, session: &KernelSession, global:
|
||||
oxide_builtins::typed_array::typed_array_subarray::<crate::vm::Vm> as *const (),
|
||||
2,
|
||||
),
|
||||
(
|
||||
"toString",
|
||||
oxide_builtins::typed_array::typed_array_to_string::<crate::vm::Vm> 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::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
shared_proto,
|
||||
"byteOffset",
|
||||
oxide_builtins::typed_array::typed_array_byte_offset_getter::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
shared_proto,
|
||||
"byteLength",
|
||||
oxide_builtins::typed_array::typed_array_byte_length_getter::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
shared_proto,
|
||||
"length",
|
||||
oxide_builtins::typed_array::typed_array_length_getter::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
shared_proto,
|
||||
"@@toStringTag",
|
||||
oxide_builtins::typed_array::typed_array_to_string_tag_getter::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
|
||||
bind_typed_array_abstract_ctor(core, session);
|
||||
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
|
||||
@@ -502,7 +502,7 @@ pub fn bind_global_builtin_slots(core: &Arc<KernelCore>, session: &KernelSession
|
||||
oxide_builtins::typed_array::biguint64array_constructor::<crate::vm::Vm> 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<JsValue, String> {
|
||||
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<f64, String> {
|
||||
let primitive = self.coerce_primitive_bounded(value, false)?;
|
||||
Ok(coercion::to_number(primitive))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user