Browse Source

Implement `JsArrayBuffer` (#2170)

This PR adds the `ArrayBuffer` rust wrapper. It also provides a capability to construct a `JsArrayBuffer` from a user defined blob of data ( `Vec<u8>` ) and it is not cloned, it is directly used as the internal buffer.

This allows us to replace the inifficent `Vec<u8>` to `JsArray` then to `TypedArray` (in typed arrays `from_iter`), with a `JsArrayBuffer` created from user data to `TypedArray`. With this `Vec<u8>` to `JsTypedArray` should be fully fixed as discussed in #2058.
pull/2173/head
Halid Odat 2 years ago
parent
commit
48d8b420c5
  1. 2
      boa_engine/src/builtins/array_buffer/mod.rs
  2. 123
      boa_engine/src/object/jsarraybuffer.rs
  3. 37
      boa_engine/src/object/jstypedarray.rs
  4. 2
      boa_engine/src/object/mod.rs
  5. 49
      boa_examples/src/bin/jsarraybuffer.rs

2
boa_engine/src/builtins/array_buffer/mod.rs

@ -139,7 +139,7 @@ impl ArrayBuffer {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength
fn get_byte_length(
pub(crate) fn get_byte_length(
this: &JsValue,
_args: &[JsValue],
context: &mut Context,

123
boa_engine/src/object/jsarraybuffer.rs

@ -0,0 +1,123 @@
use crate::{
builtins::array_buffer::ArrayBuffer,
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, JsObject, JsObjectType, ObjectData,
},
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use std::ops::Deref;
/// JavaScript `ArrayBuffer` rust object.
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsArrayBuffer {
inner: JsObject,
}
impl JsArrayBuffer {
/// Create a new array buffer with byte length.
#[inline]
pub fn new(byte_length: usize, context: &mut Context) -> JsResult<Self> {
let inner = ArrayBuffer::allocate(
&context
.intrinsics()
.constructors()
.array_buffer()
.constructor()
.into(),
byte_length,
context,
)?;
Ok(Self { inner })
}
/// Create a new array buffer from byte block.
///
/// 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()`.
#[inline]
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()
.array_buffer()
.constructor()
.into();
// 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »).
let prototype = get_prototype_from_constructor(
&constructor,
StandardConstructors::array_buffer,
context,
)?;
let obj = context.construct_object();
obj.set_prototype(prototype.into());
// 2. Let block be ? CreateByteDataBlock(byteLength).
//
// NOTE: We skip step 2. because we already have the block
// that is passed to us as a function argument.
let block = byte_block;
// 3. Set obj.[[ArrayBufferData]] to block.
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer {
array_buffer_data: Some(block),
array_buffer_byte_length: byte_length,
array_buffer_detach_key: JsValue::Undefined,
});
Ok(Self { inner: obj })
}
/// Create a [`JsArrayBuffer`] from a [`JsObject`], if the object is not an array buffer throw a `TypeError`.
///
/// This does not clone the fields of the array buffer, it only does a shallow clone of the object.
#[inline]
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
if object.borrow().is_array_buffer() {
Ok(Self { inner: object })
} else {
context.throw_type_error("object is not an ArrayBuffer")
}
}
/// Returns the byte length of the array buffer.
#[inline]
pub fn byte_length(&self, context: &mut Context) -> usize {
ArrayBuffer::get_byte_length(&self.inner.clone().into(), &[], context)
.expect("it should not throw")
.as_number()
.expect("expected a number") as usize
}
}
impl From<JsArrayBuffer> for JsObject {
#[inline]
fn from(o: JsArrayBuffer) -> Self {
o.inner.clone()
}
}
impl From<JsArrayBuffer> for JsValue {
#[inline]
fn from(o: JsArrayBuffer) -> Self {
o.inner.clone().into()
}
}
impl Deref for JsArrayBuffer {
type Target = JsObject;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl JsObjectType for JsArrayBuffer {}

37
boa_engine/src/object/jstypedarray.rs

@ -1,6 +1,6 @@
use crate::{
builtins::typed_array::TypedArray,
object::{JsArray, JsFunction, JsObject, JsObjectType},
object::{JsArrayBuffer, JsFunction, JsObject, JsObjectType},
value::IntoOrUndefined,
Context, JsResult, JsString, JsValue,
};
@ -329,12 +329,43 @@ macro_rules! JsTypedArrayType {
}
impl $name {
#[inline]
pub fn from_array_buffer(
array_buffer: JsArrayBuffer,
context: &mut Context,
) -> JsResult<Self> {
let new_target = context
.intrinsics()
.constructors()
.$constructor_object()
.constructor()
.into();
let object = crate::builtins::typed_array::$constructor_function::constructor(
&new_target,
&[array_buffer.into()],
context,
)?
.as_object()
.expect("object")
.clone();
Ok(Self {
inner: JsTypedArray {
inner: object.into(),
},
})
}
#[inline]
pub fn from_iter<I>(elements: I, context: &mut Context) -> JsResult<Self>
where
I: IntoIterator<Item = $element>,
{
let array = JsArray::from_iter(elements.into_iter().map(JsValue::new), context);
let bytes: Vec<_> = elements
.into_iter()
.flat_map(<$element>::to_ne_bytes)
.collect();
let array_buffer = JsArrayBuffer::from_byte_block(bytes, context)?;
let new_target = context
.intrinsics()
.constructors()
@ -343,7 +374,7 @@ macro_rules! JsTypedArrayType {
.into();
let object = crate::builtins::typed_array::$constructor_function::constructor(
&new_target,
&[array.into()],
&[array_buffer.into()],
context,
)?
.as_object()

2
boa_engine/src/object/mod.rs

@ -62,6 +62,7 @@ mod tests;
pub(crate) mod internal_methods;
mod jsarray;
mod jsarraybuffer;
mod jsfunction;
mod jsmap;
mod jsmap_iterator;
@ -74,6 +75,7 @@ mod operations;
mod property_map;
pub use jsarray::*;
pub use jsarraybuffer::*;
pub use jsfunction::*;
pub use jsmap::*;
pub use jsmap_iterator::*;

49
boa_examples/src/bin/jsarraybuffer.rs

@ -0,0 +1,49 @@
// This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{
object::{JsArrayBuffer, JsUint32Array, JsUint8Array},
property::Attribute,
Context, JsResult, JsValue,
};
fn main() -> JsResult<()> {
// We create a new `Context` to create a new Javascript executor.
let context = &mut Context::default();
// This create an array buffer of byte length 4
let array_buffer = JsArrayBuffer::new(4, context)?;
// We can now create an typed array to access the data.
let uint32_typed_array = JsUint32Array::from_array_buffer(array_buffer, context)?;
let value = 0x12345678u32;
uint32_typed_array.set(0, value, true, context)?;
assert_eq!(uint32_typed_array.get(0, context)?, JsValue::new(value));
// We can also create array buffers from a user defined block of data.
//
// NOTE: The block data will not be cloned.
let blob_of_data: Vec<u8> = (0..=255).collect();
let array_buffer = JsArrayBuffer::from_byte_block(blob_of_data, context)?;
// This the byte length of the new array buffer will be the length of block of data.
let byte_length = array_buffer.byte_length(context);
assert_eq!(byte_length, 256);
// We can now create an typed array to access the data.
let uint8_typed_array = JsUint8Array::from_array_buffer(array_buffer.clone(), context)?;
for i in 0..byte_length {
assert_eq!(uint8_typed_array.get(i, context)?, JsValue::new(i));
}
// We can also register it as a global property
context.register_global_property(
"myArrayBuffer",
array_buffer,
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE,
);
Ok(())
}
Loading…
Cancel
Save