From 7216411658aeea33ba870a51b6b31e7f776c1eb7 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Thu, 12 Oct 2023 04:48:32 -0600 Subject: [PATCH 1/4] Implement `SharedArrayBuffer` --- Cargo.lock | 28 + boa_engine/Cargo.toml | 2 + boa_engine/src/builtins/array_buffer/mod.rs | 719 +-- .../src/builtins/array_buffer/shared.rs | 366 ++ boa_engine/src/builtins/array_buffer/tests.rs | 17 +- boa_engine/src/builtins/array_buffer/utils.rs | 437 ++ boa_engine/src/builtins/dataview/mod.rs | 314 +- boa_engine/src/builtins/mod.rs | 10 +- .../src/builtins/typed_array/builtin.rs | 3388 ++++++++++++++ .../src/builtins/typed_array/element.rs | 398 ++ .../typed_array/integer_indexed_object.rs | 77 +- boa_engine/src/builtins/typed_array/mod.rs | 4084 ++--------------- boa_engine/src/context/hooks.rs | 19 + boa_engine/src/context/intrinsics.rs | 14 + .../src/object/builtins/jsarraybuffer.rs | 25 +- boa_engine/src/object/builtins/jsdataview.rs | 4 +- .../object/builtins/jssharedarraybuffer.rs | 125 + .../src/object/builtins/jstypedarray.rs | 38 +- boa_engine/src/object/builtins/mod.rs | 2 + .../internal_methods/integer_indexed.rs | 107 +- boa_engine/src/object/jsobject.rs | 24 + boa_engine/src/object/mod.rs | 67 +- boa_engine/src/string/common.rs | 2 + boa_engine/src/value/mod.rs | 26 +- boa_examples/src/bin/jsarraybuffer.rs | 4 +- boa_tester/src/exec/js262.rs | 13 +- test262_config.toml | 1 - 27 files changed, 5722 insertions(+), 4589 deletions(-) create mode 100644 boa_engine/src/builtins/array_buffer/shared.rs create mode 100644 boa_engine/src/builtins/array_buffer/utils.rs create mode 100644 boa_engine/src/builtins/typed_array/builtin.rs create mode 100644 boa_engine/src/builtins/typed_array/element.rs create mode 100644 boa_engine/src/object/builtins/jssharedarraybuffer.rs diff --git a/Cargo.lock b/Cargo.lock index eef0d7e161e..ae6555426b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -344,6 +344,7 @@ dependencies = [ "boa_macros", "boa_parser", "boa_profiler", + "bytemuck", "chrono", "criterion", "dashmap", @@ -373,6 +374,7 @@ dependencies = [ "once_cell", "paste", "pollster", + "portable-atomic", "rand", "regress", "rustc-hash", @@ -541,6 +543,26 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -2223,6 +2245,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "portable-atomic" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" + [[package]] name = "postcard" version = "1.0.8" diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 11a1ac7ff37..d51ec7a2570 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -84,6 +84,8 @@ thin-vec.workspace = true itertools = { version = "0.11.0", default-features = false } icu_normalizer = "~1.3.0" paste = "1.0" +portable-atomic = "1.4.3" +bytemuck = { version = "1.14.0", features = ["derive"] } # intl deps boa_icu_provider = {workspace = true, features = ["std"], optional = true } diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 084018b5d80..d2335f9e042 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -1,4 +1,4 @@ -//! Boa's implementation of ECMAScript's global `ArrayBuffer` object. +//! Boa's implementation of ECMAScript's global `ArrayBuffer` and `SharedArrayBuffer` objects //! //! More information: //! - [ECMAScript reference][spec] @@ -7,11 +7,16 @@ //! [spec]: https://tc39.es/ecma262/#sec-arraybuffer-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer +pub(crate) mod shared; +pub(crate) mod utils; + #[cfg(test)] mod tests; +pub use shared::SharedArrayBuffer; + use crate::{ - builtins::{typed_array::TypedArrayKind, BuiltInObject}, + builtins::BuiltInObject, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, @@ -20,31 +25,106 @@ use crate::{ realm::Realm, string::common::StaticJsStrings, symbol::JsSymbol, - value::{IntegerOrInfinity, Numeric}, + value::IntegerOrInfinity, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; -use num_traits::{Signed, ToPrimitive}; + +use self::utils::{SliceRef, SliceRefMut}; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; +#[derive(Debug, Clone, Copy)] +pub(crate) enum BufferRef<'a> { + Common(&'a ArrayBuffer), + Shared(&'a SharedArrayBuffer), +} + +impl BufferRef<'_> { + pub(crate) fn data(&self) -> Option> { + match self { + Self::Common(buf) => buf.data().map(SliceRef::Common), + Self::Shared(buf) => Some(SliceRef::Atomic(buf.data())), + } + } + + pub(crate) fn is_detached(&self) -> bool { + self.data().is_none() + } +} + +#[derive(Debug)] +pub(crate) enum BufferRefMut<'a> { + Common(&'a mut ArrayBuffer), + Shared(&'a mut SharedArrayBuffer), +} + +impl BufferRefMut<'_> { + pub(crate) fn data_mut(&mut self) -> Option> { + match self { + Self::Common(buf) => buf.data_mut().map(SliceRefMut::Common), + Self::Shared(buf) => Some(SliceRefMut::Atomic(buf.data())), + } + } +} + /// The internal representation of an `ArrayBuffer` object. #[derive(Debug, Clone, Trace, Finalize)] pub struct ArrayBuffer { /// The `[[ArrayBufferData]]` internal slot. - pub array_buffer_data: Option>, - - /// The `[[ArrayBufferByteLength]]` internal slot. - pub array_buffer_byte_length: u64, + data: Option>, /// The `[[ArrayBufferDetachKey]]` internal slot. - pub array_buffer_detach_key: JsValue, + detach_key: JsValue, } impl ArrayBuffer { - pub(crate) const fn array_buffer_byte_length(&self) -> u64 { - self.array_buffer_byte_length + pub(crate) fn from_data(data: Vec, detach_key: JsValue) -> Self { + Self { + data: Some(data), + detach_key, + } + } + + pub(crate) fn len(&self) -> usize { + self.data.as_ref().map_or(0, Vec::len) + } + + pub(crate) fn data(&self) -> Option<&[u8]> { + self.data.as_deref() + } + + pub(crate) fn data_mut(&mut self) -> Option<&mut [u8]> { + self.data.as_deref_mut() + } + + /// Detaches the inner data of this `ArrayBuffer`, returning the original buffer if still + /// present. + /// + /// # Errors + /// + /// Throws an error if the provided detach key is invalid. + pub fn detach(&mut self, key: &JsValue) -> JsResult>> { + if !JsValue::same_value(&self.detach_key, key) { + return Err(JsNativeError::typ() + .with_message("Cannot detach array buffer with different key") + .into()); + } + + Ok(self.data.take()) + } + + /// `25.1.2.2 IsDetachedBuffer ( arrayBuffer )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer + pub(crate) const fn is_detached(&self) -> bool { + // 1. If arrayBuffer.[[ArrayBufferData]] is null, return true. + // 2. Return false. + self.data.is_none() } } @@ -175,21 +255,15 @@ impl ArrayBuffer { JsNativeError::typ().with_message("ArrayBuffer.byteLength called with non-object value") })?; let obj = obj.borrow(); + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let buf = obj.as_array_buffer().ok_or_else(|| { JsNativeError::typ().with_message("ArrayBuffer.byteLength called with invalid object") })?; - // TODO: Shared Array Buffer - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - // 4. If IsDetachedBuffer(O) is true, return +0𝔽. - if Self::is_detached_buffer(buf) { - return Ok(0.into()); - } - // 5. Let length be O.[[ArrayBufferByteLength]]. // 6. Return 𝔽(length). - Ok(buf.array_buffer_byte_length.into()) + Ok(buf.len().into()) } /// `25.1.5.3 ArrayBuffer.prototype.slice ( start, end )` @@ -205,56 +279,28 @@ impl ArrayBuffer { JsNativeError::typ().with_message("ArrayBuffer.slice called with non-object value") })?; let obj_borrow = obj.borrow(); + + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let buf = obj_borrow.as_array_buffer().ok_or_else(|| { JsNativeError::typ().with_message("ArrayBuffer.slice called with invalid object") })?; - // TODO: Shared Array Buffer - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. - if Self::is_detached_buffer(buf) { + if buf.is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer.slice called with detached buffer") .into()); } - // 5. Let len be O.[[ArrayBufferByteLength]]. - let len = buf.array_buffer_byte_length as i64; - - // 6. Let relativeStart be ? ToIntegerOrInfinity(start). - let relative_start = args.get_or_undefined(0).to_integer_or_infinity(context)?; - - let first = match relative_start { - // 7. If relativeStart is -∞, let first be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 9. Else, let first be min(relativeStart, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; - - // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - let end = args.get_or_undefined(1); - let relative_end = if end.is_undefined() { - IntegerOrInfinity::Integer(len) - } else { - end.to_integer_or_infinity(context)? - }; - - let r#final = match relative_end { - // 11. If relativeEnd is -∞, let final be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 13. Else, let final be min(relativeEnd, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; - - // 14. Let newLen be max(final - first, 0). - let new_len = std::cmp::max(r#final - first, 0) as u64; + let SliceRange { + start: first, + length: new_len, + } = get_slice_range( + buf.len() as u64, + args.get_or_undefined(0), + args.get_or_undefined(1), + context, + )?; // 15. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%). let ctor = obj.species_constructor(StandardConstructors::array_buffer, context)?; @@ -265,15 +311,13 @@ impl ArrayBuffer { { let new_obj = new.borrow(); // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). + // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| { JsNativeError::typ().with_message("ArrayBuffer constructor returned invalid object") })?; - // TODO: Shared Array Buffer - // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. - // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. - if new_array_buffer.is_detached_buffer() { + if new_array_buffer.is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer constructor returned detached ArrayBuffer") .into()); @@ -286,7 +330,7 @@ impl ArrayBuffer { .unwrap_or_default() { return Err(JsNativeError::typ() - .with_message("New ArrayBuffer is the same as this ArrayBuffer") + .with_message("new ArrayBuffer is the same as this ArrayBuffer") .into()); } @@ -297,34 +341,31 @@ impl ArrayBuffer { .expect("Already checked that `new_obj` was an `ArrayBuffer`"); // 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. - if new_array_buffer.array_buffer_byte_length < new_len { + if (new_array_buffer.len() as u64) < new_len { return Err(JsNativeError::typ() - .with_message("New ArrayBuffer length too small") + .with_message("new ArrayBuffer length too small") .into()); } // 22. NOTE: Side-effects of the above steps may have detached O. - // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. - if Self::is_detached_buffer(buf) { + // 24. Let fromBuf be O.[[ArrayBufferData]]. + let Some(from_buf) = buf.data() else { + // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. return Err(JsNativeError::typ() .with_message("ArrayBuffer detached while ArrayBuffer.slice was running") .into()); - } - - // 24. Let fromBuf be O.[[ArrayBufferData]]. - let from_buf = buf - .array_buffer_data - .as_ref() - .expect("ArrayBuffer cannot be detached here"); + }; // 25. Let toBuf be new.[[ArrayBufferData]]. let to_buf = new_array_buffer - .array_buffer_data + .data .as_mut() .expect("ArrayBuffer cannot be detached here"); // 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). - copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len as usize); + let first = first as usize; + let new_len = new_len as usize; + to_buf[..new_len].copy_from_slice(&from_buf[first..first + new_len]); } // 27. Return new. @@ -350,7 +391,7 @@ impl ArrayBuffer { )?; // 2. Let block be ? CreateByteDataBlock(byteLength). - let block = create_byte_data_block(byte_length)?; + let block = create_byte_data_block(byte_length, context)?; // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. @@ -358,422 +399,67 @@ impl ArrayBuffer { context.root_shape(), prototype, ObjectData::array_buffer(Self { - array_buffer_data: Some(block), - array_buffer_byte_length: byte_length, - array_buffer_detach_key: JsValue::Undefined, + data: Some(block), + detach_key: JsValue::Undefined, }), ); // 5. Return obj. Ok(obj) } +} - /// `25.1.2.2 IsDetachedBuffer ( arrayBuffer )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer - pub(crate) const fn is_detached_buffer(&self) -> bool { - // 1. If arrayBuffer.[[ArrayBufferData]] is null, return true. - // 2. Return false. - self.array_buffer_data.is_none() - } - - /// `25.1.2.4 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer - pub(crate) fn clone_array_buffer( - &self, - src_byte_offset: u64, - src_length: u64, - context: &mut Context<'_>, - ) -> JsResult { - // 1. Assert: IsDetachedBuffer(srcBuffer) is false. - debug_assert!(!self.is_detached_buffer()); - - // 2. Let targetBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, srcLength). - let target_buffer = Self::allocate( - &context - .realm() - .intrinsics() - .constructors() - .array_buffer() - .constructor() - .into(), - src_length, - context, - )?; - - // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]]. - let src_block = self - .array_buffer_data - .as_deref() - .expect("ArrayBuffer cannot be detached"); - - // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. - let mut target_buffer_mut = target_buffer.borrow_mut(); - let target_array_buffer = target_buffer_mut - .as_array_buffer_mut() - .expect("This must be an ArrayBuffer"); - let target_block = target_array_buffer - .array_buffer_data - .as_mut() - .expect("ArrayBuffer cannot me detached here"); - - // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). - copy_data_block_bytes( - target_block, - 0, - src_block, - src_byte_offset as usize, - src_length as usize, - ); - - // 6. Return targetBuffer. - drop(target_buffer_mut); - Ok(target_buffer) - } - - /// `25.1.2.6 IsUnclampedIntegerElementType ( type )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-isunclampedintegerelementtype - const fn is_unclamped_integer_element_type(t: TypedArrayKind) -> bool { - // 1. If type is Int8, Uint8, Int16, Uint16, Int32, or Uint32, return true. - // 2. Return false. - matches!( - t, - TypedArrayKind::Int8 - | TypedArrayKind::Uint8 - | TypedArrayKind::Int16 - | TypedArrayKind::Uint16 - | TypedArrayKind::Int32 - | TypedArrayKind::Uint32 - ) - } - - /// `25.1.2.7 IsBigIntElementType ( type )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-isbigintelementtype - const fn is_big_int_element_type(t: TypedArrayKind) -> bool { - // 1. If type is BigUint64 or BigInt64, return true. - // 2. Return false. - matches!(t, TypedArrayKind::BigUint64 | TypedArrayKind::BigInt64) - } - - /// `25.1.2.8 IsNoTearConfiguration ( type, order )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-isnotearconfiguration - // TODO: Allow unused function until shared array buffers are implemented. - #[allow(dead_code)] - const fn is_no_tear_configuration(t: TypedArrayKind, order: SharedMemoryOrder) -> bool { - // 1. If ! IsUnclampedIntegerElementType(type) is true, return true. - if Self::is_unclamped_integer_element_type(t) { - return true; - } +/// Utility struct to return the result of the [`get_slice_range`] function. +#[derive(Debug, Clone, Copy)] +struct SliceRange { + start: u64, + length: u64, +} - // 2. If ! IsBigIntElementType(type) is true and order is not Init or Unordered, return true. - if Self::is_big_int_element_type(t) - && !matches!( - order, - SharedMemoryOrder::Init | SharedMemoryOrder::Unordered - ) - { - return true; +/// Gets the slice copy range from the original length, the relative start and the end. +fn get_slice_range( + len: u64, + relative_start: &JsValue, + end: &JsValue, + context: &mut Context<'_>, +) -> JsResult { + // 5. Let len be O.[[ArrayBufferByteLength]]. + + // 6. Let relativeStart be ? ToIntegerOrInfinity(start). + let relative_start = relative_start.to_integer_or_infinity(context)?; + + let first = match relative_start { + // 7. If relativeStart is -∞, let first be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), + // 9. Else, let first be min(relativeStart, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + let r#final = if end.is_undefined() { + len + } else { + match end.to_integer_or_infinity(context)? { + // 11. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), + // 13. Else, let final be min(relativeEnd, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), + IntegerOrInfinity::PositiveInfinity => len, } + }; - // 3. Return false. - false - } - - /// `25.1.2.9 RawBytesToNumeric ( type, rawBytes, isLittleEndian )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-rawbytestonumeric - fn raw_bytes_to_numeric(t: TypedArrayKind, bytes: &[u8], is_little_endian: bool) -> JsValue { - let n: Numeric = match t { - TypedArrayKind::Int8 => { - if is_little_endian { - i8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into() - } else { - i8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into() - } - } - TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => { - if is_little_endian { - u8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into() - } else { - u8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into() - } - } - TypedArrayKind::Int16 => { - if is_little_endian { - i16::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } else { - i16::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } - } - TypedArrayKind::Uint16 => { - if is_little_endian { - u16::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } else { - u16::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } - } - TypedArrayKind::Int32 => { - if is_little_endian { - i32::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } else { - i32::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } - } - TypedArrayKind::Uint32 => { - if is_little_endian { - u32::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } else { - u32::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } - } - TypedArrayKind::BigInt64 => { - if is_little_endian { - i64::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } else { - i64::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } - } - TypedArrayKind::BigUint64 => { - if is_little_endian { - u64::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } else { - u64::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } - } - TypedArrayKind::Float32 => { - if is_little_endian { - f32::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } else { - f32::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } - } - TypedArrayKind::Float64 => { - if is_little_endian { - f64::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } else { - f64::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) - .into() - } - } - }; - - n.into() - } - - /// `25.1.2.10 GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-getvaluefrombuffer - pub(crate) fn get_value_from_buffer( - &self, - byte_index: u64, - t: TypedArrayKind, - _is_typed_array: bool, - _order: SharedMemoryOrder, - is_little_endian: Option, - ) -> JsValue { - // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. - // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. - // 3. Let block be arrayBuffer.[[ArrayBufferData]]. - let block = self - .array_buffer_data - .as_ref() - .expect("ArrayBuffer cannot be detached here"); - - // 4. Let elementSize be the Element Size value specified in Table 73 for Element Type type. - let element_size = t.element_size() as usize; - - // TODO: Shared Array Buffer - // 5. If IsSharedArrayBuffer(arrayBuffer) is true, then - - // 6. Else, let rawValue be a List whose elements are bytes from block at indices byteIndex (inclusive) through byteIndex + elementSize (exclusive). - // 7. Assert: The number of elements in rawValue is elementSize. - let byte_index = byte_index as usize; - let raw_value = &block[byte_index..byte_index + element_size]; - - // TODO: Agent Record [[LittleEndian]] filed - // 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. - let is_little_endian = is_little_endian.unwrap_or(true); - - // 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian). - Self::raw_bytes_to_numeric(t, raw_value, is_little_endian) - } - - /// `25.1.2.11 NumericToRawBytes ( type, value, isLittleEndian )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-numerictorawbytes - fn numeric_to_raw_bytes( - t: TypedArrayKind, - value: &JsValue, - is_little_endian: bool, - context: &mut Context<'_>, - ) -> JsResult> { - Ok(match t { - TypedArrayKind::Int8 if is_little_endian => { - value.to_int8(context)?.to_le_bytes().to_vec() - } - TypedArrayKind::Int8 => value.to_int8(context)?.to_be_bytes().to_vec(), - TypedArrayKind::Uint8 if is_little_endian => { - value.to_uint8(context)?.to_le_bytes().to_vec() - } - TypedArrayKind::Uint8 => value.to_uint8(context)?.to_be_bytes().to_vec(), - TypedArrayKind::Uint8Clamped if is_little_endian => { - value.to_uint8_clamp(context)?.to_le_bytes().to_vec() - } - TypedArrayKind::Uint8Clamped => value.to_uint8_clamp(context)?.to_be_bytes().to_vec(), - TypedArrayKind::Int16 if is_little_endian => { - value.to_int16(context)?.to_le_bytes().to_vec() - } - TypedArrayKind::Int16 => value.to_int16(context)?.to_be_bytes().to_vec(), - TypedArrayKind::Uint16 if is_little_endian => { - value.to_uint16(context)?.to_le_bytes().to_vec() - } - TypedArrayKind::Uint16 => value.to_uint16(context)?.to_be_bytes().to_vec(), - TypedArrayKind::Int32 if is_little_endian => { - value.to_i32(context)?.to_le_bytes().to_vec() - } - TypedArrayKind::Int32 => value.to_i32(context)?.to_be_bytes().to_vec(), - TypedArrayKind::Uint32 if is_little_endian => { - value.to_u32(context)?.to_le_bytes().to_vec() - } - TypedArrayKind::Uint32 => value.to_u32(context)?.to_be_bytes().to_vec(), - TypedArrayKind::BigInt64 if is_little_endian => { - let big_int = value.to_big_int64(context)?; - big_int - .to_i64() - .unwrap_or_else(|| { - if big_int.is_positive() { - i64::MAX - } else { - i64::MIN - } - }) - .to_le_bytes() - .to_vec() - } - TypedArrayKind::BigInt64 => { - let big_int = value.to_big_int64(context)?; - big_int - .to_i64() - .unwrap_or_else(|| { - if big_int.is_positive() { - i64::MAX - } else { - i64::MIN - } - }) - .to_be_bytes() - .to_vec() - } - TypedArrayKind::BigUint64 if is_little_endian => value - .to_big_uint64(context)? - .to_u64() - .unwrap_or(u64::MAX) - .to_le_bytes() - .to_vec(), - TypedArrayKind::BigUint64 => value - .to_big_uint64(context)? - .to_u64() - .unwrap_or(u64::MAX) - .to_be_bytes() - .to_vec(), - TypedArrayKind::Float32 => match value.to_number(context)? { - f if is_little_endian => (f as f32).to_le_bytes().to_vec(), - f => (f as f32).to_be_bytes().to_vec(), - }, - TypedArrayKind::Float64 => match value.to_number(context)? { - f if is_little_endian => f.to_le_bytes().to_vec(), - f => f.to_be_bytes().to_vec(), - }, - }) - } - - /// `25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer - pub(crate) fn set_value_in_buffer( - &mut self, - byte_index: u64, - t: TypedArrayKind, - value: &JsValue, - _order: SharedMemoryOrder, - is_little_endian: Option, - context: &mut Context<'_>, - ) -> JsResult { - // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. - // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. - // 3. Assert: Type(value) is BigInt if ! IsBigIntElementType(type) is true; otherwise, Type(value) is Number. - // 4. Let block be arrayBuffer.[[ArrayBufferData]]. - let block = self - .array_buffer_data - .as_mut() - .expect("ArrayBuffer cannot be detached here"); - - // 5. Let elementSize be the Element Size value specified in Table 73 for Element Type type. - - // TODO: Agent Record [[LittleEndian]] filed - // 6. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. - let is_little_endian = is_little_endian.unwrap_or(true); - - // 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian). - let raw_bytes = Self::numeric_to_raw_bytes(t, value, is_little_endian, context)?; - - // TODO: Shared Array Buffer - // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then - - // 9. Else, store the individual bytes of rawBytes into block, starting at block[byteIndex]. - for (i, raw_byte) in raw_bytes.iter().enumerate() { - block[byte_index as usize + i] = *raw_byte; - } + // 14. Let newLen be max(final - first, 0). + let new_len = r#final.saturating_sub(first); - // 10. Return NormalCompletion(undefined). - Ok(JsValue::undefined()) - } + Ok(SliceRange { + start: first, + length: new_len, + }) } /// `CreateByteDataBlock ( size )` abstract operation. @@ -782,7 +468,14 @@ impl ArrayBuffer { /// integer). For more information, check the [spec][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-createbytedatablock -pub fn create_byte_data_block(size: u64) -> JsResult> { +pub(crate) fn create_byte_data_block(size: u64, context: &mut Context<'_>) -> JsResult> { + if size > context.host_hooks().max_buffer_size() { + return Err(JsNativeError::range() + .with_message( + "cannot allocate a buffer that exceeds the maximum buffer size".to_string(), + ) + .into()); + } // 1. Let db be a new Data Block value consisting of size bytes. If it is impossible to // create such a Data Block, throw a RangeError exception. let size = size.try_into().map_err(|e| { @@ -800,61 +493,3 @@ pub fn create_byte_data_block(size: u64) -> JsResult> { // 3. Return db. Ok(data_block) } - -/// `6.2.8.3 CopyDataBlockBytes ( toBlock, toIndex, fromBlock, fromIndex, count )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-copydatablockbytes -fn copy_data_block_bytes( - to_block: &mut [u8], - mut to_index: usize, - from_block: &[u8], - mut from_index: usize, - mut count: usize, -) { - // 1. Assert: fromBlock and toBlock are distinct values. - // 2. Let fromSize be the number of bytes in fromBlock. - let from_size = from_block.len(); - - // 3. Assert: fromIndex + count ≤ fromSize. - assert!(from_index + count <= from_size); - - // 4. Let toSize be the number of bytes in toBlock. - let to_size = to_block.len(); - - // 5. Assert: toIndex + count ≤ toSize. - assert!(to_index + count <= to_size); - - // 6. Repeat, while count > 0, - while count > 0 { - // a. If fromBlock is a Shared Data Block, then - // TODO: Shared Data Block - - // b. Else, - // i. Assert: toBlock is not a Shared Data Block. - // ii. Set toBlock[toIndex] to fromBlock[fromIndex]. - to_block[to_index] = from_block[from_index]; - - // c. Set toIndex to toIndex + 1. - to_index += 1; - - // d. Set fromIndex to fromIndex + 1. - from_index += 1; - - // e. Set count to count - 1. - count -= 1; - } - - // 7. Return NormalCompletion(empty). -} - -// TODO: Allow unused variants until shared array buffers are implemented. -#[allow(dead_code)] -#[derive(Debug, PartialEq, Clone, Copy)] -pub(crate) enum SharedMemoryOrder { - Init, - SeqCst, - Unordered, -} diff --git a/boa_engine/src/builtins/array_buffer/shared.rs b/boa_engine/src/builtins/array_buffer/shared.rs new file mode 100644 index 00000000000..eb2d97767ed --- /dev/null +++ b/boa_engine/src/builtins/array_buffer/shared.rs @@ -0,0 +1,366 @@ +#![allow(unstable_name_collisions)] + +use std::{alloc, sync::Arc}; + +use boa_profiler::Profiler; +use portable_atomic::AtomicU8; + +use boa_gc::{Finalize, Trace}; +use sptr::Strict; + +use crate::{ + builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, + context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, + js_string, + object::{internal_methods::get_prototype_from_constructor, ObjectData}, + property::Attribute, + realm::Realm, + string::common::StaticJsStrings, + Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, +}; + +use super::{get_slice_range, utils::copy_shared_to_shared, SliceRange}; + +/// The internal representation of a `SharedArrayBuffer` object. +/// +/// This struct implements `Send` and `Sync`, meaning it can be shared between threads +/// running different JS code at the same time. +#[derive(Debug, Clone, Trace, Finalize)] +pub struct SharedArrayBuffer { + /// The `[[ArrayBufferData]]` internal slot. + // Shared buffers cannot be detached. + #[unsafe_ignore_trace] + data: Arc>, +} + +impl SharedArrayBuffer { + /// Gets the length of this `SharedArrayBuffer`. + pub(crate) fn len(&self) -> usize { + self.data.len() + } + + /// Gets the inner bytes of this `SharedArrayBuffer`. + pub(crate) fn data(&self) -> &[AtomicU8] { + &self.data + } +} + +impl IntrinsicObject for SharedArrayBuffer { + fn init(realm: &Realm) { + let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); + + let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; + + let get_species = BuiltInBuilder::callable(realm, Self::get_species) + .name(js_string!("get [Symbol.species]")) + .build(); + + let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length) + .name(js_string!("get byteLength")) + .build(); + + BuiltInBuilder::from_standard_constructor::(realm) + .accessor( + js_string!("byteLength"), + Some(get_byte_length), + None, + flag_attributes, + ) + .static_accessor( + JsSymbol::species(), + Some(get_species), + None, + Attribute::CONFIGURABLE, + ) + .method(Self::slice, js_string!("slice"), 2) + .property( + JsSymbol::to_string_tag(), + Self::NAME, + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .build(); + } + + fn get(intrinsics: &Intrinsics) -> JsObject { + Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() + } +} + +impl BuiltInObject for SharedArrayBuffer { + const NAME: JsString = StaticJsStrings::SHARED_ARRAY_BUFFER; +} + +impl BuiltInConstructor for SharedArrayBuffer { + const LENGTH: usize = 1; + + const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = + StandardConstructors::shared_array_buffer; + + /// `25.1.3.1 SharedArrayBuffer ( length [ , options ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer-constructor + fn constructor( + new_target: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. If NewTarget is undefined, throw a TypeError exception. + if new_target.is_undefined() { + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.constructor called with undefined new target") + .into()); + } + + // 2. Let byteLength be ? ToIndex(length). + let byte_length = args.get_or_undefined(0).to_index(context)?; + + // 3. Return ? AllocateSharedArrayBuffer(NewTarget, byteLength, requestedMaxByteLength). + Ok(Self::allocate(new_target, byte_length, context)?.into()) + } +} + +impl SharedArrayBuffer { + /// `get SharedArrayBuffer [ @@species ]` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer-@@species + #[allow(clippy::unnecessary_wraps)] + fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { + // 1. Return the this value. + Ok(this.clone()) + } + + /// `get SharedArrayBuffer.prototype.byteLength` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.bytelength + pub(crate) fn get_byte_length( + this: &JsValue, + _args: &[JsValue], + _: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ() + .with_message("SharedArrayBuffer.byteLength called with non-object value") + })?; + let obj = obj.borrow(); + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + let buf = obj.as_shared_array_buffer().ok_or_else(|| { + JsNativeError::typ() + .with_message("SharedArrayBuffer.byteLength called with invalid object") + })?; + + // TODO: 4. Let length be ArrayBufferByteLength(O, seq-cst). + // 5. Return 𝔽(length). + let len = buf.data().len() as u64; + Ok(len.into()) + } + + /// `SharedArrayBuffer.prototype.slice ( start, end )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.slice + fn slice(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("ArrayBuffer.slice called with non-object value") + })?; + let obj_borrow = obj.borrow(); + + // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. + let buf = obj_borrow.as_shared_array_buffer().ok_or_else(|| { + JsNativeError::typ().with_message("ArrayBuffer.slice called with invalid object") + })?; + + let SliceRange { + start: first, + length: new_len, + } = get_slice_range( + buf.len() as u64, + args.get_or_undefined(0), + args.get_or_undefined(1), + context, + )?; + + // 14. Let ctor be ? SpeciesConstructor(O, %SharedArrayBuffer%). + + let ctor = obj.species_constructor(StandardConstructors::shared_array_buffer, context)?; + + // 15. Let new be ? Construct(ctor, « 𝔽(newLen) »). + let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; + + { + // 16. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). + // 17. If IsSharedArrayBuffer(new) is false, throw a TypeError exception. + let new_obj = new.borrow(); + let new_buf = new_obj.as_shared_array_buffer().ok_or_else(|| { + JsNativeError::typ() + .with_message("SharedArrayBuffer constructor returned invalid object") + })?; + + // 18. If new.[[ArrayBufferData]] is O.[[ArrayBufferData]], throw a TypeError exception. + if std::ptr::eq(buf.data().as_ptr(), new_buf.data().as_ptr()) { + return Err(JsNativeError::typ() + .with_message("cannot reuse the same `SharedArrayBuffer` for a slice operation") + .into()); + } + + // TODO: 19. If ArrayBufferByteLength(new, seq-cst) < newLen, throw a TypeError exception. + if (new_buf.len() as u64) < new_len { + return Err(JsNativeError::typ() + .with_message("invalid size of constructed shared array") + .into()); + } + + // 20. Let fromBuf be O.[[ArrayBufferData]]. + let from_buf = buf.data(); + // 21. Let toBuf be new.[[ArrayBufferData]]. + let to_buf = new_buf.data(); + + // 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). + + let first = first as usize; + let new_len = new_len as usize; + + // SAFETY: `get_slice_range` will always return indices that are in-bounds. + // This also means that the newly created buffer will have at least `new_len` elements + // to write to. + unsafe { copy_shared_to_shared(&from_buf[first..], to_buf, new_len) } + } + + // 23. Return new. + Ok(new.into()) + } + + /// `AllocateSharedArrayBuffer ( constructor, byteLength [ , maxByteLength ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-allocatesharedarraybuffer + pub(crate) fn allocate( + constructor: &JsValue, + byte_length: u64, + context: &mut Context<'_>, + ) -> JsResult { + // TODO: + // 1. Let slots be « [[ArrayBufferData]] ». + // 2. If maxByteLength is present and maxByteLength is not empty, let allocatingGrowableBuffer + // be true; otherwise let allocatingGrowableBuffer be false. + // 3. If allocatingGrowableBuffer is true, then + // a. If byteLength > maxByteLength, throw a RangeError exception. + // b. Append [[ArrayBufferByteLengthData]] and [[ArrayBufferMaxByteLength]] to slots. + // 4. Else, + // a. Append [[ArrayBufferByteLength]] to slots. + + // 5. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%SharedArrayBuffer.prototype%", slots). + let prototype = get_prototype_from_constructor( + constructor, + StandardConstructors::shared_array_buffer, + context, + )?; + + // TODO: 6. If allocatingGrowableBuffer is true, let allocLength be maxByteLength; + // otherwise let allocLength be byteLength. + + // 7. Let block be ? CreateSharedByteDataBlock(allocLength). + // 8. Set obj.[[ArrayBufferData]] to block. + let data = create_shared_byte_data_block(byte_length, context)?; + + // TODO: + // 9. If allocatingGrowableBuffer is true, then + // a. Assert: byteLength ≤ maxByteLength. + // b. Let byteLengthBlock be ? CreateSharedByteDataBlock(8). + // c. Perform SetValueInBuffer(byteLengthBlock, 0, biguint64, ℤ(byteLength), true, seq-cst). + // d. Set obj.[[ArrayBufferByteLengthData]] to byteLengthBlock. + // e. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength. + + // 10. Else, + // a. Set obj.[[ArrayBufferByteLength]] to byteLength. + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + prototype, + ObjectData::shared_array_buffer(Self { data }), + ); + + // 11. Return obj. + Ok(obj) + } +} + +/// [`CreateSharedByteDataBlock ( size )`][spec] abstract operation. +/// +/// Creates a new `Arc>` that can be used as a backing buffer for a [`SharedArrayBuffer`]. +/// +/// For more information, check the [spec][spec]. +/// +/// [spec]: https://tc39.es/ecma262/#sec-createsharedbytedatablock +pub(crate) fn create_shared_byte_data_block( + size: u64, + context: &mut Context<'_>, +) -> JsResult>> { + if size > context.host_hooks().max_buffer_size() { + return Err(JsNativeError::range() + .with_message( + "cannot allocate a buffer that exceeds the maximum buffer size".to_string(), + ) + .into()); + } + + // 1. Let db be a new Shared Data Block value consisting of size bytes. If it is impossible to + // create such a Shared Data Block, throw a RangeError exception. + let size = size.try_into().map_err(|e| { + JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}")) + })?; + + if size == 0 { + // Must ensure we don't allocate a zero-sized buffer. + return Ok(Arc::new(Box::new([]))); + } + + // 2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. + // 3. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose + // [[AgentSignifier]] is AgentSignifier(). + // 4. Let zero be « 0 ». + // 5. For each index i of db, do + // a. Append WriteSharedMemory { [[Order]]: init, [[NoTear]]: true, [[Block]]: db, + // [[ByteIndex]]: i, [[ElementSize]]: 1, [[Payload]]: zero } to eventsRecord.[[EventList]]. + // 6. Return db. + let layout = alloc::Layout::array::(size).map_err(|e| { + JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}")) + })?; + + // SAFETY: We already returned if `size == 0`, making this safe. + let ptr: *mut AtomicU8 = unsafe { alloc::alloc_zeroed(layout).cast() }; + + if ptr.is_null() { + return Err(JsNativeError::range() + .with_message("memory allocation failed to allocate buffer") + .into()); + } + + // SAFETY: + // - It is ensured by the layout that `buffer` has `size` contiguous elements + // on its allocation. + // - The original `ptr` doesn't escape outside this function. + // - `buffer` is a valid pointer by the null check above. + let buffer = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(ptr, size)) }; + + // Just for good measure. + assert_eq!(buffer.as_ptr().addr() % std::mem::align_of::(), 0); + + // 3. Return db. + Ok(Arc::new(buffer)) +} diff --git a/boa_engine/src/builtins/array_buffer/tests.rs b/boa_engine/src/builtins/array_buffer/tests.rs index c8996399309..5581593b254 100644 --- a/boa_engine/src/builtins/array_buffer/tests.rs +++ b/boa_engine/src/builtins/array_buffer/tests.rs @@ -1,8 +1,21 @@ +use crate::Context; + #[test] fn create_byte_data_block() { + let context = &mut Context::default(); + // Sunny day + assert!(super::create_byte_data_block(100, context).is_ok()); + + // Rainy day + assert!(super::create_byte_data_block(u64::MAX, context).is_err()); +} + +#[test] +fn create_shared_byte_data_block() { + let context = &mut Context::default(); // Sunny day - assert!(super::create_byte_data_block(100).is_ok()); + assert!(super::shared::create_shared_byte_data_block(100, context).is_ok()); // Rainy day - assert!(super::create_byte_data_block(u64::MAX).is_err()); + assert!(super::shared::create_shared_byte_data_block(u64::MAX, context).is_err()); } diff --git a/boa_engine/src/builtins/array_buffer/utils.rs b/boa_engine/src/builtins/array_buffer/utils.rs new file mode 100644 index 00000000000..dacc1126db0 --- /dev/null +++ b/boa_engine/src/builtins/array_buffer/utils.rs @@ -0,0 +1,437 @@ +#![deny(unsafe_op_in_unsafe_fn)] +#![allow(unstable_name_collisions)] + +use std::{ptr, slice::SliceIndex, sync::atomic}; + +use portable_atomic::AtomicU8; +use sptr::Strict; + +use crate::{ + builtins::typed_array::{ClampedU8, Element, TypedArrayElement, TypedArrayKind}, + Context, JsObject, JsResult, +}; + +use super::ArrayBuffer; + +#[derive(Debug, Clone, Copy)] +pub(crate) enum SliceRef<'a> { + Common(&'a [u8]), + Atomic(&'a [AtomicU8]), +} + +impl SliceRef<'_> { + pub(crate) fn len(&self) -> usize { + match self { + Self::Common(buf) => buf.len(), + Self::Atomic(buf) => buf.len(), + } + } + + pub(crate) fn subslice(&self, index: I) -> SliceRef<'_> + where + I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>, + { + match self { + Self::Common(buffer) => { + SliceRef::Common(buffer.get(index).expect("index out of bounds")) + } + Self::Atomic(buffer) => { + SliceRef::Atomic(buffer.get(index).expect("index out of bounds")) + } + } + } + + pub(crate) fn addr(&self) -> usize { + match self { + Self::Common(buf) => buf.as_ptr().addr(), + Self::Atomic(buf) => buf.as_ptr().addr(), + } + } + + /// [`GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )`][spec] + /// + /// The start offset is determined by the input buffer instead of a `byteIndex` parameter. + /// + /// # Safety + /// + /// - There must be enough bytes in `buffer` to read an element from an array with type `TypedArrayKind`. + /// - `buffer` must be aligned to the alignment of said element. + /// + /// [spec]: https://tc39.es/ecma262/#sec-getvaluefrombuffer + pub(crate) unsafe fn get_value( + &self, + kind: TypedArrayKind, + order: atomic::Ordering, + ) -> TypedArrayElement { + unsafe fn read_elem(buffer: SliceRef<'_>, order: atomic::Ordering) -> T { + // + + // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. + // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. + if cfg!(debug_assertions) { + assert!(buffer.len() >= std::mem::size_of::()); + assert_eq!(buffer.len() % std::mem::align_of::(), 0); + } + + // 3. Let block be arrayBuffer.[[ArrayBufferData]]. + // 4. Let elementSize be the Element Size value specified in Table 70 for Element Type type. + // 5. If IsSharedArrayBuffer(arrayBuffer) is true, then + // a. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. + // b. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier(). + // c. If isTypedArray is true and IsNoTearConfiguration(type, order) is true, let noTear be true; otherwise let noTear be false. + // d. Let rawValue be a List of length elementSize whose elements are nondeterministically chosen byte values. + // e. NOTE: In implementations, rawValue is the result of a non-atomic or atomic read instruction on the underlying hardware. The nondeterminism is a semantic prescription of the memory model to describe observable behaviour of hardware with weak consistency. + // f. Let readEvent be ReadSharedMemory { [[Order]]: order, [[NoTear]]: noTear, [[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize }. + // g. Append readEvent to eventsRecord.[[EventList]]. + // h. Append Chosen Value Record { [[Event]]: readEvent, [[ChosenValue]]: rawValue } to execution.[[ChosenValues]]. + // 6. Else, + // a. Let rawValue be a List whose elements are bytes from block at indices in the interval from byteIndex (inclusive) to byteIndex + elementSize (exclusive). + // 7. Assert: The number of elements in rawValue is elementSize. + // 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. + // 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian). + + // SAFETY: The invariants of this operation are ensured by the caller. + unsafe { T::read_from_buffer(buffer, order) } + } + + let buffer = *self; + + // SAFETY: The invariants of this operation are ensured by the caller. + unsafe { + match kind { + TypedArrayKind::Int8 => read_elem::(buffer, order).into(), + TypedArrayKind::Uint8 => read_elem::(buffer, order).into(), + TypedArrayKind::Uint8Clamped => read_elem::(buffer, order).into(), + TypedArrayKind::Int16 => read_elem::(buffer, order).into(), + TypedArrayKind::Uint16 => read_elem::(buffer, order).into(), + TypedArrayKind::Int32 => read_elem::(buffer, order).into(), + TypedArrayKind::Uint32 => read_elem::(buffer, order).into(), + TypedArrayKind::BigInt64 => read_elem::(buffer, order).into(), + TypedArrayKind::BigUint64 => read_elem::(buffer, order).into(), + TypedArrayKind::Float32 => read_elem::(buffer, order).into(), + TypedArrayKind::Float64 => read_elem::(buffer, order).into(), + } + } + } + + /// `25.1.2.4 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer + pub(crate) fn clone(&self, context: &mut Context<'_>) -> JsResult { + // 1. Assert: IsDetachedBuffer(srcBuffer) is false. + + // 2. Let targetBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, srcLength). + let target_buffer = ArrayBuffer::allocate( + &context + .realm() + .intrinsics() + .constructors() + .array_buffer() + .constructor() + .into(), + self.len() as u64, + context, + )?; + + // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]]. + + // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. + { + let mut target_buffer_mut = target_buffer.borrow_mut(); + let target_array_buffer = target_buffer_mut + .as_array_buffer_mut() + .expect("This must be an ArrayBuffer"); + let target_block = target_array_buffer + .data + .as_deref_mut() + .expect("ArrayBuffer cannot be detached here"); + + // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). + + // SAFETY: Both buffers are of the same length, `buffer.len()`, which makes this operation + // safe. + unsafe { memcpy(*self, SliceRefMut::Common(target_block), self.len()) } + } + + // 6. Return targetBuffer. + Ok(target_buffer) + } +} + +impl<'a> From<&'a [u8]> for SliceRef<'a> { + fn from(value: &'a [u8]) -> Self { + Self::Common(value) + } +} + +impl<'a> From<&'a [AtomicU8]> for SliceRef<'a> { + fn from(value: &'a [AtomicU8]) -> Self { + Self::Atomic(value) + } +} + +#[derive(Debug)] +pub(crate) enum SliceRefMut<'a> { + Common(&'a mut [u8]), + Atomic(&'a [AtomicU8]), +} + +impl SliceRefMut<'_> { + pub(crate) fn len(&self) -> usize { + match self { + Self::Common(buf) => buf.len(), + Self::Atomic(buf) => buf.len(), + } + } + + pub(crate) fn subslice_mut(&mut self, index: I) -> SliceRefMut<'_> + where + I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>, + { + match self { + Self::Common(buffer) => { + SliceRefMut::Common(buffer.get_mut(index).expect("index out of bounds")) + } + Self::Atomic(buffer) => { + SliceRefMut::Atomic(buffer.get(index).expect("index out of bounds")) + } + } + } + + pub(crate) fn addr(&self) -> usize { + match self { + Self::Common(buf) => buf.as_ptr().addr(), + Self::Atomic(buf) => buf.as_ptr().addr(), + } + } + + /// `25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )` + /// + /// The start offset is determined by the input buffer instead of a `byteIndex` parameter. + /// + /// # Safety + /// + /// - There must be enough bytes in `buffer` to write the `TypedArrayElement`. + /// - `buffer` must be aligned to the alignment of the `TypedArrayElement`. + /// + /// # Panics + /// + /// - Panics if the type of `value` is not equal to the content of `kind`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer + pub(crate) unsafe fn set_value(&mut self, value: TypedArrayElement, order: atomic::Ordering) { + pub(crate) unsafe fn write_to_buffer( + buffer: SliceRefMut<'_>, + value: T, + order: atomic::Ordering, + ) { + // + + // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. + // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. + // 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number. + if cfg!(debug_assertions) { + assert!(buffer.len() >= std::mem::size_of::()); + assert_eq!(buffer.len() % std::mem::align_of::(), 0); + } + + // 4. Let block be arrayBuffer.[[ArrayBufferData]]. + // 5. Let elementSize be the Element Size value specified in Table 70 for Element Type type. + // 6. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. + // 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian). + // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then + // a. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. + // b. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier(). + // c. If isTypedArray is true and IsNoTearConfiguration(type, order) is true, let noTear be true; otherwise let noTear be false. + // d. Append WriteSharedMemory { [[Order]]: order, [[NoTear]]: noTear, [[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize, [[Payload]]: rawBytes } to eventsRecord.[[EventList]]. + // 9. Else, + // a. Store the individual bytes of rawBytes into block, starting at block[byteIndex]. + // 10. Return unused. + + // SAFETY: The invariants of this operation are ensured by the caller. + unsafe { + T::write_to_buffer(buffer, value, order); + } + } + + // Have to rebind in order to remove the outer `&mut` ref. + let buffer = match self { + SliceRefMut::Common(buf) => SliceRefMut::Common(buf), + SliceRefMut::Atomic(buf) => SliceRefMut::Atomic(buf), + }; + + // SAFETY: The invariants of this operation are ensured by the caller. + unsafe { + match value { + TypedArrayElement::Int8(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::Uint8(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::Uint8Clamped(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::Int16(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::Uint16(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::Int32(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::Uint32(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::BigInt64(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::BigUint64(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::Float32(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::Float64(e) => write_to_buffer(buffer, e, order), + } + } + } +} + +impl<'a> From<&'a mut [u8]> for SliceRefMut<'a> { + fn from(value: &'a mut [u8]) -> Self { + Self::Common(value) + } +} + +impl<'a> From<&'a [AtomicU8]> for SliceRefMut<'a> { + fn from(value: &'a [AtomicU8]) -> Self { + Self::Atomic(value) + } +} + +/// Copies `count` bytes from `src` into `dest` using atomic relaxed loads and stores. +pub(super) unsafe fn copy_shared_to_shared(src: &[AtomicU8], dest: &[AtomicU8], count: usize) { + // TODO: this could be optimized with batches of writes using `u32/u64` stores instead. + for i in 0..count { + // SAFETY: The invariants of this operation are ensured by the caller of the function. + unsafe { + dest.get_unchecked(i).store( + src.get_unchecked(i).load(atomic::Ordering::Relaxed), + atomic::Ordering::Relaxed, + ); + } + } +} + +unsafe fn copy_shared_to_shared_backwards(src: &[AtomicU8], dest: &[AtomicU8], count: usize) { + for i in (0..count).rev() { + // SAFETY: The invariants of this operation are ensured by the caller of the function. + unsafe { + dest.get_unchecked(i).store( + src.get_unchecked(i).load(atomic::Ordering::Relaxed), + atomic::Ordering::Relaxed, + ); + } + } +} + +/// Copies `count` bytes from the buffer `src` into the buffer `dest`, using the atomic ordering `order` +/// if any of the buffers are atomic. +/// +/// # Safety +/// +/// - Both `src.len()` and `dest.len()` must have at least `count` bytes to read and write, +/// respectively. +pub(crate) unsafe fn memcpy(src: SliceRef<'_>, dest: SliceRefMut<'_>, count: usize) { + if cfg!(debug_assertions) { + assert!(src.len() >= count); + assert!(dest.len() >= count); + } + + // TODO: this could be optimized with batches of writes using `u32/u64` stores instead. + match (src, dest) { + // SAFETY: The invariants of this operation are ensured by the caller of the function. + (SliceRef::Common(src), SliceRefMut::Common(dest)) => unsafe { + ptr::copy_nonoverlapping(src.as_ptr(), dest.as_mut_ptr(), count); + }, + // SAFETY: The invariants of this operation are ensured by the caller of the function. + (SliceRef::Common(src), SliceRefMut::Atomic(dest)) => unsafe { + for i in 0..count { + dest.get_unchecked(i) + .store(*src.get_unchecked(i), atomic::Ordering::Relaxed); + } + }, + // SAFETY: The invariants of this operation are ensured by the caller of the function. + (SliceRef::Atomic(src), SliceRefMut::Common(dest)) => unsafe { + for i in 0..count { + *dest.get_unchecked_mut(i) = src.get_unchecked(i).load(atomic::Ordering::Relaxed); + } + }, + // SAFETY: The invariants of this operation are ensured by the caller of the function. + (SliceRef::Atomic(src), SliceRefMut::Atomic(dest)) => unsafe { + copy_shared_to_shared(src, dest, count); + }, + } +} + +/// Copies `count` bytes from the position `from` to the position `to` in `buffer`. +/// +/// # Safety +/// +/// - `from + count <= buffer.len()` +/// - `to + count <= buffer.len()` +pub(crate) unsafe fn memmove(buffer: SliceRefMut<'_>, from: usize, to: usize, count: usize) { + if cfg!(debug_assertions) { + assert!(from + count <= buffer.len()); + assert!(to + count <= buffer.len()); + } + + match buffer { + // SAFETY: The invariants of this operation are ensured by the caller of the function. + SliceRefMut::Common(buf) => unsafe { + let ptr = buf.as_mut_ptr(); + let src_ptr = ptr.add(from); + let dest_ptr = ptr.add(to); + ptr::copy(src_ptr, dest_ptr, count); + }, + // SAFETY: The invariants of this operation are ensured by the caller of the function. + SliceRefMut::Atomic(buf) => unsafe { + let src = buf.get_unchecked(from..); + let dest = buf.get_unchecked(to..); + + // Let's draw a simple array. + // + // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | + // + // Now let's define `from`, `to` and `count` such that the below condition is satisfied. + // `from = 0` + // `to = 2` + // `count = 4` + // + // We can now imagine that the array is pointer to by our indices: + // + // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | + // ^ ^ + // from to + // + // If we start copying bytes until `from + 2 = to`, we can see that the new array would be: + // + // | 0 | 1 | 0 | 1 | 0 | 5 | 6 | 7 | 8 | + // ^ ^ + // from + 2 to + 2 + // + // However, we've lost the data that was in the index 2! If this process + // continues, this'll give the incorrect result: + // + // | 0 | 1 | 0 | 1 | 0 | 1 | 6 | 7 | 8 | + // + // To solve this, we just need to copy backwards to ensure we never override data that + // we need in next iterations: + // + // | 0 | 1 | 2 | 3 | 4 | 3 | 6 | 7 | 8 | + // ^ ^ + // from to + // + // | 0 | 1 | 2 | 3 | 2 | 3 | 6 | 7 | 8 | + // ^ ^ + // from to + // + // | 0 | 1 | 0 | 1 | 2 | 3 | 6 | 7 | 8 | + // ^ ^ + // from to + if from < to && to < from + count { + copy_shared_to_shared_backwards(src, dest, count); + } else { + copy_shared_to_shared(src, dest, count); + } + }, + } +} diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index ef9a2653bfb..906a1f39322 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -7,8 +7,10 @@ //! [spec]: https://tc39.es/ecma262/#sec-dataview-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView +use std::mem; + use crate::{ - builtins::{array_buffer::SharedMemoryOrder, typed_array::TypedArrayKind, BuiltInObject}, + builtins::BuiltInObject, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, @@ -21,8 +23,13 @@ use crate::{ Context, JsArgs, JsResult, JsString, }; use boa_gc::{Finalize, Trace}; +use bytemuck::{bytes_of, bytes_of_mut}; -use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; +use super::{ + array_buffer::utils::{memcpy, SliceRef, SliceRefMut}, + typed_array::{self, TypedArrayElement}, + BuiltInBuilder, BuiltInConstructor, IntrinsicObject, +}; /// The internal representation of a `DataView` object. #[derive(Debug, Clone, Trace, Finalize)] @@ -134,28 +141,30 @@ impl BuiltInConstructor for DataView { .ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))?; // 1. If NewTarget is undefined, throw a TypeError exception. + if new_target.is_undefined() { + return Err(JsNativeError::typ() + .with_message("new target is undefined") + .into()); + } + let (offset, view_byte_length) = { - if new_target.is_undefined() { - return Err(JsNativeError::typ() - .with_message("new target is undefined") - .into()); - } // 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]). let buffer_borrow = buffer_obj.borrow(); - let buffer = buffer_borrow.as_array_buffer().ok_or_else(|| { + let buffer = buffer_borrow.as_buffer().ok_or_else(|| { JsNativeError::typ().with_message("buffer must be an ArrayBuffer") })?; // 3. Let offset be ? ToIndex(byteOffset). let offset = args.get_or_undefined(1).to_index(context)?; // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if buffer.is_detached_buffer() { + let Some(buffer) = buffer.data() else { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); - } + }; + // 5. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. - let buffer_byte_length = buffer.array_buffer_byte_length(); + let buffer_byte_length = buffer.len() as u64; // 6. If offset > bufferByteLength, throw a RangeError exception. if offset > buffer_byte_length { return Err(JsNativeError::range() @@ -188,9 +197,9 @@ impl BuiltInConstructor for DataView { // 10. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if buffer_obj .borrow() - .as_array_buffer() - .ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))? - .is_detached_buffer() + .as_buffer() + .expect("already checked that `buffer_obj` was a buffer") + .is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer can't be detached") @@ -272,10 +281,10 @@ impl DataView { // 4. Let buffer be O.[[ViewedArrayBuffer]]. let buffer_borrow = dataview.viewed_array_buffer.borrow(); let borrow = buffer_borrow - .as_array_buffer() - .expect("DataView must be constructed with an ArrayBuffer"); + .as_buffer() + .expect("DataView must be constructed with a Buffer"); // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if borrow.is_detached_buffer() { + if borrow.is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); @@ -313,12 +322,12 @@ impl DataView { // 4. Let buffer be O.[[ViewedArrayBuffer]]. let buffer_borrow = dataview.viewed_array_buffer.borrow(); let borrow = buffer_borrow - .as_array_buffer() - .expect("DataView must be constructed with an ArrayBuffer"); + .as_buffer() + .expect("DataView must be constructed with a Buffer"); // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if borrow.is_detached_buffer() { + if borrow.is_detached() { return Err(JsNativeError::typ() - .with_message("ArrayBuffer is detached") + .with_message("Buffer is detached") .into()); } // 6. Let offset be O.[[ByteOffset]]. @@ -337,11 +346,10 @@ impl DataView { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-getviewvalue - fn get_view_value( + fn get_view_value( view: &JsValue, request_index: &JsValue, is_little_endian: &JsValue, - t: TypedArrayKind, context: &mut Context<'_>, ) -> JsResult { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). @@ -360,16 +368,15 @@ impl DataView { // 5. Let buffer be view.[[ViewedArrayBuffer]]. let buffer = &view.viewed_array_buffer; let buffer_borrow = buffer.borrow(); - let buffer = buffer_borrow - .as_array_buffer() - .expect("Should be unreachable"); + let buffer = buffer_borrow.as_buffer().expect("Should be unreachable"); // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if buffer.is_detached_buffer() { + let Some(data) = buffer.data() else { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); - } + }; + // 7. Let viewOffset be view.[[ByteOffset]]. let view_offset = view.byte_offset; @@ -377,7 +384,7 @@ impl DataView { let view_size = view.byte_length; // 9. Let elementSize be the Element Size value specified in Table 72 for Element Type type. - let element_size = t.element_size(); + let element_size = mem::size_of::() as u64; // 10. If getIndex + elementSize > viewSize, throw a RangeError exception. if get_index + element_size > view_size { @@ -387,16 +394,27 @@ impl DataView { } // 11. Let bufferIndex be getIndex + viewOffset. - let buffer_index = get_index + view_offset; + let buffer_index = (get_index + view_offset) as usize; // 12. Return GetValueFromBuffer(buffer, bufferIndex, type, false, Unordered, isLittleEndian). - Ok(buffer.get_value_from_buffer( - buffer_index, - t, - false, - SharedMemoryOrder::Unordered, - Some(is_little_endian), - )) + // SAFETY: All previous checks ensure the element fits in the buffer. + let value: TypedArrayElement = unsafe { + let mut value = T::zeroed(); + memcpy( + data.subslice(buffer_index..), + SliceRefMut::Common(bytes_of_mut(&mut value)), + mem::size_of::(), + ); + + if is_little_endian { + value.to_little_endian() + } else { + value.to_big_endian() + } + .into() + }; + + Ok(value.into()) } /// `25.3.4.5 DataView.prototype.getBigInt64 ( byteOffset [ , littleEndian ] )` @@ -419,13 +437,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::BigInt64, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.4.6 DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] )` @@ -448,13 +460,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::BigUint64, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.4.7 DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] )` @@ -477,13 +483,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Float32, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.4.8 DataView.prototype.getFloat64 ( byteOffset [ , littleEndian ] )` @@ -506,13 +506,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Float64, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.4.9 DataView.prototype.getInt8 ( byteOffset [ , littleEndian ] )` @@ -535,13 +529,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Int8, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.4.10 DataView.prototype.getInt16 ( byteOffset [ , littleEndian ] )` @@ -564,13 +552,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Int16, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.4.11 DataView.prototype.getInt32 ( byteOffset [ , littleEndian ] )` @@ -593,13 +575,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Int32, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.4.12 DataView.prototype.getUint8 ( byteOffset [ , littleEndian ] )` @@ -622,13 +598,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Uint8, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.4.13 DataView.prototype.getUint16 ( byteOffset [ , littleEndian ] )` @@ -651,13 +621,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Uint16, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.4.14 DataView.prototype.getUint32 ( byteOffset [ , littleEndian ] )` @@ -680,13 +644,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(1); // 1. Let v be the this value. // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). - Self::get_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Uint32, - context, - ) + Self::get_view_value::(this, byte_offset, is_little_endian, context) } /// `25.3.1.1 SetViewValue ( view, requestIndex, isLittleEndian, type )` @@ -699,11 +657,10 @@ impl DataView { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-setviewvalue - fn set_view_value( + fn set_view_value( view: &JsValue, request_index: &JsValue, is_little_endian: &JsValue, - t: TypedArrayKind, value: &JsValue, context: &mut Context<'_>, ) -> JsResult { @@ -717,29 +674,25 @@ impl DataView { // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; - let number_value = if t.is_big_int_element_type() { - // 4. If ! IsBigIntElementType(type) is true, let numberValue be ? ToBigInt(value). - value.to_bigint(context)?.into() - } else { - // 5. Otherwise, let numberValue be ? ToNumber(value). - value.to_number(context)?.into() - }; + // 4. If ! IsBigIntElementType(type) is true, let numberValue be ? ToBigInt(value). + // 5. Otherwise, let numberValue be ? ToNumber(value). + let value = T::from_js_value(value, context)?; // 6. Set isLittleEndian to ! ToBoolean(isLittleEndian). let is_little_endian = is_little_endian.to_boolean(); // 7. Let buffer be view.[[ViewedArrayBuffer]]. let buffer = &view.viewed_array_buffer; let mut buffer_borrow = buffer.borrow_mut(); - let buffer = buffer_borrow - .as_array_buffer_mut() + let mut buffer = buffer_borrow + .as_buffer_mut() .expect("Should be unreachable"); // 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if buffer.is_detached_buffer() { + let Some(mut data) = buffer.data_mut() else { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); - } + }; // 9. Let viewOffset be view.[[ByteOffset]]. let view_offset = view.byte_offset; @@ -748,27 +701,34 @@ impl DataView { let view_size = view.byte_length; // 11. Let elementSize be the Element Size value specified in Table 72 for Element Type type. - let element_size = t.element_size(); - // 12. If getIndex + elementSize > viewSize, throw a RangeError exception. - if get_index + element_size > view_size { + if get_index + mem::size_of::() as u64 > view_size { return Err(JsNativeError::range() .with_message("Offset is outside the bounds of DataView") .into()); } // 13. Let bufferIndex be getIndex + viewOffset. - let buffer_index = get_index + view_offset; + let buffer_index = (get_index + view_offset) as usize; // 14. Return SetValueInBuffer(buffer, bufferIndex, type, numberValue, false, Unordered, isLittleEndian). - buffer.set_value_in_buffer( - buffer_index, - t, - &number_value, - SharedMemoryOrder::Unordered, - Some(is_little_endian), - context, - ) + + // SAFETY: All previous checks ensure the element fits in the buffer. + unsafe { + let value = if is_little_endian { + value.to_little_endian() + } else { + value.to_big_endian() + }; + + memcpy( + SliceRef::Common(bytes_of(&value)), + data.subslice_mut(buffer_index..), + mem::size_of::(), + ); + } + + Ok(JsValue::undefined()) } /// `25.3.4.15 DataView.prototype.setBigInt64 ( byteOffset, value [ , littleEndian ] )` @@ -792,14 +752,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::BigInt64, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `25.3.4.16 DataView.prototype.setBigUint64 ( byteOffset, value [ , littleEndian ] )` @@ -823,14 +776,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::BigUint64, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `25.3.4.17 DataView.prototype.setFloat32 ( byteOffset, value [ , littleEndian ] )` @@ -854,14 +800,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float32, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Float32, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `25.3.4.18 DataView.prototype.setFloat64 ( byteOffset, value [ , littleEndian ] )` @@ -885,14 +824,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float64, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Float64, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `25.3.4.19 DataView.prototype.setInt8 ( byteOffset, value [ , littleEndian ] )` @@ -916,14 +848,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int8, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Int8, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `25.3.4.20 DataView.prototype.setInt16 ( byteOffset, value [ , littleEndian ] )` @@ -947,14 +872,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int16, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Int16, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `25.3.4.21 DataView.prototype.setInt32 ( byteOffset, value [ , littleEndian ] )` @@ -978,14 +896,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int32, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Int32, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `25.3.4.22 DataView.prototype.setUint8 ( byteOffset, value [ , littleEndian ] )` @@ -1009,14 +920,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint8, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Uint8, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `25.3.4.23 DataView.prototype.setUint16 ( byteOffset, value [ , littleEndian ] )` @@ -1040,14 +944,7 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint16, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Uint16, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } /// `25.3.4.24 DataView.prototype.setUint32 ( byteOffset, value [ , littleEndian ] )` @@ -1071,13 +968,6 @@ impl DataView { let is_little_endian = args.get_or_undefined(2); // 1. Let v be the this value. // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint32, value). - Self::set_view_value( - this, - byte_offset, - is_little_endian, - TypedArrayKind::Uint32, - value, - context, - ) + Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } } diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index a17b3e34a5f..e150d22000a 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -80,7 +80,7 @@ pub(crate) use self::{ use crate::{ builtins::{ array::ArrayIterator, - array_buffer::ArrayBuffer, + array_buffer::{ArrayBuffer, SharedArrayBuffer}, async_generator::AsyncGenerator, async_generator_function::AsyncGeneratorFunction, error::r#type::ThrowTypeError, @@ -92,7 +92,7 @@ use crate::{ regexp::RegExpStringIterator, set::SetIterator, string::StringIterator, - typed_array::TypedArray, + typed_array::BuiltinTypedArray, uri::{DecodeUri, DecodeUriComponent, EncodeUri, EncodeUriComponent}, weak::WeakRef, weak_map::WeakMap, @@ -205,6 +205,7 @@ impl Realm { ArrayIterator::init(self); Proxy::init(self); ArrayBuffer::init(self); + SharedArrayBuffer::init(self); BigInt::init(self); Boolean::init(self); Date::init(self); @@ -223,7 +224,7 @@ impl Realm { StringIterator::init(self); RegExp::init(self); RegExpStringIterator::init(self); - TypedArray::init(self); + BuiltinTypedArray::init(self); Int8Array::init(self); Uint8Array::init(self); Uint8ClampedArray::init(self); @@ -339,6 +340,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; + global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; @@ -353,7 +355,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; - global_binding::(context)?; + global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; diff --git a/boa_engine/src/builtins/typed_array/builtin.rs b/boa_engine/src/builtins/typed_array/builtin.rs new file mode 100644 index 00000000000..98a558797bd --- /dev/null +++ b/boa_engine/src/builtins/typed_array/builtin.rs @@ -0,0 +1,3388 @@ +use std::{cmp::Ordering, ptr, sync::atomic}; + +use boa_macros::utf16; +use num_traits::Zero; + +use crate::{ + builtins::{ + array::{find_via_predicate, ArrayIterator, Direction}, + array_buffer::{ + utils::{memcpy, memmove, SliceRefMut}, + ArrayBuffer, BufferRef, + }, + iterable::iterable_to_list, + BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + }, + context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, + js_string, + object::{ + internal_methods::{get_prototype_from_constructor, integer_indexed_element_set}, + ObjectData, + }, + property::{Attribute, PropertyNameKind}, + realm::Realm, + string::common::StaticJsStrings, + value::IntegerOrInfinity, + Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, +}; + +use super::{ContentType, IntegerIndexed, TypedArray, TypedArrayKind}; + +/// The JavaScript `%TypedArray%` object. +/// +/// +#[derive(Debug, Clone, Copy)] +pub(crate) struct BuiltinTypedArray; + +impl IntrinsicObject for BuiltinTypedArray { + fn init(realm: &Realm) { + let get_species = BuiltInBuilder::callable(realm, Self::get_species) + .name(js_string!("get [Symbol.species]")) + .build(); + + let get_buffer = BuiltInBuilder::callable(realm, Self::buffer) + .name(js_string!("get buffer")) + .build(); + + let get_byte_length = BuiltInBuilder::callable(realm, Self::byte_length) + .name(js_string!("get byteLength")) + .build(); + + let get_byte_offset = BuiltInBuilder::callable(realm, Self::byte_offset) + .name(js_string!("get byteOffset")) + .build(); + + let get_length = BuiltInBuilder::callable(realm, Self::length) + .name(js_string!("get length")) + .build(); + + let get_to_string_tag = BuiltInBuilder::callable(realm, Self::to_string_tag) + .name(js_string!("get [Symbol.toStringTag]")) + .build(); + + let values_function = BuiltInBuilder::callable(realm, Self::values) + .name(js_string!("values")) + .length(0) + .build(); + + BuiltInBuilder::from_standard_constructor::(realm) + .static_accessor( + JsSymbol::species(), + Some(get_species), + None, + Attribute::CONFIGURABLE, + ) + .property( + JsSymbol::iterator(), + values_function.clone(), + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .accessor( + utf16!("buffer"), + Some(get_buffer), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .accessor( + utf16!("byteLength"), + Some(get_byte_length), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .accessor( + utf16!("byteOffset"), + Some(get_byte_offset), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .accessor( + utf16!("length"), + Some(get_length), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .accessor( + JsSymbol::to_string_tag(), + Some(get_to_string_tag), + None, + Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + ) + .static_method(Self::from, js_string!("from"), 1) + .static_method(Self::of, js_string!("of"), 0) + .method(Self::at, js_string!("at"), 1) + .method(Self::copy_within, js_string!("copyWithin"), 2) + .method(Self::entries, js_string!("entries"), 0) + .method(Self::every, js_string!("every"), 1) + .method(Self::fill, js_string!("fill"), 1) + .method(Self::filter, js_string!("filter"), 1) + .method(Self::find, js_string!("find"), 1) + .method(Self::find_index, js_string!("findIndex"), 1) + .method(Self::find_last, js_string!("findLast"), 1) + .method(Self::find_last_index, js_string!("findLastIndex"), 1) + .method(Self::foreach, js_string!("forEach"), 1) + .method(Self::includes, js_string!("includes"), 1) + .method(Self::index_of, js_string!("indexOf"), 1) + .method(Self::join, js_string!("join"), 1) + .method(Self::keys, js_string!("keys"), 0) + .method(Self::last_index_of, js_string!("lastIndexOf"), 1) + .method(Self::map, js_string!("map"), 1) + .method(Self::reduce, js_string!("reduce"), 1) + .method(Self::reduceright, js_string!("reduceRight"), 1) + .method(Self::reverse, js_string!("reverse"), 0) + .method(Self::set, js_string!("set"), 1) + .method(Self::slice, js_string!("slice"), 2) + .method(Self::some, js_string!("some"), 1) + .method(Self::sort, js_string!("sort"), 1) + .method(Self::subarray, js_string!("subarray"), 2) + .method(Self::to_locale_string, js_string!("toLocaleString"), 0) + // 23.2.3.29 %TypedArray%.prototype.toString ( ) + // The initial value of the %TypedArray%.prototype.toString data property is the same + // built-in function object as the Array.prototype.toString method defined in 23.1.3.30. + .property( + js_string!("toString"), + realm.intrinsics().objects().array_prototype_to_string(), + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .property( + js_string!("values"), + values_function, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .build(); + } + + fn get(intrinsics: &Intrinsics) -> JsObject { + Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() + } +} + +impl BuiltInObject for BuiltinTypedArray { + const NAME: JsString = StaticJsStrings::TYPED_ARRAY; +} + +impl BuiltInConstructor for BuiltinTypedArray { + const LENGTH: usize = 0; + + const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = + StandardConstructors::typed_array; + + /// `23.2.1.1 %TypedArray% ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray% + fn constructor(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { + // 1. Throw a TypeError exception. + Err(JsNativeError::typ() + .with_message("the TypedArray constructor should never be called directly") + .into()) + } +} + +impl BuiltinTypedArray { + /// `23.2.2.1 %TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.from + fn from(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { + // 1. Let C be the this value. + // 2. If IsConstructor(C) is false, throw a TypeError exception. + let constructor = match this.as_object() { + Some(obj) if obj.is_constructor() => obj, + _ => { + return Err(JsNativeError::typ() + .with_message("TypedArray.from called on non-constructable value") + .into()) + } + }; + + let mapping = match args.get(1) { + // 3. If mapfn is undefined, let mapping be false. + None | Some(JsValue::Undefined) => None, + // 4. Else, + Some(v) => match v.as_object() { + // b. Let mapping be true. + Some(obj) if obj.is_callable() => Some(obj), + // a. If IsCallable(mapfn) is false, throw a TypeError exception. + _ => { + return Err(JsNativeError::typ() + .with_message("TypedArray.from called with non-callable mapfn") + .into()) + } + }, + }; + + // 5. Let usingIterator be ? GetMethod(source, @@iterator). + let source = args.get_or_undefined(0); + let using_iterator = source.get_method(JsSymbol::iterator(), context)?; + + let this_arg = args.get_or_undefined(2); + + // 6. If usingIterator is not undefined, then + if let Some(using_iterator) = using_iterator { + // a. Let values be ? IterableToList(source, usingIterator). + let values = iterable_to_list(context, source, Some(using_iterator))?; + + // b. Let len be the number of elements in values. + // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). + let target_obj = Self::create(constructor, &[values.len().into()], context)?; + + // d. Let k be 0. + // e. Repeat, while k < len, + for (k, k_value) in values.iter().enumerate() { + // i. Let Pk be ! ToString(𝔽(k)). + // ii. Let kValue be the first element of values and remove that element from values. + // iii. If mapping is true, then + let mapped_value = if let Some(map_fn) = &mapping { + // 1. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). + map_fn.call(this_arg, &[k_value.clone(), k.into()], context)? + } + // iv. Else, let mappedValue be kValue. + else { + k_value.clone() + }; + + // v. Perform ? Set(targetObj, Pk, mappedValue, true). + target_obj.set(k, mapped_value, true, context)?; + } + + // f. Assert: values is now an empty List. + // g. Return targetObj. + return Ok(target_obj.into()); + } + + // 7. NOTE: source is not an Iterable so assume it is already an array-like object. + // 8. Let arrayLike be ! ToObject(source). + let array_like = source + .to_object(context) + .expect("ToObject cannot fail here"); + + // 9. Let len be ? LengthOfArrayLike(arrayLike). + let len = array_like.length_of_array_like(context)?; + + // 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). + let target_obj = Self::create(constructor, &[len.into()], context)?; + + // 11. Let k be 0. + // 12. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ? Get(arrayLike, Pk). + let k_value = array_like.get(k, context)?; + + // c. If mapping is true, then + let mapped_value = if let Some(map_fn) = &mapping { + // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). + map_fn.call(this_arg, &[k_value, k.into()], context)? + } + // d. Else, let mappedValue be kValue. + else { + k_value + }; + + // e. Perform ? Set(targetObj, Pk, mappedValue, true). + target_obj.set(k, mapped_value, true, context)?; + } + + // 13. Return targetObj. + Ok(target_obj.into()) + } + + /// `23.2.2.2 %TypedArray%.of ( ...items )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.of + fn of(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { + // 1. Let len be the number of elements in items. + + // 2. Let C be the this value. + // 3. If IsConstructor(C) is false, throw a TypeError exception. + let constructor = match this.as_object() { + Some(obj) if obj.is_constructor() => obj, + _ => { + return Err(JsNativeError::typ() + .with_message("TypedArray.of called on non-constructable value") + .into()) + } + }; + + // 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »). + let new_obj = Self::create(constructor, &[args.len().into()], context)?; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for (k, k_value) in args.iter().enumerate() { + // a. Let kValue be items[k]. + // b. Let Pk be ! ToString(𝔽(k)). + // c. Perform ? Set(newObj, Pk, kValue, true). + new_obj.set(k, k_value.clone(), true, context)?; + } + + // 7. Return newObj. + Ok(new_obj.into()) + } + + /// `23.2.2.4 get %TypedArray% [ @@species ]` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%-@@species + #[allow(clippy::unnecessary_wraps)] + pub(super) fn get_species( + this: &JsValue, + _: &[JsValue], + _: &mut Context<'_>, + ) -> JsResult { + // 1. Return the this value. + Ok(this.clone()) + } + + /// `23.2.3.1 %TypedArray%.prototype.at ( index )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.at + pub(crate) fn at( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + drop(obj_borrow); + + // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). + let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?; + + let k = match relative_index { + // Note: Early undefined return on infinity. + IntegerOrInfinity::PositiveInfinity | IntegerOrInfinity::NegativeInfinity => { + return Ok(JsValue::undefined()) + } + // 5. If relativeIndex ≥ 0, then + // a. Let k be relativeIndex. + IntegerOrInfinity::Integer(i) if i >= 0 => i, + // 6. Else, + // a. Let k be len + relativeIndex. + IntegerOrInfinity::Integer(i) => len + i, + }; + + // 7. If k < 0 or k ≥ len, return undefined. + if k < 0 || k >= len { + return Ok(JsValue::undefined()); + } + + // 8. Return ! Get(O, ! ToString(𝔽(k))). + Ok(obj.get(k, context).expect("Get cannot fail here")) + } + + /// `23.2.3.2 get %TypedArray%.prototype.buffer` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer + fn buffer(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + // 5. Return buffer. + Ok(typed_array.viewed_array_buffer().clone().into()) + } + + /// `23.2.3.3 get %TypedArray%.prototype.byteLength` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength + pub(crate) fn byte_length( + this: &JsValue, + _: &[JsValue], + _: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. + // 6. Let size be O.[[ByteLength]]. + // 7. Return 𝔽(size). + if typed_array.is_detached() { + Ok(0.into()) + } else { + Ok(typed_array.byte_length().into()) + } + } + + /// `23.2.3.4 get %TypedArray%.prototype.byteOffset` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset + pub(crate) fn byte_offset( + this: &JsValue, + _: &[JsValue], + _: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. + // 6. Let offset be O.[[ByteOffset]]. + // 7. Return 𝔽(offset). + if typed_array.is_detached() { + Ok(0.into()) + } else { + Ok(typed_array.byte_offset().into()) + } + } + + /// `23.2.3.6 %TypedArray%.prototype.copyWithin ( target, start [ , end ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin + fn copy_within( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + let len = { + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + // 2. Perform ? ValidateTypedArray(O). + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + o.array_length() + }; + + // 4. Let relativeTarget be ? ToIntegerOrInfinity(target). + let relative_target = args.get_or_undefined(0).to_integer_or_infinity(context)?; + + let to = match relative_target { + // 5. If relativeTarget is -∞, let to be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 6. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0). + IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), + // 7. Else, let to be min(relativeTarget, len). + // We can directly convert to `u64` since we covered the case where `i < 0`. + IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 8. Let relativeStart be ? ToIntegerOrInfinity(start). + let relative_start = args.get_or_undefined(1).to_integer_or_infinity(context)?; + + let from = match relative_start { + // 9. If relativeStart is -∞, let from be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 10. Else if relativeStart < 0, let from be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), + // 11. Else, let from be min(relativeStart, len). + // We can directly convert to `u64` since we covered the case where `i < 0`. + IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 12. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + let end = args.get_or_undefined(2); + let r#final = if end.is_undefined() { + len + } else { + match end.to_integer_or_infinity(context)? { + // 13. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 14. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), + // 15. Else, let final be min(relativeEnd, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), + IntegerOrInfinity::PositiveInfinity => len, + } + }; + + // 16. Let count be min(final - from, len - to). + let count = match (r#final.checked_sub(from), len.checked_sub(to)) { + (Some(lhs), Some(rhs)) => std::cmp::min(lhs, rhs), + _ => 0, + }; + + // 17. If count > 0, then + if count > 0 { + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + // a. NOTE: The copying must be performed in a manner that preserves the bit-level encoding of the source data. + // b. Let buffer be O.[[ViewedArrayBuffer]]. + // c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + let buffer_obj = o.viewed_array_buffer(); + let mut buffer_obj_borrow = buffer_obj.borrow_mut(); + let mut buffer = buffer_obj_borrow + .as_buffer_mut() + .expect("Already checked for detached buffer"); + let Some(buffer) = buffer.data_mut() else { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + }; + + // d. Let typedArrayName be the String value of O.[[TypedArrayName]]. + let kind = o.kind(); + + // e. Let elementSize be the Element Size value specified in Table 73 for typedArrayName. + let element_size = kind.element_size(); + + // f. Let byteOffset be O.[[ByteOffset]]. + let byte_offset = o.byte_offset(); + + // g. Let toByteIndex be to × elementSize + byteOffset. + let to_byte_index = (to * element_size + byte_offset) as usize; + + // h. Let fromByteIndex be from × elementSize + byteOffset. + let from_byte_index = (from * element_size + byte_offset) as usize; + + // i. Let countBytes be count × elementSize. + let count_bytes = (count * element_size) as usize; + + // j. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then + // ii. Set fromByteIndex to fromByteIndex + countBytes - 1. + // iii. Set toByteIndex to toByteIndex + countBytes - 1. + // i. Let direction be -1. + // k. Else, + // i. Let direction be 1. + // l. Repeat, while countBytes > 0, + // i. Let value be GetValueFromBuffer(buffer, fromByteIndex, Uint8, true, Unordered). + // ii. Perform SetValueInBuffer(buffer, toByteIndex, Uint8, value, true, Unordered). + // iii. Set fromByteIndex to fromByteIndex + direction. + // iv. Set toByteIndex to toByteIndex + direction. + // v. Set countBytes to countBytes - 1. + + // SAFETY: All previous checks are made to ensure this memmove is always in-bounds, + // making this operation safe. + unsafe { + memmove(buffer, from_byte_index, to_byte_index, count_bytes); + } + } + + // 18. Return O. + Ok(this.clone()) + } + + /// `23.2.3.7 %TypedArray%.prototype.entries ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries + fn entries(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let o = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.borrow() + .as_typed_array() + .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? + .is_detached() + { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Return CreateArrayIterator(O, key+value). + Ok(ArrayIterator::create_array_iterator( + o.clone(), + PropertyNameKind::KeyAndValue, + context, + )) + } + + /// `23.2.3.8 %TypedArray%.prototype.every ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.every + pub(crate) fn every( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + drop(obj_borrow); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.every called with non-callable callback function", + ) + .into()) + } + }; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context)?; + + // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + let test_result = callback_fn + .call( + args.get_or_undefined(1), + &[k_value, k.into(), this.clone()], + context, + )? + .to_boolean(); + + // d. If testResult is false, return false. + if !test_result { + return Ok(false.into()); + } + } + + // 7. Return true. + Ok(true.into()) + } + + /// `23.2.3.9 %TypedArray%.prototype.fill ( value [ , start [ , end ] ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill + pub(crate) fn fill( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + // 4. If O.[[ContentType]] is BigInt, set value to ? ToBigInt(value). + let value: JsValue = if o.kind().content_type() == ContentType::BigInt { + args.get_or_undefined(0).to_bigint(context)?.into() + // 5. Otherwise, set value to ? ToNumber(value). + } else { + args.get_or_undefined(0).to_number(context)?.into() + }; + + // 6. Let relativeStart be ? ToIntegerOrInfinity(start). + let mut k = match args.get_or_undefined(1).to_integer_or_infinity(context)? { + // 7. If relativeStart is -∞, let k be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 8. Else if relativeStart < 0, let k be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 9. Else, let k be min(relativeStart, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + let end = args.get_or_undefined(2); + let relative_end = if end.is_undefined() { + IntegerOrInfinity::Integer(len) + } else { + end.to_integer_or_infinity(context)? + }; + + let r#final = match relative_end { + // 11. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 13. Else, let final be min(relativeEnd, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 14. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + drop(obj_borrow); + + // 15. Repeat, while k < final, + while k < r#final { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Perform ! Set(O, Pk, value, true). + obj.set(k, value.clone(), true, context) + .expect("Set cannot fail here"); + + // c. Set k to k + 1. + k += 1; + } + + // 16. Return O. + Ok(this.clone()) + } + + /// `23.2.3.10 %TypedArray%.prototype.filter ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter + pub(crate) fn filter( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + let typed_array_name = o.kind(); + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + drop(obj_borrow); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = + match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.filter called with non-callable callback function", + ) + .into()), + }; + + // 5. Let kept be a new empty List. + let mut kept = Vec::new(); + + // 6. Let k be 0. + // 7. Let captured be 0. + let mut captured = 0; + + // 8. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).# + let selected = callback_fn + .call( + args.get_or_undefined(1), + &[k_value.clone(), k.into(), this.clone()], + context, + )? + .to_boolean(); + + // d. If selected is true, then + if selected { + // i. Append kValue to the end of kept. + kept.push(k_value); + + // ii. Set captured to captured + 1. + captured += 1; + } + } + + // 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »). + let a = Self::species_create(obj, typed_array_name, &[captured.into()], context)?; + + // 10. Let n be 0. + // 11. For each element e of kept, do + for (n, e) in kept.iter().enumerate() { + // a. Perform ! Set(A, ! ToString(𝔽(n)), e, true). + a.set(n, e.clone(), true, context) + .expect("Set cannot fail here"); + // b. Set n to n + 1. + } + + // 12. Return A. + Ok(a.into()) + } + + /// `23.2.3.11 %TypedArray%.prototype.find ( predicate [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.find + pub(crate) fn find( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + drop(obj_borrow); + + let predicate = args.get_or_undefined(0); + let this_arg = args.get_or_undefined(1); + + // 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). + let (_, value) = find_via_predicate( + obj, + len, + Direction::Ascending, + predicate, + this_arg, + context, + "TypedArray.prototype.find", + )?; + + // 5. Return findRec.[[Value]]. + Ok(value) + } + + /// `23.2.3.12 %TypedArray%.prototype.findIndex ( predicate [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex + pub(crate) fn find_index( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + drop(obj_borrow); + + let predicate = args.get_or_undefined(0); + let this_arg = args.get_or_undefined(1); + + // 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). + let (index, _) = find_via_predicate( + obj, + len, + Direction::Ascending, + predicate, + this_arg, + context, + "TypedArray.prototype.findIndex", + )?; + + // 5. Return findRec.[[Index]]. + Ok(index) + } + + /// `23.2.3.13 %TypedArray%.prototype.findLast ( predicate [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlast + pub(crate) fn find_last( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + let predicate = args.get_or_undefined(0); + let this_arg = args.get_or_undefined(1); + + // 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). + let (_, value) = find_via_predicate( + obj, + len, + Direction::Descending, + predicate, + this_arg, + context, + "TypedArray.prototype.findLast", + )?; + + // 5. Return findRec.[[Value]]. + Ok(value) + } + + /// `23.2.3.14 %TypedArray%.prototype.findLastIndex ( predicate [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlastindex + pub(crate) fn find_last_index( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + let predicate = args.get_or_undefined(0); + let this_arg = args.get_or_undefined(1); + + // 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). + let (index, _) = find_via_predicate( + obj, + len, + Direction::Descending, + predicate, + this_arg, + context, + "TypedArray.prototype.findLastIndex", + )?; + + // 5. Return findRec.[[Index]]. + Ok(index) + } + + /// `23.2.3.15 %TypedArray%.prototype.forEach ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach + pub(crate) fn foreach( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + drop(obj_borrow); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = + match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.foreach called with non-callable callback function", + ) + .into()), + }; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + callback_fn.call( + args.get_or_undefined(1), + &[k_value, k.into(), this.clone()], + context, + )?; + } + + // 7. Return undefined. + Ok(JsValue::undefined()) + } + + /// `23.2.3.14 %TypedArray%.prototype.includes ( searchElement [ , fromIndex ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes + pub(crate) fn includes( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + drop(obj_borrow); + + // 4. If len is 0, return false. + if len == 0 { + return Ok(false.into()); + } + + // 5. Let n be ? ToIntegerOrInfinity(fromIndex). + // 6. Assert: If fromIndex is undefined, then n is 0. + let n = args.get_or_undefined(1).to_integer_or_infinity(context)?; + + let n = match n { + // 7. If n is +∞, return false. + IntegerOrInfinity::PositiveInfinity => return Ok(false.into()), + // 8. Else if n is -∞, set n to 0. + IntegerOrInfinity::NegativeInfinity => 0, + IntegerOrInfinity::Integer(i) => i, + }; + + // 9. If n ≥ 0, then + let mut k = if n >= 0 { + // a. Let k be n. + n + // 10. Else, + } else { + // a. Let k be len + n. + // b. If k < 0, set k to 0. + if len + n < 0 { + 0 + } else { + len + n + } + }; + + // 11. Repeat, while k < len, + while k < len { + // a. Let elementK be ! Get(O, ! ToString(𝔽(k))). + let element_k = obj.get(k, context).expect("Get cannot fail here"); + + // b. If SameValueZero(searchElement, elementK) is true, return true. + if JsValue::same_value_zero(args.get_or_undefined(0), &element_k) { + return Ok(true.into()); + } + + // c. Set k to k + 1. + k += 1; + } + + // 12. Return false. + Ok(false.into()) + } + + /// `23.2.3.15 %TypedArray%.prototype.indexOf ( searchElement [ , fromIndex ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof + pub(crate) fn index_of( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + drop(obj_borrow); + + // 4. If len is 0, return -1𝔽. + if len == 0 { + return Ok((-1).into()); + } + + // 5. Let n be ? ToIntegerOrInfinity(fromIndex). + // 6. Assert: If fromIndex is undefined, then n is 0. + let n = args.get_or_undefined(1).to_integer_or_infinity(context)?; + + let n = match n { + // 7. If n is +∞, return -1𝔽. + IntegerOrInfinity::PositiveInfinity => return Ok((-1).into()), + // 8. Else if n is -∞, set n to 0. + IntegerOrInfinity::NegativeInfinity => 0, + IntegerOrInfinity::Integer(i) => i, + }; + + // 9. If n ≥ 0, then + let mut k = if n >= 0 { + // a. Let k be n. + n + // 10. Else, + } else { + // a. Let k be len + n. + // b. If k < 0, set k to 0. + if len + n < 0 { + 0 + } else { + len + n + } + }; + + // 11. Repeat, while k < len, + while k < len { + // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). + let k_present = obj + .has_property(k, context) + .expect("HasProperty cannot fail here"); + + // b. If kPresent is true, then + if k_present { + // i. Let elementK be ! Get(O, ! ToString(𝔽(k))). + let element_k = obj.get(k, context).expect("Get cannot fail here"); + + // ii. Let same be IsStrictlyEqual(searchElement, elementK). + // iii. If same is true, return 𝔽(k). + if args.get_or_undefined(0).strict_equals(&element_k) { + return Ok(k.into()); + } + } + + // c. Set k to k + 1. + k += 1; + } + + // 12. Return -1𝔽. + Ok((-1).into()) + } + + /// `23.2.3.16 %TypedArray%.prototype.join ( separator )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.join + pub(crate) fn join( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + drop(obj_borrow); + + // 4. If separator is undefined, let sep be the single-element String ",". + let separator = args.get_or_undefined(0); + let sep = if separator.is_undefined() { + js_string!(",") + // 5. Else, let sep be ? ToString(separator). + } else { + separator.to_string(context)? + }; + + // 6. Let R be the empty String. + let mut r = js_string!(); + + // 7. Let k be 0. + // 8. Repeat, while k < len, + for k in 0..len { + // a. If k > 0, set R to the string-concatenation of R and sep. + if k > 0 { + r = js_string!(&r, &sep); + } + + // b. Let element be ! Get(O, ! ToString(𝔽(k))). + let element = obj.get(k, context).expect("Get cannot fail here"); + + // c. If element is undefined, let next be the empty String; otherwise, let next be ! ToString(element). + // d. Set R to the string-concatenation of R and next. + if !element.is_undefined() { + r = js_string!(&r, &element.to_string(context)?); + } + } + + // 9. Return R. + Ok(r.into()) + } + + /// `23.2.3.17 %TypedArray%.prototype.keys ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys + pub(crate) fn keys( + this: &JsValue, + _: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let o = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.borrow() + .as_typed_array() + .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? + .is_detached() + { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Return CreateArrayIterator(O, key). + Ok(ArrayIterator::create_array_iterator( + o.clone(), + PropertyNameKind::Key, + context, + )) + } + + /// `23.2.3.18 %TypedArray%.prototype.lastIndexOf ( searchElement [ , fromIndex ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof + pub(crate) fn last_index_of( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + drop(obj_borrow); + + // 4. If len is 0, return -1𝔽. + if len == 0 { + return Ok((-1).into()); + } + + // 5. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1. + let n = if let Some(n) = args.get(1) { + n.to_integer_or_infinity(context)? + } else { + IntegerOrInfinity::Integer(len - 1) + }; + + let mut k = match n { + // 6. If n is -∞, return -1𝔽. + IntegerOrInfinity::NegativeInfinity => return Ok((-1).into()), + // 7. If n ≥ 0, then + // a. Let k be min(n, len - 1). + IntegerOrInfinity::Integer(i) if i >= 0 => std::cmp::min(i, len - 1), + IntegerOrInfinity::PositiveInfinity => len - 1, + // 8. Else, + // a. Let k be len + n. + IntegerOrInfinity::Integer(i) => len + i, + }; + + // 9. Repeat, while k ≥ 0, + while k >= 0 { + // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). + let k_present = obj + .has_property(k, context) + .expect("HasProperty cannot fail here"); + + // b. If kPresent is true, then + if k_present { + // i. Let elementK be ! Get(O, ! ToString(𝔽(k))). + let element_k = obj.get(k, context).expect("Get cannot fail here"); + + // ii. Let same be IsStrictlyEqual(searchElement, elementK). + // iii. If same is true, return 𝔽(k). + if args.get_or_undefined(0).strict_equals(&element_k) { + return Ok(k.into()); + } + } + + // c. Set k to k - 1. + k -= 1; + } + + // 10. Return -1𝔽. + Ok((-1).into()) + } + + /// `23.2.3.19 get %TypedArray%.prototype.length` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length + pub(crate) fn length(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has [[ViewedArrayBuffer]] and [[ArrayLength]] internal slots. + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. + // 6. Let length be O.[[ArrayLength]]. + // 7. Return 𝔽(length). + if typed_array.is_detached() { + Ok(0.into()) + } else { + Ok(typed_array.array_length().into()) + } + } + + /// `23.2.3.20 %TypedArray%.prototype.map ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.map + pub(crate) fn map( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + let typed_array_name = o.kind(); + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + drop(obj_borrow); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.map called with non-callable callback function", + ) + .into()) + } + }; + + // 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »). + let a = Self::species_create(obj, typed_array_name, &[len.into()], context)?; + + // 6. Let k be 0. + // 7. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + let mapped_value = callback_fn.call( + args.get_or_undefined(1), + &[k_value, k.into(), this.clone()], + context, + )?; + + // d. Perform ? Set(A, Pk, mappedValue, true). + a.set(k, mapped_value, true, context)?; + } + + // 8. Return A. + Ok(a.into()) + } + + /// `23.2.3.21 %TypedArray%.prototype.reduce ( callbackfn [ , initialValue ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce + pub(crate) fn reduce( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + drop(obj_borrow); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = + match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.reduce called with non-callable callback function", + ) + .into()), + }; + + // 5. If len = 0 and initialValue is not present, throw a TypeError exception. + if len == 0 && args.get(1).is_none() { + return Err(JsNativeError::typ() + .with_message("Typed array length is 0 and initial value is not present") + .into()); + } + + // 6. Let k be 0. + let mut k = 0; + + // 7. Let accumulator be undefined. + // 8. If initialValue is present, then + let mut accumulator = if let Some(initial_value) = args.get(1) { + // a. Set accumulator to initialValue. + initial_value.clone() + // 9. Else, + } else { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Set accumulator to ! Get(O, Pk). + // c. Set k to k + 1. + k += 1; + obj.get(0, context).expect("Get cannot fail here") + }; + + // 10. Repeat, while k < len, + while k < len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). + accumulator = callback_fn.call( + &JsValue::undefined(), + &[accumulator, k_value, k.into(), this.clone()], + context, + )?; + + // d. Set k to k + 1. + k += 1; + } + + // 11. Return accumulator. + Ok(accumulator) + } + + /// `23.2.3.22 %TypedArray%.prototype.reduceRight ( callbackfn [ , initialValue ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright + pub(crate) fn reduceright( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + drop(obj_borrow); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.reduceright called with non-callable callback function", + ) + .into()), + }; + + // 5. If len = 0 and initialValue is not present, throw a TypeError exception. + if len == 0 && args.get(1).is_none() { + return Err(JsNativeError::typ() + .with_message("Typed array length is 0 and initial value is not present") + .into()); + } + + // 6. Let k be len - 1. + let mut k = len - 1; + + // 7. Let accumulator be undefined. + // 8. If initialValue is present, then + let mut accumulator = if let Some(initial_value) = args.get(1) { + // a. Set accumulator to initialValue. + initial_value.clone() + // 9. Else, + } else { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Set accumulator to ! Get(O, Pk). + let accumulator = obj.get(k, context).expect("Get cannot fail here"); + + // c. Set k to k - 1. + k -= 1; + + accumulator + }; + + // 10. Repeat, while k ≥ 0, + while k >= 0 { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). + accumulator = callback_fn.call( + &JsValue::undefined(), + &[accumulator, k_value, k.into(), this.clone()], + context, + )?; + + // d. Set k to k - 1. + k -= 1; + } + + // 11. Return accumulator. + Ok(accumulator) + } + + /// `23.2.3.23 %TypedArray%.prototype.reverse ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reverse + #[allow(clippy::float_cmp)] + pub(crate) fn reverse( + this: &JsValue, + _: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as f64; + + drop(obj_borrow); + + // 4. Let middle be floor(len / 2). + let middle = (len / 2.0).floor(); + + // 5. Let lower be 0. + let mut lower = 0.0; + // 6. Repeat, while lower ≠ middle, + while lower != middle { + // a. Let upper be len - lower - 1. + let upper = len - lower - 1.0; + + // b. Let upperP be ! ToString(𝔽(upper)). + // c. Let lowerP be ! ToString(𝔽(lower)). + // d. Let lowerValue be ! Get(O, lowerP). + let lower_value = obj.get(lower, context).expect("Get cannot fail here"); + // e. Let upperValue be ! Get(O, upperP). + let upper_value = obj.get(upper, context).expect("Get cannot fail here"); + + // f. Perform ! Set(O, lowerP, upperValue, true). + obj.set(lower, upper_value, true, context) + .expect("Set cannot fail here"); + // g. Perform ! Set(O, upperP, lowerValue, true). + obj.set(upper, lower_value, true, context) + .expect("Set cannot fail here"); + + // h. Set lower to lower + 1. + lower += 1.0; + } + + // 7. Return O. + Ok(this.clone()) + } + + /// `23.2.3.24 %TypedArray%.prototype.set ( source [ , offset ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.set + pub(crate) fn set( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let target be the this value. + // 2. Perform ? RequireInternalSlot(target, [[TypedArrayName]]). + // 3. Assert: target has a [[ViewedArrayBuffer]] internal slot. + let target = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("TypedArray.set must be called on typed array object") + })?; + if !target.is_typed_array() { + return Err(JsNativeError::typ() + .with_message("TypedArray.set must be called on typed array object") + .into()); + } + + // 4. Let targetOffset be ? ToIntegerOrInfinity(offset). + let target_offset = args.get_or_undefined(1).to_integer_or_infinity(context)?; + + // 5. If targetOffset < 0, throw a RangeError exception. + let target_offset = match target_offset { + IntegerOrInfinity::Integer(i) if i < 0 => { + return Err(JsNativeError::range() + .with_message("TypedArray.set called with negative offset") + .into()) + } + IntegerOrInfinity::NegativeInfinity => { + return Err(JsNativeError::range() + .with_message("TypedArray.set called with negative offset") + .into()) + } + IntegerOrInfinity::PositiveInfinity => U64OrPositiveInfinity::PositiveInfinity, + IntegerOrInfinity::Integer(i) => U64OrPositiveInfinity::U64(i as u64), + }; + + let source = args.get_or_undefined(0); + match source { + // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then + JsValue::Object(source) if source.is_typed_array() => { + // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). + Self::set_typed_array_from_typed_array(target, &target_offset, source, context)?; + } + // 7. Else, + _ => { + // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). + Self::set_typed_array_from_array_like(target, &target_offset, source, context)?; + } + } + + // 8. Return undefined. + Ok(JsValue::undefined()) + } + + /// `3.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray + fn set_typed_array_from_typed_array( + target: &JsObject, + target_offset: &U64OrPositiveInfinity, + source: &JsObject, + context: &mut Context<'_>, + ) -> JsResult<()> { + let target_borrow = target.borrow(); + let target_array = target_borrow + .as_typed_array() + .expect("Target must be a typed array"); + + let source_borrow = source.borrow(); + let source_array = source_borrow + .as_typed_array() + .expect("Source must be a typed array"); + + // TODO: Implement growable buffers. + // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. + // 2. Let targetRecord be MakeIntegerIndexedObjectWithBufferWitnessRecord(target, seq-cst). + // 3. If IsIntegerIndexedObjectOutOfBounds(targetRecord) is true, throw a TypeError exception. + // 4. Let targetLength be IntegerIndexedObjectLength(targetRecord). + // 5. Let srcBuffer be source.[[ViewedArrayBuffer]]. + // 6. Let srcRecord be MakeIntegerIndexedObjectWithBufferWitnessRecord(source, seq-cst). + // 7. If IsIntegerIndexedObjectOutOfBounds(srcRecord) is true, throw a TypeError exception. + + if target_array.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + let target_buffer_obj = target_array.viewed_array_buffer().clone(); + + // 3. Let targetLength be target.[[ArrayLength]]. + let target_length = target_array.array_length(); + + // 4. Let srcBuffer be source.[[ViewedArrayBuffer]]. + // 5. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. + if source_array.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + let mut src_buffer_obj = source_array.viewed_array_buffer().clone(); + + // 6. Let targetName be the String value of target.[[TypedArrayName]]. + // 7. Let targetType be the Element Type value in Table 73 for targetName. + let target_type = target_array.kind(); + + // 8. Let targetElementSize be the Element Size value specified in Table 73 for targetName. + let target_element_size = target_type.element_size(); + + // 9. Let targetByteOffset be target.[[ByteOffset]]. + let target_byte_offset = target_array.byte_offset(); + + drop(target_borrow); + + // 10. Let srcName be the String value of source.[[TypedArrayName]]. + // 11. Let srcType be the Element Type value in Table 73 for srcName. + let src_type = source_array.kind(); + + // 12. Let srcElementSize be the Element Size value specified in Table 73 for srcName. + let src_element_size = src_type.element_size(); + + // 13. Let srcLength be source.[[ArrayLength]]. + let src_length = source_array.array_length(); + + // 14. Let srcByteOffset be source.[[ByteOffset]]. + let src_byte_offset = source_array.byte_offset(); + + // 15. If targetOffset is +∞, throw a RangeError exception. + let target_offset = match target_offset { + U64OrPositiveInfinity::U64(target_offset) => target_offset, + U64OrPositiveInfinity::PositiveInfinity => { + return Err(JsNativeError::range() + .with_message("Target offset cannot be Infinity") + .into()); + } + }; + + // 16. If srcLength + targetOffset > targetLength, throw a RangeError exception. + if src_length + target_offset > target_length { + return Err(JsNativeError::range() + .with_message("Source typed array and target offset longer than target typed array") + .into()); + } + + // 17. If target.[[ContentType]] ≠ source.[[ContentType]], throw a TypeError exception. + if target_type.content_type() != src_type.content_type() { + return Err(JsNativeError::typ() + .with_message( + "Source typed array and target typed array have different content types", + ) + .into()); + } + + // 18. If IsSharedArrayBuffer(srcBuffer) is true, IsSharedArrayBuffer(targetBuffer) is true, + // and srcBuffer.[[ArrayBufferData]] is targetBuffer.[[ArrayBufferData]], let + // sameSharedArrayBuffer be true; otherwise, let sameSharedArrayBuffer be false. + let same = if JsObject::equals(&src_buffer_obj, &target_buffer_obj) { + true + } else { + let src_buffer_obj = src_buffer_obj.borrow(); + let src_buffer = src_buffer_obj.as_buffer().expect("Must be an array buffer"); + + let target_buffer_obj = target_buffer_obj.borrow(); + let target_buffer = target_buffer_obj + .as_buffer() + .expect("Must be an array buffer"); + + match (src_buffer, target_buffer) { + (BufferRef::Shared(src), BufferRef::Shared(dest)) => { + ptr::eq(src.data(), dest.data()) + } + (_, _) => false, + } + }; + + // 19. If SameValue(srcBuffer, targetBuffer) is true or sameSharedArrayBuffer is true, then + let src_byte_index = if same { + // a. Let srcByteLength be source.[[ByteLength]]. + let src_byte_offset = src_byte_offset as usize; + let src_byte_length = source_array.byte_length() as usize; + + let s = { + let slice = src_buffer_obj.borrow(); + let slice = slice.as_buffer().expect("Must be an array buffer"); + let slice = slice.data().expect("Already checked for detached buffer"); + + // b. Set srcBuffer to ? CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength, %ArrayBuffer%). + // c. NOTE: %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects. + slice + .subslice(src_byte_offset..src_byte_offset + src_byte_length) + .clone(context)? + }; + src_buffer_obj = s; + + // d. Let srcByteIndex be 0. + 0 + } + // 21. Else, let srcByteIndex be srcByteOffset. + else { + src_byte_offset + }; + + drop(source_borrow); + + // 22. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. + let target_byte_index = target_offset * target_element_size + target_byte_offset; + + let src_buffer = src_buffer_obj.borrow(); + let src_buffer = src_buffer.as_buffer().expect("Must be an array buffer"); + let src_buffer = src_buffer + .data() + .expect("Already checked for detached buffer"); + + let mut target_buffer = target_buffer_obj.borrow_mut(); + let mut target_buffer = target_buffer + .as_buffer_mut() + .expect("Must be an array buffer"); + let mut target_buffer = target_buffer + .data_mut() + .expect("Already checked for detached buffer"); + + // 24. If srcType is the same as targetType, then + if src_type == target_type { + let src_byte_index = src_byte_index as usize; + let target_byte_index = target_byte_index as usize; + let count = (target_element_size * src_length) as usize; + + // a. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. + // b. Repeat, while targetByteIndex < limit, + // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). + // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). + // iii. Set srcByteIndex to srcByteIndex + 1. + // iv. Set targetByteIndex to targetByteIndex + 1. + + // SAFETY: We already asserted that the indices are in bounds. + unsafe { + memcpy( + src_buffer.subslice(src_byte_index..), + target_buffer.subslice_mut(target_byte_index..), + count, + ); + } + } + // 25. Else, + else { + // 23. Let limit be targetByteIndex + targetElementSize × srcLength. + let limit = (target_byte_index + target_element_size * src_length) as usize; + + let mut src_byte_index = src_byte_index as usize; + let mut target_byte_index = target_byte_index as usize; + + // a. Repeat, while targetByteIndex < limit, + while target_byte_index < limit { + // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, Unordered). + + let value = unsafe { + src_buffer + .subslice(src_byte_index..) + .get_value(src_type, atomic::Ordering::Relaxed) + }; + + let value = JsValue::from(value); + + let value = target_type + .get_element(&value, context) + .expect("value can only be f64 or BigInt"); + + // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, Unordered). + unsafe { + target_buffer + .subslice_mut(target_byte_index..) + .set_value(value, atomic::Ordering::Relaxed); + } + + // iii. Set srcByteIndex to srcByteIndex + srcElementSize. + src_byte_index += src_element_size as usize; + + // iv. Set targetByteIndex to targetByteIndex + targetElementSize. + target_byte_index += target_element_size as usize; + } + } + + Ok(()) + } + + /// `23.2.3.24.2 SetTypedArrayFromArrayLike ( target, targetOffset, source )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-settypedarrayfromarraylike + fn set_typed_array_from_array_like( + target: &JsObject, + target_offset: &U64OrPositiveInfinity, + source: &JsValue, + context: &mut Context<'_>, + ) -> JsResult<()> { + let target_length = { + let target_borrow = target.borrow(); + let target_array = target_borrow + .as_typed_array() + .expect("Target must be a typed array"); + + // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. + // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. + if target_array.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let targetLength be target.[[ArrayLength]]. + target_array.array_length() + }; + + // 4. Let src be ? ToObject(source). + let src = source.to_object(context)?; + + // 5. Let srcLength be ? LengthOfArrayLike(src). + let src_length = src.length_of_array_like(context)?; + + // 6. If targetOffset = +∞, throw a RangeError exception. + let target_offset = match target_offset { + U64OrPositiveInfinity::U64(target_offset) => target_offset, + U64OrPositiveInfinity::PositiveInfinity => { + return Err(JsNativeError::range() + .with_message("Target offset cannot be positive infinity") + .into()) + } + }; + + // 7. If srcLength + targetOffset > targetLength, throw a RangeError exception. + if src_length + target_offset > target_length { + return Err(JsNativeError::range() + .with_message("Source object and target offset longer than target typed array") + .into()); + } + + // 8. Let k be 0. + // 9. Repeat, while k < srcLength, + for k in 0..src_length { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let value be ? Get(src, Pk). + let value = src.get(k, context)?; + + // c. Let targetIndex be 𝔽(targetOffset + k). + let target_index = target_offset + k; + + // d. Perform ? IntegerIndexedElementSet(target, targetIndex, value). + integer_indexed_element_set(target, target_index as f64, &value, context)?; + + // e. Set k to k + 1. + } + + // 10. Return unused. + Ok(()) + } + + /// `23.2.3.25 %TypedArray%.prototype.slice ( start, end )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice + pub(crate) fn slice( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length() as i64; + + // 4. Let relativeStart be ? ToIntegerOrInfinity(start). + let mut k = match args.get_or_undefined(0).to_integer_or_infinity(context)? { + // 5. If relativeStart is -∞, let k be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 6. Else if relativeStart < 0, let k be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 7. Else, let k be min(relativeStart, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + let end = args.get_or_undefined(1); + let relative_end = if end.is_undefined() { + IntegerOrInfinity::Integer(len) + } else { + end.to_integer_or_infinity(context)? + }; + + let r#final = match relative_end { + // 9. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 10. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), + // 11. Else, let final be min(relativeEnd, len). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), + IntegerOrInfinity::PositiveInfinity => len, + }; + + // 12. Let count be max(final - k, 0). + let count = std::cmp::max(r#final - k, 0) as u64; + + // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). + let a = Self::species_create(obj, o.kind(), &[count.into()], context)?; + let a_borrow = a.borrow(); + let a_array = a_borrow + .as_typed_array() + .expect("This must be a typed array"); + + // 14. If count > 0, then + if count > 0 { + // a. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // b. Let srcName be the String value of O.[[TypedArrayName]]. + // c. Let srcType be the Element Type value in Table 73 for srcName. + let src_type = o.kind(); + + // d. Let targetName be the String value of A.[[TypedArrayName]]. + // e. Let targetType be the Element Type value in Table 73 for targetName. + let target_type = a_array.kind(); + + // f. If srcType is different from targetType, then + #[allow(clippy::if_not_else)] + if src_type != target_type { + drop(obj_borrow); + drop(a_borrow); + + // i. Let n be 0. + let mut n = 0; + + // ii. Repeat, while k < final, + while k < r#final { + // 1. Let Pk be ! ToString(𝔽(k)). + // 2. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true). + a.set(n, k_value, true, context) + .expect("Set cannot fail here"); + + // 4. Set k to k + 1. + k += 1; + + // 5. Set n to n + 1. + n += 1; + } + // g. Else, + } else { + // i. Let srcBuffer be O.[[ViewedArrayBuffer]]. + let src_buffer_obj = o.viewed_array_buffer(); + let src_buffer_obj_borrow = src_buffer_obj.borrow(); + let src_buffer = src_buffer_obj_borrow + .as_buffer() + .expect("view must be a buffer"); + let src_buffer = src_buffer.data().expect("cannot be detached here"); + + // ii. Let targetBuffer be A.[[ViewedArrayBuffer]]. + let target_buffer_obj = a_array.viewed_array_buffer(); + let mut target_buffer_obj_borrow = target_buffer_obj.borrow_mut(); + let mut target_buffer = target_buffer_obj_borrow + .as_buffer_mut() + .expect("view must be a buffer"); + let mut target_buffer = target_buffer.data_mut().expect("cannot be detached here"); + + // iii. Let elementSize be the Element Size value specified in Table 73 for Element Type srcType. + let element_size = o.kind().element_size(); + + // iv. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. + + // v. Let srcByteOffset be O.[[ByteOffset]]. + let src_byte_offset = o.byte_offset(); + + // vi. Let targetByteIndex be A.[[ByteOffset]]. + let target_byte_index = (a_array.byte_offset()) as usize; + + // vii. Let srcByteIndex be (k × elementSize) + srcByteOffset. + let src_byte_index = (k as u64 * element_size + src_byte_offset) as usize; + + let byte_count = (count * element_size) as usize; + + // viii. Let limit be targetByteIndex + count × elementSize. + // ix. Repeat, while targetByteIndex < limit, + // 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). + // 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). + // 3. Set srcByteIndex to srcByteIndex + 1. + // 4. Set targetByteIndex to targetByteIndex + 1. + + // SAFETY: All previous checks put the indices at least within the bounds of `src_buffer`. + // Also, `target_buffer` is precisely allocated to fit all sliced elements from + // `src_buffer`, making this operation safe. + unsafe { + memcpy( + src_buffer.subslice(src_byte_index..), + target_buffer.subslice_mut(target_byte_index..), + byte_count, + ); + } + + drop(target_buffer_obj_borrow); + drop(a_borrow); + } + } else { + drop(a_borrow); + } + + // 15. Return A. + Ok(a.into()) + } + + /// `23.2.3.26 %TypedArray%.prototype.some ( callbackfn [ , thisArg ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.some + pub(crate) fn some( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Let len be O.[[ArrayLength]]. + let len = o.array_length(); + + drop(obj_borrow); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = match args.get_or_undefined(0).as_object() { + Some(obj) if obj.is_callable() => obj, + _ => { + return Err(JsNativeError::typ() + .with_message( + "TypedArray.prototype.some called with non-callable callback function", + ) + .into()) + } + }; + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + let k_value = obj.get(k, context).expect("Get cannot fail here"); + + // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + // d. If testResult is true, return true. + if callback_fn + .call( + args.get_or_undefined(1), + &[k_value, k.into(), this.clone()], + context, + )? + .to_boolean() + { + return Ok(true.into()); + } + } + + // 7. Return false. + Ok(false.into()) + } + + /// `23.2.3.27 %TypedArray%.prototype.sort ( comparefn )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort + pub(crate) fn sort( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. + let compare_fn = match args.get(0) { + None | Some(JsValue::Undefined) => None, + Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj), + _ => { + return Err(JsNativeError::typ() + .with_message("TypedArray.sort called with non-callable comparefn") + .into()) + } + }; + + // 2. Let obj be the this value. + // 3. Perform ? ValidateTypedArray(obj). + // 4. Let len be obj.[[ArrayLength]]. + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ() + .with_message("TypedArray.sort must be called on typed array object") + })?; + let len = + { + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ() + .with_message("TypedArray.sort must be called on typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ().with_message( + "TypedArray.sort called on typed array object with detached array buffer", + ).into()); + } + + o.array_length() + }; + + // 5. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.30. + // 6. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called: + let sort_compare = |x: &JsValue, + y: &JsValue, + compare_fn: Option<&JsObject>, + context: &mut Context<'_>| + -> JsResult { + // a. Return ? CompareTypedArrayElements(x, y, comparefn). + compare_typed_array_elements(x, y, compare_fn, context) + }; + + // Note: This step is currently inlined. + // 7. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, read-through-holes). + // 1. Let items be a new empty List. + let mut items = Vec::with_capacity(len as usize); + + // 2. Let k be 0. + // 3. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. If holes is skip-holes, then + // i. Let kRead be ? HasProperty(obj, Pk). + // c. Else, + // i. Assert: holes is read-through-holes. + // ii. Let kRead be true. + // d. If kRead is true, then + // i. Let kValue be ? Get(obj, Pk). + let k_value = obj.get(k, context)?; + + // ii. Append kValue to items. + items.push(k_value); + // e. Set k to k + 1. + } + + // 4. Sort items using an implementation-defined sequence of calls to SortCompare. If any such call returns an abrupt completion, stop before performing any further calls to SortCompare and return that Completion Record. + // 5. Return items. + let mut sort_err = Ok(()); + items.sort_by(|x, y| { + if sort_err.is_ok() { + sort_compare(x, y, compare_fn, context).unwrap_or_else(|err| { + sort_err = Err(err); + Ordering::Equal + }) + } else { + Ordering::Equal + } + }); + sort_err?; + + // 8. Let j be 0. + // 9. Repeat, while j < len, + for (j, item) in items.into_iter().enumerate() { + // a. Perform ! Set(obj, ! ToString(𝔽(j)), sortedList[j], true). + obj.set(j, item, true, context) + .expect("cannot fail per spec"); + + // b. Set j to j + 1. + } + + // 10. Return obj. + Ok(obj.clone().into()) + } + + /// `23.2.3.28 %TypedArray%.prototype.subarray ( begin, end )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.subarray + pub(crate) fn subarray( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. + let obj = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + let obj_borrow = obj.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + // 4. Let buffer be O.[[ViewedArrayBuffer]]. + let buffer = o.viewed_array_buffer(); + + // 5. Let srcLength be O.[[ArrayLength]]. + let src_length = o.array_length() as i64; + + // 6. Let relativeBegin be ? ToIntegerOrInfinity(begin). + let begin_index = match args.get_or_undefined(0).to_integer_or_infinity(context)? { + // 7. If relativeBegin is -∞, let beginIndex be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 8. Else if relativeBegin < 0, let beginIndex be max(srcLength + relativeBegin, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(src_length + i, 0), + // 9. Else, let beginIndex be min(relativeBegin, srcLength). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, src_length), + IntegerOrInfinity::PositiveInfinity => src_length, + }; + + // 10. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end). + let end = args.get_or_undefined(1); + let relative_end = if end.is_undefined() { + IntegerOrInfinity::Integer(src_length) + } else { + end.to_integer_or_infinity(context)? + }; + + let end_index = match relative_end { + // 11. If relativeEnd is -∞, let endIndex be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 12. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(src_length + i, 0), + // 13. Else, let endIndex be min(relativeEnd, srcLength). + IntegerOrInfinity::Integer(i) => std::cmp::min(i, src_length), + IntegerOrInfinity::PositiveInfinity => src_length, + }; + + // 14. Let newLength be max(endIndex - beginIndex, 0). + let new_length = std::cmp::max(end_index - begin_index, 0); + + // 15. Let constructorName be the String value of O.[[TypedArrayName]]. + // 16. Let elementSize be the Element Size value specified in Table 73 for constructorName. + let element_size = o.kind().element_size(); + + // 17. Let srcByteOffset be O.[[ByteOffset]]. + let src_byte_offset = o.byte_offset(); + + // 18. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. + let begin_byte_offset = src_byte_offset + begin_index as u64 * element_size; + + // 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». + // 20. Return ? TypedArraySpeciesCreate(O, argumentsList). + Ok(Self::species_create( + obj, + o.kind(), + &[ + buffer.clone().into(), + begin_byte_offset.into(), + new_length.into(), + ], + context, + )? + .into()) + } + + /// `%TypedArray%.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] )` + /// `Array.prototype.toLocaleString ( [ locales [ , options ] ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [ECMA-402 reference][spec-402] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.tolocalestring + /// [spec-402]: https://402.ecma-international.org/10.0/#sup-array.prototype.tolocalestring + fn to_locale_string( + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let array be ? ToObject(this value). + // Note: ValidateTypedArray is applied to the this value prior to evaluating the algorithm. + let array = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + let len = { + let obj_borrow = array.borrow(); + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 2. Let len be array.[[ArrayLength]] + o.array_length() + }; + + // 3. Let separator be the implementation-defined list-separator String value + // appropriate for the host environment's current locale (such as ", "). + let separator = { + #[cfg(feature = "intl")] + { + // TODO: this should eventually return a locale-sensitive separator. + utf16!(", ") + } + + #[cfg(not(feature = "intl"))] + { + utf16!(", ") + } + }; + + // 4. Let R be the empty String. + let mut r = Vec::new(); + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for k in 0..len { + // a. If k > 0, then + if k > 0 { + // i. Set R to the string-concatenation of R and separator. + r.extend_from_slice(separator); + } + + // b. Let nextElement be ? Get(array, ! ToString(k)). + let next_element = array.get(k, context)?; + + // c. If nextElement is not undefined or null, then + if !next_element.is_null_or_undefined() { + // i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)). + let s = next_element + .invoke( + utf16!("toLocaleString"), + &[ + args.get_or_undefined(0).clone(), + args.get_or_undefined(1).clone(), + ], + context, + )? + .to_string(context)?; + + // ii. Set R to the string-concatenation of R and S. + r.extend_from_slice(&s); + } + + // d. Increase k by 1. + } + + // 7. Return R. + Ok(js_string!(r).into()) + } + + /// `23.2.3.31 %TypedArray%.prototype.values ( )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.values + fn values(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + let o = this.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.borrow() + .as_typed_array() + .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? + .is_detached() + { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. Return CreateArrayIterator(O, value). + Ok(ArrayIterator::create_array_iterator( + o.clone(), + PropertyNameKind::Value, + context, + )) + } + + /// `23.2.3.33 get %TypedArray%.prototype [ @@toStringTag ]` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag + #[allow(clippy::unnecessary_wraps)] + fn to_string_tag(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { + // 1. Let O be the this value. + // 2. If Type(O) is not Object, return undefined. + // 3. If O does not have a [[TypedArrayName]] internal slot, return undefined. + // 4. Let name be O.[[TypedArrayName]]. + // 5. Assert: Type(name) is String. + // 6. Return name. + Ok(this + .as_object() + .and_then(|obj| { + obj.borrow() + .as_typed_array() + .map(|o| o.kind().js_name().into()) + }) + .unwrap_or(JsValue::Undefined)) + } + + /// `23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#typedarray-species-create + fn species_create( + exemplar: &JsObject, + kind: TypedArrayKind, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let defaultConstructor be the intrinsic object listed in column one of Table 73 for exemplar.[[TypedArrayName]]. + let default_constructor = kind.standard_constructor(); + + // 2. Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor). + let constructor = exemplar.species_constructor(default_constructor, context)?; + + // 3. Let result be ? TypedArrayCreate(constructor, argumentList). + let result = Self::create(&constructor, args, context)?; + + // 4. Assert: result has [[TypedArrayName]] and [[ContentType]] internal slots. + // 5. If result.[[ContentType]] ≠ exemplar.[[ContentType]], throw a TypeError exception. + if result + .borrow() + .as_typed_array() + .expect("This can only be a typed array object") + .kind() + .content_type() + != kind.content_type() + { + return Err(JsNativeError::typ() + .with_message("New typed array has different context type than exemplar") + .into()); + } + + // 6. Return result. + Ok(result) + } + + /// `23.2.4.2 TypedArrayCreate ( constructor, argumentList )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#typedarray-create + fn create( + constructor: &JsObject, + args: &[JsValue], + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let newTypedArray be ? Construct(constructor, argumentList). + let new_typed_array = constructor.construct(args, Some(constructor), context)?; + + let obj_borrow = new_typed_array.borrow(); + // 2. Perform ? ValidateTypedArray(newTypedArray). + let o = obj_borrow.as_typed_array().ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + if o.is_detached() { + return Err(JsNativeError::typ() + .with_message("Buffer of the typed array is detached") + .into()); + } + + // 3. If argumentList is a List of a single Number, then + if args.len() == 1 { + if let Some(number) = args[0].as_number() { + // a. If newTypedArray.[[ArrayLength]] < ℝ(argumentList[0]), throw a TypeError exception. + if (o.array_length() as f64) < number { + return Err(JsNativeError::typ() + .with_message("New typed array length is smaller than expected") + .into()); + } + } + } + + // 4. Return newTypedArray. + Ok(new_typed_array.clone()) + } + + /// + fn allocate_buffer( + length: u64, + context: &mut Context<'_>, + ) -> JsResult { + // 1. Assert: O.[[ViewedArrayBuffer]] is undefined. + + // 2. Let constructorName be the String value of O.[[TypedArrayName]]. + // 3. Let elementSize be the Element Size value specified in Table 73 for constructorName. + let element_size = T::ERASED.element_size(); + + // 4. Let byteLength be elementSize × length. + let byte_length = element_size * length; + + // 5. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). + let data = ArrayBuffer::allocate( + &context + .intrinsics() + .constructors() + .array_buffer() + .constructor() + .into(), + byte_length, + context, + )?; + + // 6. Set O.[[ViewedArrayBuffer]] to data. + // 7. Set O.[[ByteLength]] to byteLength. + // 8. Set O.[[ByteOffset]] to 0. + // 9. Set O.[[ArrayLength]] to length. + + // 10. Return O. + Ok(IntegerIndexed::new(data, T::ERASED, 0, byte_length, length)) + } + + /// + pub(crate) fn initialize_from_list( + proto: JsObject, + values: Vec, + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let len be the number of elements in values. + let len = values.len() as u64; + // 2. Perform ? AllocateTypedArrayBuffer(O, len). + let buf = Self::allocate_buffer::(len, context)?; + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + proto, + ObjectData::integer_indexed(buf), + ); + + // 3. Let k be 0. + // 4. Repeat, while k < len, + for (k, k_value) in values.into_iter().enumerate() { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be the first element of values and remove that element from values. + // c. Perform ? Set(O, Pk, kValue, true). + obj.set(k, k_value, true, context)?; + // d. Set k to k + 1. + } + + // 5. Assert: values is now an empty List. + // It no longer exists. + Ok(obj) + } + + /// `AllocateTypedArray ( constructorName, newTarget, defaultProto [ , length ] )` + /// + /// It is used to validate and create an instance of a `TypedArray` constructor. If the `length` + /// argument is passed, an `ArrayBuffer` of that length is also allocated and associated with the + /// new `TypedArray` instance. `AllocateTypedArray` provides common semantics that is used by + /// `TypedArray`. + /// + /// For more information, check the [spec][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-allocatetypedarray + pub(super) fn allocate( + new_target: &JsValue, + length: u64, + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let proto be ? GetPrototypeFromConstructor(newTarget, defaultProto). + let proto = get_prototype_from_constructor(new_target, T::STANDARD_CONSTRUCTOR, context)?; + + // 3. Assert: obj.[[ViewedArrayBuffer]] is undefined. + // 4. Set obj.[[TypedArrayName]] to constructorName. + // 5. If constructorName is "BigInt64Array" or "BigUint64Array", set obj.[[ContentType]] to BigInt. + // 6. Otherwise, set obj.[[ContentType]] to Number. + // 7. If length is not present, then + // a. Set obj.[[ByteLength]] to 0. + // b. Set obj.[[ByteOffset]] to 0. + // c. Set obj.[[ArrayLength]] to 0. + + // 8. Else, + // a. Perform ? AllocateTypedArrayBuffer(obj, length). + let indexed = Self::allocate_buffer::(length, context)?; + + // 2. Let obj be ! IntegerIndexedObjectCreate(proto). + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + proto, + ObjectData::integer_indexed(indexed), + ); + + // 9. Return obj. + Ok(obj) + } + + /// `23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray + pub(super) fn initialize_from_typed_array( + proto: JsObject, + src_array: &JsObject, + context: &mut Context<'_>, + ) -> JsResult { + let src_array = src_array.borrow(); + let src_array = src_array + .as_typed_array() + .expect("this must be a typed array"); + let src_data = src_array.viewed_array_buffer(); + let src_data = src_data.borrow(); + let src_data = src_data + .as_buffer() + .expect("integer indexed must have a buffer"); + + // 1. Let srcData be srcArray.[[ViewedArrayBuffer]]. + // 2. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. + let Some(src_data) = src_data.data() else { + return Err(JsNativeError::typ() + .with_message("Cannot initialize typed array from detached buffer") + .into()); + }; + + // 3. Let elementType be TypedArrayElementType(O). + let element_type = T::ERASED; + + // 4. Let elementSize be TypedArrayElementSize(O). + let target_element_size = element_type.element_size(); + + // 5. Let srcType be TypedArrayElementType(srcArray). + let src_type = src_array.kind(); + + // 6. Let srcElementSize be TypedArrayElementSize(srcArray). + let src_element_size = src_type.element_size(); + + // 7. Let srcByteOffset be srcArray.[[ByteOffset]]. + let src_byte_offset = src_array.byte_offset(); + + // 8. Let elementLength be srcArray.[[ArrayLength]]. + let element_length = src_array.array_length(); + + // 9. Let byteLength be elementSize × elementLength. + let byte_length = target_element_size * element_length; + + // 10. If elementType is srcType, then + let new_buffer = if element_type == src_type { + let start = src_byte_offset as usize; + let end = src_byte_offset as usize; + // a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength). + src_data.subslice(start..start + end).clone(context)? + } + // 11. Else, + else { + // a. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). + let data_obj = ArrayBuffer::allocate( + &context + .realm() + .intrinsics() + .constructors() + .array_buffer() + .constructor() + .into(), + byte_length, + context, + )?; + let mut data_obj_b = data_obj.borrow_mut(); + let data = data_obj_b + .as_array_buffer_mut() + .expect("Must be ArrayBuffer"); + let mut data = + SliceRefMut::Common(data.data_mut().expect("a new buffer cannot be detached")); + + // b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception. + if src_type.content_type() != element_type.content_type() { + return Err(JsNativeError::typ() + .with_message("Cannot initialize typed array from different content type") + .into()); + } + + let src_element_size = src_element_size as usize; + let target_element_size = target_element_size as usize; + + // c. Let srcByteIndex be srcByteOffset. + let mut src_byte_index = src_byte_offset as usize; + + // d. Let targetByteIndex be 0. + let mut target_byte_index = 0; + + // e. Let count be elementLength. + let mut count = element_length; + + // f. Repeat, while count > 0, + while count > 0 { + // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered). + // SAFETY: All integer indexed objects are always in-bounds and properly + // aligned to their underlying buffer. + let value = unsafe { + src_data + .subslice(src_byte_index..) + .get_value(src_type, atomic::Ordering::Relaxed) + }; + + let value = JsValue::from(value); + + // TODO: cast between types instead of converting to `JsValue`. + let value = element_type + .get_element(&value, context) + .expect("value must be bigint or float"); + + // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, Unordered). + + // SAFETY: The newly created buffer has at least `element_size * element_length` + // bytes available, which makes `target_byte_index` always in-bounds. + unsafe { + data.subslice_mut(target_byte_index..) + .set_value(value, atomic::Ordering::Relaxed); + } + + // iii. Set srcByteIndex to srcByteIndex + srcElementSize. + src_byte_index += src_element_size; + + // iv. Set targetByteIndex to targetByteIndex + elementSize. + target_byte_index += target_element_size; + + // v. Set count to count - 1. + count -= 1; + } + + drop(data_obj_b); + data_obj + }; + + // 12. Set O.[[ViewedArrayBuffer]] to data. + // 13. Set O.[[ByteLength]] to byteLength. + // 14. Set O.[[ByteOffset]] to 0. + // 15. Set O.[[ArrayLength]] to elementLength. + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + proto, + ObjectData::integer_indexed(IntegerIndexed::new( + new_buffer, + element_type, + 0, + byte_length, + element_length, + )), + ); + + // 16. Return unused. + Ok(obj) + } + + /// `23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset, length )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer + pub(super) fn initialize_from_array_buffer( + proto: JsObject, + buffer: JsObject, + byte_offset: &JsValue, + length: &JsValue, + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let elementSize be TypedArrayElementSize(O). + let element_size = T::ERASED.element_size(); + + // 2. Let offset be ? ToIndex(byteOffset). + let offset = byte_offset.to_index(context)?; + + // 3. If offset modulo elementSize ≠ 0, throw a RangeError exception. + if offset % element_size != 0 { + return Err(JsNativeError::range() + .with_message("Invalid offset for typed array") + .into()); + } + + // 4. If length is not undefined, then + let new_length = if length.is_undefined() { + None + } else { + // a. Let newLength be ? ToIndex(length). + Some(length.to_index(context)?) + }; + + // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + // 6. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. + let buffer_byte_length = { + let buffer_borrow = buffer.borrow(); + let buffer_array = buffer_borrow.as_buffer().expect("Must be a buffer"); + + let Some(data) = buffer_array.data() else { + return Err(JsNativeError::typ() + .with_message("Cannot construct typed array from detached buffer") + .into()); + }; + + data.len() as u64 + }; + + // 7. If length is undefined, then + // 8. Else, + let new_byte_length = if let Some(new_length) = new_length { + // a. Let newByteLength be newLength × elementSize. + let new_byte_length = new_length * element_size; + + // b. If offset + newByteLength > bufferByteLength, throw a RangeError exception. + if offset + new_byte_length > buffer_byte_length { + return Err(JsNativeError::range() + .with_message("Invalid length for typed array") + .into()); + } + + new_byte_length + } else { + // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception. + if buffer_byte_length % element_size != 0 { + return Err(JsNativeError::range() + .with_message("Invalid length for typed array") + .into()); + } + + // b. Let newByteLength be bufferByteLength - offset. + let new_byte_length = buffer_byte_length as i64 - offset as i64; + + // c. If newByteLength < 0, throw a RangeError exception. + if new_byte_length < 0 { + return Err(JsNativeError::range() + .with_message("Invalid length for typed array") + .into()); + } + + new_byte_length as u64 + }; + + // 9. Set O.[[ViewedArrayBuffer]] to buffer. + // 10. Set O.[[ByteLength]] to newByteLength. + // 11. Set O.[[ByteOffset]] to offset. + // 12. Set O.[[ArrayLength]] to newByteLength / elementSize. + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + proto, + ObjectData::integer_indexed(IntegerIndexed::new( + buffer, + T::ERASED, + offset, + new_byte_length, + new_byte_length / element_size, + )), + ); + + // 13. Return unused. + Ok(obj) + } + + /// `23.2.5.1.5 InitializeTypedArrayFromArrayLike ( O, arrayLike )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike + pub(super) fn initialize_from_array_like( + proto: JsObject, + array_like: &JsObject, + context: &mut Context<'_>, + ) -> JsResult { + // 1. Let len be ? LengthOfArrayLike(arrayLike). + let len = array_like.length_of_array_like(context)?; + + // 2. Perform ? AllocateTypedArrayBuffer(O, len). + let buf = Self::allocate_buffer::(len, context)?; + let obj = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + proto, + ObjectData::integer_indexed(buf), + ); + + // 3. Let k be 0. + // 4. Repeat, while k < len, + for k in 0..len { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ? Get(arrayLike, Pk). + let k_value = array_like.get(k, context)?; + + // c. Perform ? Set(O, Pk, kValue, true). + obj.set(k, k_value, true, context)?; + } + + Ok(obj) + } +} + +/// `CompareTypedArrayElements ( x, y, comparefn )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-comparetypedarrayelements +fn compare_typed_array_elements( + x: &JsValue, + y: &JsValue, + compare_fn: Option<&JsObject>, + context: &mut Context<'_>, +) -> JsResult { + // 1. Assert: x is a Number and y is a Number, or x is a BigInt and y is a BigInt. + + // 2. If comparefn is not undefined, then + if let Some(compare_fn) = compare_fn { + // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). + let v = compare_fn + .call(&JsValue::undefined(), &[x.clone(), y.clone()], context)? + .to_number(context)?; + + // b. If v is NaN, return +0𝔽. + if v.is_nan() { + return Ok(Ordering::Equal); + } + + // c. Return v. + if v.is_sign_positive() { + return Ok(Ordering::Greater); + } + return Ok(Ordering::Less); + } + + match (x, y) { + (JsValue::BigInt(x), JsValue::BigInt(y)) => { + // Note: Other steps are not relevant for BigInts. + // 6. If x < y, return -1𝔽. + // 7. If x > y, return 1𝔽. + // 10. Return +0𝔽. + Ok(x.cmp(y)) + } + (JsValue::Integer(x), JsValue::Integer(y)) => { + // Note: Other steps are not relevant for integers. + // 6. If x < y, return -1𝔽. + // 7. If x > y, return 1𝔽. + // 10. Return +0𝔽. + Ok(x.cmp(y)) + } + (JsValue::Rational(x), JsValue::Rational(y)) => { + // 3. If x and y are both NaN, return +0𝔽. + if x.is_nan() && y.is_nan() { + return Ok(Ordering::Equal); + } + + // 4. If x is NaN, return 1𝔽. + if x.is_nan() { + return Ok(Ordering::Greater); + } + + // 5. If y is NaN, return -1𝔽. + if y.is_nan() { + return Ok(Ordering::Less); + } + + // 6. If x < y, return -1𝔽. + if x < y { + return Ok(Ordering::Less); + } + + // 7. If x > y, return 1𝔽. + if x > y { + return Ok(Ordering::Greater); + } + + // 8. If x is -0𝔽 and y is +0𝔽, return -1𝔽. + if x.is_sign_negative() && x.is_zero() && y.is_sign_positive() && y.is_zero() { + return Ok(Ordering::Less); + } + + // 9. If x is +0𝔽 and y is -0𝔽, return 1𝔽. + if x.is_sign_positive() && x.is_zero() && y.is_sign_negative() && y.is_zero() { + return Ok(Ordering::Greater); + } + + // 10. Return +0𝔽. + Ok(Ordering::Equal) + } + _ => unreachable!("x and y must be both Numbers or BigInts"), + } +} + +enum U64OrPositiveInfinity { + U64(u64), + PositiveInfinity, +} diff --git a/boa_engine/src/builtins/typed_array/element.rs b/boa_engine/src/builtins/typed_array/element.rs new file mode 100644 index 00000000000..6200728d4cd --- /dev/null +++ b/boa_engine/src/builtins/typed_array/element.rs @@ -0,0 +1,398 @@ +#![deny(unsafe_op_in_unsafe_fn)] +#![allow(clippy::cast_ptr_alignment)] // Invariants are checked by the caller. +#![allow(clippy::undocumented_unsafe_blocks)] // Invariants are checked by the caller. + +use std::sync::atomic; + +use bytemuck::{AnyBitPattern, NoUninit}; +use num_traits::ToPrimitive; +use portable_atomic::{AtomicU16, AtomicU32, AtomicU64}; + +use crate::{ + builtins::{ + array_buffer::utils::{SliceRef, SliceRefMut}, + typed_array::TypedArrayElement, + }, + value::Numeric, + Context, JsResult, JsValue, +}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, AnyBitPattern, NoUninit)] +#[repr(transparent)] +pub(crate) struct ClampedU8(pub(crate) u8); + +impl ClampedU8 { + pub(crate) fn to_be(self) -> Self { + Self(self.0.to_be()) + } + + pub(crate) fn to_le(self) -> Self { + Self(self.0.to_le()) + } +} + +impl From for Numeric { + fn from(value: ClampedU8) -> Self { + Numeric::Number(value.0.into()) + } +} + +pub(crate) trait Element: + Sized + Into + NoUninit + AnyBitPattern +{ + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult; + + /// Gets the little endian representation of `Self`. + fn to_little_endian(self) -> Self; + + /// Gets the big endian representation of `Self`. + fn to_big_endian(self) -> Self; + + /// Reads `Self` from the `buffer`. + /// + /// This will always read values in the native endianness of the target architecture. + /// + /// # Safety + /// + /// - `buffer` must be aligned to the native alignment of `Self`. + /// - `buffer` must contain enough bytes to read `std::sizeof::` bytes. + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self; + + /// Writes the bytes of this element into `buffer`. + /// + /// This will always write values in the native endianness of the target architecture. + /// + /// # Safety + /// + /// - `buffer` must be aligned to the native alignment of `Self`. + /// - `buffer` must contain enough bytes to store `std::sizeof::` bytes. + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering); +} + +impl Element for u8 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + value.to_uint8(context) + } + + fn to_big_endian(self) -> Self { + self.to_be() + } + + fn to_little_endian(self) -> Self { + self.to_le() + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + debug_assert!(buffer.len() >= 1); + + match buffer { + SliceRef::Common(buffer) => unsafe { *buffer.get_unchecked(0) }, + SliceRef::Atomic(buffer) => unsafe { buffer.get_unchecked(0).load(order) }, + } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + debug_assert!(buffer.len() >= 1); + + match buffer { + SliceRefMut::Common(buffer) => unsafe { + *buffer.get_unchecked_mut(0) = value; + }, + SliceRefMut::Atomic(buffer) => unsafe { buffer.get_unchecked(0).store(value, order) }, + } + } +} + +impl Element for u16 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + value.to_uint16(context) + } + + fn to_big_endian(self) -> Self { + self.to_be() + } + + fn to_little_endian(self) -> Self { + self.to_le() + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + if cfg!(debug_assertions) { + assert!(buffer.len() >= std::mem::size_of::()); + assert!(buffer.addr() % std::mem::align_of::() == 0); + } + + match buffer { + SliceRef::Common(buffer) => unsafe { *buffer.as_ptr().cast() }, + SliceRef::Atomic(buffer) => unsafe { + (*buffer.as_ptr().cast::()).load(order) + }, + } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + if cfg!(debug_assertions) { + assert!(buffer.len() >= std::mem::size_of::()); + assert!(buffer.addr() % std::mem::align_of::() == 0); + } + + match buffer { + SliceRefMut::Common(buffer) => unsafe { + *buffer.as_mut_ptr().cast() = value; + }, + SliceRefMut::Atomic(buffer) => unsafe { + (*buffer.as_ptr().cast::()).store(value, order); + }, + } + } +} + +impl Element for u32 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + value.to_u32(context) + } + + fn to_big_endian(self) -> Self { + self.to_be() + } + + fn to_little_endian(self) -> Self { + self.to_le() + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + if cfg!(debug_assertions) { + assert!(buffer.len() >= std::mem::size_of::()); + assert!(buffer.addr() % std::mem::align_of::() == 0); + } + + match buffer { + SliceRef::Common(buffer) => unsafe { *buffer.as_ptr().cast() }, + SliceRef::Atomic(buffer) => unsafe { + (*buffer.as_ptr().cast::()).load(order) + }, + } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + if cfg!(debug_assertions) { + assert!(buffer.len() >= std::mem::size_of::()); + assert!(buffer.addr() % std::mem::align_of::() == 0); + } + + match buffer { + SliceRefMut::Common(buffer) => unsafe { + *buffer.as_mut_ptr().cast() = value; + }, + SliceRefMut::Atomic(buffer) => unsafe { + (*buffer.as_ptr().cast::()).store(value, order); + }, + } + } +} + +impl Element for u64 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + Ok(value.to_big_uint64(context)?.to_u64().unwrap_or(u64::MAX)) + } + + fn to_big_endian(self) -> Self { + self.to_be() + } + + fn to_little_endian(self) -> Self { + self.to_le() + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + if cfg!(debug_assertions) { + assert!(buffer.len() >= std::mem::size_of::()); + assert!(buffer.addr() % std::mem::align_of::() == 0); + } + + match buffer { + SliceRef::Common(buffer) => unsafe { *buffer.as_ptr().cast() }, + SliceRef::Atomic(buffer) => unsafe { + (*buffer.as_ptr().cast::()).load(order) + }, + } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + if cfg!(debug_assertions) { + assert!(buffer.len() >= std::mem::size_of::()); + assert!(buffer.addr() % std::mem::align_of::() == 0); + } + + match buffer { + SliceRefMut::Common(buffer) => unsafe { + *buffer.as_mut_ptr().cast() = value; + }, + SliceRefMut::Atomic(buffer) => unsafe { + (*buffer.as_ptr().cast::()).store(value, order); + }, + } + } +} + +impl Element for i8 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + value.to_int8(context) + } + + fn to_big_endian(self) -> Self { + self.to_be() + } + + fn to_little_endian(self) -> Self { + self.to_le() + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + unsafe { u8::read_from_buffer(buffer, order) as i8 } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + unsafe { u8::write_to_buffer(buffer, value as u8, order) } + } +} + +impl Element for ClampedU8 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + value.to_uint8_clamp(context).map(ClampedU8) + } + + fn to_big_endian(self) -> Self { + self.to_be() + } + + fn to_little_endian(self) -> Self { + self.to_le() + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + unsafe { ClampedU8(u8::read_from_buffer(buffer, order)) } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + unsafe { u8::write_to_buffer(buffer, value.0, order) } + } +} + +impl Element for i16 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + value.to_int16(context) + } + + fn to_big_endian(self) -> Self { + self.to_be() + } + + fn to_little_endian(self) -> Self { + self.to_le() + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + unsafe { u16::read_from_buffer(buffer, order) as i16 } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + unsafe { u16::write_to_buffer(buffer, value as u16, order) } + } +} + +impl Element for i32 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + value.to_i32(context) + } + + fn to_big_endian(self) -> Self { + self.to_be() + } + + fn to_little_endian(self) -> Self { + self.to_le() + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + unsafe { u32::read_from_buffer(buffer, order) as i32 } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + unsafe { u32::write_to_buffer(buffer, value as u32, order) } + } +} + +impl Element for i64 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + let big_int = value.to_big_int64(context)?; + + Ok(big_int.to_i64().unwrap_or_else(|| { + if big_int.is_positive() { + i64::MAX + } else { + i64::MIN + } + })) + } + + fn to_big_endian(self) -> Self { + self.to_be() + } + + fn to_little_endian(self) -> Self { + self.to_le() + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + unsafe { u64::read_from_buffer(buffer, order) as i64 } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + unsafe { u64::write_to_buffer(buffer, value as u64, order) } + } +} + +impl Element for f32 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + value.to_number(context).map(|f| f as f32) + } + + fn to_big_endian(self) -> Self { + f32::from_bits(self.to_bits().to_be()) + } + + fn to_little_endian(self) -> Self { + f32::from_bits(self.to_bits().to_le()) + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + unsafe { f32::from_bits(u32::read_from_buffer(buffer, order)) } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + unsafe { u32::write_to_buffer(buffer, value.to_bits(), order) } + } +} + +impl Element for f64 { + fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult { + value.to_number(context) + } + + fn to_big_endian(self) -> Self { + f64::from_bits(self.to_bits().to_be()) + } + + fn to_little_endian(self) -> Self { + f64::from_bits(self.to_bits().to_le()) + } + + unsafe fn read_from_buffer(buffer: SliceRef<'_>, order: atomic::Ordering) -> Self { + unsafe { f64::from_bits(u64::read_from_buffer(buffer, order)) } + } + + unsafe fn write_to_buffer(buffer: SliceRefMut<'_>, value: Self, order: atomic::Ordering) { + unsafe { u64::write_to_buffer(buffer, value.to_bits(), order) } + } +} diff --git a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs index 28736df3c67..301d2a90f1c 100644 --- a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs +++ b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs @@ -1,29 +1,23 @@ //! This module implements the `Integer-Indexed` exotic object. //! -//! An `Integer-Indexed` exotic object is an exotic object that performs -//! special handling of integer index property keys. -//! -//! More information: -//! - [ECMAScript reference][spec] -//! -//! [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects -use crate::{builtins::typed_array::TypedArrayKind, object::JsObject}; +use crate::object::JsObject; use boa_gc::{Finalize, Trace}; -/// Type of the array content. -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) enum ContentType { - Number, - BigInt, -} +use super::TypedArrayKind; -/// +/// An `Integer-Indexed` exotic object is an exotic object that performs +/// special handling of integer index property keys. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects #[derive(Debug, Clone, Trace, Finalize)] pub struct IntegerIndexed { - viewed_array_buffer: Option, + viewed_array_buffer: JsObject, #[unsafe_ignore_trace] - typed_array_name: TypedArrayKind, + kind: TypedArrayKind, byte_offset: u64, byte_length: u64, array_length: u64, @@ -31,15 +25,15 @@ pub struct IntegerIndexed { impl IntegerIndexed { pub(crate) const fn new( - viewed_array_buffer: Option, - typed_array_name: TypedArrayKind, + viewed_array_buffer: JsObject, + kind: TypedArrayKind, byte_offset: u64, byte_length: u64, array_length: u64, ) -> Self { Self { viewed_array_buffer, - typed_array_name, + kind, byte_offset, byte_length, array_length, @@ -55,14 +49,11 @@ impl IntegerIndexed { /// /// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer pub(crate) fn is_detached(&self) -> bool { - if let Some(obj) = &self.viewed_array_buffer { - obj.borrow() - .as_array_buffer() - .expect("Typed array must have internal array buffer object") - .is_detached_buffer() - } else { - false - } + self.viewed_array_buffer + .borrow() + .as_buffer() + .expect("Typed array must have internal array buffer object") + .is_detached() } /// Get the integer indexed object's byte offset. @@ -71,25 +62,15 @@ impl IntegerIndexed { self.byte_offset } - /// Set the integer indexed object's byte offset. - pub(crate) fn set_byte_offset(&mut self, byte_offset: u64) { - self.byte_offset = byte_offset; - } - - /// Get the integer indexed object's typed array name. - pub(crate) const fn typed_array_name(&self) -> TypedArrayKind { - self.typed_array_name + /// Get the integer indexed object's typed array kind. + pub(crate) const fn kind(&self) -> TypedArrayKind { + self.kind } /// Get a reference to the integer indexed object's viewed array buffer. #[must_use] - pub const fn viewed_array_buffer(&self) -> Option<&JsObject> { - self.viewed_array_buffer.as_ref() - } - - ///(crate) Set the integer indexed object's viewed array buffer. - pub fn set_viewed_array_buffer(&mut self, viewed_array_buffer: Option) { - self.viewed_array_buffer = viewed_array_buffer; + pub const fn viewed_array_buffer(&self) -> &JsObject { + &self.viewed_array_buffer } /// Get the integer indexed object's byte length. @@ -98,19 +79,9 @@ impl IntegerIndexed { self.byte_length } - /// Set the integer indexed object's byte length. - pub(crate) fn set_byte_length(&mut self, byte_length: u64) { - self.byte_length = byte_length; - } - /// Get the integer indexed object's array length. #[must_use] pub const fn array_length(&self) -> u64 { self.array_length } - - /// Set the integer indexed object's array length. - pub(crate) fn set_array_length(&mut self, array_length: u64) { - self.array_length = array_length; - } } diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 4f683d7eb6c..3d0af35c551 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -14,3635 +14,323 @@ use crate::{ builtins::{ - array::{find_via_predicate, ArrayIterator, Direction}, - array_buffer::{ArrayBuffer, SharedMemoryOrder}, - iterable::iterable_to_list, - typed_array::integer_indexed_object::{ContentType, IntegerIndexed}, - BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + iterable::iterable_to_list, BuiltInBuilder, BuiltInConstructor, BuiltInObject, + IntrinsicObject, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{ - internal_methods::{get_prototype_from_constructor, integer_indexed_element_set}, - JsObject, ObjectData, ObjectKind, - }, - property::{Attribute, PropertyNameKind}, - realm::Realm, - string::{common::StaticJsStrings, utf16}, - symbol::JsSymbol, - value::{IntegerOrInfinity, JsValue}, - Context, JsArgs, JsResult, JsString, -}; -use boa_profiler::Profiler; -use num_traits::Zero; -use paste::paste; -use std::cmp::Ordering; - -pub mod integer_indexed_object; - -macro_rules! typed_array { - ($ty:ident, $variant:ident, $name:literal, $js_name:expr, $global_object_name:ident) => { - paste! { - #[doc = "JavaScript `" $name "` built-in implementation."] - #[derive(Debug, Clone, Copy)] - pub struct $ty; - } - - impl IntrinsicObject for $ty { - fn get(intrinsics: &Intrinsics) -> JsObject { - Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() - } - - fn init(realm: &Realm) { - let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); - - let get_species = BuiltInBuilder::callable(realm, TypedArray::get_species) - .name(js_string!("get [Symbol.species]")) - .build(); - - BuiltInBuilder::from_standard_constructor::(realm) - .prototype( - realm - .intrinsics() - .constructors() - .typed_array() - .constructor(), - ) - .inherits(Some( - realm.intrinsics().constructors().typed_array().prototype(), - )) - .static_accessor( - JsSymbol::species(), - Some(get_species), - None, - Attribute::CONFIGURABLE, - ) - .property( - js_string!("BYTES_PER_ELEMENT"), - TypedArrayKind::$variant.element_size(), - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, - ) - .static_property( - js_string!("BYTES_PER_ELEMENT"), - TypedArrayKind::$variant.element_size(), - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, - ) - .build(); - } - } - - impl BuiltInObject for $ty { - const NAME: JsString = $js_name; - - const ATTRIBUTE: Attribute = Attribute::WRITABLE - .union(Attribute::NON_ENUMERABLE) - .union(Attribute::CONFIGURABLE); - } - - impl BuiltInConstructor for $ty { - const LENGTH: usize = 3; - - const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = - StandardConstructors::$global_object_name; - - /// `23.2.5.1 TypedArray ( ...args )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-typedarray - fn constructor( - new_target: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. If NewTarget is undefined, throw a TypeError exception. - if new_target.is_undefined() { - return Err(JsNativeError::typ() - .with_message(concat!( - "new target was undefined when constructing an ", - $name - )) - .into()); - } - - // 2. Let constructorName be the String value of the Constructor Name value specified in Table 72 for this TypedArray constructor. - let constructor_name = TypedArrayKind::$variant; - - // 3. Let proto be "%TypedArray.prototype%". - let proto = StandardConstructors::$global_object_name; - - // 4. Let numberOfArgs be the number of elements in args. - let number_of_args = args.len(); - - // 5. If numberOfArgs = 0, then - if number_of_args == 0 { - // a. Return ? AllocateTypedArray(constructorName, NewTarget, proto, 0). - return Ok(TypedArray::allocate( - constructor_name, - new_target, - proto, - Some(0), - context, - )? - .into()); - } - // 6. Else, - - // a. Let firstArgument be args[0]. - let first_argument = &args[0]; - - // b. If Type(firstArgument) is Object, then - if let Some(first_argument) = first_argument.as_object() { - // i. Let O be ? AllocateTypedArray(constructorName, NewTarget, proto). - let o = - TypedArray::allocate(constructor_name, new_target, proto, None, context)?; - - // ii. If firstArgument has a [[TypedArrayName]] internal slot, then - if first_argument.is_typed_array() { - // 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument). - TypedArray::initialize_from_typed_array(&o, first_argument, context)?; - } else if first_argument.is_array_buffer() { - // iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then - - // 1. If numberOfArgs > 1, let byteOffset be args[1]; else let byteOffset be undefined. - let byte_offset = args.get_or_undefined(1); - - // 2. If numberOfArgs > 2, let length be args[2]; else let length be undefined. - let length = args.get_or_undefined(2); - - // 3. Perform ? InitializeTypedArrayFromArrayBuffer(O, firstArgument, byteOffset, length). - TypedArray::initialize_from_array_buffer( - &o, - first_argument.clone(), - byte_offset, - length, - context, - )?; - } else { - // iv. Else, - - // 1. Assert: Type(firstArgument) is Object and firstArgument does not have - // either a [[TypedArrayName]] or an [[ArrayBufferData]] internal slot. - - // 2. Let usingIterator be ? GetMethod(firstArgument, @@iterator). - - let first_argument_v = JsValue::from(first_argument.clone()); - let using_iterator = - first_argument_v.get_method(JsSymbol::iterator(), context)?; - - // 3. If usingIterator is not undefined, then - if let Some(using_iterator) = using_iterator { - // a. Let values be ? IterableToList(firstArgument, usingIterator). - let values = iterable_to_list( - context, - &first_argument_v, - Some(using_iterator.into()), - )?; - - // b. Perform ? InitializeTypedArrayFromList(O, values). - TypedArray::initialize_from_list(&o, values, context)?; - } else { - // 4. Else, - - // a. NOTE: firstArgument is not an Iterable so assume it is already an array-like object. - // b. Perform ? InitializeTypedArrayFromArrayLike(O, firstArgument). - TypedArray::initialize_from_array_like(&o, &first_argument, context)?; - } - } - - // v. Return O. - Ok(o.into()) - } else { - // c. Else, - - // i. Assert: firstArgument is not an Object. - assert!(!first_argument.is_object(), "firstArgument was an object"); - - // ii. Let elementLength be ? ToIndex(firstArgument). - let element_length = first_argument.to_index(context)?; - - // iii. Return ? AllocateTypedArray(constructorName, NewTarget, proto, elementLength). - Ok(TypedArray::allocate( - constructor_name, - new_target, - proto, - Some(element_length), - context, - )? - .into()) - } - } - } - }; -} - -/// The JavaScript `%TypedArray%` object. -/// -/// -#[derive(Debug, Clone, Copy)] -pub(crate) struct TypedArray; - -impl IntrinsicObject for TypedArray { - fn init(realm: &Realm) { - let get_species = BuiltInBuilder::callable(realm, Self::get_species) - .name(js_string!("get [Symbol.species]")) - .build(); - - let get_buffer = BuiltInBuilder::callable(realm, Self::buffer) - .name(js_string!("get buffer")) - .build(); - - let get_byte_length = BuiltInBuilder::callable(realm, Self::byte_length) - .name(js_string!("get byteLength")) - .build(); - - let get_byte_offset = BuiltInBuilder::callable(realm, Self::byte_offset) - .name(js_string!("get byteOffset")) - .build(); - - let get_length = BuiltInBuilder::callable(realm, Self::length) - .name(js_string!("get length")) - .build(); - - let get_to_string_tag = BuiltInBuilder::callable(realm, Self::to_string_tag) - .name(js_string!("get [Symbol.toStringTag]")) - .build(); - - let values_function = BuiltInBuilder::callable(realm, Self::values) - .name(js_string!("values")) - .length(0) - .build(); - - BuiltInBuilder::from_standard_constructor::(realm) - .static_accessor( - JsSymbol::species(), - Some(get_species), - None, - Attribute::CONFIGURABLE, - ) - .property( - JsSymbol::iterator(), - values_function.clone(), - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .accessor( - utf16!("buffer"), - Some(get_buffer), - None, - Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ) - .accessor( - utf16!("byteLength"), - Some(get_byte_length), - None, - Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ) - .accessor( - utf16!("byteOffset"), - Some(get_byte_offset), - None, - Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ) - .accessor( - utf16!("length"), - Some(get_length), - None, - Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ) - .accessor( - JsSymbol::to_string_tag(), - Some(get_to_string_tag), - None, - Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - ) - .static_method(Self::from, js_string!("from"), 1) - .static_method(Self::of, js_string!("of"), 0) - .method(Self::at, js_string!("at"), 1) - .method(Self::copy_within, js_string!("copyWithin"), 2) - .method(Self::entries, js_string!("entries"), 0) - .method(Self::every, js_string!("every"), 1) - .method(Self::fill, js_string!("fill"), 1) - .method(Self::filter, js_string!("filter"), 1) - .method(Self::find, js_string!("find"), 1) - .method(Self::find_index, js_string!("findIndex"), 1) - .method(Self::find_last, js_string!("findLast"), 1) - .method(Self::find_last_index, js_string!("findLastIndex"), 1) - .method(Self::foreach, js_string!("forEach"), 1) - .method(Self::includes, js_string!("includes"), 1) - .method(Self::index_of, js_string!("indexOf"), 1) - .method(Self::join, js_string!("join"), 1) - .method(Self::keys, js_string!("keys"), 0) - .method(Self::last_index_of, js_string!("lastIndexOf"), 1) - .method(Self::map, js_string!("map"), 1) - .method(Self::reduce, js_string!("reduce"), 1) - .method(Self::reduceright, js_string!("reduceRight"), 1) - .method(Self::reverse, js_string!("reverse"), 0) - .method(Self::set, js_string!("set"), 1) - .method(Self::slice, js_string!("slice"), 2) - .method(Self::some, js_string!("some"), 1) - .method(Self::sort, js_string!("sort"), 1) - .method(Self::subarray, js_string!("subarray"), 2) - .method(Self::to_locale_string, js_string!("toLocaleString"), 0) - // 23.2.3.29 %TypedArray%.prototype.toString ( ) - // The initial value of the %TypedArray%.prototype.toString data property is the same - // built-in function object as the Array.prototype.toString method defined in 23.1.3.30. - .property( - js_string!("toString"), - realm.intrinsics().objects().array_prototype_to_string(), - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .property( - js_string!("values"), - values_function, - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .build(); - } - - fn get(intrinsics: &Intrinsics) -> JsObject { - Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() - } -} - -impl BuiltInObject for TypedArray { - const NAME: JsString = StaticJsStrings::TYPED_ARRAY; -} - -impl BuiltInConstructor for TypedArray { - const LENGTH: usize = 0; - - const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = - StandardConstructors::typed_array; - - /// `23.2.1.1 %TypedArray% ( )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray% - fn constructor(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - // 1. Throw a TypeError exception. - Err(JsNativeError::typ() - .with_message("the TypedArray constructor should never be called directly") - .into()) - } -} - -impl TypedArray { - /// `23.2.2.1 %TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.from - fn from(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { - // 1. Let C be the this value. - // 2. If IsConstructor(C) is false, throw a TypeError exception. - let constructor = match this.as_object() { - Some(obj) if obj.is_constructor() => obj, - _ => { - return Err(JsNativeError::typ() - .with_message("TypedArray.from called on non-constructable value") - .into()) - } - }; - - let mapping = match args.get(1) { - // 3. If mapfn is undefined, let mapping be false. - None | Some(JsValue::Undefined) => None, - // 4. Else, - Some(v) => match v.as_object() { - // b. Let mapping be true. - Some(obj) if obj.is_callable() => Some(obj), - // a. If IsCallable(mapfn) is false, throw a TypeError exception. - _ => { - return Err(JsNativeError::typ() - .with_message("TypedArray.from called with non-callable mapfn") - .into()) - } - }, - }; - - // 5. Let usingIterator be ? GetMethod(source, @@iterator). - let source = args.get_or_undefined(0); - let using_iterator = source.get_method(JsSymbol::iterator(), context)?; - - let this_arg = args.get_or_undefined(2); - - // 6. If usingIterator is not undefined, then - if let Some(using_iterator) = using_iterator { - // a. Let values be ? IterableToList(source, usingIterator). - let values = iterable_to_list(context, source, Some(using_iterator))?; - - // b. Let len be the number of elements in values. - // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(constructor, &[values.len().into()], context)?; - - // d. Let k be 0. - // e. Repeat, while k < len, - for (k, k_value) in values.iter().enumerate() { - // i. Let Pk be ! ToString(𝔽(k)). - // ii. Let kValue be the first element of values and remove that element from values. - // iii. If mapping is true, then - let mapped_value = if let Some(map_fn) = &mapping { - // 1. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). - map_fn.call(this_arg, &[k_value.clone(), k.into()], context)? - } - // iv. Else, let mappedValue be kValue. - else { - k_value.clone() - }; - - // v. Perform ? Set(targetObj, Pk, mappedValue, true). - target_obj.set(k, mapped_value, true, context)?; - } - - // f. Assert: values is now an empty List. - // g. Return targetObj. - return Ok(target_obj.into()); - } - - // 7. NOTE: source is not an Iterable so assume it is already an array-like object. - // 8. Let arrayLike be ! ToObject(source). - let array_like = source - .to_object(context) - .expect("ToObject cannot fail here"); - - // 9. Let len be ? LengthOfArrayLike(arrayLike). - let len = array_like.length_of_array_like(context)?; - - // 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(constructor, &[len.into()], context)?; - - // 11. Let k be 0. - // 12. Repeat, while k < len, - for k in 0..len { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be ? Get(arrayLike, Pk). - let k_value = array_like.get(k, context)?; - - // c. If mapping is true, then - let mapped_value = if let Some(map_fn) = &mapping { - // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). - map_fn.call(this_arg, &[k_value, k.into()], context)? - } - // d. Else, let mappedValue be kValue. - else { - k_value - }; - - // e. Perform ? Set(targetObj, Pk, mappedValue, true). - target_obj.set(k, mapped_value, true, context)?; - } - - // 13. Return targetObj. - Ok(target_obj.into()) - } - - /// `23.2.2.2 %TypedArray%.of ( ...items )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.of - fn of(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult { - // 1. Let len be the number of elements in items. - - // 2. Let C be the this value. - // 3. If IsConstructor(C) is false, throw a TypeError exception. - let constructor = match this.as_object() { - Some(obj) if obj.is_constructor() => obj, - _ => { - return Err(JsNativeError::typ() - .with_message("TypedArray.of called on non-constructable value") - .into()) - } - }; - - // 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let new_obj = Self::create(constructor, &[args.len().into()], context)?; - - // 5. Let k be 0. - // 6. Repeat, while k < len, - for (k, k_value) in args.iter().enumerate() { - // a. Let kValue be items[k]. - // b. Let Pk be ! ToString(𝔽(k)). - // c. Perform ? Set(newObj, Pk, kValue, true). - new_obj.set(k, k_value.clone(), true, context)?; - } - - // 7. Return newObj. - Ok(new_obj.into()) - } - - /// `23.2.2.4 get %TypedArray% [ @@species ]` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%-@@species - #[allow(clippy::unnecessary_wraps)] - fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - // 1. Return the this value. - Ok(this.clone()) - } - - /// `23.2.3.1 %TypedArray%.prototype.at ( index )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.at - pub(crate) fn at( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; - - drop(obj_borrow); - - // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). - let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?; - - let k = match relative_index { - // Note: Early undefined return on infinity. - IntegerOrInfinity::PositiveInfinity | IntegerOrInfinity::NegativeInfinity => { - return Ok(JsValue::undefined()) - } - // 5. If relativeIndex ≥ 0, then - // a. Let k be relativeIndex. - IntegerOrInfinity::Integer(i) if i >= 0 => i, - // 6. Else, - // a. Let k be len + relativeIndex. - IntegerOrInfinity::Integer(i) => len + i, - }; - - // 7. If k < 0 or k ≥ len, return undefined. - if k < 0 || k >= len { - return Ok(JsValue::undefined()); - } - - // 8. Return ! Get(O, ! ToString(𝔽(k))). - Ok(obj.get(k, context).expect("Get cannot fail here")) - } - - /// `23.2.3.2 get %TypedArray%.prototype.buffer` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer - fn buffer(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). - // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - // 5. Return buffer. - Ok(typed_array - .viewed_array_buffer() - .map_or_else(JsValue::undefined, |buffer| buffer.clone().into())) - } - - /// `23.2.3.3 get %TypedArray%.prototype.byteLength` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength - pub(crate) fn byte_length( - this: &JsValue, - _: &[JsValue], - _: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). - // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. - // 6. Let size be O.[[ByteLength]]. - // 7. Return 𝔽(size). - if typed_array.is_detached() { - Ok(0.into()) - } else { - Ok(typed_array.byte_length().into()) - } - } - - /// `23.2.3.4 get %TypedArray%.prototype.byteOffset` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset - pub(crate) fn byte_offset( - this: &JsValue, - _: &[JsValue], - _: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). - // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. - // 6. Let offset be O.[[ByteOffset]]. - // 7. Return 𝔽(offset). - if typed_array.is_detached() { - Ok(0.into()) - } else { - Ok(typed_array.byte_offset().into()) - } - } - - /// `23.2.3.6 %TypedArray%.prototype.copyWithin ( target, start [ , end ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin - fn copy_within( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let len = { - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - // 2. Perform ? ValidateTypedArray(O). - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - o.array_length() as i64 - }; - - // 4. Let relativeTarget be ? ToIntegerOrInfinity(target). - let relative_target = args.get_or_undefined(0).to_integer_or_infinity(context)?; - - let to = match relative_target { - // 5. If relativeTarget is -∞, let to be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 6. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 7. Else, let to be min(relativeTarget, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; - - // 8. Let relativeStart be ? ToIntegerOrInfinity(start). - let relative_start = args.get_or_undefined(1).to_integer_or_infinity(context)?; - - let from = match relative_start { - // 9. If relativeStart is -∞, let from be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 10. Else if relativeStart < 0, let from be max(len + relativeStart, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 11. Else, let from be min(relativeStart, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; - - // 12. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - let end = args.get_or_undefined(2); - let relative_end = if end.is_undefined() { - IntegerOrInfinity::Integer(len) - } else { - end.to_integer_or_infinity(context)? - }; - - let r#final = match relative_end { - // 13. If relativeEnd is -∞, let final be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 14. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 15. Else, let final be min(relativeEnd, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; - - // 16. Let count be min(final - from, len - to). - let count = std::cmp::min(r#final - from, len - to); - - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - // 17. If count > 0, then - if count > 0 { - // a. NOTE: The copying must be performed in a manner that preserves the bit-level encoding of the source data. - // b. Let buffer be O.[[ViewedArrayBuffer]]. - // c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // d. Let typedArrayName be the String value of O.[[TypedArrayName]]. - let typed_array_name = o.typed_array_name(); - - // e. Let elementSize be the Element Size value specified in Table 73 for typedArrayName. - let element_size = typed_array_name.element_size() as i64; - - // f. Let byteOffset be O.[[ByteOffset]]. - let byte_offset = o.byte_offset() as i64; - - // g. Let toByteIndex be to × elementSize + byteOffset. - let mut to_byte_index = to * element_size + byte_offset; - - // h. Let fromByteIndex be from × elementSize + byteOffset. - let mut from_byte_index = from * element_size + byte_offset; - - // i. Let countBytes be count × elementSize. - let mut count_bytes = count * element_size; - - // j. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then - let direction = if from_byte_index < to_byte_index - && to_byte_index < from_byte_index + count_bytes - { - // ii. Set fromByteIndex to fromByteIndex + countBytes - 1. - from_byte_index = from_byte_index + count_bytes - 1; - - // iii. Set toByteIndex to toByteIndex + countBytes - 1. - to_byte_index = to_byte_index + count_bytes - 1; - - // i. Let direction be -1. - -1 - } - // k. Else, - else { - // i. Let direction be 1. - 1 - }; - - let buffer_obj = o - .viewed_array_buffer() - .expect("Already checked for detached buffer"); - let mut buffer_obj_borrow = buffer_obj.borrow_mut(); - let buffer = buffer_obj_borrow - .as_array_buffer_mut() - .expect("Already checked for detached buffer"); - - // l. Repeat, while countBytes > 0, - while count_bytes > 0 { - // i. Let value be GetValueFromBuffer(buffer, fromByteIndex, Uint8, true, Unordered). - let value = buffer.get_value_from_buffer( - from_byte_index as u64, - TypedArrayKind::Uint8, - true, - SharedMemoryOrder::Unordered, - None, - ); - - // ii. Perform SetValueInBuffer(buffer, toByteIndex, Uint8, value, true, Unordered). - buffer.set_value_in_buffer( - to_byte_index as u64, - TypedArrayKind::Uint8, - &value, - SharedMemoryOrder::Unordered, - None, - context, - )?; - - // iii. Set fromByteIndex to fromByteIndex + direction. - from_byte_index += direction; - - // iv. Set toByteIndex to toByteIndex + direction. - to_byte_index += direction; - - // v. Set countBytes to countBytes - 1. - count_bytes -= 1; - } - } - - // 18. Return O. - Ok(this.clone()) - } - - /// `23.2.3.7 %TypedArray%.prototype.entries ( )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries - fn entries(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.borrow() - .as_typed_array() - .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? - .is_detached() - { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Return CreateArrayIterator(O, key+value). - Ok(ArrayIterator::create_array_iterator( - o.clone(), - PropertyNameKind::KeyAndValue, - context, - )) - } - - /// `23.2.3.8 %TypedArray%.prototype.every ( callbackfn [ , thisArg ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.every - pub(crate) fn every( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(obj_borrow); - - // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => { - return Err(JsNativeError::typ() - .with_message( - "TypedArray.prototype.every called with non-callable callback function", - ) - .into()) - } - }; - - // 5. Let k be 0. - // 6. Repeat, while k < len, - for k in 0..len { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context)?; - - // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - let test_result = callback_fn - .call( - args.get_or_undefined(1), - &[k_value, k.into(), this.clone()], - context, - )? - .to_boolean(); - - // d. If testResult is false, return false. - if !test_result { - return Ok(false.into()); - } - } - - // 7. Return true. - Ok(true.into()) - } - - /// `23.2.3.9 %TypedArray%.prototype.fill ( value [ , start [ , end ] ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill - pub(crate) fn fill( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; - - // 4. If O.[[ContentType]] is BigInt, set value to ? ToBigInt(value). - let value: JsValue = if o.typed_array_name().content_type() == ContentType::BigInt { - args.get_or_undefined(0).to_bigint(context)?.into() - // 5. Otherwise, set value to ? ToNumber(value). - } else { - args.get_or_undefined(0).to_number(context)?.into() - }; - - // 6. Let relativeStart be ? ToIntegerOrInfinity(start). - let mut k = match args.get_or_undefined(1).to_integer_or_infinity(context)? { - // 7. If relativeStart is -∞, let k be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 8. Else if relativeStart < 0, let k be max(len + relativeStart, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 9. Else, let k be min(relativeStart, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; - - // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - let end = args.get_or_undefined(2); - let relative_end = if end.is_undefined() { - IntegerOrInfinity::Integer(len) - } else { - end.to_integer_or_infinity(context)? - }; - - let r#final = match relative_end { - // 11. If relativeEnd is -∞, let final be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 13. Else, let final be min(relativeEnd, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; - - // 14. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - drop(obj_borrow); - - // 15. Repeat, while k < final, - while k < r#final { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Perform ! Set(O, Pk, value, true). - obj.set(k, value.clone(), true, context) - .expect("Set cannot fail here"); - - // c. Set k to k + 1. - k += 1; - } - - // 16. Return O. - Ok(this.clone()) - } - - /// `23.2.3.10 %TypedArray%.prototype.filter ( callbackfn [ , thisArg ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter - pub(crate) fn filter( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - let typed_array_name = o.typed_array_name(); - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(obj_borrow); - - // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = - match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => return Err(JsNativeError::typ() - .with_message( - "TypedArray.prototype.filter called with non-callable callback function", - ) - .into()), - }; - - // 5. Let kept be a new empty List. - let mut kept = Vec::new(); - - // 6. Let k be 0. - // 7. Let captured be 0. - let mut captured = 0; - - // 8. Repeat, while k < len, - for k in 0..len { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); - - // c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).# - let selected = callback_fn - .call( - args.get_or_undefined(1), - &[k_value.clone(), k.into(), this.clone()], - context, - )? - .to_boolean(); - - // d. If selected is true, then - if selected { - // i. Append kValue to the end of kept. - kept.push(k_value); - - // ii. Set captured to captured + 1. - captured += 1; - } - } - - // 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »). - let a = Self::species_create(obj, typed_array_name, &[captured.into()], context)?; - - // 10. Let n be 0. - // 11. For each element e of kept, do - for (n, e) in kept.iter().enumerate() { - // a. Perform ! Set(A, ! ToString(𝔽(n)), e, true). - a.set(n, e.clone(), true, context) - .expect("Set cannot fail here"); - // b. Set n to n + 1. - } - - // 12. Return A. - Ok(a.into()) - } - - /// `23.2.3.11 %TypedArray%.prototype.find ( predicate [ , thisArg ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.find - pub(crate) fn find( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(obj_borrow); - - let predicate = args.get_or_undefined(0); - let this_arg = args.get_or_undefined(1); - - // 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). - let (_, value) = find_via_predicate( - obj, - len, - Direction::Ascending, - predicate, - this_arg, - context, - "TypedArray.prototype.find", - )?; - - // 5. Return findRec.[[Value]]. - Ok(value) - } - - /// `23.2.3.12 %TypedArray%.prototype.findIndex ( predicate [ , thisArg ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex - pub(crate) fn find_index( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(obj_borrow); - - let predicate = args.get_or_undefined(0); - let this_arg = args.get_or_undefined(1); - - // 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). - let (index, _) = find_via_predicate( - obj, - len, - Direction::Ascending, - predicate, - this_arg, - context, - "TypedArray.prototype.findIndex", - )?; - - // 5. Return findRec.[[Index]]. - Ok(index) - } - - /// `23.2.3.13 %TypedArray%.prototype.findLast ( predicate [ , thisArg ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlast - pub(crate) fn find_last( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - let predicate = args.get_or_undefined(0); - let this_arg = args.get_or_undefined(1); - - // 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). - let (_, value) = find_via_predicate( - obj, - len, - Direction::Descending, - predicate, - this_arg, - context, - "TypedArray.prototype.findLast", - )?; - - // 5. Return findRec.[[Value]]. - Ok(value) - } - - /// `23.2.3.14 %TypedArray%.prototype.findLastIndex ( predicate [ , thisArg ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlastindex - pub(crate) fn find_last_index( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - let predicate = args.get_or_undefined(0); - let this_arg = args.get_or_undefined(1); - - // 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). - let (index, _) = find_via_predicate( - obj, - len, - Direction::Descending, - predicate, - this_arg, - context, - "TypedArray.prototype.findLastIndex", - )?; - - // 5. Return findRec.[[Index]]. - Ok(index) - } - - /// `23.2.3.15 %TypedArray%.prototype.forEach ( callbackfn [ , thisArg ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach - pub(crate) fn foreach( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(obj_borrow); - - // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = - match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => return Err(JsNativeError::typ() - .with_message( - "TypedArray.prototype.foreach called with non-callable callback function", - ) - .into()), - }; - - // 5. Let k be 0. - // 6. Repeat, while k < len, - for k in 0..len { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); - - // c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). - callback_fn.call( - args.get_or_undefined(1), - &[k_value, k.into(), this.clone()], - context, - )?; - } - - // 7. Return undefined. - Ok(JsValue::undefined()) - } - - /// `23.2.3.14 %TypedArray%.prototype.includes ( searchElement [ , fromIndex ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes - pub(crate) fn includes( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; - - drop(obj_borrow); - - // 4. If len is 0, return false. - if len == 0 { - return Ok(false.into()); - } - - // 5. Let n be ? ToIntegerOrInfinity(fromIndex). - // 6. Assert: If fromIndex is undefined, then n is 0. - let n = args.get_or_undefined(1).to_integer_or_infinity(context)?; - - let n = match n { - // 7. If n is +∞, return false. - IntegerOrInfinity::PositiveInfinity => return Ok(false.into()), - // 8. Else if n is -∞, set n to 0. - IntegerOrInfinity::NegativeInfinity => 0, - IntegerOrInfinity::Integer(i) => i, - }; - - // 9. If n ≥ 0, then - let mut k = if n >= 0 { - // a. Let k be n. - n - // 10. Else, - } else { - // a. Let k be len + n. - // b. If k < 0, set k to 0. - if len + n < 0 { - 0 - } else { - len + n - } - }; - - // 11. Repeat, while k < len, - while k < len { - // a. Let elementK be ! Get(O, ! ToString(𝔽(k))). - let element_k = obj.get(k, context).expect("Get cannot fail here"); - - // b. If SameValueZero(searchElement, elementK) is true, return true. - if JsValue::same_value_zero(args.get_or_undefined(0), &element_k) { - return Ok(true.into()); - } - - // c. Set k to k + 1. - k += 1; - } - - // 12. Return false. - Ok(false.into()) - } - - /// `23.2.3.15 %TypedArray%.prototype.indexOf ( searchElement [ , fromIndex ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof - pub(crate) fn index_of( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; - - drop(obj_borrow); - - // 4. If len is 0, return -1𝔽. - if len == 0 { - return Ok((-1).into()); - } - - // 5. Let n be ? ToIntegerOrInfinity(fromIndex). - // 6. Assert: If fromIndex is undefined, then n is 0. - let n = args.get_or_undefined(1).to_integer_or_infinity(context)?; - - let n = match n { - // 7. If n is +∞, return -1𝔽. - IntegerOrInfinity::PositiveInfinity => return Ok((-1).into()), - // 8. Else if n is -∞, set n to 0. - IntegerOrInfinity::NegativeInfinity => 0, - IntegerOrInfinity::Integer(i) => i, - }; - - // 9. If n ≥ 0, then - let mut k = if n >= 0 { - // a. Let k be n. - n - // 10. Else, - } else { - // a. Let k be len + n. - // b. If k < 0, set k to 0. - if len + n < 0 { - 0 - } else { - len + n - } - }; - - // 11. Repeat, while k < len, - while k < len { - // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). - let k_present = obj - .has_property(k, context) - .expect("HasProperty cannot fail here"); - - // b. If kPresent is true, then - if k_present { - // i. Let elementK be ! Get(O, ! ToString(𝔽(k))). - let element_k = obj.get(k, context).expect("Get cannot fail here"); - - // ii. Let same be IsStrictlyEqual(searchElement, elementK). - // iii. If same is true, return 𝔽(k). - if args.get_or_undefined(0).strict_equals(&element_k) { - return Ok(k.into()); - } - } - - // c. Set k to k + 1. - k += 1; - } - - // 12. Return -1𝔽. - Ok((-1).into()) - } - - /// `23.2.3.16 %TypedArray%.prototype.join ( separator )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.join - pub(crate) fn join( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(obj_borrow); - - // 4. If separator is undefined, let sep be the single-element String ",". - let separator = args.get_or_undefined(0); - let sep = if separator.is_undefined() { - js_string!(",") - // 5. Else, let sep be ? ToString(separator). - } else { - separator.to_string(context)? - }; - - // 6. Let R be the empty String. - let mut r = js_string!(); - - // 7. Let k be 0. - // 8. Repeat, while k < len, - for k in 0..len { - // a. If k > 0, set R to the string-concatenation of R and sep. - if k > 0 { - r = js_string!(&r, &sep); - } - - // b. Let element be ! Get(O, ! ToString(𝔽(k))). - let element = obj.get(k, context).expect("Get cannot fail here"); - - // c. If element is undefined, let next be the empty String; otherwise, let next be ! ToString(element). - // d. Set R to the string-concatenation of R and next. - if !element.is_undefined() { - r = js_string!(&r, &element.to_string(context)?); - } - } - - // 9. Return R. - Ok(r.into()) - } - - /// `23.2.3.17 %TypedArray%.prototype.keys ( )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys - pub(crate) fn keys( - this: &JsValue, - _: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.borrow() - .as_typed_array() - .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? - .is_detached() - { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Return CreateArrayIterator(O, key). - Ok(ArrayIterator::create_array_iterator( - o.clone(), - PropertyNameKind::Key, - context, - )) - } - - /// `23.2.3.18 %TypedArray%.prototype.lastIndexOf ( searchElement [ , fromIndex ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof - pub(crate) fn last_index_of( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; - - drop(obj_borrow); - - // 4. If len is 0, return -1𝔽. - if len == 0 { - return Ok((-1).into()); - } - - // 5. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1. - let n = if let Some(n) = args.get(1) { - n.to_integer_or_infinity(context)? - } else { - IntegerOrInfinity::Integer(len - 1) - }; - - let mut k = match n { - // 6. If n is -∞, return -1𝔽. - IntegerOrInfinity::NegativeInfinity => return Ok((-1).into()), - // 7. If n ≥ 0, then - // a. Let k be min(n, len - 1). - IntegerOrInfinity::Integer(i) if i >= 0 => std::cmp::min(i, len - 1), - IntegerOrInfinity::PositiveInfinity => len - 1, - // 8. Else, - // a. Let k be len + n. - IntegerOrInfinity::Integer(i) => len + i, - }; - - // 9. Repeat, while k ≥ 0, - while k >= 0 { - // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). - let k_present = obj - .has_property(k, context) - .expect("HasProperty cannot fail here"); - - // b. If kPresent is true, then - if k_present { - // i. Let elementK be ! Get(O, ! ToString(𝔽(k))). - let element_k = obj.get(k, context).expect("Get cannot fail here"); - - // ii. Let same be IsStrictlyEqual(searchElement, elementK). - // iii. If same is true, return 𝔽(k). - if args.get_or_undefined(0).strict_equals(&element_k) { - return Ok(k.into()); - } - } - - // c. Set k to k - 1. - k -= 1; - } - - // 10. Return -1𝔽. - Ok((-1).into()) - } - - /// `23.2.3.19 get %TypedArray%.prototype.length` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length - pub(crate) fn length(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). - // 3. Assert: O has [[ViewedArrayBuffer]] and [[ArrayLength]] internal slots. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. - // 6. Let length be O.[[ArrayLength]]. - // 7. Return 𝔽(length). - if typed_array.is_detached() { - Ok(0.into()) - } else { - Ok(typed_array.array_length().into()) - } - } - - /// `23.2.3.20 %TypedArray%.prototype.map ( callbackfn [ , thisArg ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.map - pub(crate) fn map( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - let typed_array_name = o.typed_array_name(); - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(obj_borrow); - - // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => { - return Err(JsNativeError::typ() - .with_message( - "TypedArray.prototype.map called with non-callable callback function", - ) - .into()) - } - }; - - // 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »). - let a = Self::species_create(obj, typed_array_name, &[len.into()], context)?; - - // 6. Let k be 0. - // 7. Repeat, while k < len, - for k in 0..len { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); - - // c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). - let mapped_value = callback_fn.call( - args.get_or_undefined(1), - &[k_value, k.into(), this.clone()], - context, - )?; - - // d. Perform ? Set(A, Pk, mappedValue, true). - a.set(k, mapped_value, true, context)?; - } - - // 8. Return A. - Ok(a.into()) - } - - /// `23.2.3.21 %TypedArray%.prototype.reduce ( callbackfn [ , initialValue ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce - pub(crate) fn reduce( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(obj_borrow); - - // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = - match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => return Err(JsNativeError::typ() - .with_message( - "TypedArray.prototype.reduce called with non-callable callback function", - ) - .into()), - }; - - // 5. If len = 0 and initialValue is not present, throw a TypeError exception. - if len == 0 && args.get(1).is_none() { - return Err(JsNativeError::typ() - .with_message("Typed array length is 0 and initial value is not present") - .into()); - } - - // 6. Let k be 0. - let mut k = 0; - - // 7. Let accumulator be undefined. - // 8. If initialValue is present, then - let mut accumulator = if let Some(initial_value) = args.get(1) { - // a. Set accumulator to initialValue. - initial_value.clone() - // 9. Else, - } else { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Set accumulator to ! Get(O, Pk). - // c. Set k to k + 1. - k += 1; - obj.get(0, context).expect("Get cannot fail here") - }; - - // 10. Repeat, while k < len, - while k < len { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); - - // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). - accumulator = callback_fn.call( - &JsValue::undefined(), - &[accumulator, k_value, k.into(), this.clone()], - context, - )?; - - // d. Set k to k + 1. - k += 1; - } - - // 11. Return accumulator. - Ok(accumulator) - } - - /// `23.2.3.22 %TypedArray%.prototype.reduceRight ( callbackfn [ , initialValue ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright - pub(crate) fn reduceright( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; - - drop(obj_borrow); - - // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => return Err(JsNativeError::typ() - .with_message( - "TypedArray.prototype.reduceright called with non-callable callback function", - ) - .into()), - }; - - // 5. If len = 0 and initialValue is not present, throw a TypeError exception. - if len == 0 && args.get(1).is_none() { - return Err(JsNativeError::typ() - .with_message("Typed array length is 0 and initial value is not present") - .into()); - } - - // 6. Let k be len - 1. - let mut k = len - 1; - - // 7. Let accumulator be undefined. - // 8. If initialValue is present, then - let mut accumulator = if let Some(initial_value) = args.get(1) { - // a. Set accumulator to initialValue. - initial_value.clone() - // 9. Else, - } else { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Set accumulator to ! Get(O, Pk). - let accumulator = obj.get(k, context).expect("Get cannot fail here"); - - // c. Set k to k - 1. - k -= 1; - - accumulator - }; - - // 10. Repeat, while k ≥ 0, - while k >= 0 { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); - - // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). - accumulator = callback_fn.call( - &JsValue::undefined(), - &[accumulator, k_value, k.into(), this.clone()], - context, - )?; - - // d. Set k to k - 1. - k -= 1; - } - - // 11. Return accumulator. - Ok(accumulator) - } - - /// `23.2.3.23 %TypedArray%.prototype.reverse ( )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.reverse - #[allow(clippy::float_cmp)] - pub(crate) fn reverse( - this: &JsValue, - _: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as f64; - - drop(obj_borrow); - - // 4. Let middle be floor(len / 2). - let middle = (len / 2.0).floor(); - - // 5. Let lower be 0. - let mut lower = 0.0; - // 6. Repeat, while lower ≠ middle, - while lower != middle { - // a. Let upper be len - lower - 1. - let upper = len - lower - 1.0; - - // b. Let upperP be ! ToString(𝔽(upper)). - // c. Let lowerP be ! ToString(𝔽(lower)). - // d. Let lowerValue be ! Get(O, lowerP). - let lower_value = obj.get(lower, context).expect("Get cannot fail here"); - // e. Let upperValue be ! Get(O, upperP). - let upper_value = obj.get(upper, context).expect("Get cannot fail here"); - - // f. Perform ! Set(O, lowerP, upperValue, true). - obj.set(lower, upper_value, true, context) - .expect("Set cannot fail here"); - // g. Perform ! Set(O, upperP, lowerValue, true). - obj.set(upper, lower_value, true, context) - .expect("Set cannot fail here"); - - // h. Set lower to lower + 1. - lower += 1.0; - } - - // 7. Return O. - Ok(this.clone()) - } - - /// `23.2.3.24 %TypedArray%.prototype.set ( source [ , offset ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.set - pub(crate) fn set( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let target be the this value. - // 2. Perform ? RequireInternalSlot(target, [[TypedArrayName]]). - // 3. Assert: target has a [[ViewedArrayBuffer]] internal slot. - let target = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("TypedArray.set must be called on typed array object") - })?; - if !target.is_typed_array() { - return Err(JsNativeError::typ() - .with_message("TypedArray.set must be called on typed array object") - .into()); - } - - // 4. Let targetOffset be ? ToIntegerOrInfinity(offset). - let target_offset = args.get_or_undefined(1).to_integer_or_infinity(context)?; - - // 5. If targetOffset < 0, throw a RangeError exception. - let target_offset = match target_offset { - IntegerOrInfinity::Integer(i) if i < 0 => { - return Err(JsNativeError::range() - .with_message("TypedArray.set called with negative offset") - .into()) - } - IntegerOrInfinity::NegativeInfinity => { - return Err(JsNativeError::range() - .with_message("TypedArray.set called with negative offset") - .into()) - } - IntegerOrInfinity::PositiveInfinity => U64OrPositiveInfinity::PositiveInfinity, - IntegerOrInfinity::Integer(i) => U64OrPositiveInfinity::U64(i as u64), - }; - - let source = args.get_or_undefined(0); - match source { - // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then - JsValue::Object(source) if source.is_typed_array() => { - // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). - Self::set_typed_array_from_typed_array(target, &target_offset, source, context)?; - } - // 7. Else, - _ => { - // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). - Self::set_typed_array_from_array_like(target, &target_offset, source, context)?; - } - } - - // 8. Return undefined. - Ok(JsValue::undefined()) - } - - /// `3.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray - fn set_typed_array_from_typed_array( - target: &JsObject, - target_offset: &U64OrPositiveInfinity, - source: &JsObject, - context: &mut Context<'_>, - ) -> JsResult<()> { - let target_borrow = target.borrow(); - let target_array = target_borrow - .as_typed_array() - .expect("Target must be a typed array"); - - let source_borrow = source.borrow(); - let source_array = source_borrow - .as_typed_array() - .expect("Source must be a typed array"); - - // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. - // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. - if target_array.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - let target_buffer_obj = target_array - .viewed_array_buffer() - .expect("Already checked for detached buffer") - .clone(); - - // 3. Let targetLength be target.[[ArrayLength]]. - let target_length = target_array.array_length(); - - // 4. Let srcBuffer be source.[[ViewedArrayBuffer]]. - // 5. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. - if source_array.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - let mut src_buffer_obj = source_array - .viewed_array_buffer() - .expect("Already checked for detached buffer") - .clone(); - - // 6. Let targetName be the String value of target.[[TypedArrayName]]. - // 7. Let targetType be the Element Type value in Table 73 for targetName. - let target_name = target_array.typed_array_name(); - - // 8. Let targetElementSize be the Element Size value specified in Table 73 for targetName. - let target_element_size = target_name.element_size(); - - // 9. Let targetByteOffset be target.[[ByteOffset]]. - let target_byte_offset = target_array.byte_offset(); - - drop(target_borrow); - - // 10. Let srcName be the String value of source.[[TypedArrayName]]. - // 11. Let srcType be the Element Type value in Table 73 for srcName. - let src_name = source_array.typed_array_name(); - - // 12. Let srcElementSize be the Element Size value specified in Table 73 for srcName. - let src_element_size = src_name.element_size(); - - // 13. Let srcLength be source.[[ArrayLength]]. - let src_length = source_array.array_length(); - - // 14. Let srcByteOffset be source.[[ByteOffset]]. - let src_byte_offset = source_array.byte_offset(); - - // 15. If targetOffset is +∞, throw a RangeError exception. - let target_offset = match target_offset { - U64OrPositiveInfinity::U64(target_offset) => target_offset, - U64OrPositiveInfinity::PositiveInfinity => { - return Err(JsNativeError::range() - .with_message("Target offset cannot be Infinity") - .into()); - } - }; - - // 16. If srcLength + targetOffset > targetLength, throw a RangeError exception. - if src_length + target_offset > target_length { - return Err(JsNativeError::range() - .with_message("Source typed array and target offset longer than target typed array") - .into()); - } - - // 17. If target.[[ContentType]] ≠ source.[[ContentType]], throw a TypeError exception. - if target_name.content_type() != src_name.content_type() { - return Err(JsNativeError::typ() - .with_message( - "Source typed array and target typed array have different content types", - ) - .into()); - } - - // TODO: Shared Array Buffer - // 18. If both IsSharedArrayBuffer(srcBuffer) and IsSharedArrayBuffer(targetBuffer) are true, then - - // a. If srcBuffer.[[ArrayBufferData]] and targetBuffer.[[ArrayBufferData]] are the same Shared Data Block values, let same be true; else let same be false. - - // 19. Else, let same be SameValue(srcBuffer, targetBuffer). - let same = JsObject::equals(&src_buffer_obj, &target_buffer_obj); - - // 20. If same is true, then - let mut src_byte_index = if same { - // a. Let srcByteLength be source.[[ByteLength]]. - let src_byte_length = source_array.byte_length(); - - // b. Set srcBuffer to ? CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength, %ArrayBuffer%). - // c. NOTE: %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects. - let s = src_buffer_obj - .borrow() - .as_array_buffer() - .expect("Already checked for detached buffer") - .clone_array_buffer(src_byte_offset, src_byte_length, context)?; - src_buffer_obj = s; - - // d. Let srcByteIndex be 0. - 0 - } - // 21. Else, let srcByteIndex be srcByteOffset. - else { - src_byte_offset - }; - - drop(source_borrow); - - // 22. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. - let mut target_byte_index = target_offset * target_element_size + target_byte_offset; - - // 23. Let limit be targetByteIndex + targetElementSize × srcLength. - let limit = target_byte_index + target_element_size * src_length; - - let src_buffer_obj_borrow = src_buffer_obj.borrow(); - let src_buffer = src_buffer_obj_borrow - .as_array_buffer() - .expect("Must be an array buffer"); - - // 24. If srcType is the same as targetType, then - if src_name == target_name { - // a. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. - // b. Repeat, while targetByteIndex < limit, - while target_byte_index < limit { - // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). - let value = src_buffer.get_value_from_buffer( - src_byte_index, - TypedArrayKind::Uint8, - true, - SharedMemoryOrder::Unordered, - None, - ); - - // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). - target_buffer_obj - .borrow_mut() - .as_array_buffer_mut() - .expect("Must be an array buffer") - .set_value_in_buffer( - target_byte_index, - TypedArrayKind::Uint8, - &value, - SharedMemoryOrder::Unordered, - None, - context, - )?; - - // iii. Set srcByteIndex to srcByteIndex + 1. - src_byte_index += 1; - - // iv. Set targetByteIndex to targetByteIndex + 1. - target_byte_index += 1; - } - } - // 25. Else, - else { - // a. Repeat, while targetByteIndex < limit, - while target_byte_index < limit { - // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, Unordered). - let value = src_buffer.get_value_from_buffer( - src_byte_index, - src_name, - true, - SharedMemoryOrder::Unordered, - None, - ); - - // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, Unordered). - target_buffer_obj - .borrow_mut() - .as_array_buffer_mut() - .expect("Must be an array buffer") - .set_value_in_buffer( - target_byte_index, - target_name, - &value, - SharedMemoryOrder::Unordered, - None, - context, - )?; - - // iii. Set srcByteIndex to srcByteIndex + srcElementSize. - src_byte_index += src_element_size; - - // iv. Set targetByteIndex to targetByteIndex + targetElementSize. - target_byte_index += target_element_size; - } - } - - Ok(()) - } - - /// `23.2.3.24.2 SetTypedArrayFromArrayLike ( target, targetOffset, source )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-settypedarrayfromarraylike - fn set_typed_array_from_array_like( - target: &JsObject, - target_offset: &U64OrPositiveInfinity, - source: &JsValue, - context: &mut Context<'_>, - ) -> JsResult<()> { - let target_length = { - let target_borrow = target.borrow(); - let target_array = target_borrow - .as_typed_array() - .expect("Target must be a typed array"); - - // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. - // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. - if target_array.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let targetLength be target.[[ArrayLength]]. - target_array.array_length() - }; - - // 4. Let src be ? ToObject(source). - let src = source.to_object(context)?; - - // 5. Let srcLength be ? LengthOfArrayLike(src). - let src_length = src.length_of_array_like(context)?; - - // 6. If targetOffset = +∞, throw a RangeError exception. - let target_offset = match target_offset { - U64OrPositiveInfinity::U64(target_offset) => target_offset, - U64OrPositiveInfinity::PositiveInfinity => { - return Err(JsNativeError::range() - .with_message("Target offset cannot be positive infinity") - .into()) - } - }; - - // 7. If srcLength + targetOffset > targetLength, throw a RangeError exception. - if src_length + target_offset > target_length { - return Err(JsNativeError::range() - .with_message("Source object and target offset longer than target typed array") - .into()); - } - - // 8. Let k be 0. - // 9. Repeat, while k < srcLength, - for k in 0..src_length { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let value be ? Get(src, Pk). - let value = src.get(k, context)?; - - // c. Let targetIndex be 𝔽(targetOffset + k). - let target_index = target_offset + k; - - // d. Perform ? IntegerIndexedElementSet(target, targetIndex, value). - integer_indexed_element_set(target, target_index as f64, &value, context)?; - - // e. Set k to k + 1. - } - - // 10. Return unused. - Ok(()) - } - - /// `23.2.3.25 %TypedArray%.prototype.slice ( start, end )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice - pub(crate) fn slice( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; - - // 4. Let relativeStart be ? ToIntegerOrInfinity(start). - let mut k = match args.get_or_undefined(0).to_integer_or_infinity(context)? { - // 5. If relativeStart is -∞, let k be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 6. Else if relativeStart < 0, let k be max(len + relativeStart, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 7. Else, let k be min(relativeStart, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; - - // 8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - let end = args.get_or_undefined(1); - let relative_end = if end.is_undefined() { - IntegerOrInfinity::Integer(len) - } else { - end.to_integer_or_infinity(context)? - }; - - let r#final = match relative_end { - // 9. If relativeEnd is -∞, let final be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 10. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 11. Else, let final be min(relativeEnd, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; - - // 12. Let count be max(final - k, 0). - let count = std::cmp::max(r#final - k, 0) as u64; - - // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). - let a = Self::species_create(obj, o.typed_array_name(), &[count.into()], context)?; - let a_borrow = a.borrow(); - let a_array = a_borrow - .as_typed_array() - .expect("This must be a typed array"); - - // 14. If count > 0, then - if count > 0 { - // a. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // b. Let srcName be the String value of O.[[TypedArrayName]]. - // c. Let srcType be the Element Type value in Table 73 for srcName. - let src_type = o.typed_array_name(); - - // d. Let targetName be the String value of A.[[TypedArrayName]]. - // e. Let targetType be the Element Type value in Table 73 for targetName. - let target_type = a_array.typed_array_name(); - - // f. If srcType is different from targetType, then - #[allow(clippy::if_not_else)] - if src_type != target_type { - drop(obj_borrow); - drop(a_borrow); - - // i. Let n be 0. - let mut n = 0; - - // ii. Repeat, while k < final, - while k < r#final { - // 1. Let Pk be ! ToString(𝔽(k)). - // 2. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); - - // 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true). - a.set(n, k_value, true, context) - .expect("Set cannot fail here"); - - // 4. Set k to k + 1. - k += 1; - - // 5. Set n to n + 1. - n += 1; - } - // g. Else, - } else { - // i. Let srcBuffer be O.[[ViewedArrayBuffer]]. - let src_buffer_obj = o.viewed_array_buffer().expect("Cannot be detached here"); - let src_buffer_obj_borrow = src_buffer_obj.borrow(); - let src_buffer = src_buffer_obj_borrow - .as_array_buffer() - .expect("Cannot be detached here"); - - // ii. Let targetBuffer be A.[[ViewedArrayBuffer]]. - let target_buffer_obj = a_array - .viewed_array_buffer() - .expect("Cannot be detached here"); - let mut target_buffer_obj_borrow = target_buffer_obj.borrow_mut(); - let target_buffer = target_buffer_obj_borrow - .as_array_buffer_mut() - .expect("Cannot be detached here"); - - // iii. Let elementSize be the Element Size value specified in Table 73 for Element Type srcType. - let element_size = o.typed_array_name().element_size(); - - // iv. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. - - // v. Let srcByteOffset be O.[[ByteOffset]]. - let src_byte_offset = o.byte_offset(); - - // vi. Let targetByteIndex be A.[[ByteOffset]]. - let mut target_byte_index = a_array.byte_offset(); - - // vii. Let srcByteIndex be (k × elementSize) + srcByteOffset. - let mut src_byte_index = k as u64 * element_size + src_byte_offset; - - // viii. Let limit be targetByteIndex + count × elementSize. - let limit = target_byte_index + count * element_size; - - // ix. Repeat, while targetByteIndex < limit, - while target_byte_index < limit { - // 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). - let value = src_buffer.get_value_from_buffer( - src_byte_index, - TypedArrayKind::Uint8, - true, - SharedMemoryOrder::Unordered, - None, - ); - - // 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). - target_buffer.set_value_in_buffer( - target_byte_index, - TypedArrayKind::Uint8, - &value, - SharedMemoryOrder::Unordered, - None, - context, - )?; - - // 3. Set srcByteIndex to srcByteIndex + 1. - src_byte_index += 1; - - // 4. Set targetByteIndex to targetByteIndex + 1. - target_byte_index += 1; - } - - drop(target_buffer_obj_borrow); - drop(a_borrow); - } - } else { - drop(a_borrow); - } - - // 15. Return A. - Ok(a.into()) - } - - /// `23.2.3.26 %TypedArray%.prototype.some ( callbackfn [ , thisArg ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.some - pub(crate) fn some( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(obj_borrow); - - // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { - Some(obj) if obj.is_callable() => obj, - _ => { - return Err(JsNativeError::typ() - .with_message( - "TypedArray.prototype.some called with non-callable callback function", - ) - .into()) - } - }; - - // 5. Let k be 0. - // 6. Repeat, while k < len, - for k in 0..len { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); - - // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - // d. If testResult is true, return true. - if callback_fn - .call( - args.get_or_undefined(1), - &[k_value, k.into(), this.clone()], - context, - )? - .to_boolean() - { - return Ok(true.into()); - } - } - - // 7. Return false. - Ok(false.into()) - } - - /// `23.2.3.27 %TypedArray%.prototype.sort ( comparefn )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort - pub(crate) fn sort( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let compare_fn = match args.get(0) { - None | Some(JsValue::Undefined) => None, - Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj), - _ => { - return Err(JsNativeError::typ() - .with_message("TypedArray.sort called with non-callable comparefn") - .into()) - } - }; - - // 2. Let obj be the this value. - // 3. Perform ? ValidateTypedArray(obj). - // 4. Let len be obj.[[ArrayLength]]. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ() - .with_message("TypedArray.sort must be called on typed array object") - })?; - let len = - { - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ() - .with_message("TypedArray.sort must be called on typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ().with_message( - "TypedArray.sort called on typed array object with detached array buffer", - ).into()); - } - - o.array_length() - }; - - // 5. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.30. - // 6. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called: - let sort_compare = |x: &JsValue, - y: &JsValue, - compare_fn: Option<&JsObject>, - context: &mut Context<'_>| - -> JsResult { - // a. Return ? CompareTypedArrayElements(x, y, comparefn). - compare_typed_array_elements(x, y, compare_fn, context) - }; - - // Note: This step is currently inlined. - // 7. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, read-through-holes). - // 1. Let items be a new empty List. - let mut items = Vec::with_capacity(len as usize); - - // 2. Let k be 0. - // 3. Repeat, while k < len, - for k in 0..len { - // a. Let Pk be ! ToString(𝔽(k)). - // b. If holes is skip-holes, then - // i. Let kRead be ? HasProperty(obj, Pk). - // c. Else, - // i. Assert: holes is read-through-holes. - // ii. Let kRead be true. - // d. If kRead is true, then - // i. Let kValue be ? Get(obj, Pk). - let k_value = obj.get(k, context)?; - - // ii. Append kValue to items. - items.push(k_value); - // e. Set k to k + 1. - } - - // 4. Sort items using an implementation-defined sequence of calls to SortCompare. If any such call returns an abrupt completion, stop before performing any further calls to SortCompare and return that Completion Record. - // 5. Return items. - let mut sort_err = Ok(()); - items.sort_by(|x, y| { - if sort_err.is_ok() { - sort_compare(x, y, compare_fn, context).unwrap_or_else(|err| { - sort_err = Err(err); - Ordering::Equal - }) - } else { - Ordering::Equal - } - }); - sort_err?; + object::{internal_methods::get_prototype_from_constructor, JsObject}, + property::Attribute, + realm::Realm, + string::common::StaticJsStrings, + symbol::JsSymbol, + value::{JsValue, Numeric}, + Context, JsArgs, JsResult, JsString, +}; +use boa_profiler::Profiler; - // 8. Let j be 0. - // 9. Repeat, while j < len, - for (j, item) in items.into_iter().enumerate() { - // a. Perform ! Set(obj, ! ToString(𝔽(j)), sortedList[j], true). - obj.set(j, item, true, context) - .expect("cannot fail per spec"); +mod builtin; +mod element; +mod integer_indexed_object; - // b. Set j to j + 1. - } +pub(crate) use builtin::BuiltinTypedArray; +pub(crate) use element::{ClampedU8, Element}; +pub use integer_indexed_object::IntegerIndexed; - // 10. Return obj. - Ok(obj.clone().into()) - } +pub(crate) trait TypedArray { + type Element: Element; + const ERASED: TypedArrayKind; +} - /// `23.2.3.28 %TypedArray%.prototype.subarray ( begin, end )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.subarray - pub(crate) fn subarray( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). - // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - let buffer = o - .viewed_array_buffer() - .expect("Buffer cannot be detached here"); - - // 5. Let srcLength be O.[[ArrayLength]]. - let src_length = o.array_length() as i64; - - // 6. Let relativeBegin be ? ToIntegerOrInfinity(begin). - let begin_index = match args.get_or_undefined(0).to_integer_or_infinity(context)? { - // 7. If relativeBegin is -∞, let beginIndex be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 8. Else if relativeBegin < 0, let beginIndex be max(srcLength + relativeBegin, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(src_length + i, 0), - // 9. Else, let beginIndex be min(relativeBegin, srcLength). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, src_length), - IntegerOrInfinity::PositiveInfinity => src_length, - }; - - // 10. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end). - let end = args.get_or_undefined(1); - let relative_end = if end.is_undefined() { - IntegerOrInfinity::Integer(src_length) - } else { - end.to_integer_or_infinity(context)? - }; - - let end_index = match relative_end { - // 11. If relativeEnd is -∞, let endIndex be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 12. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(src_length + i, 0), - // 13. Else, let endIndex be min(relativeEnd, srcLength). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, src_length), - IntegerOrInfinity::PositiveInfinity => src_length, - }; - - // 14. Let newLength be max(endIndex - beginIndex, 0). - let new_length = std::cmp::max(end_index - begin_index, 0); - - // 15. Let constructorName be the String value of O.[[TypedArrayName]]. - // 16. Let elementSize be the Element Size value specified in Table 73 for constructorName. - let element_size = o.typed_array_name().element_size(); - - // 17. Let srcByteOffset be O.[[ByteOffset]]. - let src_byte_offset = o.byte_offset(); - - // 18. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. - let begin_byte_offset = src_byte_offset + begin_index as u64 * element_size; - - // 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». - // 20. Return ? TypedArraySpeciesCreate(O, argumentsList). - Ok(Self::species_create( - obj, - o.typed_array_name(), - &[ - buffer.clone().into(), - begin_byte_offset.into(), - new_length.into(), - ], - context, - )? - .into()) +impl IntrinsicObject for T { + fn get(intrinsics: &Intrinsics) -> JsObject { + Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } - /// `%TypedArray%.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] )` - /// `Array.prototype.toLocaleString ( [ locales [ , options ] ] )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [ECMA-402 reference][spec-402] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.tolocalestring - /// [spec-402]: https://402.ecma-international.org/10.0/#sup-array.prototype.tolocalestring - fn to_locale_string( - this: &JsValue, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let array be ? ToObject(this value). - // Note: ValidateTypedArray is applied to the this value prior to evaluating the algorithm. - let array = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let len = { - let obj_borrow = array.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 2. Let len be array.[[ArrayLength]] - o.array_length() - }; - - // 3. Let separator be the implementation-defined list-separator String value - // appropriate for the host environment's current locale (such as ", "). - let separator = { - #[cfg(feature = "intl")] - { - // TODO: this should eventually return a locale-sensitive separator. - utf16!(", ") - } - - #[cfg(not(feature = "intl"))] - { - utf16!(", ") - } - }; - - // 4. Let R be the empty String. - let mut r = Vec::new(); - - // 5. Let k be 0. - // 6. Repeat, while k < len, - for k in 0..len { - // a. If k > 0, then - if k > 0 { - // i. Set R to the string-concatenation of R and separator. - r.extend_from_slice(separator); - } - - // b. Let nextElement be ? Get(array, ! ToString(k)). - let next_element = array.get(k, context)?; - - // c. If nextElement is not undefined or null, then - if !next_element.is_null_or_undefined() { - // i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)). - let s = next_element - .invoke( - utf16!("toLocaleString"), - &[ - args.get_or_undefined(0).clone(), - args.get_or_undefined(1).clone(), - ], - context, - )? - .to_string(context)?; - - // ii. Set R to the string-concatenation of R and S. - r.extend_from_slice(&s); - } + fn init(realm: &Realm) { + let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); - // d. Increase k by 1. - } + let get_species = BuiltInBuilder::callable(realm, BuiltinTypedArray::get_species) + .name(js_string!("get [Symbol.species]")) + .build(); - // 7. Return R. - Ok(js_string!(r).into()) + BuiltInBuilder::from_standard_constructor::(realm) + .prototype( + realm + .intrinsics() + .constructors() + .typed_array() + .constructor(), + ) + .inherits(Some( + realm.intrinsics().constructors().typed_array().prototype(), + )) + .static_accessor( + JsSymbol::species(), + Some(get_species), + None, + Attribute::CONFIGURABLE, + ) + .property( + js_string!("BYTES_PER_ELEMENT"), + std::mem::size_of::(), + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ) + .static_property( + js_string!("BYTES_PER_ELEMENT"), + std::mem::size_of::(), + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ) + .build(); } +} - /// `23.2.3.31 %TypedArray%.prototype.values ( )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.values - fn values(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.borrow() - .as_typed_array() - .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? - .is_detached() - { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } +impl BuiltInObject for T { + const NAME: JsString = ::ERASED.js_name(); + const ATTRIBUTE: Attribute = Attribute::WRITABLE + .union(Attribute::NON_ENUMERABLE) + .union(Attribute::CONFIGURABLE); +} - // 3. Return CreateArrayIterator(O, value). - Ok(ArrayIterator::create_array_iterator( - o.clone(), - PropertyNameKind::Value, - context, - )) - } +impl BuiltInConstructor for T { + const LENGTH: usize = 3; - /// `23.2.3.33 get %TypedArray%.prototype [ @@toStringTag ]` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag - #[allow(clippy::unnecessary_wraps)] - fn to_string_tag(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult { - // 1. Let O be the this value. - // 2. If Type(O) is not Object, return undefined. - // 3. If O does not have a [[TypedArrayName]] internal slot, return undefined. - // 4. Let name be O.[[TypedArrayName]]. - // 5. Assert: Type(name) is String. - // 6. Return name. - Ok(this - .as_object() - .and_then(|obj| { - obj.borrow() - .as_typed_array() - .map(|o| o.typed_array_name().name().into()) - }) - .unwrap_or(JsValue::Undefined)) - } + const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = + ::ERASED.standard_constructor(); - /// `23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList )` + /// `23.2.5.1 TypedArray ( ...args )` /// /// More information: /// - [ECMAScript reference][spec] /// - /// [spec]: https://tc39.es/ecma262/#typedarray-species-create - fn species_create( - exemplar: &JsObject, - typed_array_name: TypedArrayKind, + /// [spec]: https://tc39.es/ecma262/#sec-typedarray + fn constructor( + new_target: &JsValue, args: &[JsValue], context: &mut Context<'_>, - ) -> JsResult { - // 1. Let defaultConstructor be the intrinsic object listed in column one of Table 73 for exemplar.[[TypedArrayName]]. - let default_constructor = match typed_array_name { - TypedArrayKind::Int8 => StandardConstructors::typed_int8_array, - TypedArrayKind::Uint8 => StandardConstructors::typed_uint8_array, - TypedArrayKind::Uint8Clamped => StandardConstructors::typed_uint8clamped_array, - TypedArrayKind::Int16 => StandardConstructors::typed_int16_array, - TypedArrayKind::Uint16 => StandardConstructors::typed_uint16_array, - TypedArrayKind::Int32 => StandardConstructors::typed_int32_array, - TypedArrayKind::Uint32 => StandardConstructors::typed_uint32_array, - TypedArrayKind::BigInt64 => StandardConstructors::typed_bigint64_array, - TypedArrayKind::BigUint64 => StandardConstructors::typed_biguint64_array, - TypedArrayKind::Float32 => StandardConstructors::typed_float32_array, - TypedArrayKind::Float64 => StandardConstructors::typed_float64_array, - }; - - // 2. Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor). - let constructor = exemplar.species_constructor(default_constructor, context)?; - - // 3. Let result be ? TypedArrayCreate(constructor, argumentList). - let result = Self::create(&constructor, args, context)?; - - // 4. Assert: result has [[TypedArrayName]] and [[ContentType]] internal slots. - // 5. If result.[[ContentType]] ≠ exemplar.[[ContentType]], throw a TypeError exception. - if result - .borrow() - .as_typed_array() - .expect("This can only be a typed array object") - .typed_array_name() - .content_type() - != typed_array_name.content_type() - { + ) -> JsResult { + // 1. If NewTarget is undefined, throw a TypeError exception. + if new_target.is_undefined() { return Err(JsNativeError::typ() - .with_message("New typed array has different context type than exemplar") + .with_message(format!( + "new target was undefined when constructing an {}", + T::ERASED.name() + )) .into()); } - // 6. Return result. - Ok(result) - } + // 2. Let constructorName be the String value of the Constructor Name value specified in Table 72 for this TypedArray constructor. + // 3. Let proto be "%TypedArray.prototype%". - /// `23.2.4.2 TypedArrayCreate ( constructor, argumentList )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#typedarray-create - fn create( - constructor: &JsObject, - args: &[JsValue], - context: &mut Context<'_>, - ) -> JsResult { - // 1. Let newTypedArray be ? Construct(constructor, argumentList). - let new_typed_array = constructor.construct(args, Some(constructor), context)?; - - let obj_borrow = new_typed_array.borrow(); - // 2. Perform ? ValidateTypedArray(newTypedArray). - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 4. Let numberOfArgs be the number of elements in args. + let number_of_args = args.len(); - // 3. If argumentList is a List of a single Number, then - if args.len() == 1 { - if let Some(number) = args[0].as_number() { - // a. If newTypedArray.[[ArrayLength]] < ℝ(argumentList[0]), throw a TypeError exception. - if (o.array_length() as f64) < number { - return Err(JsNativeError::typ() - .with_message("New typed array length is smaller than expected") - .into()); - } - } + // 5. If numberOfArgs = 0, then + if number_of_args == 0 { + // a. Return ? AllocateTypedArray(constructorName, NewTarget, proto, 0). + return Ok(BuiltinTypedArray::allocate::(new_target, 0, context)?.into()); } + // 6. Else, - // 4. Return newTypedArray. - Ok(new_typed_array.clone()) - } - - /// - fn allocate_buffer( - indexed: &mut IntegerIndexed, - length: u64, - context: &mut Context<'_>, - ) -> JsResult<()> { - // 1. Assert: O.[[ViewedArrayBuffer]] is undefined. - assert!(indexed.viewed_array_buffer().is_none()); - - // 2. Let constructorName be the String value of O.[[TypedArrayName]]. - // 3. Let elementSize be the Element Size value specified in Table 73 for constructorName. - let element_size = indexed.typed_array_name().element_size(); - - // 4. Let byteLength be elementSize × length. - let byte_length = element_size * length; - - // 5. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). - let data = ArrayBuffer::allocate( - &context - .intrinsics() - .constructors() - .array_buffer() - .constructor() - .into(), - byte_length, - context, - )?; - - // 6. Set O.[[ViewedArrayBuffer]] to data. - indexed.set_viewed_array_buffer(Some(data)); - // 7. Set O.[[ByteLength]] to byteLength. - indexed.set_byte_length(byte_length); - // 8. Set O.[[ByteOffset]] to 0. - indexed.set_byte_offset(0); - // 9. Set O.[[ArrayLength]] to length. - indexed.set_array_length(length); - - // 10. Return O. - Ok(()) - } - - /// - pub(crate) fn initialize_from_list( - o: &JsObject, - values: Vec, - context: &mut Context<'_>, - ) -> JsResult<()> { - // 1. Let len be the number of elements in values. - let len = values.len() as u64; - { - let mut o = o.borrow_mut(); - let o_inner = o.as_typed_array_mut().expect("expected a TypedArray"); - - // 2. Perform ? AllocateTypedArrayBuffer(O, len). - Self::allocate_buffer(o_inner, len, context)?; - } + // a. Let firstArgument be args[0]. + let first_argument = &args[0]; - // 3. Let k be 0. - // 4. Repeat, while k < len, - for (k, k_value) in values.into_iter().enumerate() { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be the first element of values and remove that element from values. - // c. Perform ? Set(O, Pk, kValue, true). - o.set(k, k_value, true, context)?; - // d. Set k to k + 1. - } + // b. If Type(firstArgument) is Object, then + if let Some(first_argument) = first_argument.as_object() { + // i. Let O be ? AllocateTypedArray(constructorName, NewTarget, proto). + let proto = + get_prototype_from_constructor(new_target, T::STANDARD_CONSTRUCTOR, context)?; - // 5. Assert: values is now an empty List. - // It no longer exists. - Ok(()) - } + // ii. If firstArgument has a [[TypedArrayName]] internal slot, then + let o = if first_argument.is_typed_array() { + // 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument). + BuiltinTypedArray::initialize_from_typed_array::(proto, first_argument, context)? + } else if first_argument.is_buffer() { + // iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then - /// `AllocateTypedArray ( constructorName, newTarget, defaultProto [ , length ] )` - /// - /// It is used to validate and create an instance of a `TypedArray` constructor. If the `length` - /// argument is passed, an `ArrayBuffer` of that length is also allocated and associated with the - /// new `TypedArray` instance. `AllocateTypedArray` provides common semantics that is used by - /// `TypedArray`. - /// - /// For more information, check the [spec][spec]. - /// - /// [spec]: https://tc39.es/ecma262/#sec-allocatetypedarray - fn allocate

