From 92c0410a8324fbc9a78876ccc38b87e22c8168b7 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:00:52 +0300 Subject: [PATCH 1/3] asd --- .../abstract_operations/type_conversion.rs | 34 +++-- .../array_buffer/abstract_operations.rs | 110 ++++++++++---- .../structured_data/atomics_object.rs | 134 +++++++++++++++++- .../builtins/typed_array/any_typed_array.rs | 60 ++++++++ .../src/ecmascript/types/spec/data_block.rs | 4 +- 5 files changed, 299 insertions(+), 43 deletions(-) diff --git a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs index 43b749b27..e0bf77ec4 100644 --- a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs +++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs @@ -14,6 +14,8 @@ //! The BigInt type has no implicit conversions in the ECMAScript language; //! programmers must call BigInt explicitly to convert values from other types. +use std::{convert::Infallible, hint::unreachable_unchecked}; + use num_bigint::Sign; use wtf8::Wtf8; @@ -1423,30 +1425,40 @@ pub(crate) fn canonical_numeric_index_string<'gc>( /// 2. If integer is not in the inclusive interval from 0 to 2**53 - 1, throw a /// RangeError exception. +#[inline] pub(crate) fn validate_index<'a>( agent: &mut Agent, value: i64, gc: NoGcScope<'a, '_>, ) -> JsResult<'a, u64> { if !(0..=(SmallInteger::MAX)).contains(&value) { - return Err(agent.throw_exception_with_static_message( - ExceptionType::RangeError, - "Index is out of range", - gc, - )); + return throw_index_out_of_range(agent, gc).map(|_| unreachable!()); } Ok(value as u64) } +#[inline(never)] +#[cold] +fn throw_index_out_of_range<'gc>( + agent: &mut Agent, + gc: NoGcScope<'gc, '_>, +) -> JsResult<'gc, Infallible> { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "Index is out of range", + gc, + )); +} + /// ### [7.1.22 ToIndex ( value )](https://tc39.es/ecma262/#sec-toindex) pub(crate) fn to_index<'a>( agent: &mut Agent, argument: Value, mut gc: GcScope<'a, '_>, -) -> JsResult<'a, i64> { +) -> JsResult<'a, u64> { // Fast path: A safe integer is already an integer. if let Value::Integer(integer) = argument { - return validate_index(agent, integer.into_i64(), gc.into_nogc()).map(|i| i as i64); + return validate_index(agent, integer.into_i64(), gc.into_nogc()).map(|i| i as u64); } // TODO: This can be heavily optimized by inlining `to_integer_or_infinity`. @@ -1457,7 +1469,7 @@ pub(crate) fn to_index<'a>( // 2. If integer is not in the inclusive interval from 0 to 2**53 - 1, throw a RangeError exception. // 3. Return integer. - validate_index(agent, integer, gc.into_nogc()).map(|i| i as i64) + validate_index(agent, integer, gc.into_nogc()).map(|i| i as u64) } /// ### [7.1.22 ToIndex ( value )](https://tc39.es/ecma262/#sec-toindex) @@ -1466,10 +1478,10 @@ pub(crate) fn try_to_index<'a>( agent: &mut Agent, argument: Value, gc: NoGcScope<'a, '_>, -) -> TryResult<'a, i64> { +) -> TryResult<'a, u64> { // Fast path: A safe integer is already an integer. if let Value::Integer(integer) = argument { - return js_result_into_try(validate_index(agent, integer.into_i64(), gc).map(|i| i as i64)); + return js_result_into_try(validate_index(agent, integer.into_i64(), gc)); } // TODO: This can be heavily optimized by inlining `to_integer_or_infinity`. @@ -1478,7 +1490,7 @@ pub(crate) fn try_to_index<'a>( // 2. If integer is not in the inclusive interval from 0 to 2**53 - 1, throw a RangeError exception. // 3. Return integer. - js_result_into_try(validate_index(agent, integer, gc).map(|i| i as i64)) + js_result_into_try(validate_index(agent, integer, gc)) } /// Helper function to check if a `char` is trimmable. diff --git a/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs b/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs index e33305cd6..47f8d6263 100644 --- a/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs @@ -8,7 +8,9 @@ use super::{AnyArrayBuffer, ArrayBuffer, InternalBuffer}; use crate::{ ecmascript::{ abstract_operations::{operations_on_objects::get, type_conversion::to_index}, - builtins::ordinary::ordinary_create_from_constructor, + builtins::{ + ordinary::ordinary_create_from_constructor, structured_data::data_view_objects, + }, execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType}, types::{ BUILTIN_STRING_MEMORY, Function, Number, Numeric, Object, SharedDataBlock, Value, @@ -414,31 +416,87 @@ pub(crate) fn set_value_in_buffer( /// a BigInt), and op (a read-modify-write modification function) and returns a /// Number or a BigInt. #[expect(dead_code)] -pub(crate) fn get_modify_set_value_in_buffer( - _array_buffer: ArrayBuffer, - _byte_index: u32, - _type: (), - _value: Number, - _op: (), -) { +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> { // 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. - // 4. Let block be arrayBuffer.[[ArrayBufferData]]. - // 5. Let elementSize be the Element Size value specified in Table 71 for Element Type type. - // 6. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. + debug_assert!(!array_buffer.is_detached(agent)); + // 2. Assert: There are sufficient bytes in arrayBuffer starting at + // byteIndex to represent a value of type. + debug_assert!( + byte_index + size_of::() <= array_buffer.byte_length(agent, Ordering::Unordered) + ); + // 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; + // otherwise, value is a Number. + debug_assert!(value.is_bigint() == Type::IS_BIGINT); + // 6. Let isLittleEndian be 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. Let rawBytesRead be a List of length elementSize whose elements are nondeterministically chosen byte values. - // d. NOTE: In implementations, rawBytesRead is the result of a load-link, of a load-exclusive, or of an operand of a read-modify-write instruction on the underlying hardware. The nondeterminism is a semantic prescription of the memory model to describe observable behaviour of hardware with weak consistency. - // e. Let rmwEvent be ReadModifyWriteSharedMemory { [[Order]]: SEQ-CST, [[NoTear]]: true, [[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize, [[Payload]]: rawBytes, [[ModifyOp]]: op }. - // f. Append rmwEvent to eventsRecord.[[EventList]]. - // g. Append Chosen Value Record { [[Event]]: rmwEvent, [[ChosenValue]]: rawBytesRead } to execution.[[ChosenValues]]. - // 9. Else, - // a. Let rawBytesRead be a List of length elementSize whose elements are the sequence of elementSize bytes starting with block[byteIndex]. - // b. Let rawBytesModified be op(rawBytesRead, rawBytes). - // c. Store the individual bytes of rawBytesModified into block, starting at block[byteIndex]. - // 10. Return RawBytesToNumeric(type, rawBytesRead, isLittleEndian). + let raw_bytes = Type::from_ne_value(agent, value); + match array_buffer { + AnyArrayBuffer::ArrayBuffer(array_buffer) => { + let op = get_array_buffer_op::(); + // 4. Let block be arrayBuffer.[[ArrayBufferData]]. + let block = array_buffer.as_mut_slice(agent); + // 5. Let elementSize be the Element Size value specified in Table + // 71 for Element Type type. + let element_size = size_of::(); + // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then + // 9. Else, + let slot = &mut block[byte_index..byte_index + element_size]; + // SAFETY: Viewable types are safe to transmute. + let (head, slot, tail) = unsafe { slot.align_to_mut::() }; + debug_assert!(head.is_empty() && tail.is_empty()); + // a. Let rawBytesRead be a List of length elementSize whose + // elements are the sequence of elementSize bytes starting with + // block[byteIndex]. + let raw_bytes_read = slot[0]; + // b. Let rawBytesModified be op(rawBytesRead, rawBytes). + let data_modified = op(raw_bytes_read, raw_bytes); + // c. Store the individual bytes of rawBytesModified into block, + // starting at block[byteIndex]. + slot[0] = data_modified; + // 10. Return RawBytesToNumeric(type, rawBytesRead, isLittleEndian). + raw_bytes_read.into_ne_value(agent, gc) + } + AnyArrayBuffer::SharedArrayBuffer(array_buffer) => { + let foo = array_buffer.as_slice(agent); + // 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. Let rawBytesRead be a List of length elementSize whose + // elements are nondeterministically chosen byte values. + // d. NOTE: In implementations, rawBytesRead is the result of a + // load-link, of a load-exclusive, or of an operand of a + // read-modify-write instruction on the underlying hardware. The + // nondeterminism is a semantic prescription of the memory model + // to describe observable behaviour of hardware with weak + // consistency. + // e. Let rmwEvent be ReadModifyWriteSharedMemory { .. }. + // f. Append rmwEvent to eventsRecord.[[EventList]]. + // g. Append Chosen Value Record { [[Event]]: rmwEvent, + // [[ChosenValue]]: rawBytesRead } to execution.[[ChosenValues]]. + } + } +} + +type ModifyOp = fn(T, T) -> T; + +const fn ab_add(left: T, right: T) -> T { + left + right +} + +const fn get_array_buffer_op() -> ModifyOp { + if Op == 0 { + // Add + return fn() + } + todo!(); } diff --git a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs index aa3660b86..340081baf 100644 --- a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs +++ b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs @@ -6,12 +6,22 @@ use crate::ecmascript::execution::agent::ExceptionType; use crate::{ ecmascript::{ + abstract_operations::type_conversion::{to_index, try_to_index, validate_index}, builders::ordinary_object_builder::OrdinaryObjectBuilder, - builtins::{ArgumentsList, Behaviour, Builtin}, - execution::{Agent, JsResult, Realm}, - types::{BUILTIN_STRING_MEMORY, String, Value}, + builtins::{ + array_buffer::{get_modify_set_value_in_buffer, AnyArrayBuffer}, indexed_collections::typed_array_objects::abstract_operations::{ + validate_typed_array, TypedArrayAbstractOperations, TypedArrayWithBufferWitnessRecords + }, typed_array::{for_any_typed_array, AnyTypedArray}, ArgumentsList, Behaviour, Builtin + }, + execution::{ + agent::{try_result_into_js, ExceptionType}, Agent, JsResult, Realm + }, + types::{Numeric, String, Value, BUILTIN_STRING_MEMORY}, + }, + engine::{ + context::{Bindable, GcScope}, + rootable::Scopable, }, - engine::context::GcScope, heap::WellKnownSymbolIndexes, }; @@ -135,10 +145,11 @@ impl Builtin for AtomicsObjectPause { } impl AtomicsObject { + /// ### [25.4.4 Atomics.add ( typedArray, index, value )](https://tc39.es/ecma262/#sec-atomics.add) fn add<'gc>( agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, + arguments: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { Err(agent.todo("Atomics.add", gc.into_nogc())) @@ -360,3 +371,116 @@ impl AtomicsObject { builder.build(); } } + +/// ### [25.4.3.17 AtomicReadModifyWrite ( typedArray, index, value, op )](https://tc39.es/ecma262/#sec-atomicreadmodifywrite) +/// +/// The abstract operation AtomicReadModifyWrite takes arguments typedArray (an +/// ECMAScript language value), index (an ECMAScript language value), value (an +/// ECMAScript language value), and op (a read-modify-write modification +/// function) and returns either a normal completion containing either a Number +/// or a BigInt, or a throw completion. op takes two List of byte values +/// arguments and returns a List of byte values. This operation atomically +/// loads a value, combines it with another value, and stores the combination. +/// It returns the loaded value. +fn atomic_read_modify_write<'gc, const Op: u8>( + agent: &mut Agent, + typed_array: Value, + index: Value, + value: Value, + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Numeric<'gc>> { + let typed_array = typed_array.bind(gc.nogc()); + let index = index.bind(gc.nogc()); + let value = value.bind(gc.nogc()); + + // 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index). + let ta_record = validate_typed_array( + agent, + typed_array, + ecmascript_atomics::Ordering::Unordered, + gc.nogc(), + ) + .unbind()? + .bind(gc.nogc()); + // a. Let type be TypedArrayElementType(typedArray). + // b. If IsUnclampedIntegerElementType(type) is false and + // IsBigIntElementType(type) is false, throw a TypeError exception. + if !ta_record.object.is_integer() { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "cannot use TypedArray in Atomics", + gc.into_nogc(), + )); + } + // 1. Let length be TypedArrayLength(taRecord). + let length = ta_record.typed_array_length(agent); + let index = if let (Value::Integer(index), Ok(value)) = (index, Numeric::try_from(value)) { + let gc = gc.into_nogc(); + // 2. Let accessIndex be ? ToIndex(requestIndex). + let access_index = validate_index(agent, index.into_i64(), gc)?; + // 3. If accessIndex ≥ length, throw a RangeError exception. + if access_index >= length as u64 { + todo!(); + } + // 5. Let typedArray be taRecord.[[Object]]. + let typed_array = ta_record.object; + // 7. Let offset be typedArray.[[ByteOffset]]. + let offset = typed_array.byte_offset(agent); + // 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value). + if typed_array.is_bigint() != value.is_bigint() { + todo!(); + } + let byte_index_in_buffer = offset + access_index as usize * typed_array.typed_array_element_size(); + // 5. Let buffer be typedArray.[[ViewedArrayBuffer]]. + let buffer = typed_array.viewed_array_buffer(agent); + // 6. Let elementType be TypedArrayElementType(typedArray). + // 7. Return GetModifySetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, op). + for_any_typed_array!(typed_array, _t, { + get_modify_set_value_in_buffer::(agent, buffer, byte_index_in_buffer, value, gc) + }, ElementType) + } else { + atomic_read_modify_write_slow( + agent, + ta_record.unbind(), + index.unbind(), + value.unbind(), + gc, + ) + } + // 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value). + // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). + // 4. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer). + // 5. Let buffer be typedArray.[[ViewedArrayBuffer]]. + // 6. Let elementType be TypedArrayElementType(typedArray). + // 7. Return GetModifySetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, op). +} + +#[inline(never)] +#[cold] +fn atomic_read_modify_write_slow<'gc>( + agent: &mut Agent, + ta_record: TypedArrayWithBufferWitnessRecords, + index: Value, + value: Value, + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Numeric<'gc>> { + if let Some(index) = try_result_into_js(try_to_index(agent, index, gc.nogc())).unbind()? { + todo!() + } else { + let ta = ta_record.object.scope(agent, gc.nogc()); + let cached_buffer_byte_length = ta_record.cached_buffer_byte_length; + let value = value.scope(agent, gc.nogc()); + let index = to_index(agent, index, gc).unbind()?; + // SAFETY: not shared. + let (ta_record, value) = unsafe { + ( + TypedArrayWithBufferWitnessRecords { + object: ta.take(agent), + cached_buffer_byte_length, + }, + value.take(agent), + ) + }; + todo!() + } +} diff --git a/nova_vm/src/ecmascript/builtins/typed_array/any_typed_array.rs b/nova_vm/src/ecmascript/builtins/typed_array/any_typed_array.rs index 9b6c9176f..40824bf4e 100644 --- a/nova_vm/src/ecmascript/builtins/typed_array/any_typed_array.rs +++ b/nova_vm/src/ecmascript/builtins/typed_array/any_typed_array.rs @@ -127,6 +127,66 @@ impl AnyTypedArray<'_> { } } + /// Returns true if the TypedArray is an Int32Array or BigInt64Array + /// (shared or not), false otherwise. + pub(crate) fn is_waitable(self) -> bool { + #[cfg(not(feature = "shared-array-buffer"))] + { + matches!(self, Self::Int32Array(_) | Self::BigInt64Array(_)) + } + #[cfg(feature = "shared-array-buffer")] + { + matches!( + self, + Self::Int32Array(_) + | Self::BigInt64Array(_) + | Self::SharedInt32Array(_) + | Self::SharedBigInt64Array(_) + ) + } + } + + /// Returns true if the TypedArray contains integers with wrapping overflow + /// semantics. + pub(crate) fn is_integer(self) -> bool { + #[cfg(not(feature = "shared-array-buffer"))] + { + matches!( + self, + Self::Uint8Array(_) + | Self::Int8Array(_) + | Self::Uint16Array(_) + | Self::Int16Array(_) + | Self::Uint32Array(_) + | Self::Int32Array(_) + | Self::BigUint64Array(_) + | Self::BigInt64Array(_) + ) + } + #[cfg(feature = "shared-array-buffer")] + { + matches!( + self, + Self::Uint8Array(_) + | Self::Int8Array(_) + | Self::Uint16Array(_) + | Self::Int16Array(_) + | Self::Uint32Array(_) + | Self::Int32Array(_) + | Self::BigUint64Array(_) + | Self::BigInt64Array(_) + | Self::SharedUint8Array(_) + | Self::SharedInt8Array(_) + | Self::SharedUint16Array(_) + | Self::SharedInt16Array(_) + | Self::SharedUint32Array(_) + | Self::SharedInt32Array(_) + | Self::SharedBigUint64Array(_) + | Self::SharedBigInt64Array(_) + ) + } + } + pub(crate) fn intrinsic_default_constructor(self) -> ProtoIntrinsics { match self { Self::Int8Array(_) => ProtoIntrinsics::Int8Array, diff --git a/nova_vm/src/ecmascript/types/spec/data_block.rs b/nova_vm/src/ecmascript/types/spec/data_block.rs index 3626c326e..f52d81998 100644 --- a/nova_vm/src/ecmascript/types/spec/data_block.rs +++ b/nova_vm/src/ecmascript/types/spec/data_block.rs @@ -1234,7 +1234,9 @@ mod private { impl Sealed for f64 {} } -pub trait Viewable: 'static + private::Sealed + Copy + PartialEq + core::fmt::Debug { +pub trait Viewable: + 'static + private::Sealed + Copy + PartialEq + core::fmt::Debug + core::ops::Add +{ /// Type of the data in its storage format. This is used with /// SharedDataBlock. type Storage: RacyStorage; From 0c57778332b46a30ce68942d655badfaf16b85fc Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 23:00:24 +0300 Subject: [PATCH 2/3] work towards supporting Atomics.add --- Cargo.toml | 2 +- .../abstract_operations/type_conversion.rs | 2 +- .../array_buffer/abstract_operations.rs | 47 ++-- .../structured_data/atomics_object.rs | 49 ++-- .../typed_array/shared_typed_array.rs | 88 ++++--- .../src/ecmascript/types/spec/data_block.rs | 214 +++++++++--------- 6 files changed, 214 insertions(+), 188 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cb4a5d6cc..98af42107 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ clap = { version = "4.5.48", features = ["derive"] } cliclack = "0.3.6" console = "0.15.11" ctrlc = "3.5.0" -ecmascript_atomics = "0.1.4" +ecmascript_atomics = "0.2.2" fast-float = "0.2.0" hashbrown = "0.16.0" lexical = { version = "7.0.5", default-features = false, features = [ diff --git a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs index e0bf77ec4..dac2eab10 100644 --- a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs +++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs @@ -14,7 +14,7 @@ //! The BigInt type has no implicit conversions in the ECMAScript language; //! programmers must call BigInt explicitly to convert values from other types. -use std::{convert::Infallible, hint::unreachable_unchecked}; +use std::convert::Infallible; use num_bigint::Sign; use wtf8::Wtf8; diff --git a/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs b/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs index 47f8d6263..76b81bcc6 100644 --- a/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs @@ -8,13 +8,11 @@ use super::{AnyArrayBuffer, ArrayBuffer, InternalBuffer}; use crate::{ ecmascript::{ abstract_operations::{operations_on_objects::get, type_conversion::to_index}, - builtins::{ - ordinary::ordinary_create_from_constructor, structured_data::data_view_objects, - }, + builtins::ordinary::ordinary_create_from_constructor, execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType}, types::{ - BUILTIN_STRING_MEMORY, Function, Number, Numeric, Object, SharedDataBlock, Value, - Viewable, create_byte_data_block, + BUILTIN_STRING_MEMORY, Function, Numeric, Object, SharedDataBlock, Value, Viewable, + create_byte_data_block, }, }, engine::context::{Bindable, GcScope, NoGcScope}, @@ -423,6 +421,10 @@ pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const Op: u8>( value: Numeric, gc: NoGcScope<'gc, '_>, ) -> Numeric<'gc> { + // 5. Let elementSize be the Element Size value specified in Table + // 71 for Element Type type. + let element_size = size_of::(); + // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. debug_assert!(!array_buffer.is_detached(agent)); // 2. Assert: There are sufficient bytes in arrayBuffer starting at @@ -437,14 +439,11 @@ pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const Op: u8>( // of the surrounding agent's Agent Record. // 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian). let raw_bytes = Type::from_ne_value(agent, value); - match array_buffer { + let raw_bytes_read = match array_buffer { AnyArrayBuffer::ArrayBuffer(array_buffer) => { let op = get_array_buffer_op::(); // 4. Let block be arrayBuffer.[[ArrayBufferData]]. let block = array_buffer.as_mut_slice(agent); - // 5. Let elementSize be the Element Size value specified in Table - // 71 for Element Type type. - let element_size = size_of::(); // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then // 9. Else, let slot = &mut block[byte_index..byte_index + element_size]; @@ -460,12 +459,22 @@ pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const Op: u8>( // c. Store the individual bytes of rawBytesModified into block, // starting at block[byteIndex]. slot[0] = data_modified; - // 10. Return RawBytesToNumeric(type, rawBytesRead, isLittleEndian). - raw_bytes_read.into_ne_value(agent, gc) + raw_bytes_read } AnyArrayBuffer::SharedArrayBuffer(array_buffer) => { - let foo = array_buffer.as_slice(agent); // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then + let raw_bytes = Type::into_storage(raw_bytes); + let foo = array_buffer.as_slice(agent); + let slot = foo.slice(byte_index, byte_index + element_size); + let (head, slot, tail) = slot.align_to::(); + debug_assert!(head.is_empty() && tail.is_empty()); + let slot = slot.get(0).unwrap(); + let result = if const { Op == 0 } { + // Add + slot.fetch_add(raw_bytes) + } else { + panic!("Unsupported Op value"); + }; // a. Let execution be the [[CandidateExecution]] field of the // surrounding agent's Agent Record. // b. Let eventsRecord be the Agent Events Record of @@ -483,20 +492,20 @@ pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const Op: u8>( // f. Append rmwEvent to eventsRecord.[[EventList]]. // g. Append Chosen Value Record { [[Event]]: rmwEvent, // [[ChosenValue]]: rawBytesRead } to execution.[[ChosenValues]]. + Type::from_storage(result) } - } + }; + // 10. Return RawBytesToNumeric(type, rawBytesRead, isLittleEndian). + raw_bytes_read.into_ne_value(agent, gc) } type ModifyOp = fn(T, T) -> T; -const fn ab_add(left: T, right: T) -> T { - left + right -} - const fn get_array_buffer_op() -> ModifyOp { if Op == 0 { // Add - return fn() + ::add + } else { + panic!("Unsupported Op value"); } - todo!(); } diff --git a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs index 340081baf..ba7325349 100644 --- a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs +++ b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs @@ -9,14 +9,19 @@ use crate::{ abstract_operations::type_conversion::{to_index, try_to_index, validate_index}, builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ - array_buffer::{get_modify_set_value_in_buffer, AnyArrayBuffer}, indexed_collections::typed_array_objects::abstract_operations::{ - validate_typed_array, TypedArrayAbstractOperations, TypedArrayWithBufferWitnessRecords - }, typed_array::{for_any_typed_array, AnyTypedArray}, ArgumentsList, Behaviour, Builtin + ArgumentsList, Behaviour, Builtin, + array_buffer::get_modify_set_value_in_buffer, + indexed_collections::typed_array_objects::abstract_operations::{ + TypedArrayAbstractOperations, TypedArrayWithBufferWitnessRecords, + validate_typed_array, + }, + typed_array::{AnyTypedArray, for_any_typed_array}, }, execution::{ - agent::{try_result_into_js, ExceptionType}, Agent, JsResult, Realm + Agent, JsResult, Realm, + agent::{ExceptionType, try_result_into_js}, }, - types::{Numeric, String, Value, BUILTIN_STRING_MEMORY}, + types::{BUILTIN_STRING_MEMORY, Numeric, String, Value}, }, engine::{ context::{Bindable, GcScope}, @@ -414,8 +419,13 @@ fn atomic_read_modify_write<'gc, const Op: u8>( } // 1. Let length be TypedArrayLength(taRecord). let length = ta_record.typed_array_length(agent); - let index = if let (Value::Integer(index), Ok(value)) = (index, Numeric::try_from(value)) { + if let (Value::Integer(index), Ok(value)) = (index, Numeric::try_from(value)) { + // 7. Let offset be typedArray.[[ByteOffset]]. + let typed_array = ta_record.object.unbind(); + let value = value.unbind(); let gc = gc.into_nogc(); + let value = value.bind(gc); + let typed_array = typed_array.bind(gc); // 2. Let accessIndex be ? ToIndex(requestIndex). let access_index = validate_index(agent, index.into_i64(), gc)?; // 3. If accessIndex ≥ length, throw a RangeError exception. @@ -423,21 +433,31 @@ fn atomic_read_modify_write<'gc, const Op: u8>( todo!(); } // 5. Let typedArray be taRecord.[[Object]]. - let typed_array = ta_record.object; - // 7. Let offset be typedArray.[[ByteOffset]]. let offset = typed_array.byte_offset(agent); // 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value). if typed_array.is_bigint() != value.is_bigint() { todo!(); } - let byte_index_in_buffer = offset + access_index as usize * typed_array.typed_array_element_size(); + let byte_index_in_buffer = + offset + access_index as usize * typed_array.typed_array_element_size(); // 5. Let buffer be typedArray.[[ViewedArrayBuffer]]. let buffer = typed_array.viewed_array_buffer(agent); // 6. Let elementType be TypedArrayElementType(typedArray). // 7. Return GetModifySetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, op). - for_any_typed_array!(typed_array, _t, { - get_modify_set_value_in_buffer::(agent, buffer, byte_index_in_buffer, value, gc) - }, ElementType) + return Ok(for_any_typed_array!( + typed_array, + _t, + { + get_modify_set_value_in_buffer::( + agent, + buffer, + byte_index_in_buffer, + value, + gc, + ) + }, + ElementType + )); } else { atomic_read_modify_write_slow( agent, @@ -445,8 +465,9 @@ fn atomic_read_modify_write<'gc, const Op: u8>( index.unbind(), value.unbind(), gc, - ) - } + ); + todo!() + }; // 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value). // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). // 4. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer). diff --git a/nova_vm/src/ecmascript/builtins/typed_array/shared_typed_array.rs b/nova_vm/src/ecmascript/builtins/typed_array/shared_typed_array.rs index 6dfcf86ec..76fcd4c77 100644 --- a/nova_vm/src/ecmascript/builtins/typed_array/shared_typed_array.rs +++ b/nova_vm/src/ecmascript/builtins/typed_array/shared_typed_array.rs @@ -1854,8 +1854,8 @@ impl<'a, T: Viewable> TypedArrayAbstractOperations<'a> for GenericSharedTypedArr let value = T::into_storage(T::from_ne_value(agent, value)); let slice = self.as_slice(agent); let slice = slice.slice_from(start_index).slice_to(count); - for i in 0..slice.len() { - slice.store(i, value, Ordering::Unordered).unwrap() + for item in slice.iter() { + item.store(value, Ordering::Unordered); } } @@ -1892,14 +1892,15 @@ impl<'a, T: Viewable> TypedArrayAbstractOperations<'a> for GenericSharedTypedArr .clone(); let slice = sdb_as_viewable_slice::(&sdb, byte_offset, byte_length); debug_assert!(slice.len() >= len); + let slice = slice.slice_to(len); // 6. Let captured be 0. let mut captured = 0; // 7. Let k be 0. // 8. Repeat, while k < len, - for k in 0..len { + for (k, k_item) in slice.iter().enumerate() { // b. Let kValue be ! Get(O, Pk). - let value = T::from_storage(slice.load(k, Ordering::Unordered).unwrap()); + let value = T::from_storage(k_item.load(Ordering::Unordered)); let k_value = value.into_le_value(agent, gc.nogc()).into_value(); let result = call_function( agent, @@ -1981,28 +1982,20 @@ impl<'a, T: Viewable> TypedArrayAbstractOperations<'a> for GenericSharedTypedArr ) -> Option { let search_element = T::into_storage(T::try_from_value(agent, search_element)?); let slice = self.as_slice(agent); - // Length of the TypedArray may have changed between when we measured it - // and here: We'll never try to access past the boundary of the slice if - // the backing ArrayBuffer shrank. - let end = end.min(slice.len()); - if start >= end { - return None; - } - + // Note: length of SAB cannot have shrunk. if ASCENDING { - for i in start..end { - if slice.load(i, Ordering::Unordered).unwrap() == search_element { - return Some(i + start); - } - } + slice + .slice(start, end) + .iter() + .position(|r| r.load(Ordering::Unordered) == search_element) + .map(|pos| pos + start) } else { - for i in (0..=start).rev() { - if slice.load(i, Ordering::Unordered).unwrap() == search_element { - return Some(i); - } - } + let end = start.saturating_add(1).min(slice.len()); + slice + .slice_to(end) + .iter() + .rposition(|r| r.load(Ordering::Unordered) == search_element) } - None } fn map<'gc>( @@ -2030,6 +2023,7 @@ impl<'a, T: Viewable> TypedArrayAbstractOperations<'a> for GenericSharedTypedArr .clone(); let slice = sdb_as_viewable_slice::(&sdb, byte_offset, byte_length); debug_assert!(slice.len() >= len); + let slice = slice.slice_to(len); // 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »). let a = @@ -2040,12 +2034,12 @@ impl<'a, T: Viewable> TypedArrayAbstractOperations<'a> for GenericSharedTypedArr // 6. Let k be 0. // 7. Repeat, while k < len, let a = a.scope(agent, gc.nogc()); - for k in 0..len { + for (k, k_item) in slice.iter().enumerate() { // 𝔽(k) // a. Let Pk be ! ToString(𝔽(k)). let pk = PropertyKey::try_from(k).unwrap(); // b. Let kValue be ! Get(O, Pk). - let value = T::from_storage(slice.load(k, Ordering::Unordered).unwrap()); + let value = T::from_storage(k_item.load(Ordering::Unordered)); let k_value = value.into_le_value(agent, gc.nogc()).into_value(); // c. Let mappedValue be ? Call(callback, thisArg, « kValue, 𝔽(k), O »). let mapped_value = call_function( @@ -2089,15 +2083,17 @@ impl<'a, T: Viewable> TypedArrayAbstractOperations<'a> for GenericSharedTypedArr // a. Let upper be len - lower - 1. let upper = len - lower - 1; // b. Let upperP be ! ToString(𝔽(upper)). + let o_upper_p = slice.get(upper).unwrap(); // c. Let lowerP be ! ToString(𝔽(lower)). + let o_lower_p = slice.get(lower).unwrap(); // d. Let lowerValue be ! Get(O, lowerP). - let lower_value = slice.load(lower, Ordering::Unordered).unwrap(); + let lower_value = o_lower_p.load(Ordering::Unordered); // e. Let upperValue be ! Get(O, upperP). - let upper_value = slice.load(upper, Ordering::Unordered).unwrap(); + let upper_value = o_upper_p.load(Ordering::Unordered); // f. Perform ! Set(O, lowerP, upperValue, true). - slice.store(lower, upper_value, Ordering::Unordered); + o_lower_p.store(upper_value, Ordering::Unordered); // g. Perform ! Set(O, upperP, lowerValue, true). - slice.store(upper, lower_value, Ordering::Unordered); + o_upper_p.store(lower_value, Ordering::Unordered); // h. Set lower to lower + 1. lower += 1; } @@ -2350,8 +2346,8 @@ impl<'a, T: Viewable> TypedArrayAbstractOperations<'a> for GenericSharedTypedArr let ta = self.bind(gc.nogc()); let slice = ta.as_slice(agent).slice_to(len); let mut items: Vec = Vec::with_capacity(slice.len()); - for i in 0..slice.len() { - items.push(T::from_storage(slice.load(i, Ordering::Unordered).unwrap())); + for item in slice.iter() { + items.push(T::from_storage(item.load(Ordering::Unordered))); } let mut error: Option = None; let ta = ta.scope(agent, gc.nogc()); @@ -2454,17 +2450,17 @@ fn copy_between_shared_typed_arrays( let (head, source, _) = source.align_to::(); assert!(head.is_empty()); assert_eq!(target.len(), source.len()); - if Target::IS_FLOAT { - for i in 0..source.len() { - let src = Source::from_storage(source.load(i, Ordering::Unordered).unwrap()); + if Target::IS_FLOAT || Source::IS_FLOAT { + for (src, target) in source.iter().zip(target.iter()) { + let src = Source::from_storage(src.load(Ordering::Unordered)); let value = Target::from_f64(src.into_f64()); - target.store(i, Target::into_storage(value), Ordering::Unordered); + target.store(Target::into_storage(value), Ordering::Unordered); } } else { - for i in 0..source.len() { - let src = Source::from_storage(source.load(i, Ordering::Unordered).unwrap()); + for (src, target) in source.iter().zip(target.iter()) { + let src = Source::from_storage(src.load(Ordering::Unordered)); let value = Target::from_bits(src.into_bits()); - target.store(i, Target::into_storage(value), Ordering::Unordered); + target.store(Target::into_storage(value), Ordering::Unordered); } } }; @@ -2488,13 +2484,13 @@ pub(crate) fn copy_from_shared_typed_array( assert!(head.is_empty()); assert_eq!(target.len(), source.len()); if Target::IS_FLOAT { - for (i, target) in target.iter_mut().enumerate() { - let src = Source::from_storage(source.load(i, Ordering::Unordered).unwrap()); + for (source, target) in source.iter().zip(target.iter_mut()) { + let src = Source::from_storage(source.load(Ordering::Unordered)); *target = Target::from_f64(src.into_f64()); } } else { - for (i, target) in target.iter_mut().enumerate() { - let src = Source::from_storage(source.load(i, Ordering::Unordered).unwrap()); + for (source, target) in source.iter().zip(target.iter_mut()) { + let src = Source::from_storage(source.load(Ordering::Unordered)); *target = Target::from_bits(src.into_bits()); } } @@ -2517,14 +2513,14 @@ fn copy_into_shared_typed_array( assert!(head.is_empty()); assert_eq!(target.len(), source.len()); if Target::IS_FLOAT { - for (i, source) in source.iter().enumerate() { + for (source, target) in source.iter().zip(target.iter()) { let value = Target::into_storage(Target::from_f64(source.into_f64())); - target.store(i, value, Ordering::Unordered); + target.store(value, Ordering::Unordered); } } else { - for (i, source) in source.iter().enumerate() { + for (source, target) in source.iter().zip(target.iter()) { let value = Target::into_storage(Target::from_bits(source.into_bits())); - target.store(i, value, Ordering::Unordered); + target.store(value, Ordering::Unordered); } } }; diff --git a/nova_vm/src/ecmascript/types/spec/data_block.rs b/nova_vm/src/ecmascript/types/spec/data_block.rs index f52d81998..ef98aa4d8 100644 --- a/nova_vm/src/ecmascript/types/spec/data_block.rs +++ b/nova_vm/src/ecmascript/types/spec/data_block.rs @@ -738,55 +738,35 @@ impl SharedDataBlock { order: ECMAScriptOrdering, ) -> Option { let slice = self.as_racy_slice().slice_from(byte_offset); - if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. unsafe { core::mem::transmute_copy::, Option>( - &slice.as_u8().map(|t| t.load(order)), + &slice.get(0).map(|s| s.load(order)), ) } - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. unsafe { core::mem::transmute_copy::, Option>( - &slice.as_u16().map(|t| t.load(order)), + &slice.as_aligned::().map(|t| t.load(order)), ) } - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. unsafe { core::mem::transmute_copy::, Option>( - &slice.as_u32().map(|t| t.load(order)), + &slice.as_aligned::().map(|t| t.load(order)), ) } - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. unsafe { core::mem::transmute_copy::, Option>( - &slice.as_u64().map(|t| t.load(order)), + &slice.as_aligned::().map(|t| t.load(order)), ) } } else { - #[cfg(feature = "proposal-float16array")] - if core::any::TypeId::of::() == core::any::TypeId::of::() { - // SAFETY: Type checked to match. - return unsafe { - core::mem::transmute_copy::, Option>( - &slice.as_u16().map(|t| t.load(order)), - ) - }; - } unreachable!("Unexpected load type") } } @@ -804,37 +784,27 @@ impl SharedDataBlock { #[inline(always)] pub(crate) fn load_unaligned(&self, byte_offset: usize) -> Option { let slice = self.as_racy_slice().slice_from(byte_offset); - if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. - unsafe { core::mem::transmute_copy::, Option>(&slice.load_u8()) } - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + unsafe { + core::mem::transmute_copy::, Option>(&slice.load_unaligned::()) + } + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. - unsafe { core::mem::transmute_copy::, Option>(&slice.load_u16()) } - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + unsafe { + core::mem::transmute_copy::, Option>(&slice.load_unaligned::()) + } + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. - unsafe { core::mem::transmute_copy::, Option>(&slice.load_u32()) } - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + unsafe { + core::mem::transmute_copy::, Option>(&slice.load_unaligned::()) + } + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. - unsafe { core::mem::transmute_copy::, Option>(&slice.load_u64()) } - } else { - #[cfg(feature = "proposal-float16array")] - if core::any::TypeId::of::() == core::any::TypeId::of::() { - // SAFETY: Type checked to match. - return unsafe { - core::mem::transmute_copy::, Option>(&slice.load_u16()) - }; + unsafe { + core::mem::transmute_copy::, Option>(&slice.load_unaligned::()) } + } else { unreachable!("Unexpected load_unaligned type") } } @@ -854,40 +824,23 @@ impl SharedDataBlock { order: ECMAScriptOrdering, ) -> Option<()> { let slice = self.as_racy_slice().slice_from(byte_offset); - if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. let val = unsafe { core::mem::transmute_copy::(&val) }; - slice.as_u8().map(|t| t.store(val, order)) - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + slice.as_aligned::().map(|t| t.store(val, order)) + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. let val = unsafe { core::mem::transmute_copy::(&val) }; - slice.as_u16().map(|t| t.store(val, order)) - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + slice.as_aligned::().map(|t| t.store(val, order)) + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. let val = unsafe { core::mem::transmute_copy::(&val) }; - slice.as_u32().map(|t| t.store(val, order)) - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + slice.as_aligned::().map(|t| t.store(val, order)) + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. let val = unsafe { core::mem::transmute_copy::(&val) }; - slice.as_u64().map(|t| t.store(val, order)) + slice.as_aligned::().map(|t| t.store(val, order)) } else { - #[cfg(feature = "proposal-float16array")] - if core::any::TypeId::of::() == core::any::TypeId::of::() { - // SAFETY: Type checked to match. - let val = unsafe { core::mem::transmute_copy::(&val) }; - return slice.as_u16().map(|t| t.store(val, order)); - } unreachable!("Unexpected read type {:?}", core::any::type_name::()) } } @@ -902,40 +855,23 @@ impl SharedDataBlock { /// There is no write in the Rust world: this should be pretty okay. pub(crate) fn store_unaligned(&self, byte_offset: usize, val: T) -> Option<()> { let slice = self.as_racy_slice().slice_from(byte_offset); - if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. let val = unsafe { core::mem::transmute_copy::(&val) }; - slice.store_u8(val) - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + slice.store_unaligned::(val) + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. let val = unsafe { core::mem::transmute_copy::(&val) }; - slice.store_u16(val) - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + slice.store_unaligned::(val) + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. let val = unsafe { core::mem::transmute_copy::(&val) }; - slice.store_u32(val) - } else if core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - || core::any::TypeId::of::() == core::any::TypeId::of::() - { + slice.store_unaligned::(val) + } else if const { size_of::() == size_of::() } { // SAFETY: Type checked to match. let val = unsafe { core::mem::transmute_copy::(&val) }; - slice.store_u64(val) + slice.store_unaligned::(val) } else { - #[cfg(feature = "proposal-float16array")] - if core::any::TypeId::of::() == core::any::TypeId::of::() { - // SAFETY: Type checked to match. - let val = unsafe { core::mem::transmute_copy::(&val) }; - return slice.store_u16(val); - } unreachable!("Unexpected read type") } } @@ -1234,9 +1170,7 @@ mod private { impl Sealed for f64 {} } -pub trait Viewable: - 'static + private::Sealed + Copy + PartialEq + core::fmt::Debug + core::ops::Add -{ +pub trait Viewable: 'static + private::Sealed + Copy + PartialEq + core::fmt::Debug { /// Type of the data in its storage format. This is used with /// SharedDataBlock. type Storage: RacyStorage; @@ -1314,6 +1248,8 @@ pub trait Viewable: /// Reverses the byte order of the value. fn flip_endian(self) -> Self; + fn add(self, other: Self) -> Self; + /// Compare A and B of a Viewable type and always return an Ordering. /// /// This ordering is the usual total order for integers, and the special @@ -1382,6 +1318,10 @@ impl Viewable for () { panic!("VoidArray is a marker type"); } + fn add(self, _: Self) -> Self { + panic!("VoidArray is a marker type"); + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -1465,6 +1405,11 @@ impl Viewable for u8 { self.swap_bytes() } + #[inline(always)] + fn add(self, other: Self) -> Self { + self.wrapping_add(other) + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -1547,6 +1492,11 @@ impl Viewable for U8Clamped { Self(self.0.swap_bytes()) } + #[inline(always)] + fn add(self, other: Self) -> Self { + Self(self.0.wrapping_add(other.0)) + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -1629,6 +1579,11 @@ impl Viewable for i8 { self.swap_bytes() } + #[inline(always)] + fn add(self, other: Self) -> Self { + self.wrapping_add(other) + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -1711,6 +1666,11 @@ impl Viewable for u16 { self.swap_bytes() } + #[inline(always)] + fn add(self, other: Self) -> Self { + self.wrapping_add(other) + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -1793,6 +1753,11 @@ impl Viewable for i16 { self.swap_bytes() } + #[inline(always)] + fn add(self, other: Self) -> Self { + self.wrapping_add(other) + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -1875,6 +1840,11 @@ impl Viewable for u32 { self.swap_bytes() } + #[inline(always)] + fn add(self, other: Self) -> Self { + self.wrapping_add(other) + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -1957,6 +1927,11 @@ impl Viewable for i32 { self.swap_bytes() } + #[inline(always)] + fn add(self, other: Self) -> Self { + self.wrapping_add(other) + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -2051,6 +2026,11 @@ impl Viewable for u64 { self.swap_bytes() } + #[inline(always)] + fn add(self, other: Self) -> Self { + self.wrapping_add(other) + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -2151,6 +2131,11 @@ impl Viewable for i64 { self.swap_bytes() } + #[inline(always)] + fn add(self, other: Self) -> Self { + self.wrapping_add(other) + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { self.cmp(other) @@ -2237,6 +2222,11 @@ impl Viewable for f16 { Self::from_bits(self.to_bits().swap_bytes()) } + #[inline(always)] + fn add(self, other: Self) -> Self { + self + other + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { if self.is_nan() { @@ -2341,6 +2331,11 @@ impl Viewable for f32 { Self::from_bits(self.to_bits().swap_bytes()) } + #[inline(always)] + fn add(self, other: Self) -> Self { + self + other + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { if self.is_nan() { @@ -2437,6 +2432,11 @@ impl Viewable for f64 { Self::from_bits(self.to_bits().swap_bytes()) } + #[inline(always)] + fn add(self, other: Self) -> Self { + self + other + } + #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { if self.is_nan() { From f120a216baab73408bf0a49c0b8d8c9cd77bb082 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 24 Oct 2025 23:00:59 +0300 Subject: [PATCH 3/3] AtomicReadModifyWrite mostly done --- .../abstract_operations/type_conversion.rs | 4 +- .../array_buffer/abstract_operations.rs | 38 ++- .../structured_data/atomics_object.rs | 304 ++++++++++++++---- .../src/ecmascript/types/spec/data_block.rs | 265 ++++++++++++++- 4 files changed, 533 insertions(+), 78 deletions(-) diff --git a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs index dac2eab10..7b1d5aacd 100644 --- a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs +++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs @@ -1458,7 +1458,7 @@ pub(crate) fn to_index<'a>( ) -> JsResult<'a, u64> { // Fast path: A safe integer is already an integer. if let Value::Integer(integer) = argument { - return validate_index(agent, integer.into_i64(), gc.into_nogc()).map(|i| i as u64); + return validate_index(agent, integer.into_i64(), gc.into_nogc()); } // TODO: This can be heavily optimized by inlining `to_integer_or_infinity`. @@ -1469,7 +1469,7 @@ pub(crate) fn to_index<'a>( // 2. If integer is not in the inclusive interval from 0 to 2**53 - 1, throw a RangeError exception. // 3. Return integer. - validate_index(agent, integer, gc.into_nogc()).map(|i| i as u64) + validate_index(agent, integer, gc.into_nogc()) } /// ### [7.1.22 ToIndex ( value )](https://tc39.es/ecma262/#sec-toindex) diff --git a/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs b/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs index 76b81bcc6..8ad9236f4 100644 --- a/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs @@ -413,8 +413,7 @@ pub(crate) fn set_value_in_buffer( /// non-negative integer), type (a TypedArray element type), value (a Number or /// a BigInt), and op (a read-modify-write modification function) and returns a /// Number or a BigInt. -#[expect(dead_code)] -pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const Op: u8>( +pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const OP: u8>( agent: &mut Agent, array_buffer: AnyArrayBuffer, byte_index: usize, @@ -441,7 +440,7 @@ pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const Op: u8>( 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::(); + let op = get_array_buffer_op::(); // 4. Let block be arrayBuffer.[[ArrayBufferData]]. let block = array_buffer.as_mut_slice(agent); // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then @@ -469,9 +468,19 @@ pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const Op: u8>( let (head, slot, tail) = slot.align_to::(); debug_assert!(head.is_empty() && tail.is_empty()); let slot = slot.get(0).unwrap(); - let result = if const { Op == 0 } { + let result = if const { OP == 0 } { // Add slot.fetch_add(raw_bytes) + } else if const { OP == 1 } { + slot.fetch_and(raw_bytes) + } else if const { OP == 2 } { + slot.swap(raw_bytes) + } else if const { OP == 3 } { + slot.fetch_or(raw_bytes) + } else if const { OP == 4 } { + slot.fetch_add(raw_bytes) + } else if const { OP == 5 } { + slot.fetch_xor(raw_bytes) } else { panic!("Unsupported Op value"); }; @@ -499,12 +508,27 @@ pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const Op: u8>( raw_bytes_read.into_ne_value(agent, gc) } -type ModifyOp = fn(T, T) -> T; +type ModifyOp = fn(T, T) -> T; -const fn get_array_buffer_op() -> ModifyOp { - if Op == 0 { +const fn get_array_buffer_op() -> ModifyOp { + if const { OP == 0 } { // Add ::add + } else if const { OP == 1 } { + // And + ::and + } else if const { OP == 2 } { + // Exchange + ::swap + } else if const { OP == 3 } { + // Or + ::or + } else if const { OP == 4 } { + // Sub + ::sub + } else if const { OP == 5 } { + // Xor + ::xor } else { panic!("Unsupported Op value"); } diff --git a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs index ba7325349..ae1eb8524 100644 --- a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs +++ b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs @@ -6,14 +6,16 @@ use crate::ecmascript::execution::agent::ExceptionType; use crate::{ ecmascript::{ - abstract_operations::type_conversion::{to_index, try_to_index, validate_index}, + abstract_operations::type_conversion::{ + to_big_int, to_index, to_number, try_to_index, validate_index, + }, builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ ArgumentsList, Behaviour, Builtin, array_buffer::get_modify_set_value_in_buffer, indexed_collections::typed_array_objects::abstract_operations::{ TypedArrayAbstractOperations, TypedArrayWithBufferWitnessRecords, - validate_typed_array, + make_typed_array_with_buffer_witness_record, validate_typed_array, }, typed_array::{AnyTypedArray, for_any_typed_array}, }, @@ -21,10 +23,12 @@ use crate::{ Agent, JsResult, Realm, agent::{ExceptionType, try_result_into_js}, }, - types::{BUILTIN_STRING_MEMORY, Numeric, String, Value}, + types::{ + BUILTIN_STRING_MEMORY, BigInt, IntoNumeric, IntoValue, Number, Numeric, String, Value, + }, }, engine::{ - context::{Bindable, GcScope}, + context::{Bindable, GcScope, NoGcScope}, rootable::Scopable, }, heap::WellKnownSymbolIndexes, @@ -157,16 +161,30 @@ impl AtomicsObject { arguments: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Atomics.add", gc.into_nogc())) + atomic_read_modify_write::<0>( + agent, + arguments.get(0), + arguments.get(1), + arguments.get(2), + gc, + ) + .map(|v| v.into_value()) } fn and<'gc>( agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, + arguments: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Atomics.and", gc.into_nogc())) + atomic_read_modify_write::<1>( + agent, + arguments.get(0), + arguments.get(1), + arguments.get(2), + gc, + ) + .map(|v| v.into_value()) } fn compare_exchange<'gc>( @@ -181,10 +199,17 @@ impl AtomicsObject { fn exchange<'gc>( agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, + arguments: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Atomics.exchange", gc.into_nogc())) + atomic_read_modify_write::<2>( + agent, + arguments.get(0), + arguments.get(1), + arguments.get(2), + gc, + ) + .map(|v| v.into_value()) } fn is_lock_free<'gc>( @@ -208,10 +233,17 @@ impl AtomicsObject { fn or<'gc>( agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, + arguments: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Atomics.or", gc.into_nogc())) + atomic_read_modify_write::<3>( + agent, + arguments.get(0), + arguments.get(1), + arguments.get(2), + gc, + ) + .map(|v| v.into_value()) } fn store<'gc>( @@ -226,10 +258,17 @@ impl AtomicsObject { fn sub<'gc>( agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, + arguments: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Atomics.sub", gc.into_nogc())) + atomic_read_modify_write::<4>( + agent, + arguments.get(0), + arguments.get(1), + arguments.get(2), + gc, + ) + .map(|v| v.into_value()) } fn wait<'gc>( @@ -262,10 +301,17 @@ impl AtomicsObject { fn xor<'gc>( agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, + arguments: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Atomics.xor", gc.into_nogc())) + atomic_read_modify_write::<5>( + agent, + arguments.get(0), + arguments.get(1), + arguments.get(2), + gc, + ) + .map(|v| v.into_value()) } /// ### [1 Atomics.pause ( [ N ] )](https://tc39.es/proposal-atomics-microwait/#Atomics.pause) @@ -377,6 +423,54 @@ impl AtomicsObject { } } +/// ### [25.4.3.4 RevalidateAtomicAccess ( typedArray, byteIndexInBuffer )](https://tc39.es/ecma262/#sec-revalidateatomicaccess) +/// +/// The abstract operation RevalidateAtomicAccess takes arguments typedArray (a +/// TypedArray) and byteIndexInBuffer (an integer) and returns either a normal +/// completion containing unused or a throw completion. This operation +/// revalidates the index within the backing buffer for atomic operations after +/// all argument coercions are performed in Atomics methods, as argument +/// coercions can have arbitrary side effects, which could cause the buffer to +/// become out of bounds. This operation does not throw when typedArray's +/// backing buffer is a SharedArrayBuffer. +fn revalidate_atomic_access<'gc>( + agent: &mut Agent, + typed_array: AnyTypedArray, + byte_index_in_buffer: usize, + gc: NoGcScope<'gc, '_>, +) -> JsResult<'gc, ()> { + // 1. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(typedArray, unordered). + let ta_record = make_typed_array_with_buffer_witness_record( + agent, + typed_array, + ecmascript_atomics::Ordering::Unordered, + ); + // 2. NOTE: Bounds checking is not a synchronizing operation when + // typedArray's backing buffer is a growable SharedArrayBuffer. + // 3. If IsTypedArrayOutOfBounds(taRecord) is true, + if ta_record.is_typed_array_out_of_bounds(agent) { + // throw a TypeError exception. + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "TypedArray out of bounds", + gc.into_nogc(), + )); + } + // 4. Assert: byteIndexInBuffer ≥ typedArray.[[ByteOffset]]. + debug_assert!(byte_index_in_buffer >= typed_array.byte_offset(agent)); + // 5. If byteIndexInBuffer ≥ taRecord.[[CachedBufferByteLength]], + if byte_index_in_buffer >= ta_record.cached_buffer_byte_length.0 { + // throw a RangeError exception. + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "accessIndex out of bounds", + gc.into_nogc(), + )); + } + // 6. Return unused. + Ok(()) +} + /// ### [25.4.3.17 AtomicReadModifyWrite ( typedArray, index, value, op )](https://tc39.es/ecma262/#sec-atomicreadmodifywrite) /// /// The abstract operation AtomicReadModifyWrite takes arguments typedArray (an @@ -387,12 +481,12 @@ impl AtomicsObject { /// arguments and returns a List of byte values. This operation atomically /// loads a value, combines it with another value, and stores the combination. /// It returns the loaded value. -fn atomic_read_modify_write<'gc, const Op: u8>( +fn atomic_read_modify_write<'gc, const OP: u8>( agent: &mut Agent, typed_array: Value, index: Value, value: Value, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Numeric<'gc>> { let typed_array = typed_array.bind(gc.nogc()); let index = index.bind(gc.nogc()); @@ -419,61 +513,69 @@ fn atomic_read_modify_write<'gc, const Op: u8>( } // 1. Let length be TypedArrayLength(taRecord). let length = ta_record.typed_array_length(agent); - if let (Value::Integer(index), Ok(value)) = (index, Numeric::try_from(value)) { + let (byte_index_in_buffer, typed_array, value) = if let (Value::Integer(index), Ok(value)) = + (index, Numeric::try_from(value)) + { // 7. Let offset be typedArray.[[ByteOffset]]. - let typed_array = ta_record.object.unbind(); - let value = value.unbind(); - let gc = gc.into_nogc(); - let value = value.bind(gc); - let typed_array = typed_array.bind(gc); + let typed_array = ta_record.object.bind(gc.nogc()); // 2. Let accessIndex be ? ToIndex(requestIndex). - let access_index = validate_index(agent, index.into_i64(), gc)?; + let access_index = validate_index(agent, index.into_i64(), gc.nogc()).unbind()?; // 3. If accessIndex ≥ length, throw a RangeError exception. if access_index >= length as u64 { - todo!(); + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "accessIndex out of bounds", + gc.into_nogc(), + )); } + let access_index = access_index as usize; // 5. Let typedArray be taRecord.[[Object]]. let offset = typed_array.byte_offset(agent); // 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value). if typed_array.is_bigint() != value.is_bigint() { - todo!(); + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "cannot convert between bigint and number", + gc.into_nogc(), + )); } - let byte_index_in_buffer = - offset + access_index as usize * typed_array.typed_array_element_size(); - // 5. Let buffer be typedArray.[[ViewedArrayBuffer]]. - let buffer = typed_array.viewed_array_buffer(agent); - // 6. Let elementType be TypedArrayElementType(typedArray). - // 7. Return GetModifySetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, op). - return Ok(for_any_typed_array!( - typed_array, - _t, - { - get_modify_set_value_in_buffer::( - agent, - buffer, - byte_index_in_buffer, - value, - gc, - ) - }, - ElementType - )); + let byte_index_in_buffer = offset + access_index * typed_array.typed_array_element_size(); + (byte_index_in_buffer, typed_array, value) } else { atomic_read_modify_write_slow( agent, ta_record.unbind(), index.unbind(), value.unbind(), - gc, - ); - todo!() + length, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()) }; - // 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value). - // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). - // 4. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer). + let typed_array = typed_array.unbind(); + let value = value.unbind(); + let gc = gc.into_nogc(); + let typed_array = typed_array.bind(gc); + let value = value.bind(gc); // 5. Let buffer be typedArray.[[ViewedArrayBuffer]]. + let buffer = typed_array.viewed_array_buffer(agent); // 6. Let elementType be TypedArrayElementType(typedArray). // 7. Return GetModifySetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, op). + Ok(for_any_typed_array!( + typed_array, + _t, + { + get_modify_set_value_in_buffer::( + agent, + buffer, + byte_index_in_buffer, + value, + gc, + ) + }, + ElementType + )) } #[inline(never)] @@ -483,25 +585,91 @@ fn atomic_read_modify_write_slow<'gc>( ta_record: TypedArrayWithBufferWitnessRecords, index: Value, value: Value, - gc: GcScope<'gc, '_>, -) -> JsResult<'gc, Numeric<'gc>> { - if let Some(index) = try_result_into_js(try_to_index(agent, index, gc.nogc())).unbind()? { - todo!() + length: usize, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, (usize, AnyTypedArray<'gc>, Numeric<'gc>)> { + let mut ta_record = ta_record.bind(gc.nogc()); + let index = index.bind(gc.nogc()); + let mut value = value.bind(gc.nogc()); + let mut revalidate = false; + + // 2. Let accessIndex be ? ToIndex(requestIndex). + let access_index = + if let Some(index) = try_result_into_js(try_to_index(agent, index, gc.nogc())).unbind()? { + index + } else { + let ta = ta_record.object.scope(agent, gc.nogc()); + let cached_buffer_byte_length = ta_record.cached_buffer_byte_length; + let scoped_value = value.scope(agent, gc.nogc()); + let access_index = to_index(agent, index.unbind(), gc.reborrow()).unbind()?; + revalidate = true; + // SAFETY: not shared. + (ta_record, value) = unsafe { + ( + TypedArrayWithBufferWitnessRecords { + object: ta.take(agent), + cached_buffer_byte_length, + }, + scoped_value.take(agent), + ) + }; + access_index + }; + // 3. Assert: accessIndex ≥ 0. + // 4. If accessIndex ≥ length, throw a RangeError exception. + if access_index >= length as u64 { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "accessIndex out of bounds", + gc.into_nogc(), + )); + } + let access_index = access_index as usize; + // 5. Let typedArray be taRecord.[[Object]]. + // 6. Let elementSize be TypedArrayElementSize(typedArray). + // 7. Let offset be typedArray.[[ByteOffset]]. + let offset = ta_record.object.byte_offset(agent); + // 8. Return (accessIndex × elementSize) + offset. + let byte_index_in_buffer = offset + access_index * ta_record.object.typed_array_element_size(); + // 2. If typedArray.[[ContentType]] is bigint, + let v = if let Some(v) = if ta_record.object.is_bigint() { + // let v be ? ToBigInt(value). + BigInt::try_from(value).ok().map(|v| v.into_numeric()) + } else { + // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). + Number::try_from(value).ok().map(|v| v.into_numeric()) + } { + v } else { + revalidate = true; let ta = ta_record.object.scope(agent, gc.nogc()); let cached_buffer_byte_length = ta_record.cached_buffer_byte_length; - let value = value.scope(agent, gc.nogc()); - let index = to_index(agent, index, gc).unbind()?; - // SAFETY: not shared. - let (ta_record, value) = unsafe { - ( - TypedArrayWithBufferWitnessRecords { - object: ta.take(agent), - cached_buffer_byte_length, - }, - value.take(agent), - ) + let v = if ta_record.object.is_bigint() { + to_big_int(agent, value.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()) + .into_numeric() + } else { + to_number(agent, value.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()) + .into_numeric() }; - todo!() + ta_record = unsafe { + TypedArrayWithBufferWitnessRecords { + object: ta.take(agent), + cached_buffer_byte_length, + } + }; + v + }; + let v = v.unbind(); + let typed_array = ta_record.object.unbind(); + let gc = gc.into_nogc(); + let v = v.bind(gc); + let typed_array = typed_array.bind(gc); + if revalidate { + revalidate_atomic_access(agent, typed_array, byte_index_in_buffer, gc)?; } + Ok((byte_index_in_buffer, typed_array, v)) } diff --git a/nova_vm/src/ecmascript/types/spec/data_block.rs b/nova_vm/src/ecmascript/types/spec/data_block.rs index ef98aa4d8..6f6454fb0 100644 --- a/nova_vm/src/ecmascript/types/spec/data_block.rs +++ b/nova_vm/src/ecmascript/types/spec/data_block.rs @@ -15,7 +15,10 @@ use core::{ ops::{Deref, DerefMut}, ptr::{NonNull, read_unaligned, write_unaligned}, }; -use std::alloc::{Layout, alloc_zeroed, dealloc, handle_alloc_error, realloc}; +use std::{ + alloc::{Layout, alloc_zeroed, dealloc, handle_alloc_error, realloc}, + ops::{BitAnd, BitOr, BitXor}, +}; use ecmascript_atomics::RacyStorage; #[cfg(feature = "shared-array-buffer")] @@ -1249,6 +1252,11 @@ pub trait Viewable: 'static + private::Sealed + Copy + PartialEq + core::fmt::De fn flip_endian(self) -> Self; fn add(self, other: Self) -> Self; + fn and(self, other: Self) -> Self; + fn swap(self, other: Self) -> Self; + fn or(self, other: Self) -> Self; + fn sub(self, other: Self) -> Self; + fn xor(self, other: Self) -> Self; /// Compare A and B of a Viewable type and always return an Ordering. /// @@ -1321,6 +1329,21 @@ impl Viewable for () { fn add(self, _: Self) -> Self { panic!("VoidArray is a marker type"); } + fn and(self, _: Self) -> Self { + panic!("VoidArray is a marker type"); + } + fn swap(self, _: Self) -> Self { + panic!("VoidArray is a marker type"); + } + fn or(self, _: Self) -> Self { + panic!("VoidArray is a marker type"); + } + fn sub(self, _: Self) -> Self { + panic!("VoidArray is a marker type"); + } + fn xor(self, _: Self) -> Self { + panic!("VoidArray is a marker type"); + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -1409,6 +1432,26 @@ impl Viewable for u8 { fn add(self, other: Self) -> Self { self.wrapping_add(other) } + #[inline(always)] + fn and(self, other: Self) -> Self { + self.bitand(other) + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + todo!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + self.bitor(other) + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self.wrapping_sub(other) + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + self.bitxor(other) + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -1496,6 +1539,26 @@ impl Viewable for U8Clamped { fn add(self, other: Self) -> Self { Self(self.0.wrapping_add(other.0)) } + #[inline(always)] + fn and(self, other: Self) -> Self { + Self(self.0.bitand(other.0)) + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + todo!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + Self(self.0.bitor(other.0)) + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + Self(self.0.wrapping_sub(other.0)) + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + Self(self.0.bitxor(other.0)) + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -1583,6 +1646,26 @@ impl Viewable for i8 { fn add(self, other: Self) -> Self { self.wrapping_add(other) } + #[inline(always)] + fn and(self, other: Self) -> Self { + self.bitand(other) + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + todo!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + self.bitor(other) + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self.wrapping_sub(other) + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + self.bitxor(other) + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -1670,6 +1753,26 @@ impl Viewable for u16 { fn add(self, other: Self) -> Self { self.wrapping_add(other) } + #[inline(always)] + fn and(self, other: Self) -> Self { + self.bitand(other) + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + todo!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + self.bitor(other) + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self.wrapping_sub(other) + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + self.bitxor(other) + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -1757,6 +1860,26 @@ impl Viewable for i16 { fn add(self, other: Self) -> Self { self.wrapping_add(other) } + #[inline(always)] + fn and(self, other: Self) -> Self { + self.bitand(other) + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + todo!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + self.bitor(other) + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self.wrapping_sub(other) + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + self.bitxor(other) + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -1844,6 +1967,26 @@ impl Viewable for u32 { fn add(self, other: Self) -> Self { self.wrapping_add(other) } + #[inline(always)] + fn and(self, other: Self) -> Self { + self.bitand(other) + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + todo!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + self.bitor(other) + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self.wrapping_sub(other) + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + self.bitxor(other) + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -1931,6 +2074,26 @@ impl Viewable for i32 { fn add(self, other: Self) -> Self { self.wrapping_add(other) } + #[inline(always)] + fn and(self, other: Self) -> Self { + self.bitand(other) + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + todo!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + self.bitor(other) + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self.wrapping_sub(other) + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + self.bitxor(other) + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -2030,6 +2193,26 @@ impl Viewable for u64 { fn add(self, other: Self) -> Self { self.wrapping_add(other) } + #[inline(always)] + fn and(self, other: Self) -> Self { + self.bitand(other) + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + todo!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + self.bitor(other) + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self.wrapping_sub(other) + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + self.bitxor(other) + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -2135,6 +2318,26 @@ impl Viewable for i64 { fn add(self, other: Self) -> Self { self.wrapping_add(other) } + #[inline(always)] + fn and(self, other: Self) -> Self { + self.bitand(other) + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + todo!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + self.bitor(other) + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self.wrapping_sub(other) + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + self.bitxor(other) + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -2226,6 +2429,26 @@ impl Viewable for f16 { fn add(self, other: Self) -> Self { self + other } + #[inline(always)] + fn and(self, other: Self) -> Self { + unreachable!() + } + #[inline(always)] + fn exchange(self, other: Self) -> Self { + unreachable!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + unreachable!() + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self - other + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + unreachable!() + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -2335,6 +2558,26 @@ impl Viewable for f32 { fn add(self, other: Self) -> Self { self + other } + #[inline(always)] + fn and(self, other: Self) -> Self { + unreachable!() + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + unreachable!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + unreachable!() + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self - other + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + unreachable!() + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering { @@ -2436,6 +2679,26 @@ impl Viewable for f64 { fn add(self, other: Self) -> Self { self + other } + #[inline(always)] + fn and(self, other: Self) -> Self { + unreachable!() + } + #[inline(always)] + fn swap(self, other: Self) -> Self { + unreachable!() + } + #[inline(always)] + fn or(self, other: Self) -> Self { + unreachable!() + } + #[inline(always)] + fn sub(self, other: Self) -> Self { + self - other + } + #[inline(always)] + fn xor(self, other: Self) -> Self { + unreachable!() + } #[inline(always)] fn ecmascript_cmp(&self, other: &Self) -> core::cmp::Ordering {