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 43b749b27..7b1d5aacd 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; + 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()); } // 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()) } /// ### [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..8ad9236f4 100644 --- a/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs @@ -11,8 +11,8 @@ use crate::{ 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}, @@ -413,32 +413,123 @@ 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( - _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> { + // 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. - // 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]. + 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::(); + // 4. Let block be arrayBuffer.[[ArrayBufferData]]. + let block = array_buffer.as_mut_slice(agent); + // 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; + raw_bytes_read + } + AnyArrayBuffer::SharedArrayBuffer(array_buffer) => { + // 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 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"); + }; + // 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::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 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 aa3660b86..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,12 +6,31 @@ use crate::ecmascript::execution::agent::ExceptionType; use crate::{ ecmascript::{ + 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}, - execution::{Agent, JsResult, Realm}, - types::{BUILTIN_STRING_MEMORY, String, Value}, + builtins::{ + ArgumentsList, Behaviour, Builtin, + array_buffer::get_modify_set_value_in_buffer, + indexed_collections::typed_array_objects::abstract_operations::{ + TypedArrayAbstractOperations, TypedArrayWithBufferWitnessRecords, + make_typed_array_with_buffer_witness_record, validate_typed_array, + }, + typed_array::{AnyTypedArray, for_any_typed_array}, + }, + execution::{ + Agent, JsResult, Realm, + agent::{ExceptionType, try_result_into_js}, + }, + types::{ + BUILTIN_STRING_MEMORY, BigInt, IntoNumeric, IntoValue, Number, Numeric, String, Value, + }, + }, + engine::{ + context::{Bindable, GcScope, NoGcScope}, + rootable::Scopable, }, - engine::context::GcScope, heap::WellKnownSymbolIndexes, }; @@ -135,22 +154,37 @@ 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())) + 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>( @@ -165,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>( @@ -192,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>( @@ -210,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>( @@ -246,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) @@ -360,3 +422,254 @@ impl AtomicsObject { builder.build(); } } + +/// ### [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 +/// 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, + mut 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 (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(); + 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)] +#[cold] +fn atomic_read_modify_write_slow<'gc>( + agent: &mut Agent, + 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 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 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 + }; + 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/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/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 3626c326e..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")] @@ -738,55 +741,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 +787,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 +827,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 +858,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") } } @@ -1312,6 +1251,13 @@ pub trait Viewable: 'static + private::Sealed + Copy + PartialEq + core::fmt::De /// Reverses the byte order of the value. 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. /// /// This ordering is the usual total order for integers, and the special @@ -1380,6 +1326,25 @@ impl Viewable for () { panic!("VoidArray is a marker type"); } + 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 { self.cmp(other) @@ -1463,6 +1428,31 @@ impl Viewable for u8 { self.swap_bytes() } + #[inline(always)] + 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 { self.cmp(other) @@ -1545,6 +1535,31 @@ 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 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 { self.cmp(other) @@ -1627,6 +1642,31 @@ impl Viewable for i8 { self.swap_bytes() } + #[inline(always)] + 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 { self.cmp(other) @@ -1709,6 +1749,31 @@ impl Viewable for u16 { self.swap_bytes() } + #[inline(always)] + 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 { self.cmp(other) @@ -1791,6 +1856,31 @@ impl Viewable for i16 { self.swap_bytes() } + #[inline(always)] + 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 { self.cmp(other) @@ -1873,6 +1963,31 @@ impl Viewable for u32 { self.swap_bytes() } + #[inline(always)] + 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 { self.cmp(other) @@ -1955,6 +2070,31 @@ impl Viewable for i32 { self.swap_bytes() } + #[inline(always)] + 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 { self.cmp(other) @@ -2049,6 +2189,31 @@ impl Viewable for u64 { self.swap_bytes() } + #[inline(always)] + 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 { self.cmp(other) @@ -2149,6 +2314,31 @@ impl Viewable for i64 { self.swap_bytes() } + #[inline(always)] + 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 { self.cmp(other) @@ -2235,6 +2425,31 @@ 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 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 { if self.is_nan() { @@ -2339,6 +2554,31 @@ 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 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 { if self.is_nan() { @@ -2435,6 +2675,31 @@ 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 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 { if self.is_nan() {