Skip to content

Commit 56d2f43

Browse files
authored
feat(ecmascript): Atomics.add, .and, .exchange, .or, .sub, and .xor methods (#877)
1 parent 9c6babd commit 56d2f43

File tree

11 files changed

+1012
-257
lines changed

11 files changed

+1012
-257
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ clap = { version = "4.5.48", features = ["derive"] }
2020
cliclack = "0.3.6"
2121
console = "0.15.11"
2222
ctrlc = "3.5.0"
23-
ecmascript_atomics = "=0.1.4"
23+
ecmascript_atomics = "=0.2.2"
2424
fast-float = "0.2.0"
2525
hashbrown = "0.16.0"
2626
lexical = { version = "7.0.5", default-features = false, features = [

nova_vm/src/ecmascript/abstract_operations/type_conversion.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
//! The BigInt type has no implicit conversions in the ECMAScript language;
1515
//! programmers must call BigInt explicitly to convert values from other types.
1616
17+
use std::convert::Infallible;
18+
1719
use num_bigint::Sign;
1820
use wtf8::Wtf8;
1921

@@ -1423,30 +1425,40 @@ pub(crate) fn canonical_numeric_index_string<'gc>(
14231425

14241426
/// 2. If integer is not in the inclusive interval from 0 to 2**53 - 1, throw a
14251427
/// RangeError exception.
1428+
#[inline]
14261429
pub(crate) fn validate_index<'a>(
14271430
agent: &mut Agent,
14281431
value: i64,
14291432
gc: NoGcScope<'a, '_>,
14301433
) -> JsResult<'a, u64> {
14311434
if !(0..=(SmallInteger::MAX)).contains(&value) {
1432-
return Err(agent.throw_exception_with_static_message(
1433-
ExceptionType::RangeError,
1434-
"Index is out of range",
1435-
gc,
1436-
));
1435+
return throw_index_out_of_range(agent, gc).map(|_| unreachable!());
14371436
}
14381437
Ok(value as u64)
14391438
}
14401439

1440+
#[inline(never)]
1441+
#[cold]
1442+
fn throw_index_out_of_range<'gc>(
1443+
agent: &mut Agent,
1444+
gc: NoGcScope<'gc, '_>,
1445+
) -> JsResult<'gc, Infallible> {
1446+
Err(agent.throw_exception_with_static_message(
1447+
ExceptionType::RangeError,
1448+
"Index is out of range",
1449+
gc,
1450+
))
1451+
}
1452+
14411453
/// ### [7.1.22 ToIndex ( value )](https://tc39.es/ecma262/#sec-toindex)
14421454
pub(crate) fn to_index<'a>(
14431455
agent: &mut Agent,
14441456
argument: Value,
14451457
mut gc: GcScope<'a, '_>,
1446-
) -> JsResult<'a, i64> {
1458+
) -> JsResult<'a, u64> {
14471459
// Fast path: A safe integer is already an integer.
14481460
if let Value::Integer(integer) = argument {
1449-
return validate_index(agent, integer.into_i64(), gc.into_nogc()).map(|i| i as i64);
1461+
return validate_index(agent, integer.into_i64(), gc.into_nogc());
14501462
}
14511463
// TODO: This can be heavily optimized by inlining `to_integer_or_infinity`.
14521464

@@ -1457,7 +1469,7 @@ pub(crate) fn to_index<'a>(
14571469

14581470
// 2. If integer is not in the inclusive interval from 0 to 2**53 - 1, throw a RangeError exception.
14591471
// 3. Return integer.
1460-
validate_index(agent, integer, gc.into_nogc()).map(|i| i as i64)
1472+
validate_index(agent, integer, gc.into_nogc())
14611473
}
14621474

14631475
/// ### [7.1.22 ToIndex ( value )](https://tc39.es/ecma262/#sec-toindex)
@@ -1466,10 +1478,10 @@ pub(crate) fn try_to_index<'a>(
14661478
agent: &mut Agent,
14671479
argument: Value,
14681480
gc: NoGcScope<'a, '_>,
1469-
) -> TryResult<'a, i64> {
1481+
) -> TryResult<'a, u64> {
14701482
// Fast path: A safe integer is already an integer.
14711483
if let Value::Integer(integer) = argument {
1472-
return js_result_into_try(validate_index(agent, integer.into_i64(), gc).map(|i| i as i64));
1484+
return js_result_into_try(validate_index(agent, integer.into_i64(), gc));
14731485
}
14741486
// TODO: This can be heavily optimized by inlining `to_integer_or_infinity`.
14751487

@@ -1478,7 +1490,7 @@ pub(crate) fn try_to_index<'a>(
14781490

14791491
// 2. If integer is not in the inclusive interval from 0 to 2**53 - 1, throw a RangeError exception.
14801492
// 3. Return integer.
1481-
js_result_into_try(validate_index(agent, integer, gc).map(|i| i as i64))
1493+
js_result_into_try(validate_index(agent, integer, gc))
14821494
}
14831495