( - constructor_name: TypedArrayKind, - new_target: &JsValue, - default_proto: P, - length: Option, - context: &mut Context<'_>, - ) -> JsResult - where - P: FnOnce(&StandardConstructors) -> &StandardConstructor, - { - // 1. Let proto be ? GetPrototypeFromConstructor(newTarget, defaultProto). - let proto = get_prototype_from_constructor(new_target, default_proto, context)?; - - // 3. Assert: obj.[[ViewedArrayBuffer]] is undefined. - // 4. Set obj.[[TypedArrayName]] to constructorName. - // 5. If constructorName is "BigInt64Array" or "BigUint64Array", set obj.[[ContentType]] to BigInt. - // 6. Otherwise, set obj.[[ContentType]] to Number. - // 7. If length is not present, then - // a. Set obj.[[ByteLength]] to 0. - // b. Set obj.[[ByteOffset]] to 0. - // c. Set obj.[[ArrayLength]] to 0. - let mut indexed = IntegerIndexed::new(None, constructor_name, 0, 0, 0); - - // 8. Else, - if let Some(length) = length { - // a. Perform ? AllocateTypedArrayBuffer(obj, length). - Self::allocate_buffer(&mut indexed, length, context)?; - } + // 1. If numberOfArgs > 1, let byteOffset be args[1]; else let byteOffset be undefined. + let byte_offset = args.get_or_undefined(1); - // 2. Let obj be ! IntegerIndexedObjectCreate(proto). - let obj = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - proto, - ObjectData::integer_indexed(indexed), - ); + // 2. If numberOfArgs > 2, let length be args[2]; else let length be undefined. + let length = args.get_or_undefined(2); - // 9. Return obj. - Ok(obj) - } + // 3. Perform ? InitializeTypedArrayFromArrayBuffer(O, firstArgument, byteOffset, length). + BuiltinTypedArray::initialize_from_array_buffer::( + proto, + first_argument.clone(), + byte_offset, + length, + context, + )? + } else { + // iv. Else, - /// `23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray - fn initialize_from_typed_array( - o: &JsObject, - src_array: &JsObject, - context: &mut Context<'_>, - ) -> JsResult<()> { - let o_obj = o.borrow(); - let src_array_obj = src_array.borrow(); - let o_array = o_obj.as_typed_array().expect("this must be a typed array"); - let src_array = src_array_obj - .as_typed_array() - .expect("this must be a typed array"); - - // 1. Let srcData be srcArray.[[ViewedArrayBuffer]]. - // 2. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. - if src_array.is_detached() { - return Err(JsNativeError::typ() - .with_message("Cannot initialize typed array from detached buffer") - .into()); - } + // 1. Assert: Type(firstArgument) is Object and firstArgument does not have + // either a [[TypedArrayName]] or an [[ArrayBufferData]] internal slot. - // 3. Let elementType be TypedArrayElementType(O). - let element_type = o_array.typed_array_name(); + // 2. Let usingIterator be ? GetMethod(firstArgument, @@iterator). - // 4. Let elementSize be TypedArrayElementSize(O). - let element_size = element_type.element_size(); + let first_argument_v = JsValue::from(first_argument.clone()); + let using_iterator = first_argument_v.get_method(JsSymbol::iterator(), context)?; - // 5. Let srcType be TypedArrayElementType(srcArray). - let src_type = src_array.typed_array_name(); + // 3. If usingIterator is not undefined, then + if let Some(using_iterator) = using_iterator { + // a. Let values be ? IterableToList(firstArgument, usingIterator). + let values = + iterable_to_list(context, &first_argument_v, Some(using_iterator))?; - // 6. Let srcElementSize be TypedArrayElementSize(srcArray). - let src_element_size = src_type.element_size(); + // b. Perform ? InitializeTypedArrayFromList(O, values). + BuiltinTypedArray::initialize_from_list::(proto, values, context)? + } else { + // 4. Else, - // 7. Let srcByteOffset be srcArray.[[ByteOffset]]. - let src_byte_offset = src_array.byte_offset(); + // a. NOTE: firstArgument is not an Iterable so assume it is already an array-like object. + // b. Perform ? InitializeTypedArrayFromArrayLike(O, firstArgument). + BuiltinTypedArray::initialize_from_array_like::( + proto, + first_argument, + context, + )? + } + }; - // 8. Let elementLength be srcArray.[[ArrayLength]]. - let element_length = src_array.array_length(); + // v. Return O. + Ok(o.into()) + } else { + // c. Else, - // 9. Let byteLength be elementSize × elementLength. - let byte_length = element_size * element_length; + // i. Assert: firstArgument is not an Object. + assert!(!first_argument.is_object(), "firstArgument was an object"); - let src_data_obj = src_array - .viewed_array_buffer() - .expect("Already checked for detached buffer"); - let src_data_obj_b = src_data_obj.borrow(); - let src_data = src_data_obj_b - .as_array_buffer() - .expect("Already checked for detached buffer"); + // ii. Let elementLength be ? ToIndex(firstArgument). + let element_length = first_argument.to_index(context)?; - // 10. If elementType is srcType, then - let data = if element_type == src_type { - // a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength). - src_data.clone_array_buffer(src_byte_offset, byte_length, context)? + // iii. Return ? AllocateTypedArray(constructorName, NewTarget, proto, elementLength). + Ok(BuiltinTypedArray::allocate::(new_target, element_length, context)?.into()) } - // 11. Else, - else { - // a. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). - let data_obj = ArrayBuffer::allocate( - &context - .realm() - .intrinsics() - .constructors() - .array_buffer() - .constructor() - .into(), - byte_length, - context, - )?; - let mut data_obj_b = data_obj.borrow_mut(); - let data = data_obj_b - .as_array_buffer_mut() - .expect("Must be ArrayBuffer"); - - // b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception. - if src_type.content_type() != element_type.content_type() { - return Err(JsNativeError::typ() - .with_message("Cannot initialize typed array from different content type") - .into()); - } + } +} - // c. Let srcByteIndex be srcByteOffset. - let mut src_byte_index = src_byte_offset; - - // d. Let targetByteIndex be 0. - let mut target_byte_index = 0; - - // e. Let count be elementLength. - let mut count = element_length; - - // f. Repeat, while count > 0, - while count > 0 { - // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered). - let value = src_data.get_value_from_buffer( - src_byte_index, - src_type, - true, - SharedMemoryOrder::Unordered, - None, - ); - - // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, Unordered). - data.set_value_in_buffer( - target_byte_index, - element_type, - &value, - SharedMemoryOrder::Unordered, - None, - context, - )?; +/// JavaScript `Int8Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct Int8Array; - // iii. Set srcByteIndex to srcByteIndex + srcElementSize. - src_byte_index += src_element_size; +impl TypedArray for Int8Array { + type Element = i8; - // iv. Set targetByteIndex to targetByteIndex + elementSize. - target_byte_index += element_size; + const ERASED: TypedArrayKind = TypedArrayKind::Int8; +} - // v. Set count to count - 1. - count -= 1; - } +/// JavaScript `Uint8Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct Uint8Array; - drop(data_obj_b); - data_obj - }; - - // 12. Set O.[[ViewedArrayBuffer]] to data. - // 13. Set O.[[ByteLength]] to byteLength. - // 14. Set O.[[ByteOffset]] to 0. - // 15. Set O.[[ArrayLength]] to elementLength. - drop(o_obj); - *o.borrow_mut().kind_mut() = ObjectKind::IntegerIndexed(IntegerIndexed::new( - Some(data), - element_type, - 0, - byte_length, - element_length, - )); - - // 16. Return unused. - Ok(()) - } +impl TypedArray for Uint8Array { + type Element = u8; - /// `23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset, length )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer - fn initialize_from_array_buffer( - o: &JsObject, - buffer: JsObject, - byte_offset: &JsValue, - length: &JsValue, - context: &mut Context<'_>, - ) -> JsResult<()> { - // 1. Let elementSize be TypedArrayElementSize(O). - let element_size = o - .borrow() - .as_typed_array() - .expect("Must be a typed array") - .typed_array_name() - .element_size(); - - // 2. Let offset be ? ToIndex(byteOffset). - let offset = byte_offset.to_index(context)?; - - // 3. If offset modulo elementSize ≠ 0, throw a RangeError exception. - if offset % element_size != 0 { - return Err(JsNativeError::range() - .with_message("Invalid length for typed array") - .into()); - } + const ERASED: TypedArrayKind = TypedArrayKind::Uint8; +} - // 4. If length is not undefined, then - let new_length = if length.is_undefined() { - None - } else { - // a. Let newLength be ? ToIndex(length). - Some(length.to_index(context)?) - }; - - // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - // 6. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. - let buffer_byte_length = { - let buffer_borrow = buffer.borrow(); - let buffer_array = buffer_borrow - .as_array_buffer() - .expect("Must be an ArrayBuffer"); - - if buffer_array.is_detached_buffer() { - return Err(JsNativeError::typ() - .with_message("Cannot construct typed array from detached buffer") - .into()); - } +/// JavaScript `Uint8ClampedArray` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct Uint8ClampedArray; - buffer_array.array_buffer_byte_length() - }; +impl TypedArray for Uint8ClampedArray { + type Element = ClampedU8; - // 7. If length is undefined, then - // 8. Else, - let new_byte_length = if let Some(new_length) = new_length { - // a. Let newByteLength be newLength × elementSize. - let new_byte_length = new_length * element_size; + const ERASED: TypedArrayKind = TypedArrayKind::Uint8Clamped; +} - // b. If offset + newByteLength > bufferByteLength, throw a RangeError exception. - if offset + new_byte_length > buffer_byte_length { - return Err(JsNativeError::range() - .with_message("Invalid length for typed array") - .into()); - } +/// JavaScript `Int16Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct Int16Array; - new_byte_length - } else { - // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception. - if buffer_byte_length % element_size != 0 { - return Err(JsNativeError::range() - .with_message("Invalid length for typed array") - .into()); - } +impl TypedArray for Int16Array { + type Element = i16; - // b. Let newByteLength be bufferByteLength - offset. - let new_byte_length = buffer_byte_length as i64 - offset as i64; + const ERASED: TypedArrayKind = TypedArrayKind::Int16; +} - // c. If newByteLength < 0, throw a RangeError exception. - if new_byte_length < 0 { - return Err(JsNativeError::range() - .with_message("Invalid length for typed array") - .into()); - } +/// JavaScript `Uint16Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct Uint16Array; - new_byte_length as u64 - }; - - let mut o_borrow = o.borrow_mut(); - let o = o_borrow - .as_typed_array_mut() - .expect("This must be an ArrayBuffer"); - - // 9. Set O.[[ViewedArrayBuffer]] to buffer. - o.set_viewed_array_buffer(Some(buffer)); - // 10. Set O.[[ByteLength]] to newByteLength. - o.set_byte_length(new_byte_length); - // 11. Set O.[[ByteOffset]] to offset. - o.set_byte_offset(offset); - // 12. Set O.[[ArrayLength]] to newByteLength / elementSize. - o.set_array_length(new_byte_length / element_size); - - // 13. Return unused. - Ok(()) - } +impl TypedArray for Uint16Array { + type Element = u16; - /// `23.2.5.1.5 InitializeTypedArrayFromArrayLike ( O, arrayLike )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike - fn initialize_from_array_like( - o: &JsObject, - array_like: &JsObject, - context: &mut Context<'_>, - ) -> JsResult<()> { - // 1. Let len be ? LengthOfArrayLike(arrayLike). - let len = array_like.length_of_array_like(context)?; - - // 2. Perform ? AllocateTypedArrayBuffer(O, len). - { - let mut o_borrow = o.borrow_mut(); - let o = o_borrow.as_typed_array_mut().expect("Must be typed array"); - Self::allocate_buffer(o, len, context)?; - } + const ERASED: TypedArrayKind = TypedArrayKind::Uint16; +} - // 3. Let k be 0. - // 4. Repeat, while k < len, - for k in 0..len { - // a. Let Pk be ! ToString(𝔽(k)). - // b. Let kValue be ? Get(arrayLike, Pk). - let k_value = array_like.get(k, context)?; +/// JavaScript `Int32Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct Int32Array; - // c. Perform ? Set(O, Pk, kValue, true). - o.set(k, k_value, true, context)?; - } +impl TypedArray for Int32Array { + type Element = i32; - Ok(()) - } + const ERASED: TypedArrayKind = TypedArrayKind::Int32; } -/// `CompareTypedArrayElements ( x, y, comparefn )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-comparetypedarrayelements -fn compare_typed_array_elements( - x: &JsValue, - y: &JsValue, - compare_fn: Option<&JsObject>, - context: &mut Context<'_>, -) -> JsResult { - // 1. Assert: x is a Number and y is a Number, or x is a BigInt and y is a BigInt. - - // 2. If comparefn is not undefined, then - if let Some(compare_fn) = compare_fn { - // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). - let v = compare_fn - .call(&JsValue::undefined(), &[x.clone(), y.clone()], context)? - .to_number(context)?; - - // b. If v is NaN, return +0𝔽. - if v.is_nan() { - return Ok(Ordering::Equal); - } +/// JavaScript `Uint32Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct Uint32Array; - // c. Return v. - if v.is_sign_positive() { - return Ok(Ordering::Greater); - } - return Ok(Ordering::Less); - } +impl TypedArray for Uint32Array { + type Element = u32; - match (x, y) { - (JsValue::BigInt(x), JsValue::BigInt(y)) => { - // Note: Other steps are not relevant for BigInts. - // 6. If x < y, return -1𝔽. - // 7. If x > y, return 1𝔽. - // 10. Return +0𝔽. - Ok(x.cmp(y)) - } - (JsValue::Integer(x), JsValue::Integer(y)) => { - // Note: Other steps are not relevant for integers. - // 6. If x < y, return -1𝔽. - // 7. If x > y, return 1𝔽. - // 10. Return +0𝔽. - Ok(x.cmp(y)) - } - (JsValue::Rational(x), JsValue::Rational(y)) => { - // 3. If x and y are both NaN, return +0𝔽. - if x.is_nan() && y.is_nan() { - return Ok(Ordering::Equal); - } + const ERASED: TypedArrayKind = TypedArrayKind::Uint32; +} - // 4. If x is NaN, return 1𝔽. - if x.is_nan() { - return Ok(Ordering::Greater); - } +/// JavaScript `BigInt64Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct BigInt64Array; - // 5. If y is NaN, return -1𝔽. - if y.is_nan() { - return Ok(Ordering::Less); - } +impl TypedArray for BigInt64Array { + type Element = i64; - // 6. If x < y, return -1𝔽. - if x < y { - return Ok(Ordering::Less); - } + const ERASED: TypedArrayKind = TypedArrayKind::BigInt64; +} - // 7. If x > y, return 1𝔽. - if x > y { - return Ok(Ordering::Greater); - } +/// JavaScript `BigUint64Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct BigUint64Array; - // 8. If x is -0𝔽 and y is +0𝔽, return -1𝔽. - if x.is_sign_negative() && x.is_zero() && y.is_sign_positive() && y.is_zero() { - return Ok(Ordering::Less); - } +impl TypedArray for BigUint64Array { + type Element = u64; - // 9. If x is +0𝔽 and y is -0𝔽, return 1𝔽. - if x.is_sign_positive() && x.is_zero() && y.is_sign_negative() && y.is_zero() { - return Ok(Ordering::Greater); - } + const ERASED: TypedArrayKind = TypedArrayKind::BigUint64; +} - // 10. Return +0𝔽. - Ok(Ordering::Equal) - } - _ => unreachable!("x and y must be both Numbers or BigInts"), - } +/// JavaScript `Float32Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct Float32Array; + +impl TypedArray for Float32Array { + type Element = f32; + + const ERASED: TypedArrayKind = TypedArrayKind::Float32; } -enum U64OrPositiveInfinity { - U64(u64), - PositiveInfinity, +/// JavaScript `Float64Array` built-in implementation. +#[derive(Debug, Copy, Clone)] +pub struct Float64Array; + +impl TypedArray for Float64Array { + type Element = f64; + + const ERASED: TypedArrayKind = TypedArrayKind::Float64; } -/// Names of all the typed arrays. +/// Type of the array content. #[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum ContentType { + Number, + BigInt, +} + +/// List of all typed array kinds. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(crate) enum TypedArrayKind { Int8, Uint8, @@ -3658,122 +346,218 @@ pub(crate) enum TypedArrayKind { } impl TypedArrayKind { - /// Gets the element size of the given typed array name, as per the [spec]. - /// - /// [spec]: https://tc39.es/ecma262/#table-the-typedarray-constructors + /// Gets the name of this `TypedArrayKind` as a `JsString`. + pub(crate) const fn js_name(self) -> JsString { + match self { + TypedArrayKind::Int8 => StaticJsStrings::INT8_ARRAY, + TypedArrayKind::Uint8 => StaticJsStrings::UINT8_ARRAY, + TypedArrayKind::Uint8Clamped => StaticJsStrings::UINT8_CLAMPED_ARRAY, + TypedArrayKind::Int16 => StaticJsStrings::INT16_ARRAY, + TypedArrayKind::Uint16 => StaticJsStrings::UINT16_ARRAY, + TypedArrayKind::Int32 => StaticJsStrings::INT32_ARRAY, + TypedArrayKind::Uint32 => StaticJsStrings::UINT32_ARRAY, + TypedArrayKind::BigInt64 => StaticJsStrings::BIG_INT64_ARRAY, + TypedArrayKind::BigUint64 => StaticJsStrings::BIG_UINT64_ARRAY, + TypedArrayKind::Float32 => StaticJsStrings::FLOAT32_ARRAY, + TypedArrayKind::Float64 => StaticJsStrings::FLOAT64_ARRAY, + } + } + + /// Gets the name of this `TypedArrayKind` as a `str` + pub(crate) const fn name(self) -> &'static str { + match self { + TypedArrayKind::Int8 => "Int8", + TypedArrayKind::Uint8 => "Uint8", + TypedArrayKind::Uint8Clamped => "Uint8Clamped", + TypedArrayKind::Int16 => "Int16", + TypedArrayKind::Uint16 => "Uint16", + TypedArrayKind::Int32 => "Int32", + TypedArrayKind::Uint32 => "Uint32", + TypedArrayKind::BigInt64 => "BigInt64", + TypedArrayKind::BigUint64 => "BigUint64", + TypedArrayKind::Float32 => "Float32", + TypedArrayKind::Float64 => "Float64", + } + } + + /// Gets the standard constructor accessor of this `TypedArrayKind`. + pub(crate) const fn standard_constructor( + self, + ) -> fn(&StandardConstructors) -> &StandardConstructor { + match self { + TypedArrayKind::Int8 => StandardConstructors::typed_int8_array, + TypedArrayKind::Uint8 => StandardConstructors::typed_uint8_array, + TypedArrayKind::Uint8Clamped => StandardConstructors::typed_uint8clamped_array, + TypedArrayKind::Int16 => StandardConstructors::typed_int16_array, + TypedArrayKind::Uint16 => StandardConstructors::typed_uint16_array, + TypedArrayKind::Int32 => StandardConstructors::typed_int32_array, + TypedArrayKind::Uint32 => StandardConstructors::typed_uint32_array, + TypedArrayKind::BigInt64 => StandardConstructors::typed_bigint64_array, + TypedArrayKind::BigUint64 => StandardConstructors::typed_biguint64_array, + TypedArrayKind::Float32 => StandardConstructors::typed_float32_array, + TypedArrayKind::Float64 => StandardConstructors::typed_float64_array, + } + } + + /// Gets the size of the type of element of this `TypedArrayKind`. pub(crate) const fn element_size(self) -> u64 { match self { - Self::Int8 | Self::Uint8 | Self::Uint8Clamped => 1, - Self::Int16 | Self::Uint16 => 2, - Self::Int32 | Self::Uint32 | Self::Float32 => 4, - Self::BigInt64 | Self::BigUint64 | Self::Float64 => 8, + TypedArrayKind::Int8 | TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => { + std::mem::size_of::() as u64 + } + TypedArrayKind::Int16 | TypedArrayKind::Uint16 => std::mem::size_of::() as u64, + TypedArrayKind::Int32 | TypedArrayKind::Uint32 | TypedArrayKind::Float32 => { + std::mem::size_of::() as u64 + } + TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 | TypedArrayKind::Float64 => { + std::mem::size_of::() as u64 + } } } - /// Gets the content type of this typed array name. + /// Returns the content type of this `TypedArrayKind`. pub(crate) const fn content_type(self) -> ContentType { match self { - Self::BigInt64 | Self::BigUint64 => ContentType::BigInt, - _ => ContentType::Number, + TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => ContentType::BigInt, + TypedArrayKind::Int8 + | TypedArrayKind::Uint8 + | TypedArrayKind::Uint8Clamped + | TypedArrayKind::Int16 + | TypedArrayKind::Uint16 + | TypedArrayKind::Int32 + | TypedArrayKind::Uint32 + | TypedArrayKind::Float32 + | TypedArrayKind::Float64 => ContentType::Number, } } - /// Gets the name of this typed array name. - pub(crate) const fn name(self) -> JsString { + /// Convert `value` into the typed array element corresponding to this `TypedArrayKind`. + pub(crate) fn get_element( + self, + value: &JsValue, + context: &mut Context<'_>, + ) -> JsResult { match self { - Self::Int8 => StaticJsStrings::INT8_ARRAY, - Self::Uint8 => StaticJsStrings::UINT8_ARRAY, - Self::Uint8Clamped => StaticJsStrings::UINT8_CLAMPED_ARRAY, - Self::Int16 => StaticJsStrings::INT16_ARRAY, - Self::Uint16 => StaticJsStrings::UINT16_ARRAY, - Self::Int32 => StaticJsStrings::INT32_ARRAY, - Self::Uint32 => StaticJsStrings::UINT32_ARRAY, - Self::BigInt64 => StaticJsStrings::BIG_INT64_ARRAY, - Self::BigUint64 => StaticJsStrings::BIG_UINT64_ARRAY, - Self::Float32 => StaticJsStrings::FLOAT32_ARRAY, - Self::Float64 => StaticJsStrings::FLOAT64_ARRAY, + TypedArrayKind::Int8 => value.to_int8(context).map(TypedArrayElement::Int8), + TypedArrayKind::Uint8 => value.to_uint8(context).map(TypedArrayElement::Uint8), + TypedArrayKind::Uint8Clamped => value + .to_uint8_clamp(context) + .map(|u| TypedArrayElement::Uint8Clamped(ClampedU8(u))), + TypedArrayKind::Int16 => value.to_int16(context).map(TypedArrayElement::Int16), + TypedArrayKind::Uint16 => value.to_uint16(context).map(TypedArrayElement::Uint16), + TypedArrayKind::Int32 => value.to_i32(context).map(TypedArrayElement::Int32), + TypedArrayKind::Uint32 => value.to_u32(context).map(TypedArrayElement::Uint32), + TypedArrayKind::BigInt64 => { + value.to_big_int64(context).map(TypedArrayElement::BigInt64) + } + TypedArrayKind::BigUint64 => value + .to_big_uint64(context) + .map(TypedArrayElement::BigUint64), + TypedArrayKind::Float32 => value + .to_number(context) + .map(|f| TypedArrayElement::Float32(f as f32)), + TypedArrayKind::Float64 => value.to_number(context).map(TypedArrayElement::Float64), } } +} + +/// An element of a certain `TypedArray` kind. +#[derive(Debug, Copy, Clone, PartialEq)] +pub(crate) enum TypedArrayElement { + Int8(i8), + Uint8(u8), + Uint8Clamped(ClampedU8), + Int16(i16), + Uint16(u16), + Int32(i32), + Uint32(u32), + BigInt64(i64), + BigUint64(u64), + Float32(f32), + Float64(f64), +} - pub(crate) const fn is_big_int_element_type(self) -> bool { - matches!(self, Self::BigUint64 | Self::BigInt64) +impl From for TypedArrayElement { + fn from(value: i8) -> Self { + Self::Int8(value) } } -typed_array!( - Int8Array, - Int8, - "Int8Array", - StaticJsStrings::INT8_ARRAY, - typed_int8_array -); -typed_array!( - Uint8Array, - Uint8, - "UInt8Array", - StaticJsStrings::UINT8_ARRAY, - typed_uint8_array -); -typed_array!( - Uint8ClampedArray, - Uint8Clamped, - "UInt8ClampedArray", - StaticJsStrings::UINT8_CLAMPED_ARRAY, - typed_uint8clamped_array -); -typed_array!( - Int16Array, - Int16, - "Int16Array", - StaticJsStrings::INT16_ARRAY, - typed_int16_array -); -typed_array!( - Uint16Array, - Uint16, - "UInt16Array", - StaticJsStrings::UINT16_ARRAY, - typed_uint16_array -); -typed_array!( - Int32Array, - Int32, - "Int32Array", - StaticJsStrings::INT32_ARRAY, - typed_int32_array -); -typed_array!( - Uint32Array, - Uint32, - "UInt32Array", - StaticJsStrings::UINT32_ARRAY, - typed_uint32_array -); -typed_array!( - BigInt64Array, - BigInt64, - "BigInt64Array", - StaticJsStrings::BIG_INT64_ARRAY, - typed_bigint64_array -); -typed_array!( - BigUint64Array, - BigUint64, - "BigUint64Array", - StaticJsStrings::BIG_UINT64_ARRAY, - typed_biguint64_array -); -typed_array!( - Float32Array, - Float32, - "Float32Array", - StaticJsStrings::FLOAT32_ARRAY, - typed_float32_array -); -typed_array!( - Float64Array, - Float64, - "Float64Array", - StaticJsStrings::FLOAT64_ARRAY, - typed_float64_array -); +impl From for TypedArrayElement { + fn from(value: u8) -> Self { + Self::Uint8(value) + } +} + +impl From for TypedArrayElement { + fn from(value: ClampedU8) -> Self { + Self::Uint8Clamped(value) + } +} + +impl From for TypedArrayElement { + fn from(value: i16) -> Self { + Self::Int16(value) + } +} + +impl From for TypedArrayElement { + fn from(value: u16) -> Self { + Self::Uint16(value) + } +} + +impl From for TypedArrayElement { + fn from(value: i32) -> Self { + Self::Int32(value) + } +} + +impl From for TypedArrayElement { + fn from(value: u32) -> Self { + Self::Uint32(value) + } +} + +impl From for TypedArrayElement { + fn from(value: i64) -> Self { + Self::BigInt64(value) + } +} + +impl From for TypedArrayElement { + fn from(value: u64) -> Self { + Self::BigUint64(value) + } +} + +impl From for TypedArrayElement { + fn from(value: f32) -> Self { + Self::Float32(value) + } +} + +impl From for TypedArrayElement { + fn from(value: f64) -> Self { + Self::Float64(value) + } +} + +impl From for JsValue { + fn from(value: TypedArrayElement) -> Self { + match value { + TypedArrayElement::Int8(value) => Numeric::from(value), + TypedArrayElement::Uint8(value) => Numeric::from(value), + TypedArrayElement::Uint8Clamped(value) => Numeric::from(value), + TypedArrayElement::Int16(value) => Numeric::from(value), + TypedArrayElement::Uint16(value) => Numeric::from(value), + TypedArrayElement::Int32(value) => Numeric::from(value), + TypedArrayElement::Uint32(value) => Numeric::from(value), + TypedArrayElement::BigInt64(value) => Numeric::from(value), + TypedArrayElement::BigUint64(value) => Numeric::from(value), + TypedArrayElement::Float32(value) => Numeric::from(value), + TypedArrayElement::Float64(value) => Numeric::from(value), + } + .into() + } +} diff --git a/boa_engine/src/context/hooks.rs b/boa_engine/src/context/hooks.rs index 7d73198ef75..2bbaa042627 100644 --- a/boa_engine/src/context/hooks.rs +++ b/boa_engine/src/context/hooks.rs @@ -218,6 +218,25 @@ pub trait HostHooks { } } } + + /// Gets the maximum size in bits that can be allocated for an `ArrayBuffer` or a + /// `SharedArrayBuffer`. + /// + /// This hook will be called before any buffer allocation, which allows to dinamically change + /// the maximum size at runtime. By default, this is set to 1.5GiB per the recommendations of the + /// [specification]: + /// + /// > If a host is multi-tenanted (i.e. it runs many ECMAScript applications simultaneously), + /// such as a web browser, and its implementations choose to implement in-place growth by reserving + /// virtual memory, we recommend that both 32-bit and 64-bit implementations throw for values of + /// "`maxByteLength`" ≥ 1GiB to 1.5GiB. This is to reduce the likelihood a single application can + /// exhaust the virtual memory address space and to reduce interoperability risk. + /// + /// + /// [specification]: https://tc39.es/ecma262/multipage/structured-data.html#sec-resizable-arraybuffer-guidelines + fn max_buffer_size(&self) -> u64 { + 1_610_612_736 // 1.5 GiB + } } /// Default implementation of [`HostHooks`], which doesn't carry any state. diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 4a671e38f37..18cb4bb2596 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -150,6 +150,7 @@ pub struct StandardConstructors { typed_float32_array: StandardConstructor, typed_float64_array: StandardConstructor, array_buffer: StandardConstructor, + shared_array_buffer: StandardConstructor, data_view: StandardConstructor, date_time_format: StandardConstructor, promise: StandardConstructor, @@ -246,6 +247,7 @@ impl Default for StandardConstructors { typed_float32_array: StandardConstructor::default(), typed_float64_array: StandardConstructor::default(), array_buffer: StandardConstructor::default(), + shared_array_buffer: StandardConstructor::default(), data_view: StandardConstructor::default(), date_time_format: StandardConstructor::default(), promise: StandardConstructor::default(), @@ -731,6 +733,18 @@ impl StandardConstructors { &self.array_buffer } + /// Returns the `SharedArrayBuffer` constructor. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer-constructor + #[inline] + #[must_use] + pub const fn shared_array_buffer(&self) -> &StandardConstructor { + &self.shared_array_buffer + } + /// Returns the `DataView` constructor. /// /// More information: diff --git a/boa_engine/src/object/builtins/jsarraybuffer.rs b/boa_engine/src/object/builtins/jsarraybuffer.rs index 324d5d60c78..d0077dba036 100644 --- a/boa_engine/src/object/builtins/jsarraybuffer.rs +++ b/boa_engine/src/object/builtins/jsarraybuffer.rs @@ -18,6 +18,7 @@ pub struct JsArrayBuffer { inner: JsObject, } +// TODO: Add constructors that also take the `detach_key` as argument. impl JsArrayBuffer { /// Create a new array buffer with byte length. /// @@ -58,7 +59,7 @@ impl JsArrayBuffer { /// This uses the passed byte block as the internal storage, it does not clone it! /// /// The `byte_length` will be set to `byte_block.len()`. - /// + /// /// ``` /// # use boa_engine::{ /// # object::builtins::JsArrayBuffer, @@ -77,8 +78,6 @@ impl JsArrayBuffer { /// # } /// ``` pub fn from_byte_block(byte_block: Vec, context: &mut Context<'_>) -> JsResult { - let byte_length = byte_block.len(); - let constructor = context .intrinsics() .constructors() @@ -104,11 +103,7 @@ impl JsArrayBuffer { let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::array_buffer(ArrayBuffer { - array_buffer_data: Some(block), - array_buffer_byte_length: byte_length as u64, - array_buffer_detach_key: JsValue::Undefined, - }), + ObjectData::array_buffer(ArrayBuffer::from_data(block, JsValue::Undefined)), ); Ok(Self { inner: obj }) @@ -159,7 +154,10 @@ impl JsArrayBuffer { /// Take the inner `ArrayBuffer`'s `array_buffer_data` field and replace it with `None` /// - /// Note: This causes the pre-existing `JsArrayBuffer` to become detached. + /// # Note + /// + /// This tries to detach the pre-existing `JsArrayBuffer`, meaning the original detached + /// key is required. By default, the key is set to `undefined`. /// /// ``` /// # use boa_engine::{ @@ -174,24 +172,23 @@ impl JsArrayBuffer { /// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?; /// /// // Take the inner buffer - /// let internal_buffer = array_buffer.take()?; + /// let internal_buffer = array_buffer.take(&JsValue::undefined())?; /// /// assert_eq!(internal_buffer, (0..5).collect::>()); /// /// // Anymore interaction with the buffer will return an error - /// let detached_err = array_buffer.take(); + /// let detached_err = array_buffer.take(&JsValue::undefined()); /// assert!(detached_err.is_err()); /// # Ok(()) /// # } /// ``` #[inline] - pub fn take(&self) -> JsResult> { + pub fn take(&self, detach_key: &JsValue) -> JsResult> { self.inner .borrow_mut() .as_array_buffer_mut() .expect("inner must be an ArrayBuffer") - .array_buffer_data - .take() + .detach(detach_key)? .ok_or_else(|| { JsNativeError::typ() .with_message("ArrayBuffer is detached") diff --git a/boa_engine/src/object/builtins/jsdataview.rs b/boa_engine/src/object/builtins/jsdataview.rs index e665b822b65..e8f355e639d 100644 --- a/boa_engine/src/object/builtins/jsdataview.rs +++ b/boa_engine/src/object/builtins/jsdataview.rs @@ -55,13 +55,13 @@ impl JsDataView { let provided_offset = offset.unwrap_or(0_u64); // Check if buffer is detached. - if buffer.is_detached_buffer() { + if buffer.is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); }; - let array_buffer_length = buffer.array_buffer_byte_length(); + let array_buffer_length = buffer.len() as u64; if provided_offset > array_buffer_length { return Err(JsNativeError::range() diff --git a/boa_engine/src/object/builtins/jssharedarraybuffer.rs b/boa_engine/src/object/builtins/jssharedarraybuffer.rs new file mode 100644 index 00000000000..b78464d8f77 --- /dev/null +++ b/boa_engine/src/object/builtins/jssharedarraybuffer.rs @@ -0,0 +1,125 @@ +//! A Rust API wrapper for Boa's `SharedArrayBuffer` Builtin ECMAScript Object +use crate::{ + builtins::array_buffer::SharedArrayBuffer, + error::JsNativeError, + object::{JsObject, JsObjectType, ObjectData}, + value::TryFromJs, + Context, JsResult, JsValue, +}; +use boa_gc::{Finalize, Trace}; +use std::ops::Deref; + +/// `JsSharedArrayBuffer` provides a wrapper for Boa's implementation of the ECMAScript `ArrayBuffer` object +#[derive(Debug, Clone, Trace, Finalize)] +pub struct JsSharedArrayBuffer { + inner: JsObject, +} + +impl JsSharedArrayBuffer { + /// Creates a new [`JsSharedArrayBuffer`] with `byte_length` bytes of allocated space. + #[inline] + pub fn new(byte_length: usize, context: &mut Context<'_>) -> JsResult { + let inner = SharedArrayBuffer::allocate( + &context + .intrinsics() + .constructors() + .shared_array_buffer() + .constructor() + .into(), + byte_length as u64, + context, + )?; + + Ok(Self { inner }) + } + + /// Creates a [`JsSharedArrayBuffer`] from a shared raw buffer. + #[inline] + pub fn from_buffer(buffer: SharedArrayBuffer, context: &mut Context<'_>) -> Self { + let proto = context + .intrinsics() + .constructors() + .shared_array_buffer() + .prototype(); + + let inner = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + proto, + ObjectData::shared_array_buffer(buffer), + ); + + Self { inner } + } + + /// Creates a [`JsSharedArrayBuffer`] from a [`JsObject`], throwing a `TypeError` if the object + /// is not a shared array buffer. + /// + /// This does not clone the fields of the shared array buffer, it only does a shallow clone of + /// the object. + #[inline] + pub fn from_object(object: JsObject) -> JsResult { + if object.is_shared_array_buffer() { + Ok(Self { inner: object }) + } else { + Err(JsNativeError::typ() + .with_message("object is not an ArrayBuffer") + .into()) + } + } + + /// Returns the byte length of the array buffer. + #[inline] + #[must_use] + pub fn byte_length(&self) -> usize { + self.borrow() + .as_shared_array_buffer() + .expect("should be an array buffer") + .len() + } + + /// Gets the raw buffer of this `JsSharedArrayBuffer`. + #[inline] + #[must_use] + pub fn inner(&self) -> SharedArrayBuffer { + self.borrow() + .as_shared_array_buffer() + .expect("should be an array buffer") + .clone() + } +} + +impl From for JsObject { + #[inline] + fn from(o: JsSharedArrayBuffer) -> Self { + o.inner.clone() + } +} + +impl From for JsValue { + #[inline] + fn from(o: JsSharedArrayBuffer) -> Self { + o.inner.clone().into() + } +} + +impl Deref for JsSharedArrayBuffer { + type Target = JsObject; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl JsObjectType for JsSharedArrayBuffer {} + +impl TryFromJs for JsSharedArrayBuffer { + fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult { + match value { + JsValue::Object(o) => Self::from_object(o.clone()), + _ => Err(JsNativeError::typ() + .with_message("value is not a SharedArrayBuffer object") + .into()), + } + } +} diff --git a/boa_engine/src/object/builtins/jstypedarray.rs b/boa_engine/src/object/builtins/jstypedarray.rs index 3777b0d08ef..947192edeb4 100644 --- a/boa_engine/src/object/builtins/jstypedarray.rs +++ b/boa_engine/src/object/builtins/jstypedarray.rs @@ -1,6 +1,6 @@ //! Rust API wrappers for the `TypedArray` Builtin ECMAScript Objects use crate::{ - builtins::typed_array::TypedArray, + builtins::typed_array::BuiltinTypedArray, builtins::BuiltInConstructor, error::JsNativeError, object::{JsArrayBuffer, JsFunction, JsObject, JsObjectType}, @@ -41,7 +41,7 @@ impl JsTypedArray { /// Same a `array.length` in JavaScript. #[inline] pub fn length(&self, context: &mut Context<'_>) -> JsResult { - Ok(TypedArray::length(&self.inner, &[], context)? + Ok(BuiltinTypedArray::length(&self.inner, &[], context)? .as_number() .map(|x| x as usize) .expect("length should return a number")) @@ -58,13 +58,13 @@ impl JsTypedArray { where T: Into, { - TypedArray::at(&self.inner, &[index.into().into()], context) + BuiltinTypedArray::at(&self.inner, &[index.into().into()], context) } /// Returns `TypedArray.prototype.byteLength`. #[inline] pub fn byte_length(&self, context: &mut Context<'_>) -> JsResult { - Ok(TypedArray::byte_length(&self.inner, &[], context)? + Ok(BuiltinTypedArray::byte_length(&self.inner, &[], context)? .as_number() .map(|x| x as usize) .expect("byteLength should return a number")) @@ -73,7 +73,7 @@ impl JsTypedArray { /// Returns `TypedArray.prototype.byteOffset`. #[inline] pub fn byte_offset(&self, context: &mut Context<'_>) -> JsResult { - Ok(TypedArray::byte_offset(&self.inner, &[], context)? + Ok(BuiltinTypedArray::byte_offset(&self.inner, &[], context)? .as_number() .map(|x| x as usize) .expect("byteLength should return a number")) @@ -90,7 +90,7 @@ impl JsTypedArray { where T: Into, { - TypedArray::fill( + BuiltinTypedArray::fill( &self.inner, &[ value.into(), @@ -109,7 +109,7 @@ impl JsTypedArray { this_arg: Option, context: &mut Context<'_>, ) -> JsResult { - let result = TypedArray::every( + let result = BuiltinTypedArray::every( &self.inner, &[predicate.into(), this_arg.into_or_undefined()], context, @@ -128,7 +128,7 @@ impl JsTypedArray { this_arg: Option, context: &mut Context<'_>, ) -> JsResult { - let result = TypedArray::some( + let result = BuiltinTypedArray::some( &self.inner, &[callback.into(), this_arg.into_or_undefined()], context, @@ -146,7 +146,7 @@ impl JsTypedArray { compare_fn: Option, context: &mut Context<'_>, ) -> JsResult { - TypedArray::sort(&self.inner, &[compare_fn.into_or_undefined()], context)?; + BuiltinTypedArray::sort(&self.inner, &[compare_fn.into_or_undefined()], context)?; Ok(self.clone()) } @@ -159,7 +159,7 @@ impl JsTypedArray { this_arg: Option, context: &mut Context<'_>, ) -> JsResult { - let object = TypedArray::filter( + let object = BuiltinTypedArray::filter( &self.inner, &[callback.into(), this_arg.into_or_undefined()], context, @@ -176,7 +176,7 @@ impl JsTypedArray { this_arg: Option, context: &mut Context<'_>, ) -> JsResult { - let object = TypedArray::map( + let object = BuiltinTypedArray::map( &self.inner, &[callback.into(), this_arg.into_or_undefined()], context, @@ -193,7 +193,7 @@ impl JsTypedArray { initial_value: Option, context: &mut Context<'_>, ) -> JsResult { - TypedArray::reduce( + BuiltinTypedArray::reduce( &self.inner, &[callback.into(), initial_value.into_or_undefined()], context, @@ -208,7 +208,7 @@ impl JsTypedArray { initial_value: Option, context: &mut Context<'_>, ) -> JsResult { - TypedArray::reduceright( + BuiltinTypedArray::reduceright( &self.inner, &[callback.into(), initial_value.into_or_undefined()], context, @@ -218,7 +218,7 @@ impl JsTypedArray { /// Calls `TypedArray.prototype.reverse()`. #[inline] pub fn reverse(&self, context: &mut Context<'_>) -> JsResult { - TypedArray::reverse(&self.inner, &[], context)?; + BuiltinTypedArray::reverse(&self.inner, &[], context)?; Ok(self.clone()) } @@ -230,7 +230,7 @@ impl JsTypedArray { end: Option, context: &mut Context<'_>, ) -> JsResult { - let object = TypedArray::slice( + let object = BuiltinTypedArray::slice( &self.inner, &[start.into_or_undefined(), end.into_or_undefined()], context, @@ -247,7 +247,7 @@ impl JsTypedArray { this_arg: Option, context: &mut Context<'_>, ) -> JsResult { - TypedArray::find( + BuiltinTypedArray::find( &self.inner, &[predicate.into(), this_arg.into_or_undefined()], context, @@ -264,7 +264,7 @@ impl JsTypedArray { where T: Into, { - let index = TypedArray::index_of( + let index = BuiltinTypedArray::index_of( &self.inner, &[search_element.into(), from_index.into_or_undefined()], context, @@ -290,7 +290,7 @@ impl JsTypedArray { where T: Into, { - let index = TypedArray::last_index_of( + let index = BuiltinTypedArray::last_index_of( &self.inner, &[search_element.into(), from_index.into_or_undefined()], context, @@ -313,7 +313,7 @@ impl JsTypedArray { separator: Option, context: &mut Context<'_>, ) -> JsResult { - TypedArray::join(&self.inner, &[separator.into_or_undefined()], context).map(|x| { + BuiltinTypedArray::join(&self.inner, &[separator.into_or_undefined()], context).map(|x| { x.as_string() .cloned() .expect("TypedArray.prototype.join always returns string") diff --git a/boa_engine/src/object/builtins/mod.rs b/boa_engine/src/object/builtins/mod.rs index f0d61475e43..923f5ad9fb1 100644 --- a/boa_engine/src/object/builtins/mod.rs +++ b/boa_engine/src/object/builtins/mod.rs @@ -15,6 +15,7 @@ mod jsproxy; mod jsregexp; mod jsset; mod jsset_iterator; +mod jssharedarraybuffer; mod jstypedarray; pub use jsarray::*; @@ -30,4 +31,5 @@ pub use jsproxy::{JsProxy, JsProxyBuilder, JsRevocableProxy}; pub use jsregexp::JsRegExp; pub use jsset::*; pub use jsset_iterator::*; +pub use jssharedarraybuffer::*; pub use jstypedarray::*; diff --git a/boa_engine/src/object/internal_methods/integer_indexed.rs b/boa_engine/src/object/internal_methods/integer_indexed.rs index cd722b935d2..3a9f95fa622 100644 --- a/boa_engine/src/object/internal_methods/integer_indexed.rs +++ b/boa_engine/src/object/internal_methods/integer_indexed.rs @@ -1,9 +1,9 @@ +use std::sync::atomic; + use boa_macros::utf16; use crate::{ - builtins::{ - array_buffer::SharedMemoryOrder, typed_array::integer_indexed_object::ContentType, Number, - }, + builtins::Number, object::JsObject, property::{PropertyDescriptor, PropertyKey}, Context, JsResult, JsString, JsValue, @@ -380,38 +380,38 @@ fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option { } let obj = obj.borrow(); - let inner = obj - .as_typed_array() - .expect("Already checked for detached buffer"); - let buffer_obj = inner - .viewed_array_buffer() - .expect("Already checked for detached buffer"); - let buffer_obj_borrow = buffer_obj.borrow(); - let buffer = buffer_obj_borrow - .as_array_buffer() - .expect("Already checked for detached buffer"); + let inner = obj.as_typed_array().expect("Must be a typed array"); + let buffer = inner.viewed_array_buffer(); + let buffer = buffer.borrow(); + let buffer = buffer.as_buffer().expect("Must be a buffer"); + let buffer = buffer + .data() + .expect("already checked that it's not detached"); // 2. Let offset be O.[[ByteOffset]]. let offset = inner.byte_offset(); // 3. Let arrayTypeName be the String value of O.[[TypedArrayName]]. // 6. Let elementType be the Element Type value in Table 73 for arrayTypeName. - let elem_type = inner.typed_array_name(); + let elem_type = inner.kind(); // 4. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. let size = elem_type.element_size(); // 5. Let indexedPosition be (ℝ(index) × elementSize) + offset. - let indexed_position = (index as u64 * size) + offset; + let indexed_position = ((index as u64 * size) + offset) as usize; // 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered). - Some(buffer.get_value_from_buffer( - indexed_position, - elem_type, - true, - SharedMemoryOrder::Unordered, - None, - )) + + // SAFETY: The integer indexed object guarantees that the buffer is aligned. + // The call to `is_valid_integer_index` guarantees that the index is in-bounds. + let value = unsafe { + buffer + .subslice(indexed_position..) + .get_value(elem_type, atomic::Ordering::Relaxed) + }; + + Some(value.into()) } /// Abstract operation `IntegerIndexedElementSet ( O, index, value )`. @@ -431,48 +431,43 @@ pub(crate) fn integer_indexed_element_set( "integer indexed exotic method should only be callable from integer indexed objects", ); - let num_value = if inner.typed_array_name().content_type() == ContentType::BigInt { - // 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value). - value.to_bigint(context)?.into() - } else { - // 2. Otherwise, let numValue be ? ToNumber(value). - value.to_number(context)?.into() - }; + // 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value). + // 2. Otherwise, let numValue be ? ToNumber(value). + let value = inner.kind().get_element(value, context)?; + + if !is_valid_integer_index(obj, index) { + return Ok(()); + } // 3. If ! IsValidIntegerIndex(O, index) is true, then - if is_valid_integer_index(obj, index) { - // a. Let offset be O.[[ByteOffset]]. - let offset = inner.byte_offset(); + // a. Let offset be O.[[ByteOffset]]. + let offset = inner.byte_offset(); + + // b. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + // e. Let elementType be the Element Type value in Table 73 for arrayTypeName. + let elem_type = inner.kind(); - // b. Let arrayTypeName be the String value of O.[[TypedArrayName]]. - // e. Let elementType be the Element Type value in Table 73 for arrayTypeName. - let elem_type = inner.typed_array_name(); + // c. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. + let size = elem_type.element_size(); - // c. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. - let size = elem_type.element_size(); + // d. Let indexedPosition be (ℝ(index) × elementSize) + offset. + let indexed_position = ((index as u64 * size) + offset) as usize; - // d. Let indexedPosition be (ℝ(index) × elementSize) + offset. - let indexed_position = (index as u64 * size) + offset; + let buffer = inner.viewed_array_buffer(); + let mut buffer = buffer.borrow_mut(); + let mut buffer = buffer.as_buffer_mut().expect("Must be a buffer"); + let mut buffer = buffer + .data_mut() + .expect("already checked that it's not detached"); - let buffer_obj = inner - .viewed_array_buffer() - .expect("Already checked for detached buffer"); - let mut buffer_obj_borrow = buffer_obj.borrow_mut(); - let buffer = buffer_obj_borrow - .as_array_buffer_mut() - .expect("Already checked for detached buffer"); + // f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered). - // f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered). + // SAFETY: The integer indexed object guarantees that the buffer is aligned. + // The call to `is_valid_integer_index` guarantees that the index is in-bounds. + unsafe { buffer - .set_value_in_buffer( - indexed_position, - elem_type, - &num_value, - SharedMemoryOrder::Unordered, - None, - context, - ) - .expect("SetValueInBuffer cannot fail here"); + .subslice_mut(indexed_position..) + .set_value(value, atomic::Ordering::Relaxed); } // 4. Return NormalCompletion(undefined). diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 756850c3212..600460675da 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -424,6 +424,30 @@ impl JsObject { self.borrow().is_array_buffer() } + /// Checks if it's a `SharedArrayBuffer` object. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + #[inline] + #[must_use] + #[track_caller] + pub fn is_shared_array_buffer(&self) -> bool { + self.borrow().as_shared_array_buffer().is_some() + } + + /// Checks if it's an `ArrayBuffer` or `SharedArrayBuffer` object. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + #[inline] + #[must_use] + #[track_caller] + pub fn is_buffer(&self) -> bool { + self.borrow().as_buffer().is_some() + } + /// Checks if it is a `Map` object. /// /// # Panics diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index d9223d5603d..dea8e3542c4 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -46,7 +46,7 @@ use crate::builtins::temporal::{ use crate::{ builtins::{ array::ArrayIterator, - array_buffer::ArrayBuffer, + array_buffer::{ArrayBuffer, BufferRef, BufferRefMut, SharedArrayBuffer}, async_generator::AsyncGenerator, error::ErrorKind, function::arguments::Arguments, @@ -61,7 +61,7 @@ use crate::{ set::ordered_set::OrderedSet, set::SetIterator, string::StringIterator, - typed_array::{integer_indexed_object::IntegerIndexed, TypedArrayKind}, + typed_array::{IntegerIndexed, TypedArrayKind}, DataView, Date, Promise, RegExp, }, context::intrinsics::StandardConstructor, @@ -316,6 +316,9 @@ pub enum ObjectKind { /// The `ArrayBuffer` object kind. ArrayBuffer(ArrayBuffer), + /// The `SharedArrayBuffer` object kind. + SharedArrayBuffer(SharedArrayBuffer), + /// The `Map` object kind. Map(OrderedMap), @@ -547,7 +550,8 @@ unsafe impl Trace for ObjectKind { | Self::Ordinary | Self::Global | Self::Number(_) - | Self::Symbol(_) => {} + | Self::Symbol(_) + | Self::SharedArrayBuffer(_) => {} #[cfg(feature = "temporal")] Self::Instant(_) | Self::PlainDateTime(_) @@ -626,6 +630,15 @@ impl ObjectData { } } + /// Create the `SharedArrayBuffer` object data + #[must_use] + pub fn shared_array_buffer(shared_array_buffer: SharedArrayBuffer) -> Self { + Self { + kind: ObjectKind::SharedArrayBuffer(shared_array_buffer), + internal_methods: &ORDINARY_INTERNAL_METHODS, + } + } + /// Create the `Map` object data #[must_use] pub fn map(map: OrderedMap) -> Self { @@ -1123,6 +1136,7 @@ impl Debug for ObjectKind { Self::Array => "Array", Self::ArrayIterator(_) => "ArrayIterator", Self::ArrayBuffer(_) => "ArrayBuffer", + Self::SharedArrayBuffer(_) => "SharedArrayBuffer", Self::ForInIterator(_) => "ForInIterator", Self::OrdinaryFunction(_) => "Function", Self::BoundFunction(_) => "BoundFunction", @@ -1296,6 +1310,16 @@ impl Object { } } + /// Gets the shared array buffer data if the object is a `SharedArrayBuffer`. + #[inline] + #[must_use] + pub const fn as_shared_array_buffer(&self) -> Option<&SharedArrayBuffer> { + match &self.kind { + ObjectKind::SharedArrayBuffer(buffer) => Some(buffer), + _ => None, + } + } + /// Gets the mutable array buffer data if the object is a `ArrayBuffer`. #[inline] pub fn as_array_buffer_mut(&mut self) -> Option<&mut ArrayBuffer> { @@ -1305,6 +1329,27 @@ impl Object { } } + /// Gets the buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. + #[inline] + #[must_use] + pub(crate) const fn as_buffer(&self) -> Option> { + match &self.kind { + ObjectKind::ArrayBuffer(buffer) => Some(BufferRef::Common(buffer)), + ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRef::Shared(buffer)), + _ => None, + } + } + + /// Gets the mutable buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. + #[inline] + pub(crate) fn as_buffer_mut(&mut self) -> Option> { + match &mut self.kind { + ObjectKind::ArrayBuffer(buffer) => Some(BufferRefMut::Common(buffer)), + ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRefMut::Shared(buffer)), + _ => None, + } + } + /// Checks if the object is a `ArrayIterator` object. #[inline] #[must_use] @@ -1693,7 +1738,7 @@ impl Object { #[must_use] pub const fn is_typed_uint8_array(&self) -> bool { if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.typed_array_name(), TypedArrayKind::Uint8) + matches!(int.kind(), TypedArrayKind::Uint8) } else { false } @@ -1704,7 +1749,7 @@ impl Object { #[must_use] pub const fn is_typed_int8_array(&self) -> bool { if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.typed_array_name(), TypedArrayKind::Int8) + matches!(int.kind(), TypedArrayKind::Int8) } else { false } @@ -1715,7 +1760,7 @@ impl Object { #[must_use] pub const fn is_typed_uint16_array(&self) -> bool { if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.typed_array_name(), TypedArrayKind::Uint16) + matches!(int.kind(), TypedArrayKind::Uint16) } else { false } @@ -1726,7 +1771,7 @@ impl Object { #[must_use] pub const fn is_typed_int16_array(&self) -> bool { if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.typed_array_name(), TypedArrayKind::Int16) + matches!(int.kind(), TypedArrayKind::Int16) } else { false } @@ -1737,7 +1782,7 @@ impl Object { #[must_use] pub const fn is_typed_uint32_array(&self) -> bool { if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.typed_array_name(), TypedArrayKind::Uint32) + matches!(int.kind(), TypedArrayKind::Uint32) } else { false } @@ -1748,7 +1793,7 @@ impl Object { #[must_use] pub const fn is_typed_int32_array(&self) -> bool { if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.typed_array_name(), TypedArrayKind::Int32) + matches!(int.kind(), TypedArrayKind::Int32) } else { false } @@ -1759,7 +1804,7 @@ impl Object { #[must_use] pub const fn is_typed_float32_array(&self) -> bool { if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.typed_array_name(), TypedArrayKind::Float32) + matches!(int.kind(), TypedArrayKind::Float32) } else { false } @@ -1770,7 +1815,7 @@ impl Object { #[must_use] pub const fn is_typed_float64_array(&self) -> bool { if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.typed_array_name(), TypedArrayKind::Float64) + matches!(int.kind(), TypedArrayKind::Float64) } else { false } diff --git a/boa_engine/src/string/common.rs b/boa_engine/src/string/common.rs index 8a81ed910bc..45a8d9e4a6d 100644 --- a/boa_engine/src/string/common.rs +++ b/boa_engine/src/string/common.rs @@ -110,6 +110,7 @@ impl StaticJsStrings { // Builtins (ARRAY, "Array"), (ARRAY_BUFFER, "ArrayBuffer"), + (SHARED_ARRAY_BUFFER, "SharedArrayBuffer"), (ASYNC_FUNCTION, "AsyncFunction"), (ASYNC_GENERATOR, "AsyncGenerator"), (ASYNC_GENERATOR_FUNCTION, "AsyncGeneratorFunction"), @@ -250,6 +251,7 @@ const RAW_STATICS: &[&[u16]] = &[ // Well known builtins utf16!("Array"), utf16!("ArrayBuffer"), + utf16!("SharedArrayBuffer"), utf16!("AsyncFunction"), utf16!("AsyncGenerator"), utf16!("AsyncGeneratorFunction"), diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 035146e6c29..57c42bcb51b 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -29,7 +29,7 @@ use boa_gc::{custom_trace, Finalize, Trace}; use boa_profiler::Profiler; use num_bigint::BigInt; use num_integer::Integer; -use num_traits::Zero; +use num_traits::{ToPrimitive, Zero}; use once_cell::sync::Lazy; use std::{ collections::HashSet, @@ -757,7 +757,7 @@ impl JsValue { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-tobigint64 - pub fn to_big_int64(&self, context: &mut Context<'_>) -> JsResult { + pub fn to_big_int64(&self, context: &mut Context<'_>) -> JsResult { // 1. Let n be ? ToBigInt(argument). let n = self.to_bigint(context)?; @@ -765,11 +765,15 @@ impl JsValue { let int64_bit = n.as_inner().mod_floor(&TWO_E_64); // 3. If int64bit ≥ 2^63, return ℤ(int64bit - 2^64); otherwise return ℤ(int64bit). - if int64_bit >= *TWO_E_63 { - Ok(int64_bit.sub(&*TWO_E_64)) + let value = if int64_bit >= *TWO_E_63 { + int64_bit.sub(&*TWO_E_64) } else { - Ok(int64_bit) - } + int64_bit + }; + + Ok(value + .to_i64() + .expect("should be within the range of `i64` by the mod operation")) } /// `7.1.16 ToBigUint64 ( argument )` @@ -778,16 +782,16 @@ impl JsValue { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-tobiguint64 - pub fn to_big_uint64(&self, context: &mut Context<'_>) -> JsResult { - let two_e_64: u128 = 0x1_0000_0000_0000_0000; - let two_e_64 = BigInt::from(two_e_64); - + pub fn to_big_uint64(&self, context: &mut Context<'_>) -> JsResult { // 1. Let n be ? ToBigInt(argument). let n = self.to_bigint(context)?; // 2. Let int64bit be ℝ(n) modulo 2^64. // 3. Return ℤ(int64bit). - Ok(n.as_inner().mod_floor(&two_e_64)) + Ok(n.as_inner() + .mod_floor(&TWO_E_64) + .to_u64() + .expect("should be within the range of `u64` by the mod operation")) } /// Converts a value to a non-negative integer if it is a valid integer index value. diff --git a/boa_examples/src/bin/jsarraybuffer.rs b/boa_examples/src/bin/jsarraybuffer.rs index 221e90ef256..6325e7f52ed 100644 --- a/boa_examples/src/bin/jsarraybuffer.rs +++ b/boa_examples/src/bin/jsarraybuffer.rs @@ -63,10 +63,10 @@ fn main() -> JsResult<()> { let data_block: Vec = (0..5).collect(); let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?; - let internal_buffer = array_buffer.take()?; + let internal_buffer = array_buffer.take(&JsValue::undefined())?; assert_eq!(internal_buffer, (0..5).collect::>()); - let detached_err = array_buffer.take(); + let detached_err = array_buffer.take(&JsValue::undefined()); assert!(detached_err.is_err()); Ok(()) diff --git a/boa_tester/src/exec/js262.rs b/boa_tester/src/exec/js262.rs index cec67874476..b66d9ed1661 100644 --- a/boa_tester/src/exec/js262.rs +++ b/boa_tester/src/exec/js262.rs @@ -72,24 +72,17 @@ fn detach_array_buffer(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> Js .and_then(JsValue::as_object) .ok_or_else(type_err)?; let mut array_buffer = array_buffer.borrow_mut(); + + // 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false. let array_buffer = array_buffer.as_array_buffer_mut().ok_or_else(type_err)?; - // 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false. TODO // 2. If key is not present, set key to undefined. let key = args.get_or_undefined(1); // 3. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception. - if !JsValue::same_value(&array_buffer.array_buffer_detach_key, key) { - return Err(JsNativeError::typ() - .with_message("Cannot detach array buffer with different key") - .into()); - } - // 4. Set arrayBuffer.[[ArrayBufferData]] to null. - array_buffer.array_buffer_data = None; - // 5. Set arrayBuffer.[[ArrayBufferByteLength]] to 0. - array_buffer.array_buffer_byte_length = 0; + array_buffer.detach(key)?; // 6. Return NormalCompletion(null). Ok(JsValue::null()) diff --git a/test262_config.toml b/test262_config.toml index a4ac92e8bb9..aa9fda32e7f 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -7,7 +7,6 @@ flags = [] features = [ ### Unimplemented features: - "SharedArrayBuffer", "FinalizationRegistry", "IsHTMLDDA", "Atomics", From d73cf5f7a4680cbd488e8d80c8fea0832fd6214b Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Fri, 13 Oct 2023 23:27:27 -0600 Subject: [PATCH 2/4] Add small documentation --- boa_engine/src/builtins/array_buffer/mod.rs | 3 +++ boa_engine/src/builtins/array_buffer/shared.rs | 11 ++++++++++- boa_engine/src/builtins/array_buffer/utils.rs | 1 - 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index d2335f9e042..2b56efc45b3 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -7,6 +7,9 @@ //! [spec]: https://tc39.es/ecma262/#sec-arraybuffer-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer +#![deny(unsafe_op_in_unsafe_fn)] +#![deny(clippy::undocumented_unsafe_blocks)] + pub(crate) mod shared; pub(crate) mod utils; diff --git a/boa_engine/src/builtins/array_buffer/shared.rs b/boa_engine/src/builtins/array_buffer/shared.rs index eb2d97767ed..68eca904560 100644 --- a/boa_engine/src/builtins/array_buffer/shared.rs +++ b/boa_engine/src/builtins/array_buffer/shared.rs @@ -338,6 +338,11 @@ pub(crate) fn create_shared_byte_data_block( // a. Append WriteSharedMemory { [[Order]]: init, [[NoTear]]: true, [[Block]]: db, // [[ByteIndex]]: i, [[ElementSize]]: 1, [[Payload]]: zero } to eventsRecord.[[EventList]]. // 6. Return db. + + // Initializing a boxed slice of atomics is almost impossible using safe code. + // This replaces that with a simple `alloc` and some casts to convert the allocation + // to `Box<[AtomicU8]>`. + let layout = alloc::Layout::array::(size).map_err(|e| { JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}")) })?; @@ -358,7 +363,11 @@ pub(crate) fn create_shared_byte_data_block( // - `buffer` is a valid pointer by the null check above. let buffer = unsafe { Box::from_raw(std::slice::from_raw_parts_mut(ptr, size)) }; - // Just for good measure. + // Just for good measure, since our implementation depends on having a pointer aligned + // to the alignment of `u64`. + // This could be replaced with a custom `Box` implementation, but most architectures + // already align pointers to 8 bytes, so it's a lot of work for such a small + // compatibility improvement. assert_eq!(buffer.as_ptr().addr() % std::mem::align_of::(), 0); // 3. Return db. diff --git a/boa_engine/src/builtins/array_buffer/utils.rs b/boa_engine/src/builtins/array_buffer/utils.rs index dacc1126db0..2da2be482bc 100644 --- a/boa_engine/src/builtins/array_buffer/utils.rs +++ b/boa_engine/src/builtins/array_buffer/utils.rs @@ -1,4 +1,3 @@ -#![deny(unsafe_op_in_unsafe_fn)] #![allow(unstable_name_collisions)] use std::{ptr, slice::SliceIndex, sync::atomic}; From 7fc444cb2f99f44baaaec51cff2053ad5723a18a Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Fri, 13 Oct 2023 23:50:17 -0600 Subject: [PATCH 3/4] Fix docs --- .../src/builtins/array_buffer/shared.rs | 2 +- boa_engine/src/builtins/temporal/fields.rs | 1 - .../typed_array/integer_indexed_object.rs | 1 - .../src/object/builtins/jsarraybuffer.rs | 21 +++++++++++-------- .../src/object/internal_methods/function.rs | 1 - boa_examples/src/bin/jsarraybuffer.rs | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/boa_engine/src/builtins/array_buffer/shared.rs b/boa_engine/src/builtins/array_buffer/shared.rs index 68eca904560..e013d3e5e02 100644 --- a/boa_engine/src/builtins/array_buffer/shared.rs +++ b/boa_engine/src/builtins/array_buffer/shared.rs @@ -352,7 +352,7 @@ pub(crate) fn create_shared_byte_data_block( if ptr.is_null() { return Err(JsNativeError::range() - .with_message("memory allocation failed to allocate buffer") + .with_message("memory allocator failed to allocate buffer") .into()); } diff --git a/boa_engine/src/builtins/temporal/fields.rs b/boa_engine/src/builtins/temporal/fields.rs index d0a860d5eb7..39c60911ab4 100644 --- a/boa_engine/src/builtins/temporal/fields.rs +++ b/boa_engine/src/builtins/temporal/fields.rs @@ -55,7 +55,6 @@ bitflags! { /// | "era" | `ToPrimitiveAndRequireString` | undefined | /// | "eraYear" | `ToIntegerWithTruncation` | undefined | /// | "timeZone" | | undefined | -/// #[derive(Debug)] pub(crate) struct TemporalFields { bit_map: FieldMap, diff --git a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs index 301d2a90f1c..033af0fdc14 100644 --- a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs +++ b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs @@ -1,5 +1,4 @@ //! This module implements the `Integer-Indexed` exotic object. -//! use crate::object::JsObject; use boa_gc::{Finalize, Trace}; diff --git a/boa_engine/src/object/builtins/jsarraybuffer.rs b/boa_engine/src/object/builtins/jsarraybuffer.rs index d0077dba036..c364741e8f6 100644 --- a/boa_engine/src/object/builtins/jsarraybuffer.rs +++ b/boa_engine/src/object/builtins/jsarraybuffer.rs @@ -25,7 +25,7 @@ impl JsArrayBuffer { /// ``` /// # use boa_engine::{ /// # object::builtins::JsArrayBuffer, - /// # Context, JsResult + /// # Context, JsResult, JsValue /// # }; /// # fn main() -> JsResult<()> { /// # // Initialize context @@ -33,7 +33,7 @@ impl JsArrayBuffer { /// // Creates a blank array buffer of n bytes /// let array_buffer = JsArrayBuffer::new(4, context)?; /// - /// assert_eq!(array_buffer.take()?, vec![0_u8; 4]); + /// assert_eq!(array_buffer.detach(&JsValue::undefined())?, vec![0_u8; 4]); /// /// # Ok(()) /// # } @@ -63,7 +63,7 @@ impl JsArrayBuffer { /// ``` /// # use boa_engine::{ /// # object::builtins::JsArrayBuffer, - /// # Context, JsResult, + /// # Context, JsResult, JsValue, /// # }; /// # fn main() -> JsResult<()> { /// # // Initialize context @@ -73,7 +73,10 @@ impl JsArrayBuffer { /// let data_block: Vec = (0..5).collect(); /// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?; /// - /// assert_eq!(array_buffer.take()?, (0..5).collect::>()); + /// assert_eq!( + /// array_buffer.detach(&JsValue::undefined())?, + /// (0..5).collect::>() + /// ); /// # Ok(()) /// # } /// ``` @@ -162,7 +165,7 @@ impl JsArrayBuffer { /// ``` /// # use boa_engine::{ /// # object::builtins::JsArrayBuffer, - /// # Context, JsResult, + /// # Context, JsResult, JsValue /// # }; /// # fn main() -> JsResult<()> { /// # // Initialize context @@ -172,18 +175,18 @@ impl JsArrayBuffer { /// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?; /// /// // Take the inner buffer - /// let internal_buffer = array_buffer.take(&JsValue::undefined())?; + /// let internal_buffer = array_buffer.detach(&JsValue::undefined())?; /// /// assert_eq!(internal_buffer, (0..5).collect::>()); /// /// // Anymore interaction with the buffer will return an error - /// let detached_err = array_buffer.take(&JsValue::undefined()); + /// let detached_err = array_buffer.detach(&JsValue::undefined()); /// assert!(detached_err.is_err()); /// # Ok(()) /// # } /// ``` #[inline] - pub fn take(&self, detach_key: &JsValue) -> JsResult> { + pub fn detach(&self, detach_key: &JsValue) -> JsResult> { self.inner .borrow_mut() .as_array_buffer_mut() @@ -191,7 +194,7 @@ impl JsArrayBuffer { .detach(detach_key)? .ok_or_else(|| { JsNativeError::typ() - .with_message("ArrayBuffer is detached") + .with_message("ArrayBuffer was already detached") .into() }) } diff --git a/boa_engine/src/object/internal_methods/function.rs b/boa_engine/src/object/internal_methods/function.rs index 92ceb2afdec..5bf30535b5f 100644 --- a/boa_engine/src/object/internal_methods/function.rs +++ b/boa_engine/src/object/internal_methods/function.rs @@ -80,7 +80,6 @@ pub(crate) static NATIVE_CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = /// # Panics /// /// Panics if the object is currently mutably borrowed. -/// // #[track_caller] pub(crate) fn native_function_call( diff --git a/boa_examples/src/bin/jsarraybuffer.rs b/boa_examples/src/bin/jsarraybuffer.rs index 6325e7f52ed..26df61bac8c 100644 --- a/boa_examples/src/bin/jsarraybuffer.rs +++ b/boa_examples/src/bin/jsarraybuffer.rs @@ -63,10 +63,10 @@ fn main() -> JsResult<()> { let data_block: Vec = (0..5).collect(); let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?; - let internal_buffer = array_buffer.take(&JsValue::undefined())?; + let internal_buffer = array_buffer.detach(&JsValue::undefined())?; assert_eq!(internal_buffer, (0..5).collect::>()); - let detached_err = array_buffer.take(&JsValue::undefined()); + let detached_err = array_buffer.detach(&JsValue::undefined()); assert!(detached_err.is_err()); Ok(()) From 671d884c611f68cba6633ec6a3de3729bd63b838 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sat, 14 Oct 2023 23:55:27 -0600 Subject: [PATCH 4/4] Apply review --- boa_engine/src/builtins/array_buffer/mod.rs | 16 +-- .../src/builtins/array_buffer/shared.rs | 3 +- boa_engine/src/builtins/array_buffer/utils.rs | 128 ++++++++++-------- boa_engine/src/builtins/dataview/mod.rs | 4 +- .../src/builtins/typed_array/builtin.rs | 4 +- .../src/builtins/typed_array/element.rs | 34 ++--- boa_engine/src/object/mod.rs | 8 +- 7 files changed, 110 insertions(+), 87 deletions(-) diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 2b56efc45b3..ede5583529c 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -40,15 +40,15 @@ use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; #[derive(Debug, Clone, Copy)] pub(crate) enum BufferRef<'a> { - Common(&'a ArrayBuffer), - Shared(&'a SharedArrayBuffer), + Buffer(&'a ArrayBuffer), + SharedBuffer(&'a SharedArrayBuffer), } impl BufferRef<'_> { pub(crate) fn data(&self) -> Option> { match self { - Self::Common(buf) => buf.data().map(SliceRef::Common), - Self::Shared(buf) => Some(SliceRef::Atomic(buf.data())), + Self::Buffer(buf) => buf.data().map(SliceRef::Slice), + Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.data())), } } @@ -59,15 +59,15 @@ impl BufferRef<'_> { #[derive(Debug)] pub(crate) enum BufferRefMut<'a> { - Common(&'a mut ArrayBuffer), - Shared(&'a mut SharedArrayBuffer), + Buffer(&'a mut ArrayBuffer), + SharedBuffer(&'a mut SharedArrayBuffer), } impl BufferRefMut<'_> { pub(crate) fn data_mut(&mut self) -> Option> { match self { - Self::Common(buf) => buf.data_mut().map(SliceRefMut::Common), - Self::Shared(buf) => Some(SliceRefMut::Atomic(buf.data())), + Self::Buffer(buf) => buf.data_mut().map(SliceRefMut::Slice), + Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice(buf.data())), } } } diff --git a/boa_engine/src/builtins/array_buffer/shared.rs b/boa_engine/src/builtins/array_buffer/shared.rs index e013d3e5e02..dd44c175e0a 100644 --- a/boa_engine/src/builtins/array_buffer/shared.rs +++ b/boa_engine/src/builtins/array_buffer/shared.rs @@ -195,7 +195,6 @@ impl SharedArrayBuffer { )?; // 14. Let ctor be ? SpeciesConstructor(O, %SharedArrayBuffer%). - let ctor = obj.species_constructor(StandardConstructors::shared_array_buffer, context)?; // 15. Let new be ? Construct(ctor, « 𝔽(newLen) »). @@ -226,11 +225,11 @@ impl SharedArrayBuffer { // 20. Let fromBuf be O.[[ArrayBufferData]]. let from_buf = buf.data(); + // 21. Let toBuf be new.[[ArrayBufferData]]. let to_buf = new_buf.data(); // 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). - let first = first as usize; let new_len = new_len as usize; diff --git a/boa_engine/src/builtins/array_buffer/utils.rs b/boa_engine/src/builtins/array_buffer/utils.rs index 2da2be482bc..6190ab6d38a 100644 --- a/boa_engine/src/builtins/array_buffer/utils.rs +++ b/boa_engine/src/builtins/array_buffer/utils.rs @@ -14,36 +14,37 @@ use super::ArrayBuffer; #[derive(Debug, Clone, Copy)] pub(crate) enum SliceRef<'a> { - Common(&'a [u8]), - Atomic(&'a [AtomicU8]), + Slice(&'a [u8]), + AtomicSlice(&'a [AtomicU8]), } impl SliceRef<'_> { + /// Gets the byte length of this `SliceRef`. pub(crate) fn len(&self) -> usize { match self { - Self::Common(buf) => buf.len(), - Self::Atomic(buf) => buf.len(), + Self::Slice(buf) => buf.len(), + Self::AtomicSlice(buf) => buf.len(), } } + /// Gets a subslice of this `SliceRef`. pub(crate) fn subslice(&self, index: I) -> SliceRef<'_> where I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>, { match self { - Self::Common(buffer) => { - SliceRef::Common(buffer.get(index).expect("index out of bounds")) - } - Self::Atomic(buffer) => { - SliceRef::Atomic(buffer.get(index).expect("index out of bounds")) + Self::Slice(buffer) => SliceRef::Slice(buffer.get(index).expect("index out of bounds")), + Self::AtomicSlice(buffer) => { + SliceRef::AtomicSlice(buffer.get(index).expect("index out of bounds")) } } } + /// Gets the starting address of this `SliceRef`. pub(crate) fn addr(&self) -> usize { match self { - Self::Common(buf) => buf.as_ptr().addr(), - Self::Atomic(buf) => buf.as_ptr().addr(), + Self::Slice(buf) => buf.as_ptr().addr(), + Self::AtomicSlice(buf) => buf.as_ptr().addr(), } } @@ -69,7 +70,7 @@ impl SliceRef<'_> { // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. if cfg!(debug_assertions) { assert!(buffer.len() >= std::mem::size_of::()); - assert_eq!(buffer.len() % std::mem::align_of::(), 0); + assert_eq!(buffer.addr() % std::mem::align_of::(), 0); } // 3. Let block be arrayBuffer.[[ArrayBufferData]]. @@ -152,7 +153,7 @@ impl SliceRef<'_> { // SAFETY: Both buffers are of the same length, `buffer.len()`, which makes this operation // safe. - unsafe { memcpy(*self, SliceRefMut::Common(target_block), self.len()) } + unsafe { memcpy(*self, SliceRefMut::Slice(target_block), self.len()) } } // 6. Return targetBuffer. @@ -162,48 +163,51 @@ impl SliceRef<'_> { impl<'a> From<&'a [u8]> for SliceRef<'a> { fn from(value: &'a [u8]) -> Self { - Self::Common(value) + Self::Slice(value) } } impl<'a> From<&'a [AtomicU8]> for SliceRef<'a> { fn from(value: &'a [AtomicU8]) -> Self { - Self::Atomic(value) + Self::AtomicSlice(value) } } #[derive(Debug)] pub(crate) enum SliceRefMut<'a> { - Common(&'a mut [u8]), - Atomic(&'a [AtomicU8]), + Slice(&'a mut [u8]), + AtomicSlice(&'a [AtomicU8]), } impl SliceRefMut<'_> { + /// Gets the byte length of this `SliceRefMut`. pub(crate) fn len(&self) -> usize { match self { - Self::Common(buf) => buf.len(), - Self::Atomic(buf) => buf.len(), + Self::Slice(buf) => buf.len(), + Self::AtomicSlice(buf) => buf.len(), } } + /// Gets a mutable subslice of this `SliceRefMut`. pub(crate) fn subslice_mut(&mut self, index: I) -> SliceRefMut<'_> where I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>, { match self { - Self::Common(buffer) => { - SliceRefMut::Common(buffer.get_mut(index).expect("index out of bounds")) + Self::Slice(buffer) => { + SliceRefMut::Slice(buffer.get_mut(index).expect("index out of bounds")) } - Self::Atomic(buffer) => { - SliceRefMut::Atomic(buffer.get(index).expect("index out of bounds")) + Self::AtomicSlice(buffer) => { + SliceRefMut::AtomicSlice(buffer.get(index).expect("index out of bounds")) } } } + /// Gets the starting address of this `SliceRefMut`. pub(crate) fn addr(&self) -> usize { match self { - Self::Common(buf) => buf.as_ptr().addr(), - Self::Atomic(buf) => buf.as_ptr().addr(), + Self::Slice(buf) => buf.as_ptr().addr(), + Self::AtomicSlice(buf) => buf.as_ptr().addr(), } } @@ -225,7 +229,7 @@ impl SliceRefMut<'_> { /// /// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer pub(crate) unsafe fn set_value(&mut self, value: TypedArrayElement, order: atomic::Ordering) { - pub(crate) unsafe fn write_to_buffer( + unsafe fn write_elem( buffer: SliceRefMut<'_>, value: T, order: atomic::Ordering, @@ -237,7 +241,7 @@ impl SliceRefMut<'_> { // 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number. if cfg!(debug_assertions) { assert!(buffer.len() >= std::mem::size_of::()); - assert_eq!(buffer.len() % std::mem::align_of::(), 0); + assert_eq!(buffer.addr() % std::mem::align_of::(), 0); } // 4. Let block be arrayBuffer.[[ArrayBufferData]]. @@ -261,24 +265,24 @@ impl SliceRefMut<'_> { // Have to rebind in order to remove the outer `&mut` ref. let buffer = match self { - SliceRefMut::Common(buf) => SliceRefMut::Common(buf), - SliceRefMut::Atomic(buf) => SliceRefMut::Atomic(buf), + SliceRefMut::Slice(buf) => SliceRefMut::Slice(buf), + SliceRefMut::AtomicSlice(buf) => SliceRefMut::AtomicSlice(buf), }; // SAFETY: The invariants of this operation are ensured by the caller. unsafe { match value { - TypedArrayElement::Int8(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::Uint8(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::Uint8Clamped(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::Int16(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::Uint16(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::Int32(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::Uint32(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::BigInt64(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::BigUint64(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::Float32(e) => write_to_buffer(buffer, e, order), - TypedArrayElement::Float64(e) => write_to_buffer(buffer, e, order), + TypedArrayElement::Int8(e) => write_elem(buffer, e, order), + TypedArrayElement::Uint8(e) => write_elem(buffer, e, order), + TypedArrayElement::Uint8Clamped(e) => write_elem(buffer, e, order), + TypedArrayElement::Int16(e) => write_elem(buffer, e, order), + TypedArrayElement::Uint16(e) => write_elem(buffer, e, order), + TypedArrayElement::Int32(e) => write_elem(buffer, e, order), + TypedArrayElement::Uint32(e) => write_elem(buffer, e, order), + TypedArrayElement::BigInt64(e) => write_elem(buffer, e, order), + TypedArrayElement::BigUint64(e) => write_elem(buffer, e, order), + TypedArrayElement::Float32(e) => write_elem(buffer, e, order), + TypedArrayElement::Float64(e) => write_elem(buffer, e, order), } } } @@ -286,17 +290,22 @@ impl SliceRefMut<'_> { impl<'a> From<&'a mut [u8]> for SliceRefMut<'a> { fn from(value: &'a mut [u8]) -> Self { - Self::Common(value) + Self::Slice(value) } } impl<'a> From<&'a [AtomicU8]> for SliceRefMut<'a> { fn from(value: &'a [AtomicU8]) -> Self { - Self::Atomic(value) + Self::AtomicSlice(value) } } /// Copies `count` bytes from `src` into `dest` using atomic relaxed loads and stores. +/// +/// # Safety +/// +/// - Both `src` and `dest` must have at least `count` bytes to read and write, +/// respectively. pub(super) unsafe fn copy_shared_to_shared(src: &[AtomicU8], dest: &[AtomicU8], count: usize) { // TODO: this could be optimized with batches of writes using `u32/u64` stores instead. for i in 0..count { @@ -310,6 +319,12 @@ pub(super) unsafe fn copy_shared_to_shared(src: &[AtomicU8], dest: &[AtomicU8], } } +/// Copies `count` bytes backwards from `src` into `dest` using atomic relaxed loads and stores. +/// +/// # Safety +/// +/// - Both `src` and `dest` must have at least `count` bytes to read and write, +/// respectively. unsafe fn copy_shared_to_shared_backwards(src: &[AtomicU8], dest: &[AtomicU8], count: usize) { for i in (0..count).rev() { // SAFETY: The invariants of this operation are ensured by the caller of the function. @@ -327,35 +342,42 @@ unsafe fn copy_shared_to_shared_backwards(src: &[AtomicU8], dest: &[AtomicU8], c /// /// # Safety /// -/// - Both `src.len()` and `dest.len()` must have at least `count` bytes to read and write, -/// respectively. +/// - Both `src` and `dest` must have at least `count` bytes to read and write, respectively. +/// - The region of memory referenced by `src` must not overlap with the region of memory +/// referenced by `dest`. This is guaranteed if either of them are slices +/// (you cannot borrow and mutably borrow a slice at the same time), but cannot be guaranteed +/// for atomic slices. pub(crate) unsafe fn memcpy(src: SliceRef<'_>, dest: SliceRefMut<'_>, count: usize) { if cfg!(debug_assertions) { assert!(src.len() >= count); assert!(dest.len() >= count); + let src_range = src.addr()..src.addr() + src.len(); + let dest_range = dest.addr()..dest.addr() + dest.len(); + assert!(!src_range.contains(&dest_range.start)); + assert!(!src_range.contains(&dest_range.end)); } // TODO: this could be optimized with batches of writes using `u32/u64` stores instead. match (src, dest) { // SAFETY: The invariants of this operation are ensured by the caller of the function. - (SliceRef::Common(src), SliceRefMut::Common(dest)) => unsafe { + (SliceRef::Slice(src), SliceRefMut::Slice(dest)) => unsafe { ptr::copy_nonoverlapping(src.as_ptr(), dest.as_mut_ptr(), count); }, // SAFETY: The invariants of this operation are ensured by the caller of the function. - (SliceRef::Common(src), SliceRefMut::Atomic(dest)) => unsafe { + (SliceRef::Slice(src), SliceRefMut::AtomicSlice(dest)) => unsafe { for i in 0..count { dest.get_unchecked(i) .store(*src.get_unchecked(i), atomic::Ordering::Relaxed); } }, // SAFETY: The invariants of this operation are ensured by the caller of the function. - (SliceRef::Atomic(src), SliceRefMut::Common(dest)) => unsafe { + (SliceRef::AtomicSlice(src), SliceRefMut::Slice(dest)) => unsafe { for i in 0..count { *dest.get_unchecked_mut(i) = src.get_unchecked(i).load(atomic::Ordering::Relaxed); } }, // SAFETY: The invariants of this operation are ensured by the caller of the function. - (SliceRef::Atomic(src), SliceRefMut::Atomic(dest)) => unsafe { + (SliceRef::AtomicSlice(src), SliceRefMut::AtomicSlice(dest)) => unsafe { copy_shared_to_shared(src, dest, count); }, } @@ -365,8 +387,8 @@ pub(crate) unsafe fn memcpy(src: SliceRef<'_>, dest: SliceRefMut<'_>, count: usi /// /// # Safety /// -/// - `from + count <= buffer.len()` -/// - `to + count <= buffer.len()` +/// - `buffer` must contain at least `from + count` bytes to be read. +/// - `buffer` must contain at least `to + count` bytes to be written. pub(crate) unsafe fn memmove(buffer: SliceRefMut<'_>, from: usize, to: usize, count: usize) { if cfg!(debug_assertions) { assert!(from + count <= buffer.len()); @@ -375,14 +397,14 @@ pub(crate) unsafe fn memmove(buffer: SliceRefMut<'_>, from: usize, to: usize, co match buffer { // SAFETY: The invariants of this operation are ensured by the caller of the function. - SliceRefMut::Common(buf) => unsafe { + SliceRefMut::Slice(buf) => unsafe { let ptr = buf.as_mut_ptr(); let src_ptr = ptr.add(from); let dest_ptr = ptr.add(to); ptr::copy(src_ptr, dest_ptr, count); }, // SAFETY: The invariants of this operation are ensured by the caller of the function. - SliceRefMut::Atomic(buf) => unsafe { + SliceRefMut::AtomicSlice(buf) => unsafe { let src = buf.get_unchecked(from..); let dest = buf.get_unchecked(to..); @@ -395,7 +417,7 @@ pub(crate) unsafe fn memmove(buffer: SliceRefMut<'_>, from: usize, to: usize, co // `to = 2` // `count = 4` // - // We can now imagine that the array is pointer to by our indices: + // We can now imagine that the array is pointed to by our indices: // // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | // ^ ^ diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 906a1f39322..92856fa2866 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -402,7 +402,7 @@ impl DataView { let mut value = T::zeroed(); memcpy( data.subslice(buffer_index..), - SliceRefMut::Common(bytes_of_mut(&mut value)), + SliceRefMut::Slice(bytes_of_mut(&mut value)), mem::size_of::(), ); @@ -722,7 +722,7 @@ impl DataView { }; memcpy( - SliceRef::Common(bytes_of(&value)), + SliceRef::Slice(bytes_of(&value)), data.subslice_mut(buffer_index..), mem::size_of::(), ); diff --git a/boa_engine/src/builtins/typed_array/builtin.rs b/boa_engine/src/builtins/typed_array/builtin.rs index 98a558797bd..eaa05703b87 100644 --- a/boa_engine/src/builtins/typed_array/builtin.rs +++ b/boa_engine/src/builtins/typed_array/builtin.rs @@ -2035,7 +2035,7 @@ impl BuiltinTypedArray { .expect("Must be an array buffer"); match (src_buffer, target_buffer) { - (BufferRef::Shared(src), BufferRef::Shared(dest)) => { + (BufferRef::SharedBuffer(src), BufferRef::SharedBuffer(dest)) => { ptr::eq(src.data(), dest.data()) } (_, _) => false, @@ -3068,7 +3068,7 @@ impl BuiltinTypedArray { .as_array_buffer_mut() .expect("Must be ArrayBuffer"); let mut data = - SliceRefMut::Common(data.data_mut().expect("a new buffer cannot be detached")); + SliceRefMut::Slice(data.data_mut().expect("a new buffer cannot be detached")); // b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception. if src_type.content_type() != element_type.content_type() { diff --git a/boa_engine/src/builtins/typed_array/element.rs b/boa_engine/src/builtins/typed_array/element.rs index 6200728d4cd..f1ed71da9e4 100644 --- a/boa_engine/src/builtins/typed_array/element.rs +++ b/boa_engine/src/builtins/typed_array/element.rs @@ -86,8 +86,8 @@ impl Element for u8 { debug_assert!(buffer.len() >= 1); match buffer { - SliceRef::Common(buffer) => unsafe { *buffer.get_unchecked(0) }, - SliceRef::Atomic(buffer) => unsafe { buffer.get_unchecked(0).load(order) }, + SliceRef::Slice(buffer) => unsafe { *buffer.get_unchecked(0) }, + SliceRef::AtomicSlice(buffer) => unsafe { buffer.get_unchecked(0).load(order) }, } } @@ -95,10 +95,12 @@ impl Element for u8 { debug_assert!(buffer.len() >= 1); match buffer { - SliceRefMut::Common(buffer) => unsafe { + SliceRefMut::Slice(buffer) => unsafe { *buffer.get_unchecked_mut(0) = value; }, - SliceRefMut::Atomic(buffer) => unsafe { buffer.get_unchecked(0).store(value, order) }, + SliceRefMut::AtomicSlice(buffer) => unsafe { + buffer.get_unchecked(0).store(value, order); + }, } } } @@ -123,8 +125,8 @@ impl Element for u16 { } match buffer { - SliceRef::Common(buffer) => unsafe { *buffer.as_ptr().cast() }, - SliceRef::Atomic(buffer) => unsafe { + SliceRef::Slice(buffer) => unsafe { *buffer.as_ptr().cast() }, + SliceRef::AtomicSlice(buffer) => unsafe { (*buffer.as_ptr().cast::()).load(order) }, } @@ -137,10 +139,10 @@ impl Element for u16 { } match buffer { - SliceRefMut::Common(buffer) => unsafe { + SliceRefMut::Slice(buffer) => unsafe { *buffer.as_mut_ptr().cast() = value; }, - SliceRefMut::Atomic(buffer) => unsafe { + SliceRefMut::AtomicSlice(buffer) => unsafe { (*buffer.as_ptr().cast::()).store(value, order); }, } @@ -167,8 +169,8 @@ impl Element for u32 { } match buffer { - SliceRef::Common(buffer) => unsafe { *buffer.as_ptr().cast() }, - SliceRef::Atomic(buffer) => unsafe { + SliceRef::Slice(buffer) => unsafe { *buffer.as_ptr().cast() }, + SliceRef::AtomicSlice(buffer) => unsafe { (*buffer.as_ptr().cast::()).load(order) }, } @@ -181,10 +183,10 @@ impl Element for u32 { } match buffer { - SliceRefMut::Common(buffer) => unsafe { + SliceRefMut::Slice(buffer) => unsafe { *buffer.as_mut_ptr().cast() = value; }, - SliceRefMut::Atomic(buffer) => unsafe { + SliceRefMut::AtomicSlice(buffer) => unsafe { (*buffer.as_ptr().cast::()).store(value, order); }, } @@ -211,8 +213,8 @@ impl Element for u64 { } match buffer { - SliceRef::Common(buffer) => unsafe { *buffer.as_ptr().cast() }, - SliceRef::Atomic(buffer) => unsafe { + SliceRef::Slice(buffer) => unsafe { *buffer.as_ptr().cast() }, + SliceRef::AtomicSlice(buffer) => unsafe { (*buffer.as_ptr().cast::()).load(order) }, } @@ -225,10 +227,10 @@ impl Element for u64 { } match buffer { - SliceRefMut::Common(buffer) => unsafe { + SliceRefMut::Slice(buffer) => unsafe { *buffer.as_mut_ptr().cast() = value; }, - SliceRefMut::Atomic(buffer) => unsafe { + SliceRefMut::AtomicSlice(buffer) => unsafe { (*buffer.as_ptr().cast::()).store(value, order); }, } diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index dea8e3542c4..1b215db797e 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -1334,8 +1334,8 @@ impl Object { #[must_use] pub(crate) const fn as_buffer(&self) -> Option> { match &self.kind { - ObjectKind::ArrayBuffer(buffer) => Some(BufferRef::Common(buffer)), - ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRef::Shared(buffer)), + ObjectKind::ArrayBuffer(buffer) => Some(BufferRef::Buffer(buffer)), + ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRef::SharedBuffer(buffer)), _ => None, } } @@ -1344,8 +1344,8 @@ impl Object { #[inline] pub(crate) fn as_buffer_mut(&mut self) -> Option> { match &mut self.kind { - ObjectKind::ArrayBuffer(buffer) => Some(BufferRefMut::Common(buffer)), - ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRefMut::Shared(buffer)), + ObjectKind::ArrayBuffer(buffer) => Some(BufferRefMut::Buffer(buffer)), + ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRefMut::SharedBuffer(buffer)), _ => None, } }