Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
34 changes: 23 additions & 11 deletions nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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`.

Expand All @@ -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)
Expand All @@ -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`.

Expand All @@ -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.
Expand Down
145 changes: 118 additions & 27 deletions nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -413,32 +413,123 @@ pub(crate) fn set_value_in_buffer<T: Viewable>(
/// 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::<Type>();

// 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::<Type>() <= 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::<Type, 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::<Type>() };
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::<Type::Storage>();
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<T> = fn(T, T) -> T;

const fn get_array_buffer_op<T: Viewable, const OP: u8>() -> ModifyOp<T> {
if const { OP == 0 } {
// Add
<T as Viewable>::add
} else if const { OP == 1 } {
// And
<T as Viewable>::and
} else if const { OP == 2 } {
// Exchange
<T as Viewable>::swap
} else if const { OP == 3 } {
// Or
<T as Viewable>::or
} else if const { OP == 4 } {
// Sub
<T as Viewable>::sub
} else if const { OP == 5 } {
// Xor
<T as Viewable>::xor
} else {
panic!("Unsupported Op value");
}
}
Loading
Loading