use ecmascript_atomics::Ordering;
use super::{AnyArrayBuffer, ArrayBuffer, InternalBuffer};
#[cfg(feature = "shared-array-buffer")]
use crate::ecmascript::SharedDataBlock;
use crate::{
ecmascript::{
Agent, BUILTIN_STRING_MEMORY, ExceptionType, Function, JsResult, Numeric, Object,
ProtoIntrinsics, Value, Viewable, create_byte_data_block, get,
ordinary_create_from_constructor, to_index,
},
engine::{Bindable, GcScope, NoGcScope},
heap::{ArenaAccess, ArenaAccessMut},
};
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct DetachKey {}
pub(crate) fn allocate_array_buffer<'a>(
agent: &mut Agent,
constructor: Function,
byte_length: u64,
max_byte_length: Option<u64>,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, ArrayBuffer<'a>> {
let constructor = constructor.bind(gc.nogc());
if let Some(max_byte_length) = max_byte_length {
if byte_length > max_byte_length {
return Err(agent.throw_exception_with_static_message(
ExceptionType::RangeError,
"Byte length is over maximumm byte length",
gc.into_nogc(),
));
}
}
let Object::ArrayBuffer(obj) = ordinary_create_from_constructor(
agent,
constructor.unbind(),
ProtoIntrinsics::ArrayBuffer,
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc()) else {
unreachable!()
};
let obj = obj.unbind();
let gc = gc.into_nogc();
let obj = obj.bind(gc);
let buffer = if let Some(max_byte_length) = max_byte_length {
let mut block = create_byte_data_block(agent, max_byte_length, gc)?;
block.realloc(byte_length as usize);
InternalBuffer::resizable(block, max_byte_length as usize)
} else {
InternalBuffer::fixed_length(create_byte_data_block(agent, byte_length, gc)?)
};
obj.get_mut(agent).buffer = buffer;
Ok(obj)
}
#[inline]
pub(crate) fn is_detached_buffer(agent: &Agent, array_buffer: ArrayBuffer) -> bool {
array_buffer.get(agent).is_detached()
}
pub(crate) fn detach_array_buffer<'a>(
agent: &mut Agent,
array_buffer: ArrayBuffer,
key: Option<DetachKey>,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, ()> {
if array_buffer.get_detach_key(agent) != key {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Mismatching array buffer detach keys",
gc,
));
}
array_buffer.get_mut(agent).buffer.detach();
Ok(())
}
pub(crate) fn get_array_buffer_max_byte_length_option<'a>(
agent: &mut Agent,
options: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Option<u64>> {
let options = options.bind(gc.nogc());
let options = if let Ok(options) = Object::try_from(options) {
options
} else {
return Ok(None);
};
let max_byte_length = get(
agent,
options.unbind(),
BUILTIN_STRING_MEMORY.maxByteLength.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if max_byte_length.is_undefined() {
return Ok(None);
}
Ok(Some(to_index(agent, max_byte_length.unbind(), gc)? as u64))
}
#[inline(always)]
pub(crate) fn is_fixed_length_array_buffer(agent: &Agent, array_buffer: AnyArrayBuffer) -> bool {
!array_buffer.is_resizable(agent)
}
#[inline(always)]
pub(crate) fn raw_bytes_to_numeric<'a, T: Viewable>(
agent: &mut Agent,
raw_bytes: T,
is_little_endian: bool,
gc: NoGcScope<'a, '_>,
) -> Numeric<'a> {
if is_little_endian {
raw_bytes.into_le_value(agent, gc)
} else {
raw_bytes.into_be_value(agent, gc)
}
}
#[inline(always)]
#[cfg(feature = "shared-array-buffer")]
pub(crate) fn get_raw_bytes_from_shared_block<T: Viewable>(
block: &SharedDataBlock,
byte_index: usize,
is_typed_array: bool,
order: Ordering,
) -> T {
if is_typed_array {
block.load::<T>(byte_index, order).expect("Invalid GetRawBytesFromSharedBlock call: did not check index alignment and buffer length beforehand")
} else {
block.load_unaligned::<T>(byte_index).expect(
"Invalid GetRawBytesFromSharedBlock call: did not check buffer length beforehand",
)
}
}
pub(crate) fn get_value_from_buffer<'a, T: Viewable>(
agent: &mut Agent,
array_buffer: AnyArrayBuffer,
byte_index: usize,
is_typed_array: bool,
order: Ordering,
is_little_endian: Option<bool>,
gc: NoGcScope<'a, '_>,
) -> Numeric<'a> {
debug_assert!(!array_buffer.is_detached(agent));
let raw_value = match array_buffer {
#[cfg(feature = "shared-array-buffer")]
AnyArrayBuffer::SharedArrayBuffer(sab) => {
let block = sab.get_data_block(agent);
get_raw_bytes_from_shared_block::<T>(block, byte_index, is_typed_array, order)
}
AnyArrayBuffer::ArrayBuffer(ab) => {
let _ = order;
let block = ab.get(agent).get_data_block();
if is_typed_array {
unsafe { block.read_aligned(byte_index) }
} else {
unsafe { block.read_unaligned(byte_index) }
}
}
};
let is_little_endian = is_little_endian.unwrap_or({
#[cfg(target_endian = "little")]
{
true
}
#[cfg(target_endian = "big")]
{
false
}
});
raw_bytes_to_numeric::<T>(agent, raw_value, is_little_endian, gc)
}
pub(crate) fn numeric_to_raw_bytes<T: Viewable>(
agent: &mut Agent,
value: Numeric,
is_little_endian: bool,
) -> T {
if is_little_endian {
T::from_le_value(agent, value)
} else {
T::from_be_value(agent, value)
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn set_value_in_buffer<T: Viewable>(
agent: &mut Agent,
array_buffer: AnyArrayBuffer,
byte_index: usize,
value: Numeric,
is_typed_array: bool,
order: Ordering,
is_little_endian: Option<bool>,
) {
debug_assert!(!array_buffer.is_detached(agent));
let is_little_endian = is_little_endian.unwrap_or({
#[cfg(target_endian = "little")]
{
true
}
#[cfg(target_endian = "big")]
{
false
}
});
let raw_bytes = numeric_to_raw_bytes::<T>(agent, value, is_little_endian);
match array_buffer {
#[cfg(feature = "shared-array-buffer")]
AnyArrayBuffer::SharedArrayBuffer(sab) => {
let block = sab.get_data_block(agent);
if is_typed_array {
block
.store::<T>(byte_index, raw_bytes, order)
.expect("Unaligned store or SharedDataBlock not large enough")
} else {
block
.store_unaligned::<T>(byte_index, raw_bytes)
.expect("SharedDataBlock not large enough")
}
}
AnyArrayBuffer::ArrayBuffer(ab) => {
let _ = order;
let _ = is_typed_array;
let block = ab.get_mut(agent).get_data_block_mut();
block.set_offset_by_byte::<T>(byte_index, raw_bytes);
}
}
}
#[cfg(feature = "atomics")]
pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const OP: u8>(
agent: &mut Agent,
array_buffer: AnyArrayBuffer,
byte_index: usize,
value: Numeric,
gc: NoGcScope<'gc, '_>,
) -> Numeric<'gc> {
let element_size = size_of::<Type>();
debug_assert!(!array_buffer.is_detached(agent));
debug_assert!(
byte_index + size_of::<Type>() <= array_buffer.byte_length(agent, Ordering::Unordered)
);
debug_assert!(value.is_bigint() == Type::IS_BIGINT);
let raw_bytes = Type::from_ne_value(agent, value);
let raw_bytes_read = match array_buffer {
AnyArrayBuffer::ArrayBuffer(array_buffer) => {
let op = get_array_buffer_op::<Type, OP>();
let block = array_buffer.as_mut_slice(agent);
let slot = &mut block[byte_index..byte_index + element_size];
let (head, slot, tail) = unsafe { slot.align_to_mut::<Type>() };
debug_assert!(head.is_empty() && tail.is_empty());
let raw_bytes_read = slot[0];
let data_modified = op(raw_bytes_read, raw_bytes);
slot[0] = data_modified;
raw_bytes_read
}
AnyArrayBuffer::SharedArrayBuffer(array_buffer) => {
let buffer = array_buffer.as_slice(agent);
let slot = buffer.slice(byte_index, byte_index + element_size);
let (head, slot, tail) = slot.align_to::<Type::Storage>();
debug_assert!(head.is_empty() && tail.is_empty());
let slot = slot.get(0).unwrap();
let result = if const { OP == 0 } {
let raw_bytes = Type::into_storage(raw_bytes);
slot.fetch_add(raw_bytes)
} else if const { OP == 1 } {
let raw_bytes = Type::into_storage(raw_bytes);
slot.fetch_and(raw_bytes)
} else if const { OP == 2 } {
let raw_bytes = Type::into_storage(raw_bytes);
slot.swap(raw_bytes)
} else if const { OP == 3 } {
let raw_bytes = Type::into_storage(raw_bytes);
slot.fetch_or(raw_bytes)
} else if const { OP == 4 } {
let raw_bytes = raw_bytes.neg();
let raw_bytes = Type::into_storage(raw_bytes);
slot.fetch_add(raw_bytes)
} else if const { OP == 5 } {
let raw_bytes = Type::into_storage(raw_bytes);
slot.fetch_xor(raw_bytes)
} else {
panic!("Unsupported Op value");
};
Type::from_storage(result)
}
};
raw_bytes_read.into_ne_value(agent, gc)
}
#[cfg(feature = "atomics")]
type ModifyOp<T> = fn(T, T) -> T;
#[cfg(feature = "atomics")]
const fn get_array_buffer_op<T: Viewable, const OP: u8>() -> ModifyOp<T> {
if const { OP == 0 } {
<T as Viewable>::add
} else if const { OP == 1 } {
<T as Viewable>::and
} else if const { OP == 2 } {
<T as Viewable>::swap
} else if const { OP == 3 } {
<T as Viewable>::or
} else if const { OP == 4 } {
<T as Viewable>::sub
} else if const { OP == 5 } {
<T as Viewable>::xor
} else {
panic!("Unsupported Op value");
}
}
#[cfg(feature = "atomics")]
pub(crate) fn compare_exchange_in_buffer<'gc, Type: Viewable>(
agent: &mut Agent,
array_buffer: AnyArrayBuffer,
byte_index: usize,
expected: Numeric,
replacement: Numeric,
gc: NoGcScope<'gc, '_>,
) -> Numeric<'gc> {
let element_size = size_of::<Type>();
debug_assert!(!array_buffer.is_detached(agent));
debug_assert!(
byte_index + size_of::<Type>() <= array_buffer.byte_length(agent, Ordering::Unordered)
);
debug_assert!(
expected.is_bigint() == Type::IS_BIGINT && replacement.is_bigint() == Type::IS_BIGINT
);
let expected_bytes = Type::from_ne_value(agent, expected);
let replacement_bytes = Type::from_ne_value(agent, replacement);
let raw_bytes_read = match array_buffer {
AnyArrayBuffer::SharedArrayBuffer(array_buffer) => {
let buffer = array_buffer.as_slice(agent);
let slot = buffer.slice(byte_index, byte_index + element_size);
let (head, slot, tail) = slot.align_to::<Type::Storage>();
debug_assert!(head.is_empty() && tail.is_empty());
let slot = slot.get(0).unwrap();
let expected_bytes = Type::into_storage(expected_bytes);
let replacement_bytes = Type::into_storage(replacement_bytes);
match slot.compare_exchange(expected_bytes, replacement_bytes) {
Ok(r) => Type::from_storage(r),
Err(r) => Type::from_storage(r),
}
}
AnyArrayBuffer::ArrayBuffer(array_buffer) => {
let block = array_buffer.as_mut_slice(agent);
let slot = &mut block[byte_index..byte_index + element_size];
let (head, slot, tail) = unsafe { slot.align_to_mut::<Type>() };
debug_assert!(head.is_empty() && tail.is_empty());
let raw_bytes_read = slot[0];
if raw_bytes_read == expected_bytes {
slot[0] = replacement_bytes;
}
raw_bytes_read
}
};
raw_bytes_read.into_ne_value(agent, gc)
}