mod abstract_operations;
mod data;
use std::collections::hash_map::Entry;
pub(crate) use abstract_operations::*;
pub(crate) use data::*;
#[cfg(feature = "shared-array-buffer")]
use super::shared_array_buffer::SharedArrayBuffer;
#[cfg(feature = "shared-array-buffer")]
use crate::ecmascript::types::SHARED_ARRAY_BUFFER_DISCRIMINANT;
use crate::{
ecmascript::{
Agent, JsResult, ProtoIntrinsics,
types::{
ARRAY_BUFFER_DISCRIMINANT, InternalMethods, InternalSlots, Object, OrdinaryObject,
Value, Viewable, copy_data_block_bytes, create_byte_data_block,
},
},
engine::{Bindable, HeapRootData, NoGcScope, bindable_handle},
heap::{
ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
HeapIndexHandle, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
},
};
use ecmascript_atomics::Ordering;
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ArrayBuffer<'a>(BaseIndex<'a, ArrayBufferHeapData<'static>>);
array_buffer_handle!(ArrayBuffer);
arena_vec_access!(ArrayBuffer, 'a, ArrayBufferHeapData, array_buffers);
impl<'ab> ArrayBuffer<'ab> {
pub fn new<'gc>(
agent: &mut Agent,
byte_length: usize,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ArrayBuffer<'gc>> {
let data_block = create_byte_data_block(agent, byte_length as u64, gc)?;
let block = data_block;
Ok(agent
.heap
.create(ArrayBufferHeapData::new_fixed_length(block))
.bind(gc))
}
#[inline]
pub fn is_detached(self, agent: &Agent) -> bool {
self.get(agent).is_detached()
}
#[inline]
pub fn is_resizable(self, agent: &Agent) -> bool {
self.get(agent).is_resizable()
}
#[inline]
pub fn byte_length(self, agent: &Agent) -> usize {
self.get(agent).byte_length()
}
#[inline]
pub fn max_byte_length(self, agent: &Agent) -> usize {
self.get(agent).max_byte_length()
}
#[inline]
pub(crate) fn get_detach_key(self, agent: &Agent) -> Option<DetachKey> {
agent.heap.array_buffer_detach_keys.get(&self).copied()
}
#[inline]
pub fn set_detach_key(self, agent: &mut Agent, key: DetachKey) {
match agent.heap.array_buffer_detach_keys.entry(self.unbind()) {
Entry::Occupied(_) => {
}
Entry::Vacant(e) => {
e.insert(key);
agent.heap.alloc_counter += core::mem::size_of::<(ArrayBuffer, DetachKey)>();
}
}
}
pub fn detach<'a>(
self,
agent: &mut Agent,
key: Option<DetachKey>,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, ()> {
detach_array_buffer(agent, self, key, gc)
}
pub(crate) fn resize(self, agent: &mut Agent, new_byte_length: usize) {
self.get_mut(agent).resize(new_byte_length);
}
#[inline]
pub fn as_slice(self, agent: &'ab Agent) -> &'ab [u8] {
self.get(agent).get_data_block()
}
#[inline]
pub fn as_mut_slice(self, agent: &'ab mut Agent) -> &'ab mut [u8] {
self.get_mut(agent).buffer.get_data_block_mut()
}
pub(crate) fn as_viewable_slice<T: Viewable>(
self,
agent: &'ab Agent,
byte_offset: usize,
byte_length: Option<usize>,
) -> &'ab [T] {
let byte_slice = self.as_slice(agent);
let byte_limit = byte_length.map(|byte_length| byte_offset.saturating_add(byte_length));
if byte_limit.unwrap_or(byte_offset) > byte_slice.len() {
return &[];
}
let byte_slice = if let Some(byte_limit) = byte_limit {
&byte_slice[byte_offset..byte_limit]
} else {
&byte_slice[byte_offset..]
};
let (head, slice, _) = unsafe { byte_slice.align_to::<T>() };
if !head.is_empty() {
panic!("ArrayBuffer is not properly aligned for T");
}
slice
}
pub(crate) fn as_mut_viewable_slice<T: Viewable>(
self,
agent: &'ab mut Agent,
byte_offset: usize,
byte_length: Option<usize>,
) -> &'ab mut [T] {
let byte_slice = self.as_mut_slice(agent);
let byte_limit = byte_length.map(|byte_length| byte_offset.saturating_add(byte_length));
if byte_limit.unwrap_or(byte_offset) > byte_slice.len() {
return &mut [];
}
let byte_slice = if let Some(byte_limit) = byte_limit {
&mut byte_slice[byte_offset..byte_limit]
} else {
&mut byte_slice[byte_offset..]
};
let (head, slice, _) = unsafe { byte_slice.align_to_mut::<T>() };
if !head.is_empty() {
panic!("ArrayBuffer is not properly aligned for T");
}
slice
}
pub(crate) fn copy_array_buffer_data(
self,
agent: &mut Agent,
source: ArrayBuffer,
first: usize,
count: usize,
) {
debug_assert_ne!(self, source);
let array_buffers = &mut *agent.heap.array_buffers;
let (source_data, target_data) = if self.get_index() > source.get_index() {
let (before, after) = array_buffers.split_at_mut(self.get_index());
(&before[source.get_index()], &mut after[0])
} else {
let (before, after) = array_buffers.split_at_mut(source.get_index());
(&after[0], &mut before[self.get_index()])
};
let source_data = source_data.buffer.get_data_block();
let target_data = target_data.buffer.get_data_block_mut();
copy_data_block_bytes(target_data, 0, source_data, first, count);
}
}
impl<'a> InternalSlots<'a> for ArrayBuffer<'a> {
const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::ArrayBuffer;
#[inline(always)]
fn get_backing_object(self, agent: &Agent) -> Option<OrdinaryObject<'static>> {
self.get(agent).object_index.unbind()
}
fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) {
assert!(
self.get_mut(agent)
.object_index
.replace(backing_object.unbind())
.is_none()
);
}
}
impl<'a> InternalMethods<'a> for ArrayBuffer<'a> {}
impl HeapMarkAndSweep for ArrayBuffer<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.array_buffers.push(*self);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
compactions.array_buffers.shift_index(&mut self.0);
}
}
impl HeapSweepWeakReference for ArrayBuffer<'static> {
fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
compactions.array_buffers.shift_weak_index(self.0).map(Self)
}
}
impl<'a> CreateHeapData<ArrayBufferHeapData<'a>, ArrayBuffer<'a>> for Heap {
fn create(&mut self, data: ArrayBufferHeapData<'a>) -> ArrayBuffer<'a> {
self.array_buffers.push(data.unbind());
self.alloc_counter += core::mem::size_of::<ArrayBufferHeapData<'static>>();
ArrayBuffer(BaseIndex::last(&self.array_buffers))
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum AnyArrayBuffer<'a> {
ArrayBuffer(ArrayBuffer<'a>) = ARRAY_BUFFER_DISCRIMINANT,
#[cfg(feature = "shared-array-buffer")]
SharedArrayBuffer(SharedArrayBuffer<'a>) = SHARED_ARRAY_BUFFER_DISCRIMINANT,
}
bindable_handle!(AnyArrayBuffer);
impl<'ab> AnyArrayBuffer<'ab> {
#[inline(always)]
pub fn is_shared(self) -> bool {
match self {
Self::ArrayBuffer(_) => false,
#[cfg(feature = "shared-array-buffer")]
Self::SharedArrayBuffer(_) => true,
}
}
#[inline(always)]
pub fn is_detached(self, agent: &Agent) -> bool {
match self {
Self::ArrayBuffer(ta) => ta.is_detached(agent),
#[cfg(feature = "shared-array-buffer")]
Self::SharedArrayBuffer(_) => false,
}
}
#[inline(always)]
pub fn is_resizable(self, agent: &Agent) -> bool {
match self {
Self::ArrayBuffer(ta) => ta.is_resizable(agent),
#[cfg(feature = "shared-array-buffer")]
Self::SharedArrayBuffer(sta) => sta.is_growable(agent),
}
}
#[inline(always)]
pub fn byte_length(self, agent: &Agent, order: Ordering) -> usize {
#[cfg(not(feature = "shared-array-buffer"))]
let _ = order;
match self {
Self::ArrayBuffer(ta) => ta.byte_length(agent),
#[cfg(feature = "shared-array-buffer")]
Self::SharedArrayBuffer(sta) => sta.byte_length(agent, order),
}
}
#[inline(always)]
pub fn max_byte_length(self, agent: &Agent) -> usize {
match self {
Self::ArrayBuffer(ta) => ta.max_byte_length(agent),
#[cfg(feature = "shared-array-buffer")]
Self::SharedArrayBuffer(sta) => sta.max_byte_length(agent),
}
}
}
impl<'a> From<AnyArrayBuffer<'a>> for Object<'a> {
#[inline(always)]
fn from(value: AnyArrayBuffer<'a>) -> Self {
match value {
AnyArrayBuffer::ArrayBuffer(dv) => Self::ArrayBuffer(dv),
#[cfg(feature = "shared-array-buffer")]
AnyArrayBuffer::SharedArrayBuffer(sdv) => Self::SharedArrayBuffer(sdv),
}
}
}
impl<'a> From<AnyArrayBuffer<'a>> for Value<'a> {
#[inline(always)]
fn from(value: AnyArrayBuffer<'a>) -> Self {
match value {
AnyArrayBuffer::ArrayBuffer(dv) => Self::ArrayBuffer(dv),
#[cfg(feature = "shared-array-buffer")]
AnyArrayBuffer::SharedArrayBuffer(sdv) => Self::SharedArrayBuffer(sdv),
}
}
}
impl<'a> From<AnyArrayBuffer<'a>> for HeapRootData {
#[inline(always)]
fn from(value: AnyArrayBuffer<'a>) -> Self {
match value {
AnyArrayBuffer::ArrayBuffer(dv) => Self::from(dv),
#[cfg(feature = "shared-array-buffer")]
AnyArrayBuffer::SharedArrayBuffer(sdv) => Self::from(sdv),
}
}
}
impl<'a> TryFrom<Object<'a>> for AnyArrayBuffer<'a> {
type Error = ();
fn try_from(value: Object<'a>) -> Result<Self, Self::Error> {
match value {
Object::ArrayBuffer(ab) => Ok(Self::ArrayBuffer(ab)),
#[cfg(feature = "shared-array-buffer")]
Object::SharedArrayBuffer(sab) => Ok(Self::SharedArrayBuffer(sab)),
_ => Err(()),
}
}
}
impl<'a> TryFrom<Value<'a>> for AnyArrayBuffer<'a> {
type Error = ();
fn try_from(value: Value<'a>) -> Result<Self, Self::Error> {
match value {
Value::ArrayBuffer(ab) => Ok(Self::ArrayBuffer(ab)),
#[cfg(feature = "shared-array-buffer")]
Value::SharedArrayBuffer(sab) => Ok(Self::SharedArrayBuffer(sab)),
_ => Err(()),
}
}
}
impl TryFrom<HeapRootData> for AnyArrayBuffer<'_> {
type Error = ();
#[inline]
fn try_from(value: HeapRootData) -> Result<Self, Self::Error> {
match value {
HeapRootData::ArrayBuffer(dv) => Ok(AnyArrayBuffer::ArrayBuffer(dv)),
#[cfg(feature = "shared-array-buffer")]
HeapRootData::SharedArrayBuffer(sdv) => Ok(AnyArrayBuffer::SharedArrayBuffer(sdv)),
_ => Err(()),
}
}
}
macro_rules! array_buffer_handle {
($name: ident) => {
crate::ecmascript::types::object_handle!($name);
impl<'a> From<$name<'a>> for crate::ecmascript::builtins::array_buffer::AnyArrayBuffer<'a> {
fn from(value: $name<'a>) -> Self {
Self::$name(value)
}
}
impl<'a> TryFrom<crate::ecmascript::builtins::array_buffer::AnyArrayBuffer<'a>>
for $name<'a>
{
type Error = ();
fn try_from(
value: crate::ecmascript::builtins::array_buffer::AnyArrayBuffer<'a>,
) -> Result<Self, Self::Error> {
match value {
crate::ecmascript::builtins::array_buffer::AnyArrayBuffer::$name(data) => {
Ok(data)
}
_ => Err(()),
}
}
}
};
}
pub(crate) use array_buffer_handle;