From b48509f594881b0e321892c6b3ff8b225a46e483 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 6 Nov 2025 06:33:24 +0200 Subject: [PATCH 1/3] feat(ecmascript): Atomics.store --- .../structured_data/atomics_object.rs | 98 ++++++++++++++++++- 1 file changed, 94 insertions(+), 4 deletions(-) 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 1612d06b9..0f3191d7c 100644 --- a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs +++ b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs @@ -12,7 +12,9 @@ use crate::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ ArgumentsList, Behaviour, Builtin, - array_buffer::{get_modify_set_value_in_buffer, get_value_from_buffer}, + array_buffer::{ + get_modify_set_value_in_buffer, get_value_from_buffer, set_value_in_buffer, + }, indexed_collections::typed_array_objects::abstract_operations::{ TypedArrayAbstractOperations, TypedArrayWithBufferWitnessRecords, make_typed_array_with_buffer_witness_record, validate_typed_array, @@ -325,13 +327,101 @@ impl AtomicsObject { .map(|v| v.into_value()) } + /// ### [25.4.11 Atomics.store ( typedArray, index, value )](https://tc39.es/ecma262/#sec-atomics.store) fn store<'gc>( agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, - gc: GcScope<'gc, '_>, + arguments: ArgumentsList, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Atomics.store", gc.into_nogc())) + let arguments = arguments.bind(gc.nogc()); + let typed_array = arguments.get(0); + let index = arguments.get(1); + let value = arguments.get(2); + + // 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()); + + // 1. Let length be TypedArrayLength(taRecord). + let length = ta_record.typed_array_length(agent); + 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.bind(gc.nogc()); + // 2. Let accessIndex be ? ToIndex(requestIndex). + 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 { + 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). + // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). + if typed_array.is_bigint() != value.is_bigint() { + 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 * typed_array.typed_array_element_size(); + (byte_index_in_buffer, typed_array, value) + } else { + // 4. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer). + atomic_read_modify_write_slow( + agent, + ta_record.unbind(), + index.unbind(), + value.unbind(), + length, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()) + }; + 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]]. + // 6. Let elementType be TypedArrayElementType(typedArray). + let buffer = typed_array.viewed_array_buffer(agent); + + // 7. Perform SetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, true, seq-cst). + for_any_typed_array!( + typed_array, + _t, + { + set_value_in_buffer::( + agent, + buffer, + byte_index_in_buffer, + value, + true, + Ordering::SeqCst, + None, + ); + }, + ElementType + ); + // 8. Return v. + Ok(value.into_value()) } fn sub<'gc>( From ba21f48b755325b27c75109235664c6a5365b4e7 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 8 Nov 2025 12:05:12 +0200 Subject: [PATCH 2/3] feat(ecmascript): Atomics.store --- .../abstract_operations/type_conversion.rs | 69 +++ .../structured_data/atomics_object.rs | 456 ++++++++++-------- .../builtins/typed_array/any_typed_array.rs | 2 +- 3 files changed, 322 insertions(+), 205 deletions(-) diff --git a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs index ee716a4e3..20183f605 100644 --- a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs +++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs @@ -596,6 +596,75 @@ pub(crate) fn try_to_integer_or_infinity<'a>( TryResult::Continue(to_integer_or_infinity_number(agent, number)) } +/// ### [7.1.5 ToIntegerOrInfinity ( argument )](https://tc39.es/ecma262/#sec-tointegerorinfinity) +pub(crate) fn to_integer_number_or_infinity<'a>( + agent: &mut Agent, + argument: Value, + mut gc: GcScope<'a, '_>, +) -> JsResult<'a, Number<'a>> { + // Fast path: A safe integer is already an integer. + if let Value::Integer(int) = argument { + return Ok(Number::Integer(int)); + } + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument, gc.reborrow()).unbind()?; + let gc = gc.into_nogc(); + let number = number.bind(gc); + + // Fast path: The value might've been eg. parsed into an integer. + if let Number::Integer(int) = number { + return Ok(Number::Integer(int)); + } + + // 2. If number is one of NaN, +0𝔽, or -0𝔽, return 0. + if number.is_nan(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(Number::from(0)); + } + + // 3. If number is +∞𝔽, return +∞. + if number.is_pos_infinity(agent) { + return Ok(Number::pos_inf()); + } + + // 4. If number is -∞𝔽, return -∞. + if number.is_neg_infinity(agent) { + return Ok(Number::neg_inf()); + } + + // 5. Return truncate(ℝ(number)). + Ok(number.unbind().truncate(agent, gc)) +} + +/// ### [7.1.5 ToIntegerOrInfinity ( argument )](https://tc39.es/ecma262/#sec-tointegerorinfinity) +pub(crate) fn number_convert_to_integer_or_infinity<'a>( + agent: &mut Agent, + number: Number<'a>, + gc: NoGcScope<'a, '_>, +) -> Number<'a> { + // Fast path: A safe integer is already an integer. + if let Number::Integer(int) = number { + return Number::Integer(int); + } + + // 2. If number is one of NaN, +0𝔽, or -0𝔽, return 0. + if number.is_nan(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Number::from(0); + } + + // 3. If number is +∞𝔽, return +∞. + if number.is_pos_infinity(agent) { + return Number::pos_inf(); + } + + // 4. If number is -∞𝔽, return -∞. + if number.is_neg_infinity(agent) { + return Number::neg_inf(); + } + + // 5. Return truncate(ℝ(number)). + number.unbind().truncate(agent, gc) +} + /// ### [7.1.6 ToInt32 ( argument )](https://tc39.es/ecma262/#sec-toint32) pub(crate) fn to_int32<'a>( agent: &mut Agent, 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 0f3191d7c..7ef588c29 100644 --- a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs +++ b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs @@ -2,12 +2,15 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use std::ops::ControlFlow; + use ecmascript_atomics::Ordering; use crate::{ ecmascript::{ abstract_operations::type_conversion::{ - to_big_int, to_index, to_number, try_to_index, validate_index, + number_convert_to_integer_or_infinity, to_big_int, to_index, + to_integer_number_or_infinity, try_to_index, validate_index, }, builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ @@ -23,7 +26,7 @@ use crate::{ }, execution::{ Agent, JsResult, Realm, - agent::{ExceptionType, try_result_into_js}, + agent::{ExceptionType, TryError, TryResult, try_result_into_js}, }, types::{ BUILTIN_STRING_MEMORY, BigInt, IntoNumeric, IntoValue, Number, Numeric, String, Value, @@ -332,72 +335,19 @@ impl AtomicsObject { agent: &mut Agent, _this_value: Value, arguments: ArgumentsList, - mut gc: GcScope<'gc, '_>, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - let arguments = arguments.bind(gc.nogc()); - let typed_array = arguments.get(0); - let index = arguments.get(1); - let value = arguments.get(2); + let typed_array = arguments.get(0).bind(gc.nogc()); + let index = arguments.get(1).bind(gc.nogc()); + let value = arguments.get(2).bind(gc.nogc()); - // 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index). - let ta_record = validate_typed_array( + let (typed_array, byte_index_in_buffer, v) = handle_typed_array_index_value( agent, - typed_array, - ecmascript_atomics::Ordering::Unordered, - gc.nogc(), - ) - .unbind()? - .bind(gc.nogc()); - - // 1. Let length be TypedArrayLength(taRecord). - let length = ta_record.typed_array_length(agent); - 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.bind(gc.nogc()); - // 2. Let accessIndex be ? ToIndex(requestIndex). - 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 { - 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). - // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). - if typed_array.is_bigint() != value.is_bigint() { - 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 * typed_array.typed_array_element_size(); - (byte_index_in_buffer, typed_array, value) - } else { - // 4. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer). - atomic_read_modify_write_slow( - agent, - ta_record.unbind(), - index.unbind(), - value.unbind(), - length, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()) - }; - 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); + typed_array.unbind(), + index.unbind(), + value.unbind(), + gc, + )?; // 5. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 6. Let elementType be TypedArrayElementType(typedArray). @@ -412,7 +362,7 @@ impl AtomicsObject { agent, buffer, byte_index_in_buffer, - value, + v, true, Ordering::SeqCst, None, @@ -421,7 +371,7 @@ impl AtomicsObject { ElementType ); // 8. Return v. - Ok(value.into_value()) + Ok(v.into_value()) } fn sub<'gc>( @@ -592,6 +542,163 @@ impl AtomicsObject { } } +/// ### [25.4.3.1 ValidateIntegerTypedArray ( typedArray, waitable )](https://tc39.es/ecma262/#sec-validateintegertypedarray) +/// +/// The abstract operation ValidateIntegerTypedArray takes arguments typedArray +/// (an ECMAScript language value) and waitable (a Boolean) and returns either +/// a normal completion containing a TypedArray With Buffer Witness Record, or +/// a throw completion. +fn validate_integer_typed_array<'gc, const WAITABLE: bool>( + agent: &mut Agent, + typed_array: Value, + gc: NoGcScope<'gc, '_>, +) -> JsResult<'gc, TypedArrayWithBufferWitnessRecords<'gc>> { + // 1. Let taRecord be ? ValidateTypedArray(typedArray, unordered). + let ta_record = validate_typed_array( + agent, + typed_array, + ecmascript_atomics::Ordering::Unordered, + gc, + )?; + // 2. NOTE: Bounds checking is not a synchronizing operation when + // typedArray's backing buffer is a growable SharedArrayBuffer. + // 3. If waitable is true, then + let is_valid_type = if WAITABLE { + // a. If typedArray.[[TypedArrayName]] is neither "Int32Array" nor + // "BigInt64Array", throw a TypeError exception. + ta_record.object.is_waitable() + } else { + // 4. Else, + // a. Let type be TypedArrayElementType(typedArray). + // b. If IsUnclampedIntegerElementType(type) is false and + // IsBigIntElementType(type) is false, throw a TypeError exception. + ta_record.object.is_integer() + }; + if is_valid_type { + // 5. Return taRecord. + Ok(ta_record) + } else { + // throw a TypeError exception. + Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "cannot use TypedArray in Atomics", + gc, + )) + } +} + +/// 25.4.3.2 ValidateAtomicAccess ( taRecord, requestIndex ) +/// +/// The abstract operation ValidateAtomicAccess takes arguments taRecord (a +/// TypedArray With Buffer Witness Record) and requestIndex (an ECMAScript +/// language value) and returns either a normal completion containing an +/// integer or a throw completion. +fn validate_atomic_access<'gc>( + agent: &mut Agent, + ta_record: TypedArrayWithBufferWitnessRecords, + request_index: Value, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, usize> { + // 1. Let length be TypedArrayLength(taRecord). + let length = ta_record.typed_array_length(agent); + // 2. Let accessIndex be ? ToIndex(requestIndex). + let access_index = to_index(agent, request_index, gc.reborrow()).unbind()?; + // 3. Assert: accessIndex ≥ 0. + // 4. If accessIndex ≥ length, throw a RangeError exception. + if usize::try_from(access_index) + .ok() + .is_none_or(|access_index| access_index >= length) + { + 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 typed_array = ta_record.object; + // 6. Let elementSize be TypedArrayElementSize(typedArray). + let element_size = typed_array.typed_array_element_size(); + // 7. Let offset be typedArray.[[ByteOffset]]. + let offset = typed_array.byte_offset(agent); + // 8. Return (accessIndex × elementSize) + offset. + // SAFETY: access_index has been checked to be within length of the + // typed_array buffer, which means that its byte_offset must also be. + Ok(unsafe { + access_index + .unchecked_mul(element_size) + .unchecked_add(offset) + }) +} + +fn try_validate_atomic_access<'gc>( + agent: &mut Agent, + ta_record: &TypedArrayWithBufferWitnessRecords, + request_index: Value, + gc: NoGcScope<'gc, '_>, +) -> TryResult<'gc, usize> { + // 1. Let length be TypedArrayLength(taRecord). + let length = ta_record.typed_array_length(agent); + // 2. Let accessIndex be ? ToIndex(requestIndex). + let access_index = try_to_index(agent, request_index, gc)?; + // 3. Assert: accessIndex ≥ 0. + // 4. If accessIndex ≥ length, throw a RangeError exception. + if usize::try_from(access_index) + .ok() + .is_none_or(|access_index| access_index >= length) + { + return agent + .throw_exception_with_static_message( + ExceptionType::RangeError, + "accessIndex out of bounds", + gc, + ) + .into(); + } + let access_index = access_index as usize; + // 5. Let typedArray be taRecord.[[Object]]. + let typed_array = ta_record.object; + // 6. Let elementSize be TypedArrayElementSize(typedArray). + let element_size = typed_array.typed_array_element_size(); + // 7. Let offset be typedArray.[[ByteOffset]]. + let offset = typed_array.byte_offset(agent); + // 8. Return (accessIndex × elementSize) + offset. + // SAFETY: access_index has been checked to be within length of the + // typed_array buffer, which means that its byte_offset must also be. + TryResult::Continue(unsafe { + access_index + .unchecked_mul(element_size) + .unchecked_add(offset) + }) +} + +/// 25.4.3.3 ValidateAtomicAccessOnIntegerTypedArray ( typedArray, requestIndex ) +/// +/// The abstract operation ValidateAtomicAccessOnIntegerTypedArray takes +/// arguments typedArray (an ECMAScript language value) and requestIndex (an +/// ECMAScript language value) and returns either a normal completion containing +/// an integer or a throw completion. +fn try_validate_atomic_access_on_integer_typed_array<'gc>( + agent: &mut Agent, + typed_array: Value, + request_index: Value, + gc: NoGcScope<'gc, '_>, +) -> JsResult<'gc, (TypedArrayWithBufferWitnessRecords<'gc>, Option)> { + // 1. Let taRecord be ? ValidateIntegerTypedArray(typedArray, false). + let ta_record = validate_integer_typed_array::(agent, typed_array, gc)?; + // 2. Return ? ValidateAtomicAccess(taRecord, requestIndex). + match try_validate_atomic_access(agent, &ta_record, request_index, gc) { + ControlFlow::Continue(i) => Ok((ta_record, Some(i))), + ControlFlow::Break(b) => match b { + TryError::Err(err) => Err(err), + // If atomic access couldn't be validated it means that the + // requestIndex value couldn't be converted into an index. + TryError::GcError => Ok((ta_record, None)), + }, + } +} + /// ### [25.4.3.4 RevalidateAtomicAccess ( typedArray, byteIndexInBuffer )](https://tc39.es/ecma262/#sec-revalidateatomicaccess) /// /// The abstract operation RevalidateAtomicAccess takes arguments typedArray (a @@ -661,72 +768,18 @@ fn atomic_read_modify_write<'gc, const OP: u8>( 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( + let (typed_array, byte_index_in_buffer, v) = handle_typed_array_index_value( agent, - typed_array, - ecmascript_atomics::Ordering::Unordered, - gc.nogc(), + typed_array.unbind(), + index.unbind(), + value.unbind(), + gc.reborrow(), ) - .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 (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.bind(gc.nogc()); - // 2. Let accessIndex be ? ToIndex(requestIndex). - 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 { - 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() { - 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 * 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(), - length, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()) - }; - let typed_array = typed_array.unbind(); - let value = value.unbind(); + .unbind()?; let gc = gc.into_nogc(); let typed_array = typed_array.bind(gc); - let value = value.bind(gc); + let v = v.bind(gc); + // 5. Let buffer be typedArray.[[ViewedArrayBuffer]]. let buffer = typed_array.viewed_array_buffer(agent); // 6. Let elementType be TypedArrayElementType(typedArray). @@ -739,7 +792,7 @@ fn atomic_read_modify_write<'gc, const OP: u8>( agent, buffer, byte_index_in_buffer, - value, + v, gc, ) }, @@ -747,6 +800,53 @@ fn atomic_read_modify_write<'gc, const OP: u8>( )) } +fn handle_typed_array_index_value<'gc>( + agent: &mut Agent, + typed_array: Value, + index: Value, + value: Value, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, (AnyTypedArray<'gc>, usize, 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, byte_index_in_buffer) = + try_validate_atomic_access_on_integer_typed_array(agent, typed_array, index, gc.nogc()) + .unbind()? + .bind(gc.nogc()); + let (byte_index_in_buffer, typed_array, value) = + if let (Some(byte_index_in_buffer), Ok(value)) = ( + byte_index_in_buffer, + if ta_record.object.is_bigint() { + BigInt::try_from(value).map(|value| value.into_numeric()) + } else { + Number::try_from(value).map(|value| { + number_convert_to_integer_or_infinity(agent, value, gc.nogc()).into_numeric() + }) + }, + ) { + let typed_array = ta_record.object; + (byte_index_in_buffer, typed_array, value) + } else { + atomic_read_modify_write_slow( + agent, + ta_record.unbind(), + index.unbind(), + value.unbind(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()) + }; + 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); + Ok((typed_array, byte_index_in_buffer, value)) +} + #[inline(never)] #[cold] fn atomic_read_modify_write_slow<'gc>( @@ -754,92 +854,40 @@ fn atomic_read_modify_write_slow<'gc>( ta_record: TypedArrayWithBufferWitnessRecords, index: Value, value: Value, - length: usize, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, (usize, AnyTypedArray<'gc>, Numeric<'gc>)> { - let mut ta_record = ta_record.bind(gc.nogc()); + let ta_record = ta_record.bind(gc.nogc()); + let is_bigint = ta_record.object.is_bigint(); + let typed_array = ta_record.object.scope(agent, gc.nogc()); let index = index.bind(gc.nogc()); - let mut value = value.bind(gc.nogc()); - let mut revalidate = false; + let value = value.scope(agent, gc.nogc()); + + let byte_index_in_buffer = + validate_atomic_access(agent, ta_record.unbind(), index.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + + let value = unsafe { value.take(agent) }.bind(gc.nogc()); - // 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 = if is_bigint { // let v be ? ToBigInt(value). - BigInt::try_from(value).ok().map(|v| v.into_numeric()) + to_big_int(agent, value.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()) + .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 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() - }; - ta_record = unsafe { - TypedArrayWithBufferWitnessRecords { - object: ta.take(agent), - cached_buffer_byte_length, - } - }; - v + to_integer_number_or_infinity(agent, value.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()) + .into_numeric() }; 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)?; - } + let typed_array = unsafe { typed_array.take(agent) }.bind(gc); + 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/builtins/typed_array/any_typed_array.rs b/nova_vm/src/ecmascript/builtins/typed_array/any_typed_array.rs index 3893b745f..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 @@ -129,7 +129,7 @@ impl AnyTypedArray<'_> { /// Returns true if the TypedArray is an Int32Array or BigInt64Array /// (shared or not), false otherwise. - pub(crate) fn _is_waitable(self) -> bool { + pub(crate) fn is_waitable(self) -> bool { #[cfg(not(feature = "shared-array-buffer"))] { matches!(self, Self::Int32Array(_) | Self::BigInt64Array(_)) From fa0dbe9eb0854ba84d478bc564e6e793d3057d3b Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 8 Nov 2025 12:05:15 +0200 Subject: [PATCH 3/3] chore(test262): Update expectations --- tests/expectations.json | 30 +----------------------------- tests/metrics.json | 6 +++--- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/tests/expectations.json b/tests/expectations.json index ba704a306..04bcf56c8 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -238,10 +238,6 @@ "built-ins/AsyncIteratorPrototype/Symbol.asyncDispose/throw-rejected-return.js": "FAIL", "built-ins/AsyncIteratorPrototype/Symbol.asyncDispose/throw-return-getter.js": "FAIL", "built-ins/AsyncIteratorPrototype/Symbol.asyncDispose/throw-return.js": "FAIL", - "built-ins/Atomics/add/bigint/good-views.js": "FAIL", - "built-ins/Atomics/add/good-views.js": "FAIL", - "built-ins/Atomics/and/bigint/good-views.js": "FAIL", - "built-ins/Atomics/and/good-views.js": "FAIL", "built-ins/Atomics/compareExchange/bad-range.js": "FAIL", "built-ins/Atomics/compareExchange/bigint/bad-range.js": "FAIL", "built-ins/Atomics/compareExchange/bigint/good-views.js": "FAIL", @@ -254,13 +250,9 @@ "built-ins/Atomics/compareExchange/validate-arraytype-before-expectedValue-coercion.js": "FAIL", "built-ins/Atomics/compareExchange/validate-arraytype-before-index-coercion.js": "FAIL", "built-ins/Atomics/compareExchange/validate-arraytype-before-replacementValue-coercion.js": "FAIL", - "built-ins/Atomics/exchange/bigint/good-views.js": "FAIL", - "built-ins/Atomics/exchange/good-views.js": "FAIL", "built-ins/Atomics/isLockFree/bigint/expected-return-value.js": "FAIL", "built-ins/Atomics/isLockFree/corner-cases.js": "FAIL", "built-ins/Atomics/isLockFree/expected-return-value.js": "FAIL", - "built-ins/Atomics/load/bigint/good-views.js": "FAIL", - "built-ins/Atomics/load/good-views.js": "FAIL", "built-ins/Atomics/notify/bad-range.js": "FAIL", "built-ins/Atomics/notify/bigint/bad-range.js": "FAIL", "built-ins/Atomics/notify/bigint/non-bigint64-typedarray-throws.js": "FAIL", @@ -308,29 +300,11 @@ "built-ins/Atomics/notify/undefined-index-defaults-to-zero.js": "FAIL", "built-ins/Atomics/notify/validate-arraytype-before-count-coercion.js": "FAIL", "built-ins/Atomics/notify/validate-arraytype-before-index-coercion.js": "FAIL", - "built-ins/Atomics/or/bigint/good-views.js": "FAIL", - "built-ins/Atomics/or/good-views.js": "FAIL", "built-ins/Atomics/pause/descriptor.js": "FAIL", "built-ins/Atomics/pause/length.js": "FAIL", "built-ins/Atomics/pause/name.js": "FAIL", "built-ins/Atomics/pause/not-a-constructor.js": "FAIL", "built-ins/Atomics/pause/returns-undefined.js": "FAIL", - "built-ins/Atomics/store/bad-range.js": "FAIL", - "built-ins/Atomics/store/bigint/bad-range.js": "FAIL", - "built-ins/Atomics/store/bigint/good-views.js": "FAIL", - "built-ins/Atomics/store/bigint/non-shared-bufferdata.js": "FAIL", - "built-ins/Atomics/store/expected-return-value-negative-zero.js": "FAIL", - "built-ins/Atomics/store/expected-return-value.js": "FAIL", - "built-ins/Atomics/store/good-views.js": "FAIL", - "built-ins/Atomics/store/non-shared-bufferdata.js": "FAIL", - "built-ins/Atomics/store/non-shared-int-views-throws.js": "FAIL", - "built-ins/Atomics/store/non-views.js": "FAIL", - "built-ins/Atomics/store/validate-arraytype-before-index-coercion.js": "FAIL", - "built-ins/Atomics/store/validate-arraytype-before-value-coercion.js": "FAIL", - "built-ins/Atomics/sub/bigint/good-views.js": "FAIL", - "built-ins/Atomics/sub/bigint/non-shared-bufferdata.js": "FAIL", - "built-ins/Atomics/sub/good-views.js": "FAIL", - "built-ins/Atomics/sub/non-shared-bufferdata.js": "FAIL", "built-ins/Atomics/wait/bad-range.js": "FAIL", "built-ins/Atomics/wait/bigint/bad-range.js": "FAIL", "built-ins/Atomics/wait/bigint/cannot-suspend-throws.js": "FAIL", @@ -502,8 +476,6 @@ "built-ins/Atomics/waitAsync/value-not-equal.js": "FAIL", "built-ins/Atomics/waitAsync/waiterlist-block-indexedposition-wake.js": "FAIL", "built-ins/Atomics/waitAsync/was-woken-before-timeout.js": "FAIL", - "built-ins/Atomics/xor/bigint/good-views.js": "FAIL", - "built-ins/Atomics/xor/good-views.js": "FAIL", "built-ins/BigInt/asUintN/arithmetic.js": "CRASH", "built-ins/Boolean/proto-from-ctor-realm.js": "FAIL", "built-ins/DataView/proto-from-ctor-realm-sab.js": "FAIL", @@ -8139,4 +8111,4 @@ "staging/sm/syntax/yield-as-identifier.js": "FAIL", "staging/source-phase-imports/import-source-source-text-module.js": "FAIL", "staging/top-level-await/tla-hang-entry.js": "FAIL" -} +} \ No newline at end of file diff --git a/tests/metrics.json b/tests/metrics.json index 2fadff19f..1e0b8bbe7 100644 --- a/tests/metrics.json +++ b/tests/metrics.json @@ -1,11 +1,11 @@ { "results": { "crash": 107, - "fail": 7986, - "pass": 39267, + "fail": 7958, + "pass": 39296, "skip": 3325, "timeout": 13, "unresolved": 34 }, "total": 50733 -} +} \ No newline at end of file