Browse Source

Implement `SharedArrayBuffer` (#3384)

* Implement `SharedArrayBuffer`

* Add small documentation

* Fix docs

* Apply review
pull/3387/head
José Julián Espina 1 year ago committed by GitHub
parent
commit
dd05f53468
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      Cargo.lock
  2. 2
      boa_engine/Cargo.toml
  3. 722
      boa_engine/src/builtins/array_buffer/mod.rs
  4. 374
      boa_engine/src/builtins/array_buffer/shared.rs
  5. 17
      boa_engine/src/builtins/array_buffer/tests.rs
  6. 458
      boa_engine/src/builtins/array_buffer/utils.rs
  7. 314
      boa_engine/src/builtins/dataview/mod.rs
  8. 10
      boa_engine/src/builtins/mod.rs
  9. 1
      boa_engine/src/builtins/temporal/fields.rs
  10. 3388
      boa_engine/src/builtins/typed_array/builtin.rs
  11. 400
      boa_engine/src/builtins/typed_array/element.rs
  12. 78
      boa_engine/src/builtins/typed_array/integer_indexed_object.rs
  13. 4084
      boa_engine/src/builtins/typed_array/mod.rs
  14. 19
      boa_engine/src/context/hooks.rs
  15. 14
      boa_engine/src/context/intrinsics.rs
  16. 40
      boa_engine/src/object/builtins/jsarraybuffer.rs
  17. 4
      boa_engine/src/object/builtins/jsdataview.rs
  18. 125
      boa_engine/src/object/builtins/jssharedarraybuffer.rs
  19. 38
      boa_engine/src/object/builtins/jstypedarray.rs
  20. 2
      boa_engine/src/object/builtins/mod.rs
  21. 1
      boa_engine/src/object/internal_methods/function.rs
  22. 107
      boa_engine/src/object/internal_methods/integer_indexed.rs
  23. 24
      boa_engine/src/object/jsobject.rs
  24. 67
      boa_engine/src/object/mod.rs
  25. 2
      boa_engine/src/string/common.rs
  26. 26
      boa_engine/src/value/mod.rs
  27. 4
      boa_examples/src/bin/jsarraybuffer.rs
  28. 13
      boa_tester/src/exec/js262.rs
  29. 1
      test262_config.toml

28
Cargo.lock generated

@ -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"

2
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 }

722
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,19 @@
//! [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;
#[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 +28,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> {
Buffer(&'a ArrayBuffer),
SharedBuffer(&'a SharedArrayBuffer),
}
impl BufferRef<'_> {
pub(crate) fn data(&self) -> Option<SliceRef<'_>> {
match self {
Self::Buffer(buf) => buf.data().map(SliceRef::Slice),
Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.data())),
}
}
pub(crate) fn is_detached(&self) -> bool {
self.data().is_none()
}
}
#[derive(Debug)]
pub(crate) enum BufferRefMut<'a> {
Buffer(&'a mut ArrayBuffer),
SharedBuffer(&'a mut SharedArrayBuffer),
}
impl BufferRefMut<'_> {
pub(crate) fn data_mut(&mut self) -> Option<SliceRefMut<'_>> {
match self {
Self::Buffer(buf) => buf.data_mut().map(SliceRefMut::Slice),
Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice(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<Vec<u8>>,
/// The `[[ArrayBufferByteLength]]` internal slot.
pub array_buffer_byte_length: u64,
data: Option<Vec<u8>>,
/// 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<u8>, 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<Option<Vec<u8>>> {
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 +258,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 +282,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 +314,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 +333,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 +344,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 +394,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 +402,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<JsObject> {
// 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<SliceRange> {
// 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
}
// 14. Let newLen be max(final - first, 0).
let new_len = r#final.saturating_sub(first);
/// `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<bool>,
) -> 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<Vec<u8>> {
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<bool>,
context: &mut Context<'_>,
) -> JsResult<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. 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;
}
// 10. Return NormalCompletion(undefined).
Ok(JsValue::undefined())
}
Ok(SliceRange {
start: first,
length: new_len,
})
}
/// `CreateByteDataBlock ( size )` abstract operation.
@ -782,7 +471,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<Vec<u8>> {
pub(crate) fn create_byte_data_block(size: u64, context: &mut Context<'_>) -> JsResult<Vec<u8>> {
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 +496,3 @@ pub fn create_byte_data_block(size: u64) -> JsResult<Vec<u8>> {
// 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,
}

374
boa_engine/src/builtins/array_buffer/shared.rs

@ -0,0 +1,374 @@
#![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<Box<[AtomicU8]>>,
}
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::<Self>(), "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::<Self>(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<JsValue> {
// 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<JsValue> {
// 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<JsValue> {
// 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<JsValue> {
// 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<JsObject> {
// 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<Vec<AtomicU8>>` 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<Arc<Box<[AtomicU8]>>> {
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.
// 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::<AtomicU8>(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 allocator 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, 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::<u64>(), 0);
// 3. Return db.
Ok(Arc::new(buffer))
}