14841496
/// Helper function to check if a `char` is trimmable.

nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs

Lines changed: 129 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use crate::{
1111
builtins::ordinary::ordinary_create_from_constructor,
1212
execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType},
1313
types::{
14-
BUILTIN_STRING_MEMORY, Function, Number, Numeric, Object, SharedDataBlock, Value,
15-
Viewable, create_byte_data_block,
14+
BUILTIN_STRING_MEMORY, Function, Numeric, Object, SharedDataBlock, Value, Viewable,
15+
create_byte_data_block,
1616
},
1717
},
1818
engine::context::{Bindable, GcScope, NoGcScope},
@@ -413,32 +413,134 @@ pub(crate) fn set_value_in_buffer<T: Viewable>(
413413
/// non-negative integer), type (a TypedArray element type), value (a Number or
414414
/// a BigInt), and op (a read-modify-write modification function) and returns a
415415
/// Number or a BigInt.
416-
#[expect(dead_code)]
417-
pub(crate) fn get_modify_set_value_in_buffer(
418-
_array_buffer: ArrayBuffer,
419-
_byte_index: u32,
420-
_type: (),
421-
_value: Number,
422-
_op: (),
423-
) {
416+
pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const OP: u8>(
417+
agent: &mut Agent,
418+
array_buffer: AnyArrayBuffer,
419+
byte_index: usize,
420+
value: Numeric,
421+
gc: NoGcScope<'gc, '_>,
422+
) -> Numeric<'gc> {
423+
// 5. Let elementSize be the Element Size value specified in Table
424+
// 71 for Element Type type.
425+
let element_size = size_of::<Type>();
426+
424427
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
425-
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
426-
// 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number.
427-
// 4. Let block be arrayBuffer.[[ArrayBufferData]].
428-
// 5. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
429-
// 6. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
428+
debug_assert!(!array_buffer.is_detached(agent));
429+
// 2. Assert: There are sufficient bytes in arrayBuffer starting at
430+
// byteIndex to represent a value of type.
431+
debug_assert!(
432+
byte_index + size_of::<Type>() <= array_buffer.byte_length(agent, Ordering::Unordered)
433+
);
434+
// 3. Assert: value is a BigInt if IsBigIntElementType(type) is true;
435+
// otherwise, value is a Number.
436+
debug_assert!(value.is_bigint() == Type::IS_BIGINT);
437+
// 6. Let isLittleEndian be the value of the [[LittleEndian]] field
438+
// of the surrounding agent's Agent Record.
430439
// 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian).
431-
// 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
432-
// a. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
433-
// b. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
434-
// c. Let rawBytesRead be a List of length elementSize whose elements are nondeterministically chosen byte values.
435-
// 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.
436-
// e. Let rmwEvent be ReadModifyWriteSharedMemory { [[Order]]: SEQ-CST, [[NoTear]]: true, [[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize, [[Payload]]: rawBytes, [[ModifyOp]]: op }.
437-
// f. Append rmwEvent to eventsRecord.[[EventList]].
438-
// g. Append Chosen Value Record { [[Event]]: rmwEvent, [[ChosenValue]]: rawBytesRead } to execution.[[ChosenValues]].
439-
// 9. Else,
440-
// a. Let rawBytesRead be a List of length elementSize whose elements are the sequence of elementSize bytes starting with block[byteIndex].
441-
// b. Let rawBytesModified be op(rawBytesRead, rawBytes).
442-
// c. Store the individual bytes of rawBytesModified into block, starting at block[byteIndex].
440+
let raw_bytes = Type::from_ne_value(agent, value);
441+
let raw_bytes_read = match array_buffer {
442+
AnyArrayBuffer::ArrayBuffer(array_buffer) => {
443+
let op = get_array_buffer_op::<Type, OP>();
444+
// 4. Let block be arrayBuffer.[[ArrayBufferData]].
445+
let block = array_buffer.as_mut_slice(agent);
446+
// 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
447+
// 9. Else,
448+
let slot = &mut block[byte_index..byte_index + element_size];
449+
// SAFETY: Viewable types are safe to transmute.
450+
let (head, slot, tail) = unsafe { slot.align_to_mut::<Type>() };
451+
debug_assert!(head.is_empty() && tail.is_empty());
452+
// a. Let rawBytesRead be a List of length elementSize whose
453+
// elements are the sequence of elementSize bytes starting with
454+
// block[byteIndex].
455+
let raw_bytes_read = slot[0];
456+
// b. Let rawBytesModified be op(rawBytesRead, rawBytes).
457+
let data_modified = op(raw_bytes_read, raw_bytes);
458+
// c. Store the individual bytes of rawBytesModified into block,
459+
// starting at block[byteIndex].
460+
slot[0] = data_modified;
461+
raw_bytes_read
462+
}
463+
AnyArrayBuffer::SharedArrayBuffer(array_buffer) => {
464+
// 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
465+
let buffer = array_buffer.as_slice(agent);
466+
let slot = buffer.slice(byte_index, byte_index + element_size);
467+
let (head, slot, tail) = slot.align_to::<Type::Storage>();
468+
debug_assert!(head.is_empty() && tail.is_empty());
469+
let slot = slot.get(0).unwrap();
470+
let result = if const { OP == 0 } {
471+
// Add
472+
let raw_bytes = Type::into_storage(raw_bytes);
473+
slot.fetch_add(raw_bytes)
474+
} else if const { OP == 1 } {
475+
// And
476+
let raw_bytes = Type::into_storage(raw_bytes);
477+
slot.fetch_and(raw_bytes)
478+
} else if const { OP == 2 } {
479+
// Exchange
480+
let raw_bytes = Type::into_storage(raw_bytes);
481+
slot.swap(raw_bytes)
482+
} else if const { OP == 3 } {
483+
// Or
484+
let raw_bytes = Type::into_storage(raw_bytes);
485+
slot.fetch_or(raw_bytes)
486+
} else if const { OP == 4 } {
487+
// Sub
488+
let raw_bytes = raw_bytes.neg();
489+
let raw_bytes = Type::into_storage(raw_bytes);
490+
slot.fetch_add(raw_bytes)
491+
} else if const { OP == 5 } {
492+
// Xor
493+
let raw_bytes = Type::into_storage(raw_bytes);
494+
slot.fetch_xor(raw_bytes)
495+
} else {
496+
panic!("Unsupported Op value");
497+
};
498+
// a. Let execution be the [[CandidateExecution]] field of the
499+
// surrounding agent's Agent Record.
500+
// b. Let eventsRecord be the Agent Events Record of
501+
// execution.[[EventsRecords]] whose [[AgentSignifier]] is
502+
// AgentSignifier().
503+
// c. Let rawBytesRead be a List of length elementSize whose
504+
// elements are nondeterministically chosen byte values.
505+
// d. NOTE: In implementations, rawBytesRead is the result of a
506+
// load-link, of a load-exclusive, or of an operand of a
507+
// read-modify-write instruction on the underlying hardware. The
508+
// nondeterminism is a semantic prescription of the memory model
509+
// to describe observable behaviour of hardware with weak
510+
// consistency.
511+
// e. Let rmwEvent be ReadModifyWriteSharedMemory { .. }.
512+
// f. Append rmwEvent to eventsRecord.[[EventList]].
513+
// g. Append Chosen Value Record { [[Event]]: rmwEvent,
514+
// [[ChosenValue]]: rawBytesRead } to execution.[[ChosenValues]].
515+
Type::from_storage(result)
516+
}
517+
};
443518
// 10. Return RawBytesToNumeric(type, rawBytesRead, isLittleEndian).
519+
raw_bytes_read.into_ne_value(agent, gc)
520+
}
521+
522+
type ModifyOp<T> = fn(T, T) -> T;
523+
524+
const fn get_array_buffer_op<T: Viewable, const OP: u8>() -> ModifyOp<T> {
525+
if const { OP == 0 } {
526+
// Add
527+
<T as Viewable>::add
528+
} else if const { OP == 1 } {
529+
// And
530+
<T as Viewable>::and
531+
} else if const { OP == 2 } {
532+
// Exchange
533+
<T as Viewable>::swap
534+
} else if const { OP == 3 } {
535+
// Or
536+
<T as Viewable>::or
537+
} else if const { OP == 4 } {
538+
// Sub
539+
<T as Viewable>::sub
540+
} else if const { OP == 5 } {
541+
// Xor
542+
<T as Viewable>::xor
543+
} else {
544+
panic!("Unsupported Op value");
545+
}
444546
}

nova_vm/src/ecmascript/builtins/indexed_collections/typed_array_objects/abstract_operations.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ pub(crate) fn initialize_typed_array_from_array_buffer<'gc, T: Viewable>(
658658
.unbind()?
659659
.bind(gc.nogc())
660660
{
661-
offset as u64
661+
offset
662662
} else {
663663
let nogc = gc.nogc();
664664
let o = o_proto.map(|p| p.scope(agent, nogc));
@@ -698,7 +698,7 @@ pub(crate) fn initialize_typed_array_from_array_buffer<'gc, T: Viewable>(
698698
.unbind()?
699699
.bind(gc.nogc())
700700
{
701-
Some(length as u64)
701+
Some(length)
702702
} else {
703703
let nogc = gc.nogc();
704704
let o = o_proto.map(|p| p.scope(agent, nogc));

0 commit comments

Comments
 (0)