use std::{ptr, slice::SliceIndex, sync::atomic::Ordering};
use portable_atomic::{AtomicU8, AtomicU64};
use crate::{
Context, JsResult,
builtins::typed_array::{ClampedU8, Element, TypedArrayElement, TypedArrayKind},
object::JsObject,
};
use super::ArrayBuffer;
#[derive(Clone, Copy)]
pub(crate) enum BytesConstPtr {
Bytes(*const u8),
AtomicBytes(*const AtomicU8),
}
impl BytesConstPtr {
pub(crate) unsafe fn add(self, count: usize) -> Self {
unsafe {
match self {
Self::Bytes(p) => Self::Bytes(p.add(count)),
Self::AtomicBytes(p) => Self::AtomicBytes(p.add(count)),
}
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum BytesMutPtr {
Bytes(*mut u8),
AtomicBytes(*const AtomicU8),
}
impl BytesMutPtr {
pub(crate) unsafe fn add(self, count: usize) -> Self {
unsafe {
match self {
Self::Bytes(p) => Self::Bytes(p.add(count)),
Self::AtomicBytes(p) => Self::AtomicBytes(p.add(count)),
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum SliceRef<'a> {
Slice(&'a [u8]),
AtomicSlice(&'a [AtomicU8]),
}
impl SliceRef<'_> {
pub(crate) fn len(&self) -> usize {
match self {
Self::Slice(buf) => buf.len(),
Self::AtomicSlice(buf) => buf.len(),
}
}
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"))
}
}
}
#[must_use]
pub(crate) fn to_vec(self) -> Vec<u8> {
match self {
Self::Slice(s) => s.to_vec(),
Self::AtomicSlice(s) => {
let count = s.len();
let mut target = Vec::with_capacity(count);
unsafe {
memcpy(
BytesConstPtr::AtomicBytes(s.as_ptr()),
BytesMutPtr::Bytes(target.as_mut_ptr()),
count,
);
target.set_len(s.len());
}
target
}
}
}
#[cfg(debug_assertions)]
pub(crate) fn addr(&self) -> usize {
match self {
Self::Slice(buf) => buf.as_ptr().addr(),
Self::AtomicSlice(buf) => buf.as_ptr().addr(),
}
}
pub(crate) fn as_ptr(&self) -> BytesConstPtr {
match self {
SliceRef::Slice(s) => BytesConstPtr::Bytes(s.as_ptr()),
SliceRef::AtomicSlice(s) => BytesConstPtr::AtomicBytes(s.as_ptr()),
}
}
pub(crate) unsafe fn get_value(
&self,
kind: TypedArrayKind,
order: Ordering,
) -> TypedArrayElement {
unsafe fn read_elem<T: Element>(buffer: SliceRef<'_>, order: Ordering) -> T {
#[cfg(debug_assertions)]
{
assert!(buffer.len() >= size_of::<T>());
assert_eq!(buffer.addr() % align_of::<T>(), 0);
}
unsafe { T::read(buffer).load(order) }
}
let buffer = *self;
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(),
#[cfg(feature = "float16")]
TypedArrayKind::Float16 => {
read_elem::<crate::builtins::typed_array::Float16>(buffer, order).into()
}
TypedArrayKind::Float32 => read_elem::<f32>(buffer, order).into(),
TypedArrayKind::Float64 => read_elem::<f64>(buffer, order).into(),
}
}
}
pub(crate) fn clone(&self, context: &mut Context) -> JsResult<JsObject<ArrayBuffer>> {
let target_buffer = ArrayBuffer::allocate(
&context
.realm()
.intrinsics()
.constructors()
.array_buffer()
.constructor()
.into(),
self.len() as u64,
None,
context,
)?;
{
let mut target_buffer = target_buffer.borrow_mut();
let target_block = target_buffer
.data_mut()
.bytes_mut()
.expect("ArrayBuffer cannot be detached here");
unsafe {
memcpy(
self.as_ptr(),
BytesMutPtr::Bytes(target_block.as_mut_ptr()),
self.len(),
);
}
}
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<'_> {
pub(crate) fn len(&self) -> usize {
match self {
Self::Slice(buf) => buf.len(),
Self::AtomicSlice(buf) => buf.len(),
}
}
#[expect(unused, reason = "could still be useful in the future")]
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"))
}
}
}
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"))
}
}
}
#[cfg(debug_assertions)]
pub(crate) fn addr(&self) -> usize {
match self {
Self::Slice(buf) => buf.as_ptr().addr(),
Self::AtomicSlice(buf) => buf.as_ptr().addr(),
}
}
pub(crate) fn as_ptr(&mut self) -> BytesMutPtr {
match self {
Self::Slice(s) => BytesMutPtr::Bytes(s.as_mut_ptr()),
Self::AtomicSlice(s) => BytesMutPtr::AtomicBytes(s.as_ptr()),
}
}
pub(crate) unsafe fn set_value(&mut self, value: TypedArrayElement, order: Ordering) {
unsafe fn write_elem<T: Element>(buffer: SliceRefMut<'_>, value: T, order: Ordering) {
#[cfg(debug_assertions)]
{
assert!(buffer.len() >= size_of::<T>());
assert_eq!(buffer.addr() % align_of::<T>(), 0);
}
unsafe {
T::read_mut(buffer).store(value, order);
}
}
let buffer = match self {
SliceRefMut::Slice(buf) => SliceRefMut::Slice(buf),
SliceRefMut::AtomicSlice(buf) => SliceRefMut::AtomicSlice(buf),
};
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),
#[cfg(feature = "float16")]
TypedArrayElement::Float16(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)
}
}
const BATCH_SIZE: usize = size_of::<u64>();
fn compute_batch_offsets(ptr_addr: usize, count: usize) -> (usize, usize, usize) {
let misalign = ptr_addr % BATCH_SIZE;
let head = if misalign == 0 {
0
} else {
(BATCH_SIZE - misalign).min(count)
};
let remaining = count - head;
let chunks = remaining / BATCH_SIZE;
let tail = remaining % BATCH_SIZE;
(head, chunks, tail)
}
unsafe fn batched_atomic_copy_forward(src: *const AtomicU8, dest: *const AtomicU8, count: usize) {
if count == 0 {
return;
}
if (src as usize) % BATCH_SIZE != (dest as usize) % BATCH_SIZE {
unsafe {
for i in 0..count {
(*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed);
}
}
return;
}
let (head, chunks, tail) = compute_batch_offsets(dest as usize, count);
if chunks == 0 {
unsafe {
for i in 0..count {
(*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed);
}
}
return;
}
unsafe {
for i in 0..head {
(*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed);
}
}
#[cfg(debug_assertions)]
{
debug_assert_eq!((dest as usize + head) % BATCH_SIZE, 0);
debug_assert_eq!((src as usize + head) % BATCH_SIZE, 0);
}
#[allow(clippy::cast_ptr_alignment)]
unsafe {
let src_u64 = src.add(head).cast::<AtomicU64>();
let dest_u64 = dest.add(head).cast::<AtomicU64>();
for i in 0..chunks {
(*dest_u64.add(i)).store((*src_u64.add(i)).load(Ordering::Relaxed), Ordering::Relaxed);
}
}
let tail_start = head + chunks * BATCH_SIZE;
unsafe {
for i in 0..tail {
(*dest.add(tail_start + i)).store(
(*src.add(tail_start + i)).load(Ordering::Relaxed),
Ordering::Relaxed,
);
}
}
}
unsafe fn batched_atomic_copy_backward(src: *const AtomicU8, dest: *const AtomicU8, count: usize) {
if count == 0 {
return;
}
if (src as usize) % BATCH_SIZE != (dest as usize) % BATCH_SIZE {
unsafe {
for i in (0..count).rev() {
(*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed);
}
}
return;
}
let (head, chunks, tail) = compute_batch_offsets(dest as usize, count);
if chunks == 0 {
unsafe {
for i in (0..count).rev() {
(*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed);
}
}
return;
}
let tail_start = head + chunks * BATCH_SIZE;
unsafe {
for i in (0..tail).rev() {
(*dest.add(tail_start + i)).store(
(*src.add(tail_start + i)).load(Ordering::Relaxed),
Ordering::Relaxed,
);
}
}
#[cfg(debug_assertions)]
{
debug_assert_eq!((dest as usize + tail_start) % BATCH_SIZE, 0);
debug_assert_eq!((src as usize + tail_start) % BATCH_SIZE, 0);
}
#[allow(clippy::cast_ptr_alignment)]
unsafe {
let src_u64 = src.add(head).cast::<AtomicU64>();
let dest_u64 = dest.add(head).cast::<AtomicU64>();
for i in (0..chunks).rev() {
(*dest_u64.add(i)).store((*src_u64.add(i)).load(Ordering::Relaxed), Ordering::Relaxed);
}
}
unsafe {
for i in (0..head).rev() {
(*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed);
}
}
}
unsafe fn batched_copy_bytes_to_atomic(src: *const u8, dest: *const AtomicU8, count: usize) {
if count == 0 {
return;
}
if (src as usize) % BATCH_SIZE != (dest as usize) % BATCH_SIZE {
unsafe {
for i in 0..count {
(*dest.add(i)).store(*src.add(i), Ordering::Relaxed);
}
}
return;
}
let (head, chunks, tail) = compute_batch_offsets(dest as usize, count);
if chunks == 0 {
unsafe {
for i in 0..count {
(*dest.add(i)).store(*src.add(i), Ordering::Relaxed);
}
}
return;
}
unsafe {
for i in 0..head {
(*dest.add(i)).store(*src.add(i), Ordering::Relaxed);
}
}
#[cfg(debug_assertions)]
{
debug_assert_eq!((dest as usize + head) % BATCH_SIZE, 0);
debug_assert_eq!((src as usize + head) % BATCH_SIZE, 0);
}
#[allow(clippy::cast_ptr_alignment)]
unsafe {
let src_u64 = src.add(head).cast::<u64>();
let dest_u64 = dest.add(head).cast::<AtomicU64>();
for i in 0..chunks {
(*dest_u64.add(i)).store(ptr::read(src_u64.add(i)), Ordering::Relaxed);
}
}
let tail_start = head + chunks * BATCH_SIZE;
unsafe {
for i in 0..tail {
(*dest.add(tail_start + i)).store(*src.add(tail_start + i), Ordering::Relaxed);
}
}
}
unsafe fn batched_copy_atomic_to_bytes(src: *const AtomicU8, dest: *mut u8, count: usize) {
if count == 0 {
return;
}
if (src as usize) % BATCH_SIZE != (dest as usize) % BATCH_SIZE {
unsafe {
for i in 0..count {
*dest.add(i) = (*src.add(i)).load(Ordering::Relaxed);
}
}
return;
}
let (head, chunks, tail) = compute_batch_offsets(src as usize, count);
if chunks == 0 {
unsafe {
for i in 0..count {
*dest.add(i) = (*src.add(i)).load(Ordering::Relaxed);
}
}
return;
}
unsafe {
for i in 0..head {
*dest.add(i) = (*src.add(i)).load(Ordering::Relaxed);
}
}
#[cfg(debug_assertions)]
{
debug_assert_eq!((src as usize + head) % BATCH_SIZE, 0);
debug_assert_eq!((dest as usize + head) % BATCH_SIZE, 0);
}
#[allow(clippy::cast_ptr_alignment)]
unsafe {
let src_u64 = src.add(head).cast::<AtomicU64>();
let dest_u64 = dest.add(head).cast::<u64>();
for i in 0..chunks {
ptr::write(dest_u64.add(i), (*src_u64.add(i)).load(Ordering::Relaxed));
}
}
let tail_start = head + chunks * BATCH_SIZE;
unsafe {
for i in 0..tail {
*dest.add(tail_start + i) = (*src.add(tail_start + i)).load(Ordering::Relaxed);
}
}
}
pub(super) unsafe fn copy_shared_to_shared(
src: *const AtomicU8,
dest: *const AtomicU8,
count: usize,
) {
unsafe { batched_atomic_copy_forward(src, dest, count) }
}
unsafe fn copy_shared_to_shared_backwards(
src: *const AtomicU8,
dest: *const AtomicU8,
count: usize,
) {
unsafe { batched_atomic_copy_backward(src, dest, count) }
}
pub(crate) unsafe fn memcpy(src: BytesConstPtr, dest: BytesMutPtr, count: usize) {
match (src, dest) {
(BytesConstPtr::Bytes(src), BytesMutPtr::Bytes(dest)) => unsafe {
ptr::copy_nonoverlapping(src, dest, count);
},
(BytesConstPtr::Bytes(src), BytesMutPtr::AtomicBytes(dest)) => unsafe {
batched_copy_bytes_to_atomic(src, dest, count);
},
(BytesConstPtr::AtomicBytes(src), BytesMutPtr::Bytes(dest)) => unsafe {
batched_copy_atomic_to_bytes(src, dest, count);
},
(BytesConstPtr::AtomicBytes(src), BytesMutPtr::AtomicBytes(dest)) => unsafe {
copy_shared_to_shared(src, dest, count);
},
}
}
pub(crate) unsafe fn memmove_naive(ptr: BytesMutPtr, from: usize, to: usize, count: usize) {
match ptr {
BytesMutPtr::Bytes(ptr) => unsafe {
for i in 0..count {
ptr::copy(ptr.add(from + i), ptr.add(to + i), 1);
}
},
BytesMutPtr::AtomicBytes(ptr) => unsafe {
let src = ptr.add(from);
let dest = ptr.add(to);
copy_shared_to_shared(src, dest, count);
},
}
}
pub(crate) unsafe fn memmove(ptr: BytesMutPtr, from: usize, to: usize, count: usize) {
match ptr {
BytesMutPtr::Bytes(ptr) => unsafe {
let src = ptr.add(from);
let dest = ptr.add(to);
ptr::copy(src, dest, count);
},
BytesMutPtr::AtomicBytes(ptr) => unsafe {
let src = ptr.add(from);
let dest = ptr.add(to);
if src < dest {
copy_shared_to_shared_backwards(src, dest, count);
} else {
copy_shared_to_shared(src, dest, count);
}
},
}
}
#[cfg(test)]
mod tests_miri {
use super::*;
use portable_atomic::AtomicU8;
use std::sync::atomic::Ordering;
#[test]
fn batched_forward_misaligned_fallback() {
let src_data: Vec<AtomicU8> = (0..32).map(|i| AtomicU8::new(i as u8)).collect();
let dest_data: Vec<AtomicU8> = (0..32).map(|_| AtomicU8::new(0)).collect();
let src = unsafe { src_data.as_ptr().add(1) };
let dest = unsafe { dest_data.as_ptr().add(2) };
let count = 20;
unsafe { batched_atomic_copy_forward(src, dest, count) };
for i in 0..count {
let expected = unsafe { (*src.add(i)).load(Ordering::Relaxed) };
let actual = unsafe { (*dest.add(i)).load(Ordering::Relaxed) };
assert_eq!(actual, expected, "mismatch at index {i}");
}
}
#[test]
fn batched_backward_misaligned_fallback() {
let src_data: Vec<AtomicU8> = (0..32).map(|i| AtomicU8::new(i as u8)).collect();
let dest_data: Vec<AtomicU8> = (0..32).map(|_| AtomicU8::new(0)).collect();
let src = unsafe { src_data.as_ptr().add(1) };
let dest = unsafe { dest_data.as_ptr().add(2) };
let count = 20;
unsafe { batched_atomic_copy_backward(src, dest, count) };
for i in 0..count {
let expected = unsafe { (*src.add(i)).load(Ordering::Relaxed) };
let actual = unsafe { (*dest.add(i)).load(Ordering::Relaxed) };
assert_eq!(actual, expected, "mismatch at index {i}");
}
}
#[test]
fn batched_bytes_to_atomic_misaligned_fallback() {
let src_data: Vec<u8> = (0..32).map(|i| i as u8).collect();
let dest_data: Vec<AtomicU8> = (0..32).map(|_| AtomicU8::new(0)).collect();
let src = unsafe { src_data.as_ptr().add(1) };
let dest = unsafe { dest_data.as_ptr().add(2) };
let count = 20;
unsafe { batched_copy_bytes_to_atomic(src, dest, count) };
for i in 0..count {
let expected = unsafe { *src.add(i) };
let actual = unsafe { (*dest.add(i)).load(Ordering::Relaxed) };
assert_eq!(actual, expected, "mismatch at index {i}");
}
}
#[test]
fn batched_atomic_to_bytes_misaligned_fallback() {
let src_data: Vec<AtomicU8> = (0..32).map(|i| AtomicU8::new(i as u8)).collect();
let mut dest_data: Vec<u8> = vec![0u8; 32];
let src = unsafe { src_data.as_ptr().add(1) };
let dest = unsafe { dest_data.as_mut_ptr().add(2) };
let count = 20;
unsafe { batched_copy_atomic_to_bytes(src, dest, count) };
for i in 0..count {
let expected = unsafe { (*src.add(i)).load(Ordering::Relaxed) };
let actual = unsafe { *dest.add(i) };
assert_eq!(actual, expected, "mismatch at index {i}");
}
}
}