17
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());
}

458
boa_engine/src/builtins/array_buffer/utils.rs

@ -0,0 +1,458 @@
#![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> {
Slice(&'a [u8]),
AtomicSlice(&'a [AtomicU8]),
}
impl SliceRef<'_> {
/// Gets the byte length of this `SliceRef`.
pub(crate) fn len(&self) -> usize {
match self {
Self::Slice(buf) => buf.len(),
Self::AtomicSlice(buf) => buf.len(),
}
}
/// Gets a subslice of this `SliceRef`.
pub(crate) fn subslice<I>(&self, index: I) -> SliceRef<'_>
where
I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>,
{
match self {
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::Slice(buf) => buf.as_ptr().addr(),
Self::AtomicSlice(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<T: Element>(buffer: SliceRef<'_>, order: atomic::Ordering) -> T {
// <https://tc39.es/ecma262/#sec-getvaluefrombuffer>
// 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::<T>());
assert_eq!(buffer.addr() % std::mem::align_of::<T>(), 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::<i8>(buffer, order).into(),
TypedArrayKind::Uint8 => read_elem::<u8>(buffer, order).into(),
TypedArrayKind::Uint8Clamped => read_elem::<ClampedU8>(buffer, order).into(),
TypedArrayKind::Int16 => read_elem::<i16>(buffer, order).into(),
TypedArrayKind::Uint16 => read_elem::<u16>(buffer, order).into(),
TypedArrayKind::Int32 => read_elem::<i32>(buffer, order).into(),
TypedArrayKind::Uint32 => read_elem::<u32>(buffer, order).into(),
TypedArrayKind::BigInt64 => read_elem::<i64>(buffer, order).into(),
TypedArrayKind::BigUint64 => read_elem::<u64>(buffer, order).into(),
TypedArrayKind::Float32 => read_elem::<f32>(buffer, order).into(),
TypedArrayKind::Float64 => read_elem::<f64>(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<JsObject> {
// 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::Slice(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::Slice(value)
}
}
impl<'a> From<&'a [AtomicU8]> for SliceRef<'a> {
fn from(value: &'a [AtomicU8]) -> Self {
Self::AtomicSlice(value)
}
}
#[derive(Debug)]
pub(crate) enum SliceRefMut<'a> {
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::Slice(buf) => buf.len(),
Self::AtomicSlice(buf) => buf.len(),
}
}
/// Gets a mutable subslice of this `SliceRefMut`.
pub(crate) fn subslice_mut<I>(&mut self, index: I) -> SliceRefMut<'_>
where
I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>,
{
match self {
Self::Slice(buffer) => {
SliceRefMut::Slice(buffer.get_mut(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::Slice(buf) => buf.as_ptr().addr(),
Self::AtomicSlice(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) {
unsafe fn write_elem<T: Element>(
buffer: SliceRefMut<'_>,
value: T,
order: atomic::Ordering,
) {
// <https://tc39.es/ecma262/#sec-setvalueinbuffer>
// 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::<T>());
assert_eq!(buffer.addr() % std::mem::align_of::<T>(), 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::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_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),
}
}
}
}
impl<'a> From<&'a mut [u8]> for SliceRefMut<'a> {
fn from(value: &'a mut [u8]) -> Self {
Self::Slice(value)
}
}
impl<'a> From<&'a [AtomicU8]> for SliceRefMut<'a> {
fn from(value: &'a [AtomicU8]) -> Self {
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 {
// 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 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.
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` 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::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::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::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::AtomicSlice(src), SliceRefMut::AtomicSlice(dest)) => unsafe {
copy_shared_to_shared(src, dest, count);
},
}
}
/// Copies `count` bytes from the position `from` to the position `to` in `buffer`.
///
/// # Safety
///
/// - `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());
assert!(to + count <= buffer.len());
}
match buffer {
// SAFETY: The invariants of this operation are ensured by the caller of the function.
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::AtomicSlice(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 pointed 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);
}
},
}
}

314
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<T: typed_array::Element>(
view: &JsValue,
request_index: &JsValue,
is_little_endian: &JsValue,
t: TypedArrayKind,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 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::<T>() 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::Slice(bytes_of_mut(&mut value)),
mem::size_of::<T>(),
);
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::<i64>(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::<u64>(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::<f32>(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::<f64>(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::<i8>(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::<i16>(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::<i32>(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::<u8>(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::<u16>(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::<u32>(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<T: typed_array::Element>(
view: &JsValue,
request_index: &JsValue,
is_little_endian: &JsValue,
t: TypedArrayKind,
value: &JsValue,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
@ -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::<T>() 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::Slice(bytes_of(&value)),
data.subslice_mut(buffer_index..),
mem::size_of::<T>(),
);
}
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::<i64>(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::<u64>(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::<f32>(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::<f64>(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::<i8>(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::<i16>(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::<i32>(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::<u8>(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::<u16>(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::<u32>(this, byte_offset, is_little_endian, value, context)
}
}

10
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::<Array>(context)?;
global_binding::<Proxy>(context)?;
global_binding::<ArrayBuffer>(context)?;
global_binding::<SharedArrayBuffer>(context)?;
global_binding::<BigInt>(context)?;
global_binding::<Boolean>(context)?;
global_binding::<Date>(context)?;
@ -353,7 +355,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
global_binding::<Set>(context)?;
global_binding::<String>(context)?;
global_binding::<RegExp>(context)?;
global_binding::<TypedArray>(context)?;
global_binding::<BuiltinTypedArray>(context)?;
global_binding::<Int8Array>(context)?;
global_binding::<Uint8Array>(context)?;
global_binding::<Uint8ClampedArray>(context)?;

1
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,

3388
boa_engine/src/builtins/typed_array/builtin.rs

File diff suppressed because it is too large Load Diff

400
boa_engine/src/builtins/typed_array/element.rs

@ -0,0 +1,400 @@
#![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<ClampedU8> for Numeric {
fn from(value: ClampedU8) -> Self {
Numeric::Number(value.0.into())
}
}
pub(crate) trait Element:
Sized + Into<TypedArrayElement> + NoUninit + AnyBitPattern
{
fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult<Self>;
/// 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::<Self>` 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::<Self>` 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<Self> {
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::Slice(buffer) => unsafe { *buffer.get_unchecked(0) },
SliceRef::AtomicSlice(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::Slice(buffer) => unsafe {
*buffer.get_unchecked_mut(0) = value;
},
SliceRefMut::AtomicSlice(buffer) => unsafe {
buffer.get_unchecked(0).store(value, order);
},
}
}
}
impl Element for u16 {
fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult<Self> {
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::<u16>());
assert!(buffer.addr() % std::mem::align_of::<u16>() == 0);
}
match buffer {
SliceRef::Slice(buffer) => unsafe { *buffer.as_ptr().cast() },
SliceRef::AtomicSlice(buffer) => unsafe {
(*buffer.as_ptr().cast::<AtomicU16>()).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::<u16>());
assert!(buffer.addr() % std::mem::align_of::<u16>() == 0);
}
match buffer {
SliceRefMut::Slice(buffer) => unsafe {
*buffer.as_mut_ptr().cast() = value;
},
SliceRefMut::AtomicSlice(buffer) => unsafe {
(*buffer.as_ptr().cast::<AtomicU16>()).store(value, order);
},
}
}
}
impl Element for u32 {
fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult<Self> {
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::<u32>());
assert!(buffer.addr() % std::mem::align_of::<u32>() == 0);
}
match buffer {
SliceRef::Slice(buffer) => unsafe { *buffer.as_ptr().cast() },
SliceRef::AtomicSlice(buffer) => unsafe {
(*buffer.as_ptr().cast::<AtomicU32>()).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::<u32>());
assert!(buffer.addr() % std::mem::align_of::<u32>() == 0);
}
match buffer {
SliceRefMut::Slice(buffer) => unsafe {
*buffer.as_mut_ptr().cast() = value;
},
SliceRefMut::AtomicSlice(buffer) => unsafe {
(*buffer.as_ptr().cast::<AtomicU32>()).store(value, order);
},
}
}
}
impl Element for u64 {
fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult<Self> {
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::<u64>());
assert!(buffer.addr() % std::mem::align_of::<u64>() == 0);
}
match buffer {
SliceRef::Slice(buffer) => unsafe { *buffer.as_ptr().cast() },
SliceRef::AtomicSlice(buffer) => unsafe {
(*buffer.as_ptr().cast::<AtomicU64>()).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::<u64>());
assert!(buffer.addr() % std::mem::align_of::<u64>() == 0);
}
match buffer {
SliceRefMut::Slice(buffer) => unsafe {
*buffer.as_mut_ptr().cast() = value;
},
SliceRefMut::AtomicSlice(buffer) => unsafe {
(*buffer.as_ptr().cast::<AtomicU64>()).store(value, order);
},
}
}
}
impl Element for i8 {
fn from_js_value(value: &JsValue, context: &mut Context<'_>) -> JsResult<Self> {
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<Self> {
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<Self> {
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<Self> {
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<Self> {
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<Self> {
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<Self> {
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) }
}
}

78
boa_engine/src/builtins/typed_array/integer_indexed_object.rs

@ -1,29 +1,22 @@
//! 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;
/// <https://tc39.es/ecma262/#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
#[derive(Debug, Clone, Trace, Finalize)]
pub struct IntegerIndexed {
viewed_array_buffer: Option<JsObject>,
viewed_array_buffer: JsObject,
#[unsafe_ignore_trace]
typed_array_name: TypedArrayKind,
kind: TypedArrayKind,
byte_offset: u64,
byte_length: u64,
array_length: u64,
@ -31,15 +24,15 @@ pub struct IntegerIndexed {
impl IntegerIndexed {
pub(crate) const fn new(
viewed_array_buffer: Option<JsObject>,
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 +48,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 +61,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<JsObject>) {
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 +78,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;
}
}

4084
boa_engine/src/builtins/typed_array/mod.rs

File diff suppressed because it is too large Load Diff

19
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.

14
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:

40
boa_engine/src/object/builtins/jsarraybuffer.rs

@ -18,13 +18,14 @@ 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.
///
/// ```
/// # use boa_engine::{
/// # object::builtins::JsArrayBuffer,
/// # Context, JsResult
/// # Context, JsResult, JsValue
/// # };
/// # fn main() -> JsResult<()> {
/// # // Initialize context
@ -32,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(())
/// # }
@ -58,11 +59,11 @@ 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,
/// # Context, JsResult,
/// # Context, JsResult, JsValue,
/// # };
/// # fn main() -> JsResult<()> {
/// # // Initialize context
@ -72,13 +73,14 @@ impl JsArrayBuffer {
/// let data_block: Vec<u8> = (0..5).collect();
/// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?;
///
/// assert_eq!(array_buffer.take()?, (0..5).collect::<Vec<u8>>());
/// assert_eq!(
/// array_buffer.detach(&JsValue::undefined())?,
/// (0..5).collect::<Vec<u8>>()
/// );
/// # Ok(())
/// # }
/// ```
pub fn from_byte_block(byte_block: Vec<u8>, context: &mut Context<'_>) -> JsResult<Self> {
let byte_length = byte_block.len();
let constructor = context
.intrinsics()
.constructors()
@ -104,11 +106,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,12 +157,15 @@ 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::{
/// # object::builtins::JsArrayBuffer,
/// # Context, JsResult,
/// # Context, JsResult, JsValue
/// # };
/// # fn main() -> JsResult<()> {
/// # // Initialize context
@ -174,27 +175,26 @@ 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.detach(&JsValue::undefined())?;
///
/// assert_eq!(internal_buffer, (0..5).collect::<Vec<u8>>());
///
/// // Anymore interaction with the buffer will return an error
/// let detached_err = array_buffer.take();
/// let detached_err = array_buffer.detach(&JsValue::undefined());
/// assert!(detached_err.is_err());
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn take(&self) -> JsResult<Vec<u8>> {
pub fn detach(&self, detach_key: &JsValue) -> JsResult<Vec<u8>> {
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")
.with_message("ArrayBuffer was already detached")
.into()
})
}

4
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()

125
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<Self> {
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<Self> {
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<JsSharedArrayBuffer> for JsObject {
#[inline]
fn from(o: JsSharedArrayBuffer) -> Self {
o.inner.clone()
}
}
impl From<JsSharedArrayBuffer> 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<Self> {
match value {
JsValue::Object(o) => Self::from_object(o.clone()),
_ => Err(JsNativeError::typ()
.with_message("value is not a SharedArrayBuffer object")
.into()),
}
}
}

38
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<usize> {
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<i64>,
{
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<usize> {
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<usize> {
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<JsValue>,
{
TypedArray::fill(
BuiltinTypedArray::fill(
&self.inner,
&[
value.into(),
@ -109,7 +109,7 @@ impl JsTypedArray {
this_arg: Option<JsValue>,
context: &mut Context<'_>,
) -> JsResult<bool> {
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<JsValue>,
context: &mut Context<'_>,
) -> JsResult<bool> {
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<JsFunction>,
context: &mut Context<'_>,
) -> JsResult<Self> {
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<JsValue>,
context: &mut Context<'_>,
) -> JsResult<Self> {
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<JsValue>,
context: &mut Context<'_>,
) -> JsResult<Self> {
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<JsValue>,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
TypedArray::reduce(
BuiltinTypedArray::reduce(
&self.inner,
&[callback.into(), initial_value.into_or_undefined()],
context,
@ -208,7 +208,7 @@ impl JsTypedArray {
initial_value: Option<JsValue>,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
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<Self> {
TypedArray::reverse(&self.inner, &[], context)?;
BuiltinTypedArray::reverse(&self.inner, &[], context)?;
Ok(self.clone())
}
@ -230,7 +230,7 @@ impl JsTypedArray {
end: Option<usize>,
context: &mut Context<'_>,
) -> JsResult<Self> {
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<JsValue>,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
TypedArray::find(
BuiltinTypedArray::find(
&self.inner,
&[predicate.into(), this_arg.into_or_undefined()],
context,
@ -264,7 +264,7 @@ impl JsTypedArray {
where
T: Into<JsValue>,
{
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<JsValue>,
{
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<JsString>,
context: &mut Context<'_>,
) -> JsResult<JsString> {
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")

2
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::*;

1
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.
///
// <https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist>
#[track_caller]
pub(crate) fn native_function_call(

107
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<JsValue> {
}
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).

24
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

67
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<JsValue>),
@ -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<JsValue>) -> 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<BufferRef<'_>> {
match &self.kind {
ObjectKind::ArrayBuffer(buffer) => Some(BufferRef::Buffer(buffer)),
ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRef::SharedBuffer(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<BufferRefMut<'_>> {
match &mut self.kind {
ObjectKind::ArrayBuffer(buffer) => Some(BufferRefMut::Buffer(buffer)),
ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRefMut::SharedBuffer(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
}

2
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"),

26
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<BigInt> {
pub fn to_big_int64(&self, context: &mut Context<'_>) -> JsResult<i64> {
// 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<BigInt> {
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<u64> {
// 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.

4
boa_examples/src/bin/jsarraybuffer.rs

@ -63,10 +63,10 @@ fn main() -> JsResult<()> {
let data_block: Vec<u8> = (0..5).collect();
let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?;
let internal_buffer = array_buffer.take()?;
let internal_buffer = array_buffer.detach(&JsValue::undefined())?;
assert_eq!(internal_buffer, (0..5).collect::<Vec<u8>>());
let detached_err = array_buffer.take();
let detached_err = array_buffer.detach(&JsValue::undefined());
assert!(detached_err.is_err());
Ok(())

13
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())

1
test262_config.toml

@ -7,7 +7,6 @@ flags = []
features = [
### Unimplemented features:
"SharedArrayBuffer",
"FinalizationRegistry",
"IsHTMLDDA",
"Atomics",

Loading…
Cancel
Save