feat: 添加 TypedArray 全局构造器
This commit is contained in:
@@ -199,6 +199,29 @@ pub struct BuiltinWorld {
|
||||
pub array_buffer_proto: P<JsObject>,
|
||||
pub data_view_constructor: P<JsObject>,
|
||||
pub data_view_proto: P<JsObject>,
|
||||
pub typed_array_proto: P<JsObject>,
|
||||
pub int8array_constructor: P<JsObject>,
|
||||
pub int8array_proto: P<JsObject>,
|
||||
pub uint8array_constructor: P<JsObject>,
|
||||
pub uint8array_proto: P<JsObject>,
|
||||
pub uint8clampedarray_constructor: P<JsObject>,
|
||||
pub uint8clampedarray_proto: P<JsObject>,
|
||||
pub int16array_constructor: P<JsObject>,
|
||||
pub int16array_proto: P<JsObject>,
|
||||
pub uint16array_constructor: P<JsObject>,
|
||||
pub uint16array_proto: P<JsObject>,
|
||||
pub int32array_constructor: P<JsObject>,
|
||||
pub int32array_proto: P<JsObject>,
|
||||
pub uint32array_constructor: P<JsObject>,
|
||||
pub uint32array_proto: P<JsObject>,
|
||||
pub float32array_constructor: P<JsObject>,
|
||||
pub float32array_proto: P<JsObject>,
|
||||
pub float64array_constructor: P<JsObject>,
|
||||
pub float64array_proto: P<JsObject>,
|
||||
pub bigint64array_constructor: P<JsObject>,
|
||||
pub bigint64array_proto: P<JsObject>,
|
||||
pub biguint64array_constructor: P<JsObject>,
|
||||
pub biguint64array_proto: P<JsObject>,
|
||||
pub sym_match: P<JsObject>,
|
||||
pub sym_replace: P<JsObject>,
|
||||
pub sym_search: P<JsObject>,
|
||||
@@ -293,6 +316,30 @@ impl BuiltinWorld {
|
||||
make_pair(string_forge, shape_forge, "ArrayBuffer", si_prototype, si_constructor, si_name);
|
||||
let (data_view_proto, data_view_constructor) =
|
||||
make_pair(string_forge, shape_forge, "DataView", si_prototype, si_constructor, si_name);
|
||||
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));
|
||||
let (int8array_proto, int8array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "Int8Array", si_prototype, si_constructor, si_name);
|
||||
let (uint8array_proto, uint8array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "Uint8Array", si_prototype, si_constructor, si_name);
|
||||
let (uint8clampedarray_proto, uint8clampedarray_constructor) =
|
||||
make_pair(string_forge, shape_forge, "Uint8ClampedArray", si_prototype, si_constructor, si_name);
|
||||
let (int16array_proto, int16array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "Int16Array", si_prototype, si_constructor, si_name);
|
||||
let (uint16array_proto, uint16array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "Uint16Array", si_prototype, si_constructor, si_name);
|
||||
let (int32array_proto, int32array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "Int32Array", si_prototype, si_constructor, si_name);
|
||||
let (uint32array_proto, uint32array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "Uint32Array", si_prototype, si_constructor, si_name);
|
||||
let (float32array_proto, float32array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "Float32Array", si_prototype, si_constructor, si_name);
|
||||
let (float64array_proto, float64array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "Float64Array", si_prototype, si_constructor, si_name);
|
||||
let (bigint64array_proto, bigint64array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "BigInt64Array", si_prototype, si_constructor, si_name);
|
||||
let (biguint64array_proto, biguint64array_constructor) =
|
||||
make_pair(string_forge, shape_forge, "BigUint64Array", si_prototype, si_constructor, si_name);
|
||||
|
||||
let sym_match = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let sym_replace = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
@@ -332,10 +379,20 @@ impl BuiltinWorld {
|
||||
wire_ctor_proto(&map_constructor, &map_proto);
|
||||
wire_ctor_proto(&array_buffer_constructor, &array_buffer_proto);
|
||||
wire_ctor_proto(&data_view_constructor, &data_view_proto);
|
||||
wire_ctor_proto(&int8array_constructor, &int8array_proto);
|
||||
wire_ctor_proto(&uint8array_constructor, &uint8array_proto);
|
||||
wire_ctor_proto(&uint8clampedarray_constructor, &uint8clampedarray_proto);
|
||||
wire_ctor_proto(&int16array_constructor, &int16array_proto);
|
||||
wire_ctor_proto(&uint16array_constructor, &uint16array_proto);
|
||||
wire_ctor_proto(&int32array_constructor, &int32array_proto);
|
||||
wire_ctor_proto(&uint32array_constructor, &uint32array_proto);
|
||||
wire_ctor_proto(&float32array_constructor, &float32array_proto);
|
||||
wire_ctor_proto(&float64array_constructor, &float64array_proto);
|
||||
wire_ctor_proto(&bigint64array_constructor, &bigint64array_proto);
|
||||
wire_ctor_proto(&biguint64array_constructor, &biguint64array_proto);
|
||||
|
||||
// Wire prototype chains: all constructor prototypes inherit from Object.prototype.
|
||||
let obj_proto_val = JsValue::from_js_object(object_proto.as_ptr() as *mut JsObject);
|
||||
let non_object_protos: [&P<JsObject>; 13] = [
|
||||
let non_object_protos: [&P<JsObject>; 14] = [
|
||||
&array_proto,
|
||||
&function_proto,
|
||||
&string_proto,
|
||||
@@ -349,11 +406,30 @@ impl BuiltinWorld {
|
||||
®exp_proto,
|
||||
&array_buffer_proto,
|
||||
&data_view_proto,
|
||||
&typed_array_proto,
|
||||
];
|
||||
for proto in &non_object_protos {
|
||||
let p = proto.as_ptr() as *mut JsObject;
|
||||
unsafe { &mut *p }.set_proto(obj_proto_val).ok();
|
||||
}
|
||||
let typed_array_proto_val = JsValue::from_js_object(typed_array_proto.as_ptr() as *mut JsObject);
|
||||
let typed_array_protos: [&P<JsObject>; 11] = [
|
||||
&int8array_proto,
|
||||
&uint8array_proto,
|
||||
&uint8clampedarray_proto,
|
||||
&int16array_proto,
|
||||
&uint16array_proto,
|
||||
&int32array_proto,
|
||||
&uint32array_proto,
|
||||
&float32array_proto,
|
||||
&float64array_proto,
|
||||
&bigint64array_proto,
|
||||
&biguint64array_proto,
|
||||
];
|
||||
for proto in &typed_array_protos {
|
||||
let p = proto.as_ptr() as *mut JsObject;
|
||||
unsafe { &mut *p }.set_proto(typed_array_proto_val).ok();
|
||||
}
|
||||
wire_ctor_proto(®exp_constructor, ®exp_proto);
|
||||
|
||||
Self {
|
||||
@@ -393,6 +469,29 @@ impl BuiltinWorld {
|
||||
array_buffer_proto,
|
||||
data_view_constructor,
|
||||
data_view_proto,
|
||||
typed_array_proto,
|
||||
int8array_constructor,
|
||||
int8array_proto,
|
||||
uint8array_constructor,
|
||||
uint8array_proto,
|
||||
uint8clampedarray_constructor,
|
||||
uint8clampedarray_proto,
|
||||
int16array_constructor,
|
||||
int16array_proto,
|
||||
uint16array_constructor,
|
||||
uint16array_proto,
|
||||
int32array_constructor,
|
||||
int32array_proto,
|
||||
uint32array_constructor,
|
||||
uint32array_proto,
|
||||
float32array_constructor,
|
||||
float32array_proto,
|
||||
float64array_constructor,
|
||||
float64array_proto,
|
||||
bigint64array_constructor,
|
||||
bigint64array_proto,
|
||||
biguint64array_constructor,
|
||||
biguint64array_proto,
|
||||
sym_match,
|
||||
sym_replace,
|
||||
sym_search,
|
||||
|
||||
@@ -41,6 +41,32 @@ unsafe impl Sync for NativeFnPtr {}
|
||||
|
||||
pub type ShapeId = u32;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TypedArrayKind {
|
||||
Int8,
|
||||
Uint8,
|
||||
Uint8Clamped,
|
||||
Int16,
|
||||
Uint16,
|
||||
Int32,
|
||||
Uint32,
|
||||
Float32,
|
||||
Float64,
|
||||
BigInt64,
|
||||
BigUint64,
|
||||
}
|
||||
|
||||
impl TypedArrayKind {
|
||||
pub const fn bytes_per_element(self) -> usize {
|
||||
match self {
|
||||
Self::Int8 | Self::Uint8 | Self::Uint8Clamped => 1,
|
||||
Self::Int16 | Self::Uint16 => 2,
|
||||
Self::Int32 | Self::Uint32 | Self::Float32 => 4,
|
||||
Self::Float64 | Self::BigInt64 | Self::BigUint64 => 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct PropAttributes(pub u8);
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::bind_constructor_hash;
|
||||
use crate::bindings::{apply_binding_table, configure_native_constructor};
|
||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||
use oxide_types::object::JsObject;
|
||||
|
||||
macro_rules! bind_typed_array_constructor {
|
||||
($core:expr, $global:expr, $name:literal, $ctor_ptr:expr, $ctor_fn:path) => {{
|
||||
let ctor = unsafe { &mut *$ctor_ptr };
|
||||
configure_native_constructor(ctor, $ctor_fn as *const (), 1);
|
||||
bind_constructor_hash!($core, $global, $name, $ctor_ptr, $ctor_fn, 1);
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
||||
let shared_proto_ptr = session.builtin_world().typed_array_proto.as_ptr() as *mut JsObject;
|
||||
let shared_proto = unsafe { &mut *shared_proto_ptr };
|
||||
apply_binding_table(
|
||||
session.builtin_world(),
|
||||
shared_proto,
|
||||
core,
|
||||
&[
|
||||
("at", crate::builtins::typed_array::typed_array_at as *const (), 1),
|
||||
("fill", crate::builtins::typed_array::typed_array_fill as *const (), 3),
|
||||
("set", crate::builtins::typed_array::typed_array_set as *const (), 2),
|
||||
("slice", crate::builtins::typed_array::typed_array_slice as *const (), 2),
|
||||
("subarray", crate::builtins::typed_array::typed_array_subarray as *const (), 2),
|
||||
("toString", crate::builtins::typed_array::typed_array_to_string as *const (), 0),
|
||||
],
|
||||
);
|
||||
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"Int8Array",
|
||||
session.builtin_world().int8array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::int8array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"Uint8Array",
|
||||
session.builtin_world().uint8array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::uint8array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"Uint8ClampedArray",
|
||||
session.builtin_world().uint8clampedarray_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::uint8clampedarray_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"Int16Array",
|
||||
session.builtin_world().int16array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::int16array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"Uint16Array",
|
||||
session.builtin_world().uint16array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::uint16array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"Int32Array",
|
||||
session.builtin_world().int32array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::int32array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"Uint32Array",
|
||||
session.builtin_world().uint32array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::uint32array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"Float32Array",
|
||||
session.builtin_world().float32array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::float32array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"Float64Array",
|
||||
session.builtin_world().float64array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::float64array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"BigInt64Array",
|
||||
session.builtin_world().bigint64array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::bigint64array_constructor
|
||||
);
|
||||
bind_typed_array_constructor!(
|
||||
core,
|
||||
global,
|
||||
"BigUint64Array",
|
||||
session.builtin_world().biguint64array_constructor.as_ptr() as *mut JsObject,
|
||||
crate::builtins::typed_array::biguint64array_constructor
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ pub mod bind_set;
|
||||
pub mod bind_string;
|
||||
pub mod bind_stubs;
|
||||
pub mod bind_symbol;
|
||||
pub mod bind_typed_array;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -91,6 +92,7 @@ pub fn init_kernel_builtins(core: &Arc<KernelCore>, session: &mut KernelSession)
|
||||
bind_array::bind_array(core, session, global);
|
||||
bind_array_buffer::bind_array_buffer(core, session, global);
|
||||
bind_data_view::bind_data_view(core, session, global);
|
||||
bind_typed_array::bind_typed_array(core, session, global);
|
||||
bind_error::bind_error(core, session, global);
|
||||
bind_string::bind_string(core, session, global);
|
||||
bind_number::bind_number(core, session, global);
|
||||
|
||||
@@ -50,7 +50,7 @@ fn set_named_prop(vm: &mut Vm, obj: &mut JsObject, name: &str, value: JsValue, a
|
||||
let _ = vm.define_data_property(obj, si, value, attributes);
|
||||
}
|
||||
|
||||
fn new_array_buffer(vm: &mut Vm, data: Vec<u8>) -> *mut JsObject {
|
||||
pub(crate) fn new_array_buffer(vm: &mut Vm, data: Vec<u8>) -> *mut JsObject {
|
||||
let proto = vm.session().builtin_world().array_buffer_proto.as_ptr() as *mut JsObject;
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
|
||||
obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER;
|
||||
|
||||
@@ -18,3 +18,4 @@ pub mod set;
|
||||
pub mod string;
|
||||
pub mod stubs;
|
||||
pub mod symbol;
|
||||
pub mod typed_array;
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes, TypedArrayKind};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::builtins::array_buffer::{array_buffer_data_ptr, new_array_buffer};
|
||||
use crate::coercion;
|
||||
use crate::native::NativeResult;
|
||||
use crate::vm::Vm;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct TypedArrayData {
|
||||
pub kind: TypedArrayKind,
|
||||
pub buffer: JsValue,
|
||||
pub byte_offset: usize,
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
macro_rules! native_try {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Ok(value) => value,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn type_error(vm: &mut Vm, msg: &str) -> JsValue {
|
||||
crate::builtins::error::create_type_error(vm, msg)
|
||||
}
|
||||
|
||||
fn range_error(vm: &mut Vm, msg: &str) -> JsValue {
|
||||
crate::builtins::error::create_range_error(vm, msg)
|
||||
}
|
||||
|
||||
fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result<usize, JsValue> {
|
||||
let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref());
|
||||
if n.is_nan() {
|
||||
return Ok(0);
|
||||
}
|
||||
if !n.is_finite() || n < 0.0 {
|
||||
return Err(range_error(vm, msg));
|
||||
}
|
||||
Ok(n.trunc() as usize)
|
||||
}
|
||||
|
||||
fn normalize_index(vm: &mut Vm, value: JsValue, len: usize) -> usize {
|
||||
let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref());
|
||||
if n.is_nan() {
|
||||
return 0;
|
||||
}
|
||||
let int = n.trunc() as isize;
|
||||
if int < 0 {
|
||||
len.saturating_sub((-int) as usize)
|
||||
} else {
|
||||
(int as usize).min(len)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_named_prop(vm: &mut Vm, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) {
|
||||
let si = vm.kernel_core().string_forge().intern(name).0;
|
||||
let _ = vm.define_data_property(obj, si, value, attributes);
|
||||
}
|
||||
|
||||
fn typed_array_proto_ptr(vm: &mut Vm, kind: TypedArrayKind) -> *mut JsObject {
|
||||
let world = vm.session().builtin_world();
|
||||
match kind {
|
||||
TypedArrayKind::Int8 => world.int8array_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::Uint8 => world.uint8array_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::Uint8Clamped => world.uint8clampedarray_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::Int16 => world.int16array_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::Uint16 => world.uint16array_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::Int32 => world.int32array_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::Uint32 => world.uint32array_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::Float32 => world.float32array_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::Float64 => world.float64array_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::BigInt64 => world.bigint64array_proto.as_ptr() as *mut JsObject,
|
||||
TypedArrayKind::BigUint64 => world.biguint64array_proto.as_ptr() as *mut JsObject,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_typed_array(
|
||||
vm: &mut Vm, kind: TypedArrayKind, buffer: JsValue, byte_offset: usize, length: usize,
|
||||
) -> *mut JsObject {
|
||||
let proto = typed_array_proto_ptr(vm, kind);
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto));
|
||||
obj.type_tag = JsObject::OBJ_TYPE_TYPED_ARRAY;
|
||||
let data = Box::into_raw(Box::new(TypedArrayData {
|
||||
kind,
|
||||
buffer,
|
||||
byte_offset,
|
||||
length,
|
||||
}));
|
||||
// SAFETY: TypedArray instances are not callable; native_fn stores opaque Box<TypedArrayData>,
|
||||
// matching ArrayBuffer/DataView typed-object storage in this VM.
|
||||
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.epoch().alloc(obj)
|
||||
}
|
||||
|
||||
pub(crate) fn get_typed_array_data(vm: &mut Vm, this_val: JsValue) -> Result<TypedArrayData, JsValue> {
|
||||
if !this_val.is_object() {
|
||||
return Err(type_error(vm, "TypedArray method called on incompatible receiver"));
|
||||
}
|
||||
let obj_ptr = this_val.as_js_object_ptr();
|
||||
if obj_ptr.is_null() {
|
||||
return Err(type_error(vm, "TypedArray internal state invalid"));
|
||||
}
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
if !obj.is_typed_array_obj() {
|
||||
return Err(type_error(vm, "TypedArray method called on incompatible receiver"));
|
||||
}
|
||||
let Some(data_ptr) = obj.native_fn() else {
|
||||
return Err(type_error(vm, "TypedArray internal state invalid"));
|
||||
};
|
||||
let data_ptr = data_ptr.as_ptr() as *const TypedArrayData;
|
||||
if data_ptr.is_null() {
|
||||
return Err(type_error(vm, "TypedArray internal state invalid"));
|
||||
}
|
||||
Ok(unsafe { *data_ptr })
|
||||
}
|
||||
|
||||
fn absolute_byte_offset(view: TypedArrayData, index: usize) -> usize {
|
||||
view.byte_offset + index * view.kind.bytes_per_element()
|
||||
}
|
||||
|
||||
fn read_element(kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue {
|
||||
match kind {
|
||||
TypedArrayKind::Int8 => JsValue::int(bytes[offset] as i8 as i32),
|
||||
TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => JsValue::int(bytes[offset] as i32),
|
||||
TypedArrayKind::Int16 => JsValue::int(i16::from_ne_bytes(bytes[offset..offset + 2].try_into().unwrap()) as i32),
|
||||
TypedArrayKind::Uint16 => {
|
||||
JsValue::int(u16::from_ne_bytes(bytes[offset..offset + 2].try_into().unwrap()) as i32)
|
||||
}
|
||||
TypedArrayKind::Int32 => JsValue::int(i32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap())),
|
||||
TypedArrayKind::Uint32 => {
|
||||
JsValue::float(u32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap()) as f64)
|
||||
}
|
||||
TypedArrayKind::Float32 => {
|
||||
JsValue::float(f32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap()) as f64)
|
||||
}
|
||||
TypedArrayKind::Float64 => JsValue::float(f64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap())),
|
||||
TypedArrayKind::BigInt64 => {
|
||||
JsValue::float(i64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap()) as f64)
|
||||
}
|
||||
TypedArrayKind::BigUint64 => {
|
||||
JsValue::float(u64::from_ne_bytes(bytes[offset..offset + 8].try_into().unwrap()) as f64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn numeric_value(vm: &mut Vm, value: JsValue) -> f64 {
|
||||
coercion::to_number(value, vm.kernel_core().string_forge().as_ref())
|
||||
}
|
||||
|
||||
fn write_element(kind: TypedArrayKind, bytes: &mut [u8], offset: usize, value: f64) {
|
||||
match kind {
|
||||
TypedArrayKind::Int8 => bytes[offset] = value as i32 as u8 as i8 as u8,
|
||||
TypedArrayKind::Uint8 => bytes[offset] = value as i32 as u8,
|
||||
TypedArrayKind::Uint8Clamped => bytes[offset] = value.clamp(0.0, 255.0).round() as u8,
|
||||
TypedArrayKind::Int16 => bytes[offset..offset + 2].copy_from_slice(&(value as i32 as u16 as i16).to_ne_bytes()),
|
||||
TypedArrayKind::Uint16 => bytes[offset..offset + 2].copy_from_slice(&(value as i32 as u16).to_ne_bytes()),
|
||||
TypedArrayKind::Int32 => bytes[offset..offset + 4].copy_from_slice(&(value as i32).to_ne_bytes()),
|
||||
TypedArrayKind::Uint32 => bytes[offset..offset + 4].copy_from_slice(&(value as u32).to_ne_bytes()),
|
||||
TypedArrayKind::Float32 => bytes[offset..offset + 4].copy_from_slice(&(value as f32).to_ne_bytes()),
|
||||
TypedArrayKind::Float64 => bytes[offset..offset + 8].copy_from_slice(&value.to_ne_bytes()),
|
||||
TypedArrayKind::BigInt64 => bytes[offset..offset + 8].copy_from_slice(&(value as i64).to_ne_bytes()),
|
||||
TypedArrayKind::BigUint64 => bytes[offset..offset + 8].copy_from_slice(&(value as u64).to_ne_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_array_like(vm: &mut Vm, value: JsValue) -> Result<Vec<JsValue>, JsValue> {
|
||||
if !value.is_object() {
|
||||
return Err(type_error(vm, "TypedArray source must be array-like"));
|
||||
}
|
||||
let ptr = value.as_js_object_ptr();
|
||||
if ptr.is_null() {
|
||||
return Err(type_error(vm, "TypedArray source must be array-like"));
|
||||
}
|
||||
let obj = unsafe { &*ptr };
|
||||
if obj.is_array() {
|
||||
let len = obj.prop_count() as usize;
|
||||
return Ok((0..len).map(|i| obj.get_prop_at(i)).collect());
|
||||
}
|
||||
if obj.is_typed_array_obj() {
|
||||
let view = get_typed_array_data(vm, value)?;
|
||||
let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?;
|
||||
let buffer = unsafe { &*buffer_ptr };
|
||||
return Ok((0..view.length)
|
||||
.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"))
|
||||
}
|
||||
|
||||
fn typed_array_new(vm: &mut Vm, args: &[u8], kind: TypedArrayKind) -> NativeResult {
|
||||
let first = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::int(0) };
|
||||
let bpe = kind.bytes_per_element();
|
||||
|
||||
let (buffer, byte_offset, length) = if first.is_int() || first.is_double() || first.is_undefined() {
|
||||
let len = native_try!(to_index(vm, first, "invalid TypedArray length"));
|
||||
let Some(byte_len) = len.checked_mul(bpe) else {
|
||||
return NativeResult::Err(range_error(vm, "invalid TypedArray length"));
|
||||
};
|
||||
let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len]));
|
||||
(buffer, 0, len)
|
||||
} else if first.is_object() {
|
||||
let first_ptr = first.as_js_object_ptr();
|
||||
let first_obj = unsafe { &*first_ptr };
|
||||
if first_obj.is_array_buffer_obj() {
|
||||
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, first));
|
||||
let buffer_len = unsafe { (*buffer_ptr).len() };
|
||||
let byte_offset = if args.len() > 2 {
|
||||
native_try!(to_index(vm, vm.reg(args[2]), "TypedArray byteOffset out of bounds"))
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if byte_offset > buffer_len || byte_offset % bpe != 0 {
|
||||
return NativeResult::Err(range_error(vm, "TypedArray byteOffset out of bounds"));
|
||||
}
|
||||
let remaining = buffer_len - byte_offset;
|
||||
let length = if args.len() > 3 {
|
||||
native_try!(to_index(vm, vm.reg(args[3]), "TypedArray length out of bounds"))
|
||||
} else {
|
||||
remaining / bpe
|
||||
};
|
||||
let Some(byte_length) = length.checked_mul(bpe) else {
|
||||
return NativeResult::Err(range_error(vm, "TypedArray length out of bounds"));
|
||||
};
|
||||
if byte_length > remaining {
|
||||
return NativeResult::Err(range_error(vm, "TypedArray length out of bounds"));
|
||||
}
|
||||
(first, byte_offset, length)
|
||||
} else {
|
||||
let values = native_try!(collect_array_like(vm, first));
|
||||
let byte_len = values.len().saturating_mul(bpe);
|
||||
let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; byte_len]));
|
||||
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, buffer));
|
||||
let buffer_ref = unsafe { &mut *buffer_ptr };
|
||||
for (idx, value) in values.into_iter().enumerate() {
|
||||
let n = numeric_value(vm, value);
|
||||
write_element(kind, buffer_ref, idx * bpe, n);
|
||||
}
|
||||
(buffer, 0, byte_len / bpe)
|
||||
}
|
||||
} else {
|
||||
return NativeResult::Err(type_error(vm, "invalid TypedArray constructor argument"));
|
||||
};
|
||||
|
||||
NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, kind, buffer, byte_offset, length)))
|
||||
}
|
||||
|
||||
macro_rules! typed_array_ctor {
|
||||
($name:ident, $kind:ident) => {
|
||||
pub fn $name(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
typed_array_new(vm, args, TypedArrayKind::$kind)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
typed_array_ctor!(int8array_constructor, Int8);
|
||||
typed_array_ctor!(uint8array_constructor, Uint8);
|
||||
typed_array_ctor!(uint8clampedarray_constructor, Uint8Clamped);
|
||||
typed_array_ctor!(int16array_constructor, Int16);
|
||||
typed_array_ctor!(uint16array_constructor, Uint16);
|
||||
typed_array_ctor!(int32array_constructor, Int32);
|
||||
typed_array_ctor!(uint32array_constructor, Uint32);
|
||||
typed_array_ctor!(float32array_constructor, Float32);
|
||||
typed_array_ctor!(float64array_constructor, Float64);
|
||||
typed_array_ctor!(bigint64array_constructor, BigInt64);
|
||||
typed_array_ctor!(biguint64array_constructor, BigUint64);
|
||||
|
||||
pub fn typed_array_at(vm: &mut Vm, 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));
|
||||
let raw_index = if args.len() > 1 {
|
||||
numeric_value(vm, vm.reg(args[1])).trunc() as isize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let idx = if raw_index < 0 { view.length as isize + raw_index } else { raw_index };
|
||||
if idx < 0 || idx as usize >= view.length {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
|
||||
let buffer = unsafe { &*buffer_ptr };
|
||||
NativeResult::Ok(read_element(view.kind, buffer, absolute_byte_offset(view, idx as usize)))
|
||||
}
|
||||
|
||||
pub fn typed_array_fill(vm: &mut Vm, 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));
|
||||
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let start = if args.len() > 2 {
|
||||
normalize_index(vm, vm.reg(args[2]), view.length)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let end = if args.len() > 3 {
|
||||
normalize_index(vm, vm.reg(args[3]), view.length)
|
||||
} else {
|
||||
view.length
|
||||
};
|
||||
let n = numeric_value(vm, value);
|
||||
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
|
||||
let buffer = unsafe { &mut *buffer_ptr };
|
||||
for idx in start..end.max(start) {
|
||||
write_element(view.kind, buffer, absolute_byte_offset(view, idx), n);
|
||||
}
|
||||
NativeResult::Ok(this_val)
|
||||
}
|
||||
|
||||
pub fn typed_array_slice(vm: &mut Vm, 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));
|
||||
let start = if args.len() > 1 {
|
||||
normalize_index(vm, vm.reg(args[1]), view.length)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let end = if args.len() > 2 {
|
||||
normalize_index(vm, vm.reg(args[2]), view.length)
|
||||
} else {
|
||||
view.length
|
||||
};
|
||||
let count = end.max(start).saturating_sub(start);
|
||||
let bpe = view.kind.bytes_per_element();
|
||||
let src_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
|
||||
let src = unsafe { &*src_ptr };
|
||||
let mut out = vec![0u8; count * bpe];
|
||||
let src_start = absolute_byte_offset(view, start);
|
||||
let src_end = src_start + count * bpe;
|
||||
out.copy_from_slice(&src[src_start..src_end]);
|
||||
let buffer = JsValue::from_js_object(new_array_buffer(vm, out));
|
||||
NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, view.kind, buffer, 0, count)))
|
||||
}
|
||||
|
||||
pub fn typed_array_subarray(vm: &mut Vm, 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));
|
||||
let start = if args.len() > 1 {
|
||||
normalize_index(vm, vm.reg(args[1]), view.length)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let end = if args.len() > 2 {
|
||||
normalize_index(vm, vm.reg(args[2]), view.length)
|
||||
} else {
|
||||
view.length
|
||||
};
|
||||
let count = end.max(start).saturating_sub(start);
|
||||
let byte_offset = view.byte_offset + start * view.kind.bytes_per_element();
|
||||
NativeResult::Ok(JsValue::from_js_object(create_typed_array(
|
||||
vm,
|
||||
view.kind,
|
||||
view.buffer,
|
||||
byte_offset,
|
||||
count,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn typed_array_set(vm: &mut Vm, 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));
|
||||
if args.len() < 2 {
|
||||
return NativeResult::Err(type_error(vm, "TypedArray.set source required"));
|
||||
}
|
||||
let source = vm.reg(args[1]);
|
||||
let offset = if args.len() > 2 {
|
||||
native_try!(to_index(vm, vm.reg(args[2]), "TypedArray.set offset out of bounds"))
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let values = native_try!(collect_array_like(vm, source));
|
||||
if offset > view.length || values.len() > view.length - offset {
|
||||
return NativeResult::Err(range_error(vm, "TypedArray.set offset out of bounds"));
|
||||
}
|
||||
let numbers: Vec<f64> = values.into_iter().map(|v| numeric_value(vm, v)).collect();
|
||||
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
|
||||
let buffer = unsafe { &mut *buffer_ptr };
|
||||
for (i, n) in numbers.into_iter().enumerate() {
|
||||
write_element(view.kind, buffer, absolute_byte_offset(view, offset + i), n);
|
||||
}
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
pub fn typed_array_to_string(vm: &mut Vm, 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.intern("[object TypedArray]"))
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
|
||||
vm.run(&module)
|
||||
}
|
||||
|
||||
fn to_str(vm: &Vm, val: JsValue) -> String {
|
||||
vm.kernel_core()
|
||||
.string_forge()
|
||||
.lookup(val.as_string_index())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_globals_exist() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"typeof Int8Array === 'function' && typeof Uint8Array === 'function' && \
|
||||
typeof Uint8ClampedArray === 'function' && typeof Int16Array === 'function' && \
|
||||
typeof Uint16Array === 'function' && typeof Int32Array === 'function' && \
|
||||
typeof Uint32Array === 'function' && typeof Float32Array === 'function' && \
|
||||
typeof Float64Array === 'function' && typeof BigInt64Array === 'function' && \
|
||||
typeof BigUint64Array === 'function'",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_length_constructor_sets_metadata() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Int16Array(4); ta.length === 4 && ta.byteLength === 8 && ta.byteOffset === 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_fill_and_at_read_elements() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var ta = new Int8Array(4); ta.fill(7); ta.at(0) === 7 && ta.at(3) === 7").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_uint8_clamped_clamps_values() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var ta = new Uint8ClampedArray(2); ta.fill(300); ta.at(0) === 255").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_slice_copies_values() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Int8Array([1, 2, 3, 4]); var out = ta.slice(1, 3); \
|
||||
out.length === 2 && out.at(0) === 2 && out.at(1) === 3",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_subarray_shares_buffer() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Int8Array([1, 2, 3, 4]); var sub = ta.subarray(1, 3); \
|
||||
sub.fill(9); ta.at(1) === 9 && ta.at(2) === 9",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_set_copies_from_array() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var ta = new Int32Array(4); var src = [5, 6]; var offset = 1; \
|
||||
ta.set(src, offset); ta.at(0) === 0 && ta.at(1) === 5 && ta.at(2) === 6",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_constructs_from_array_buffer() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var buf = new ArrayBuffer(8); var view = new Int32Array(buf); \
|
||||
view.length === 2 && view.byteLength === 8 && view.buffer === buf && ArrayBuffer.isView(view)",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_views_share_data_with_data_view() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var buf = new ArrayBuffer(8); var dv = new DataView(buf); var ta = new Uint8Array(buf); \
|
||||
ta.fill(12); dv.getUint8(0) === 12 && dv.getUint8(7) === 12",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_float64_roundtrips() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var ta = new Float64Array(1); ta.fill(3.5); ta.at(0)").unwrap();
|
||||
assert_eq!(result.as_double(), 3.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_array_bigint_fallback_returns_float() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var ta = new BigInt64Array(1); ta.fill(42); ta.at(0)").unwrap();
|
||||
assert_eq!(result.as_double(), 42.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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]");
|
||||
}
|
||||
Reference in New Issue
Block a user