use std::{
hint::assert_unchecked,
ops::ControlFlow,
sync::{
Arc,
atomic::{AtomicBool, Ordering as StdOrdering},
},
thread::{self, JoinHandle},
time::Duration,
};
use ecmascript_atomics::Ordering;
use crate::{
ecmascript::{
Agent, AnyArrayBuffer, AnyTypedArray, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour,
BigInt, Builtin, ExceptionType, InnerJob, Job, JsResult, Number, Numeric, OrdinaryObject,
Promise, PromiseCapability, Realm, SharedArrayBuffer, SharedDataBlock, SharedTypedArray,
String, TryError, TryResult, TypedArrayAbstractOperations,
TypedArrayWithBufferWitnessRecords, Value, WaitResult, WaiterRecord,
builders::OrdinaryObjectBuilder, compare_exchange_in_buffer, for_any_typed_array,
get_modify_set_value_in_buffer, get_value_from_buffer,
make_typed_array_with_buffer_witness_record, number_convert_to_integer_or_infinity,
set_value_in_buffer, to_big_int, to_big_int64, to_big_int64_big_int, to_index, to_int32,
to_int32_number, to_integer_number_or_infinity, to_integer_or_infinity, to_number,
try_result_into_js, try_to_index, unwrap_try, validate_index, validate_typed_array,
},
engine::{Bindable, GcScope, Global, NoGcScope, Scopable},
heap::{ObjectEntry, WellKnownSymbols},
};
pub(crate) struct AtomicsObject;
struct AtomicsObjectAdd;
impl Builtin for AtomicsObjectAdd {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.add;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::add);
}
struct AtomicsObjectAnd;
impl Builtin for AtomicsObjectAnd {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.and;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::and);
}
struct AtomicsObjectCompareExchange;
impl Builtin for AtomicsObjectCompareExchange {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.compareExchange;
const LENGTH: u8 = 4;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::compare_exchange);
}
struct AtomicsObjectExchange;
impl Builtin for AtomicsObjectExchange {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.exchange;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::exchange);
}
struct AtomicsObjectIsLockFree;
impl Builtin for AtomicsObjectIsLockFree {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.isLockFree;
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::is_lock_free);
}
struct AtomicsObjectLoad;
impl Builtin for AtomicsObjectLoad {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.load;
const LENGTH: u8 = 2;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::load);
}
struct AtomicsObjectOr;
impl Builtin for AtomicsObjectOr {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.or;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::or);
}
struct AtomicsObjectStore;
impl Builtin for AtomicsObjectStore {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.store;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::store);
}
struct AtomicsObjectSub;
impl Builtin for AtomicsObjectSub {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.sub;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::sub);
}
struct AtomicsObjectWait;
impl Builtin for AtomicsObjectWait {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.wait;
const LENGTH: u8 = 4;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::wait);
}
struct AtomicsObjectWaitAsync;
impl Builtin for AtomicsObjectWaitAsync {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.waitAsync;
const LENGTH: u8 = 4;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::wait_async);
}
struct AtomicsObjectNotify;
impl Builtin for AtomicsObjectNotify {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.notify;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::notify);
}
struct AtomicsObjectXor;
impl Builtin for AtomicsObjectXor {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.xor;
const LENGTH: u8 = 3;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::xor);
}
#[cfg(feature = "proposal-atomics-microwait")]
struct AtomicsObjectPause;
#[cfg(feature = "proposal-atomics-microwait")]
impl Builtin for AtomicsObjectPause {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.pause;
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(AtomicsObject::pause);
}
impl AtomicsObject {
fn add<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
atomic_read_modify_write::<0>(
agent,
arguments.get(0),
arguments.get(1),
arguments.get(2),
gc,
)
.map(|v| v.into())
}
fn and<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
atomic_read_modify_write::<1>(
agent,
arguments.get(0),
arguments.get(1),
arguments.get(2),
gc,
)
.map(|v| v.into())
}
fn compare_exchange<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let typed_array = arguments.get(0).bind(gc.nogc());
let index = arguments.get(1).bind(gc.nogc());
let expected_value = arguments.get(2).bind(gc.nogc());
let replacement_value = arguments.get(3).bind(gc.nogc());
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 typed_array = ta_record.object;
let (byte_index_in_buffer, typed_array, expected, replacement) =
if let (Some(byte_index_in_buffer), (Ok(expected), Ok(replacement))) = (
byte_index_in_buffer,
if typed_array.is_bigint() {
(
BigInt::try_from(expected_value).map(|value| value.into()),
BigInt::try_from(replacement_value).map(|value| value.into()),
)
} else {
(
Number::try_from(expected_value).map(|value| {
number_convert_to_integer_or_infinity(agent, value, gc.nogc()).into()
}),
Number::try_from(replacement_value).map(|value| {
number_convert_to_integer_or_infinity(agent, value, gc.nogc()).into()
}),
)
},
) {
(byte_index_in_buffer, typed_array, expected, replacement)
} else {
handle_typed_array_index_two_values_slow(
agent,
ta_record.unbind(),
index.unbind(),
expected_value.unbind(),
replacement_value.unbind(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc())
};
let typed_array = typed_array.unbind();
let expected = expected.unbind();
let replacement = replacement.unbind();
let gc = gc.into_nogc();
let typed_array = typed_array.bind(gc);
let expected = expected.bind(gc);
let replacement = replacement.bind(gc);
let buffer = typed_array.viewed_array_buffer(agent);
Ok(for_any_typed_array!(
typed_array,
_t,
{
compare_exchange_in_buffer::<ElementType>(
agent,
buffer,
byte_index_in_buffer,
expected,
replacement,
gc,
)
.into()
},
ElementType
))
}
fn exchange<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
atomic_read_modify_write::<2>(
agent,
arguments.get(0),
arguments.get(1),
arguments.get(2),
gc,
)
.map(|v| v.into())
}
fn is_lock_free<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let size = arguments.get(0).bind(gc.nogc());
let n = to_integer_or_infinity(agent, size.unbind(), gc)?.into_i64();
#[cfg(target_has_atomic = "8")]
if n == 1 {
return Ok(true.into());
}
#[cfg(target_has_atomic = "16")]
if n == 2 {
return Ok(true.into());
}
#[cfg(target_has_atomic = "32")]
if n == 4 {
return Ok(true.into());
}
#[cfg(not(target_has_atomic = "32"))]
const {
panic!("Atomics requires 32-bit lock-free atomics")
};
#[cfg(target_has_atomic = "64")]
if n == 8 {
return Ok(true.into());
}
Ok(false.into())
}
fn load<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut 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 ta_record = validate_typed_array(
agent,
typed_array,
ecmascript_atomics::Ordering::Unordered,
gc.nogc(),
)
.unbind()?
.bind(gc.nogc());
if !ta_record.object.is_integer() {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"cannot use TypedArray in Atomics",
gc.into_nogc(),
));
}
let length = ta_record.typed_array_length(agent);
let (byte_index_in_buffer, typed_array) = if let Value::Integer(index) = index {
let typed_array = ta_record.object.bind(gc.nogc());
let access_index = validate_index(agent, index.into_i64(), gc.nogc()).unbind()?;
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;
let offset = typed_array.byte_offset(agent);
let byte_index_in_buffer =
offset + access_index * typed_array.typed_array_element_size();
(byte_index_in_buffer, typed_array)
} else {
atomic_load_slow(
agent,
ta_record.unbind(),
index.unbind(),
length,
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc())
};
let typed_array = typed_array.unbind();
let gc = gc.into_nogc();
let typed_array = typed_array.bind(gc);
let buffer = typed_array.viewed_array_buffer(agent);
Ok(for_any_typed_array!(
typed_array,
_t,
{
get_value_from_buffer::<ElementType>(
agent,
buffer,
byte_index_in_buffer,
true,
Ordering::SeqCst,
None,
gc,
)
},
ElementType
)
.into())
}
fn or<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
atomic_read_modify_write::<3>(
agent,
arguments.get(0),
arguments.get(1),
arguments.get(2),
gc,
)
.map(|v| v.into())
}
fn store<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
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());
let (typed_array, byte_index_in_buffer, v) = handle_typed_array_index_value(
agent,
typed_array.unbind(),
index.unbind(),
value.unbind(),
gc,
)?;
let buffer = typed_array.viewed_array_buffer(agent);
for_any_typed_array!(
typed_array,
_t,
{
set_value_in_buffer::<ElementType>(
agent,
buffer,
byte_index_in_buffer,
v,
true,
Ordering::SeqCst,
None,
);
},
ElementType
);
Ok(v.into())
}
fn sub<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
atomic_read_modify_write::<4>(
agent,
arguments.get(0),
arguments.get(1),
arguments.get(2),
gc,
)
.map(|v| v.into())
}
fn wait<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let (buffer, byte_index_in_buffer, value, is_i64, t) = do_wait_preparation(
agent,
arguments.get(0),
arguments.get(1),
arguments.get(2),
arguments.get(3),
gc.reborrow(),
)
.unbind()?;
if !agent.can_suspend() {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"agent is not allowed to suspend",
gc.into_nogc(),
));
}
if is_i64 {
Ok(do_wait_critical::<false, true>(
agent,
buffer,
byte_index_in_buffer,
value,
t,
gc.into_nogc(),
))
} else {
Ok(do_wait_critical::<false, false>(
agent,
buffer,
byte_index_in_buffer,
value,
t,
gc.into_nogc(),
))
}
}
fn wait_async<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let (buffer, byte_index_in_buffer, value, is_i64, t) = do_wait_preparation(
agent,
arguments.get(0),
arguments.get(1),
arguments.get(2),
arguments.get(3),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if is_i64 {
Ok(do_wait_critical::<true, true>(
agent,
buffer.unbind(),
byte_index_in_buffer,
value,
t,
gc.into_nogc(),
))
} else {
Ok(do_wait_critical::<true, false>(
agent,
buffer.unbind(),
byte_index_in_buffer,
value,
t,
gc.into_nogc(),
))
}
}
fn notify<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.nogc();
let typed_array = arguments.get(0).bind(nogc);
let index = arguments.get(1).bind(nogc);
let count = arguments.get(2).scope(agent, nogc);
let ta_record = validate_integer_typed_array::<true>(agent, typed_array, nogc)
.unbind()?
.bind(nogc);
let typed_array = ta_record.object.scope(agent, nogc);
let byte_index_in_buffer =
validate_atomic_access(agent, ta_record.unbind(), index.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
let count = unsafe { count.take(agent) }.bind(gc.nogc());
let c = if count.is_undefined() {
usize::MAX
} else {
let int_count = to_integer_or_infinity(agent, count.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
usize::try_from(int_count.into_i64().max(0).cast_unsigned()).unwrap_or(usize::MAX)
};
let gc = gc.into_nogc();
let typed_array = unsafe { typed_array.take(agent) }.bind(gc);
if c == 0 {
return Ok(0.into());
}
let buffer = typed_array.viewed_array_buffer(agent);
let AnyArrayBuffer::SharedArrayBuffer(buffer) = buffer else {
return Ok(0.into());
};
let data_block = buffer.get_data_block(agent);
let mut n = 0;
let Some(waiters) = (unsafe { data_block.get_waiters() }) else {
return Ok(0.into());
};
let mut guard = waiters.lock().unwrap();
let Some(list) = guard.get_list_mut(byte_index_in_buffer) else {
return Ok(0.into());
};
while n < c {
let Some(w) = list.pop() else {
break;
};
w.notify_waiters();
n += 1;
}
drop(guard);
Ok(Number::from_usize(agent, n, gc).into())
}
fn xor<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
atomic_read_modify_write::<5>(
agent,
arguments.get(0),
arguments.get(1),
arguments.get(2),
gc,
)
.map(|v| v.into())
}
#[cfg(feature = "proposal-atomics-microwait")]
fn pause<'gc>(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let nogc = gc.into_nogc();
let n = arguments.get(0);
if !n.is_undefined() && !n.is_integer() {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Atomics.pause called with non-integral Number",
nogc,
));
}
let n = if let Value::Integer(n) = n {
let n = n.into_i64();
u16::try_from(n).unwrap_or(if n > 0 { u16::MAX } else { 1 })
} else {
1
};
for _ in 0..n {
std::hint::spin_loop();
}
Ok(Value::Undefined)
}
pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>) {
let intrinsics = agent.get_realm_record_by_id(realm).intrinsics();
let object_prototype = intrinsics.object_prototype();
let this = intrinsics.atomics();
let mut property_capacity = 14;
if cfg!(feature = "proposal-atomics-microwait") {
property_capacity += 1;
}
let builder = OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this)
.with_property_capacity(property_capacity)
.with_prototype(object_prototype)
.with_builtin_function_property::<AtomicsObjectAdd>()
.with_builtin_function_property::<AtomicsObjectAnd>()
.with_builtin_function_property::<AtomicsObjectCompareExchange>()
.with_builtin_function_property::<AtomicsObjectExchange>()
.with_builtin_function_property::<AtomicsObjectIsLockFree>()
.with_builtin_function_property::<AtomicsObjectLoad>()
.with_builtin_function_property::<AtomicsObjectOr>()
.with_builtin_function_property::<AtomicsObjectStore>()
.with_builtin_function_property::<AtomicsObjectSub>()
.with_builtin_function_property::<AtomicsObjectWait>()
.with_builtin_function_property::<AtomicsObjectWaitAsync>()
.with_builtin_function_property::<AtomicsObjectNotify>()
.with_builtin_function_property::<AtomicsObjectXor>()
.with_property(|builder| {
builder
.with_key(WellKnownSymbols::ToStringTag.into())
.with_value_readonly(BUILTIN_STRING_MEMORY.Atomics.into())
.with_enumerable(false)
.with_configurable(true)
.build()
});
#[cfg(feature = "proposal-atomics-microwait")]
let builder = builder.with_builtin_function_property::<AtomicsObjectPause>();
builder.build();
}
}
fn validate_integer_typed_array<'gc, const WAITABLE: bool>(
agent: &mut Agent,
typed_array: Value,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, TypedArrayWithBufferWitnessRecords<'gc>> {
let ta_record = validate_typed_array(
agent,
typed_array,
ecmascript_atomics::Ordering::Unordered,
gc,
)?;
let is_valid_type = if WAITABLE {
ta_record.object.is_waitable()
} else {
ta_record.object.is_integer()
};
if is_valid_type {
Ok(ta_record)
} else {
Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"cannot use TypedArray in Atomics",
gc,
))
}
}
fn validate_atomic_access<'gc>(
agent: &mut Agent,
ta_record: TypedArrayWithBufferWitnessRecords,
request_index: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, usize> {
let length = ta_record.typed_array_length(agent);
let access_index = to_index(agent, request_index, gc.reborrow()).unbind()?;
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;
let typed_array = ta_record.object;
let element_size = typed_array.typed_array_element_size();
let offset = typed_array.byte_offset(agent);
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> {
let length = ta_record.typed_array_length(agent);
let access_index = try_to_index(agent, request_index, gc)?;
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;
let typed_array = ta_record.object;
let element_size = typed_array.typed_array_element_size();
let offset = typed_array.byte_offset(agent);
TryResult::Continue(unsafe {
access_index
.unchecked_mul(element_size)
.unchecked_add(offset)
})
}
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<usize>)> {
let ta_record = validate_integer_typed_array::<false>(agent, typed_array, gc)?;
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),
TryError::GcError => Ok((ta_record, None)),
},
}
}
fn revalidate_atomic_access<'gc>(
agent: &mut Agent,
typed_array: AnyTypedArray,
byte_index_in_buffer: usize,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let ta_record = make_typed_array_with_buffer_witness_record(
agent,
typed_array,
ecmascript_atomics::Ordering::Unordered,
);
if ta_record.is_typed_array_out_of_bounds(agent) {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"TypedArray out of bounds",
gc.into_nogc(),
));
}
debug_assert!(byte_index_in_buffer >= typed_array.byte_offset(agent));
if byte_index_in_buffer >= ta_record.cached_buffer_byte_length.0 {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"accessIndex out of bounds",
gc.into_nogc(),
));
}
Ok(())
}
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());
let (typed_array, byte_index_in_buffer, v) = handle_typed_array_index_value(
agent,
typed_array.unbind(),
index.unbind(),
value.unbind(),
gc.reborrow(),
)
.unbind()?;
let gc = gc.into_nogc();
let typed_array = typed_array.bind(gc);
let v = v.bind(gc);
let buffer = typed_array.viewed_array_buffer(agent);
Ok(for_any_typed_array!(
typed_array,
_t,
{
get_modify_set_value_in_buffer::<ElementType, OP>(
agent,
buffer,
byte_index_in_buffer,
v,
gc,
)
},
ElementType
))
}
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());
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())
} else {
Number::try_from(value).map(|value| {
number_convert_to_integer_or_infinity(agent, value, gc.nogc()).into()
})
},
) {
let typed_array = ta_record.object;
(byte_index_in_buffer, typed_array, value)
} else {
handle_typed_array_index_value_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 handle_typed_array_index_value_slow<'gc>(
agent: &mut Agent,
ta_record: TypedArrayWithBufferWitnessRecords,
index: Value,
value: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, (usize, AnyTypedArray<'gc>, Numeric<'gc>)> {
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 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());
let v: Numeric = if is_bigint {
to_big_int(agent, value.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc())
.into()
} else {
to_integer_number_or_infinity(agent, value.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc())
.into()
};
let v = v.unbind();
let gc = gc.into_nogc();
let v = v.bind(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))
}
#[inline(never)]
#[cold]
fn handle_typed_array_index_two_values_slow<'gc>(
agent: &mut Agent,
ta_record: TypedArrayWithBufferWitnessRecords,
index: Value,
expected_value: Value,
replacement_value: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, (usize, AnyTypedArray<'gc>, Numeric<'gc>, Numeric<'gc>)> {
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 expected_value = expected_value.scope(agent, gc.nogc());
let replacement_value = replacement_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 (expected, replacement): (Numeric, Numeric) = if is_bigint {
let expected = to_big_int(agent, expected_value.get(agent), gc.reborrow())
.unbind()?
.bind(gc.nogc());
let expected = unsafe { expected_value.replace_self(agent, expected.unbind()) };
let replacement = to_big_int(
agent,
unsafe { replacement_value.take(agent) },
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
(
unsafe { expected.take(agent) }.bind(gc.nogc()).into(),
replacement.into(),
)
} else {
let expected =
to_integer_number_or_infinity(agent, expected_value.get(agent), gc.reborrow())
.unbind()?
.bind(gc.nogc());
let expected = unsafe { expected_value.replace_self(agent, expected.unbind()) };
let replacement = to_integer_number_or_infinity(
agent,
unsafe { replacement_value.take(agent) },
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
(
unsafe { expected.take(agent) }.bind(gc.nogc()).into(),
replacement.into(),
)
};
let expected = expected.unbind();
let replacement = replacement.unbind();
let gc = gc.into_nogc();
let expected = expected.bind(gc);
let replacement = replacement.bind(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, expected, replacement))
}
#[inline(never)]
#[cold]
fn atomic_load_slow<'gc>(
agent: &mut Agent,
ta_record: TypedArrayWithBufferWitnessRecords,
index: Value,
length: usize,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, (usize, AnyTypedArray<'gc>)> {
let mut ta_record = ta_record.bind(gc.nogc());
let index = index.bind(gc.nogc());
let mut revalidate = false;
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 access_index = to_index(agent, index.unbind(), gc.reborrow()).unbind()?;
revalidate = true;
ta_record = unsafe {
TypedArrayWithBufferWitnessRecords {
object: ta.take(agent),
cached_buffer_byte_length,
}
};
access_index
};
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;
let offset = ta_record.object.byte_offset(agent);
let byte_index_in_buffer = offset + access_index * ta_record.object.typed_array_element_size();
let typed_array = ta_record.object.unbind();
let gc = gc.into_nogc();
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))
}
fn do_wait_preparation<'gc>(
agent: &mut Agent,
typed_array: Value,
index: Value,
value: Value,
timeout: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, (SharedArrayBuffer<'gc>, usize, i64, bool, u64)> {
let nogc = gc.nogc();
let typed_array = typed_array.bind(nogc);
let index = index.bind(nogc);
let value = value.bind(nogc);
let timeout = timeout.bind(nogc);
let ta_record = validate_integer_typed_array::<true>(agent, typed_array, nogc)
.unbind()?
.bind(nogc);
let Ok(typed_array) = SharedTypedArray::try_from(ta_record.object) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"cannot wait on ArrayBuffer",
gc.into_nogc(),
));
};
unsafe {
assert_unchecked(matches!(
typed_array,
SharedTypedArray::SharedInt32Array(_) | SharedTypedArray::SharedBigInt64Array(_)
));
};
let (ta_record, byte_index_in_buffer) =
match try_validate_atomic_access(agent, &ta_record, index, nogc) {
ControlFlow::Continue(byte_index_in_buffer) => (ta_record, Some(byte_index_in_buffer)),
ControlFlow::Break(b) => match b {
TryError::Err(err) => return Err(err.unbind()),
TryError::GcError => (ta_record, None),
},
};
let is_big_int_64_array = matches!(typed_array, SharedTypedArray::SharedBigInt64Array(_));
let (typed_array, byte_index_in_buffer, v, t) =
if let (Some(byte_index_in_buffer), Ok(v), Some(q)) = (
byte_index_in_buffer,
if is_big_int_64_array {
BigInt::try_from(value).map(|v| to_big_int64_big_int(agent, v))
} else {
Number::try_from(value).map(|v| to_int32_number(agent, v) as i64)
},
if timeout.is_undefined() {
Some(u64::MAX)
} else if let Value::Integer(q) = timeout {
Some(q.into_i64().max(0).unsigned_abs())
} else {
None
},
) {
(typed_array, byte_index_in_buffer, v, q)
} else {
do_wait_slow(
agent,
ta_record.unbind(),
is_big_int_64_array,
index.unbind(),
value.unbind(),
timeout.unbind(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc())
};
let typed_array = typed_array.unbind().bind(gc.into_nogc());
let buffer = typed_array.viewed_array_buffer(agent);
Ok((buffer, byte_index_in_buffer, v, is_big_int_64_array, t))
}
fn do_wait_critical<'gc, const IS_ASYNC: bool, const IS_I64: bool>(
agent: &mut Agent,
buffer: SharedArrayBuffer,
byte_index_in_buffer: usize,
v: i64,
t: u64,
gc: NoGcScope<'gc, '_>,
) -> Value<'gc> {
let slot = buffer.as_slice(agent).slice_from(byte_index_in_buffer);
let v_not_equal_to_w = if IS_I64 {
let v = v as u64;
let slot = unsafe { slot.as_aligned::<u64>().unwrap_unchecked() };
let w = slot.load(Ordering::SeqCst);
v != w
} else {
let v = v as i32 as u32;
let slot = unsafe { slot.as_aligned::<u32>().unwrap_unchecked() };
let w = slot.load(Ordering::SeqCst);
v != w
};
if v_not_equal_to_w {
if !IS_ASYNC {
return BUILTIN_STRING_MEMORY.not_equal.into();
}
let result_object =
create_wait_result_object(agent, false, BUILTIN_STRING_MEMORY.not_equal.into());
return result_object.into();
}
if t == 0 && IS_ASYNC {
let result_object =
create_wait_result_object(agent, false, BUILTIN_STRING_MEMORY.timed_out.into());
return result_object.into();
}
if !IS_ASYNC {
let data_block = buffer.get_data_block(agent);
let waiters = unsafe { data_block.get_or_init_waiters() };
let waiter_record = WaiterRecord::new_shared();
let mut guard = waiters.lock().unwrap();
let slot = data_block.as_racy_slice().slice_from(byte_index_in_buffer);
let v_changed = if IS_I64 {
let slot = unsafe { slot.as_aligned::<u64>().unwrap_unchecked() };
v as u64 != slot.load(Ordering::SeqCst)
} else {
let slot = unsafe { slot.as_aligned::<u32>().unwrap_unchecked() };
v as i32 as u32 != slot.load(Ordering::SeqCst)
};
if v_changed {
return BUILTIN_STRING_MEMORY.not_equal.into();
}
guard.push_to_list(byte_index_in_buffer, waiter_record.clone());
if t == u64::MAX {
waiter_record.wait(guard);
} else {
let dur = Duration::from_millis(t);
let (new_guard, timeout) = waiter_record.wait_timeout(guard, dur);
guard = new_guard;
if timeout.timed_out() {
guard.remove_from_list(byte_index_in_buffer, waiter_record);
return BUILTIN_STRING_MEMORY.timed_out.into();
}
}
BUILTIN_STRING_MEMORY.ok.into()
} else {
let promise_capability = PromiseCapability::new(agent, gc);
let promise = Global::new(agent, promise_capability.promise.unbind());
let buffer = buffer.get_data_block(agent).clone();
enqueue_atomics_wait_async_job::<IS_I64>(
agent,
buffer,
byte_index_in_buffer,
v,
t,
promise,
gc,
);
let result_object =
create_wait_result_object(agent, true, promise_capability.promise().into());
result_object.into()
}
}
#[cold]
#[inline(never)]
fn do_wait_slow<'gc>(
agent: &mut Agent,
ta_record: TypedArrayWithBufferWitnessRecords,
is_big_int_64_array: bool,
index: Value,
value: Value,
timeout: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, (SharedTypedArray<'gc>, usize, i64, u64)> {
let nogc = gc.nogc();
let ta_record = ta_record.bind(nogc);
let typed_array = unsafe { SharedTypedArray::try_from(ta_record.object).unwrap_unchecked() };
let scoped_typed_array = typed_array.scope(agent, nogc);
let index = index.bind(nogc);
let scoped_timeout = timeout.scope(agent, nogc);
let scoped_value = value.scope(agent, nogc);
let i = validate_atomic_access(agent, ta_record.unbind(), index.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
let value = unsafe { scoped_value.take(agent) }.bind(gc.nogc());
let v = if is_big_int_64_array {
to_big_int64(agent, value.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc())
} else {
to_int32(agent, value.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc()) as i64
};
let q = to_number(agent, unsafe { scoped_timeout.take(agent) }, gc.reborrow()).unbind()?;
let gc = gc.into_nogc();
let q = q.bind(gc);
let t = if q.is_nan_(agent) || q.is_pos_infinity_(agent) {
u64::MAX
} else if q.is_neg_infinity_(agent) {
0
} else {
q.into_i64_(agent).max(0) as u64
};
Ok((unsafe { scoped_typed_array.take(agent) }.bind(gc), i, v, t))
}
fn create_wait_result_object<'gc>(
agent: &mut Agent,
is_async: bool,
value: Value<'gc>,
) -> OrdinaryObject<'gc> {
OrdinaryObject::create_object(
agent,
Some(
agent
.current_realm_record()
.intrinsics()
.object_prototype()
.into(),
),
&[
ObjectEntry::new_data_entry(BUILTIN_STRING_MEMORY.r#async.into(), is_async.into()),
ObjectEntry::new_data_entry(BUILTIN_STRING_MEMORY.value.into(), value),
],
)
.expect("Should perform GC here")
}
#[derive(Debug)]
struct WaitAsyncJobInner {
promise_to_resolve: Global<Promise<'static>>,
join_handle: JoinHandle<WaitResult>,
_has_timeout: bool,
}
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct WaitAsyncJob(Box<WaitAsyncJobInner>);
impl WaitAsyncJob {
pub(crate) fn is_finished(&self) -> bool {
self.0.join_handle.is_finished()
}
pub(crate) fn _will_halt(&self) -> bool {
self.0._has_timeout
}
#[allow(unknown_lints, can_use_no_gc_scope)]
pub(crate) fn run<'gc>(self, agent: &mut Agent, gc: GcScope) -> JsResult<'gc, ()> {
let gc = gc.into_nogc();
let promise = self.0.promise_to_resolve.take(agent).bind(gc);
let Ok(result) = self.0.join_handle.join() else {
return Ok(());
};
let promise_capability = PromiseCapability::from_promise(promise, true);
let result = match result {
WaitResult::Ok | WaitResult::NotEqual => BUILTIN_STRING_MEMORY.ok.into(),
WaitResult::TimedOut => BUILTIN_STRING_MEMORY.timed_out.into(),
};
unwrap_try(promise_capability.try_resolve(agent, result, gc));
Ok(())
}
}
fn enqueue_atomics_wait_async_job<const IS_I64: bool>(
agent: &mut Agent,
buffer: SharedDataBlock,
byte_index_in_buffer: usize,
v: i64,
t: u64,
promise: Global<Promise>,
gc: NoGcScope,
) {
let signal = Arc::new(AtomicBool::new(false));
let s = signal.clone();
let handle = thread::spawn(move || {
let waiters = unsafe { buffer.get_or_init_waiters() };
let waiter_record = WaiterRecord::new_shared();
let mut guard = waiters.lock().unwrap();
let slot = buffer.as_racy_slice().slice_from(byte_index_in_buffer);
let v_not_equal = if IS_I64 {
let slot = unsafe { slot.as_aligned::<u64>().unwrap_unchecked() };
v as u64 != slot.load(Ordering::SeqCst)
} else {
let slot = unsafe { slot.as_aligned::<u32>().unwrap_unchecked() };
v as i32 as u32 != slot.load(Ordering::SeqCst)
};
s.store(true, StdOrdering::Release);
if v_not_equal {
return WaitResult::NotEqual;
}
guard.push_to_list(byte_index_in_buffer, waiter_record.clone());
if t == u64::MAX {
waiter_record.wait(guard);
} else {
let dur = Duration::from_millis(t);
let (new_guard, timeout) = waiter_record.wait_timeout(guard, dur);
guard = new_guard;
if timeout.timed_out() {
guard.remove_from_list(byte_index_in_buffer, waiter_record);
return WaitResult::TimedOut;
}
}
WaitResult::Ok
});
let wait_async_job = Job {
realm: Some(Global::new(agent, agent.current_realm(gc).unbind())),
inner: InnerJob::WaitAsync(WaitAsyncJob(Box::new(WaitAsyncJobInner {
promise_to_resolve: promise,
join_handle: handle,
_has_timeout: t != u64::MAX,
}))),
};
while !signal.load(StdOrdering::Acquire) {
}
agent.host_hooks.enqueue_generic_job(wait_async_job);
}