55use std:: {
66 hint:: assert_unchecked,
77 ops:: ControlFlow ,
8- sync:: { Arc , atomic:: AtomicBool } ,
8+ sync:: {
9+ Arc ,
10+ atomic:: { AtomicBool , Ordering as StdOrdering } ,
11+ } ,
912 thread:: { self , JoinHandle } ,
1013 time:: Duration ,
1114} ;
1215
1316use ecmascript_atomics:: Ordering ;
14- use ecmascript_futex:: { ECMAScriptAtomicWait , FutexError } ;
1517
1618use crate :: {
1719 ecmascript:: {
1820 Agent , AnyArrayBuffer , AnyTypedArray , ArgumentsList , BUILTIN_STRING_MEMORY , Behaviour ,
1921 BigInt , Builtin , ExceptionType , InnerJob , Job , JsResult , Number , Numeric , OrdinaryObject ,
2022 Promise , PromiseCapability , Realm , SharedArrayBuffer , SharedDataBlock , SharedTypedArray ,
2123 String , TryError , TryResult , TypedArrayAbstractOperations ,
22- TypedArrayWithBufferWitnessRecords , Value , builders :: OrdinaryObjectBuilder ,
23- compare_exchange_in_buffer , for_any_typed_array , get_modify_set_value_in_buffer ,
24- get_value_from_buffer , make_typed_array_with_buffer_witness_record ,
25- number_convert_to_integer_or_infinity , set_value_in_buffer , to_big_int , to_big_int64 ,
26- to_big_int64_big_int , to_index , to_int32 , to_int32_number , to_integer_number_or_infinity ,
27- to_integer_or_infinity , to_number , try_result_into_js , try_to_index , unwrap_try ,
28- validate_index, validate_typed_array,
24+ TypedArrayWithBufferWitnessRecords , Value , WaitResult , WaiterRecord ,
25+ builders :: OrdinaryObjectBuilder , compare_exchange_in_buffer , for_any_typed_array ,
26+ get_modify_set_value_in_buffer , get_value_from_buffer ,
27+ make_typed_array_with_buffer_witness_record , number_convert_to_integer_or_infinity ,
28+ set_value_in_buffer , to_big_int , to_big_int64 , to_big_int64_big_int , to_index , to_int32 ,
29+ to_int32_number , to_integer_number_or_infinity , to_integer_or_infinity , to_number ,
30+ try_result_into_js , try_to_index , unwrap_try , validate_index, validate_typed_array,
2931 } ,
3032 engine:: { Bindable , GcScope , Global , NoGcScope , Scopable } ,
3133 heap:: { ObjectEntry , WellKnownSymbols } ,
@@ -666,36 +668,34 @@ impl AtomicsObject {
666668 } ;
667669 // 6. Let block be buffer.[[ArrayBufferData]].
668670 // 8. Let WL be GetWaiterList(block, byteIndexInBuffer).
669- let is_big_int_64_array = matches ! ( typed_array, AnyTypedArray :: SharedBigInt64Array ( _) ) ;
670- let slot = buffer. as_slice ( agent) . slice_from ( byte_index_in_buffer) ;
671- let n = if is_big_int_64_array {
672- // SAFETY: offset was checked.
673- let slot = unsafe { slot. as_aligned :: < u64 > ( ) . unwrap_unchecked ( ) } ;
674- if c == usize:: MAX {
675- // Force the notify count down into a reasonable range: the
676- // ecmascript_futex may return usize::MAX if the OS doesn't
677- // give us a count number.
678- slot. notify_all ( ) . min ( i32:: MAX as usize )
679- } else {
680- slot. notify_many ( c)
681- }
682- } else {
683- // SAFETY: offset was checked.
684- let slot = unsafe { slot. as_aligned :: < u32 > ( ) . unwrap_unchecked ( ) } ;
685- if c == usize:: MAX {
686- // Force the notify count down into a reasonable range: the
687- // ecmascript_futex may return usize::MAX if the OS doesn't
688- // give us a count number.
689- slot. notify_all ( ) . min ( i32:: MAX as usize )
690- } else {
691- slot. notify_many ( c)
692- }
693- } ;
671+ let data_block = buffer. get_data_block ( agent) ;
694672 // 9. Perform EnterCriticalSection(WL).
673+ // SAFETY: buffer is a valid SharedArrayBuffer and cannot be detached. A 0-sized SAB has a
674+ // dangling data block with no backing allocation, but `get_waiters` returns `None` in that case.
675+ let mut n = 0 ;
676+ let Some ( waiters) = ( unsafe { data_block. get_waiters ( ) } ) else {
677+ return Ok ( 0 . into ( ) ) ;
678+ } ;
679+
680+ let mut guard = waiters. lock ( ) . unwrap ( ) ;
695681 // 10. Let S be RemoveWaiters(WL, c).
682+ let Some ( list) = guard. get_list_mut ( byte_index_in_buffer) else {
683+ return Ok ( 0 . into ( ) ) ;
684+ } ;
685+
696686 // 11. For each element W of S, do
697- // a. Perform NotifyWaiter(WL, W).
687+ while n < c {
688+ let Some ( w) = list. pop ( ) else {
689+ break ;
690+ } ;
691+ // a. Perform NotifyWaiter(WL, W).
692+ w. notify_waiters ( ) ;
693+ n += 1 ;
694+ }
695+
698696 // 12. Perform LeaveCriticalSection(WL).
697+ drop ( guard) ;
698+
699699 // 13. Let n be the number of elements in S.
700700 // 14. Return 𝔽(n).
701701 Ok ( Number :: from_usize ( agent, n, gc) . into ( ) )
@@ -1486,37 +1486,47 @@ fn do_wait_critical<'gc, const IS_ASYNC: bool, const IS_I64: bool>(
14861486 // 28. Perform AddWaiter(WL, waiterRecord).
14871487 // 29. If mode is sync, then
14881488 if !IS_ASYNC {
1489- // a. Perform SuspendThisAgent(WL, waiterRecord).
1490- let result = if IS_I64 {
1491- let v = v as u64 ;
1492- // SAFETY: buffer is still live and index was checked.
1489+ let data_block = buffer. get_data_block ( agent) ;
1490+ // SAFETY: buffer is a valid SharedArrayBuffer and cannot be detached. A 0-sized SAB would
1491+ // have a dangling data block, but Atomics.wait requires `byteIndex` to be within bounds,
1492+ // so a 0-sized SAB would have been rejected earlier with a RangeError.
1493+ let waiters = unsafe { data_block. get_or_init_waiters ( ) } ;
1494+ let waiter_record = WaiterRecord :: new_shared ( ) ;
1495+ let mut guard = waiters. lock ( ) . unwrap ( ) ;
1496+
1497+ // Re-read value under critical section to avoid TOCTOU race.
1498+ let slot = data_block. as_racy_slice ( ) . slice_from ( byte_index_in_buffer) ;
1499+ let v_changed = if IS_I64 {
14931500 let slot = unsafe { slot. as_aligned :: < u64 > ( ) . unwrap_unchecked ( ) } ;
1494- if t == u64:: MAX {
1495- slot. wait ( v)
1496- } else {
1497- slot. wait_timeout ( v, Duration :: from_millis ( t) )
1498- }
1501+ v as u64 != slot. load ( Ordering :: SeqCst )
14991502 } else {
1500- let v = v as u32 ;
1501- // SAFETY: buffer is still live and index was checked.
15021503 let slot = unsafe { slot. as_aligned :: < u32 > ( ) . unwrap_unchecked ( ) } ;
1503- if t == u64:: MAX {
1504- slot. wait ( v)
1505- } else {
1506- slot. wait_timeout ( v, Duration :: from_millis ( t) )
1507- }
1504+ v as i32 as u32 != slot. load ( Ordering :: SeqCst )
15081505 } ;
1509- // 31. Perform LeaveCriticalSection(WL).
1510- // 32. If mode is sync, return waiterRecord.[[Result]].
1506+ if v_changed {
1507+ return BUILTIN_STRING_MEMORY . not_equal . into ( ) ;
1508+ }
15111509
1512- match result {
1513- Ok ( _) => BUILTIN_STRING_MEMORY . ok . into ( ) ,
1514- Err ( err) => match err {
1515- FutexError :: Timeout => BUILTIN_STRING_MEMORY . timed_out . into ( ) ,
1516- FutexError :: NotEqual => BUILTIN_STRING_MEMORY . not_equal . into ( ) ,
1517- FutexError :: Unknown => panic ! ( ) ,
1518- } ,
1510+ // a. Perform SuspendThisAgent(WL, waiterRecord).
1511+ guard. push_to_list ( byte_index_in_buffer, waiter_record. clone ( ) ) ;
1512+
1513+ if t == u64:: MAX {
1514+ waiter_record. wait ( guard) ;
1515+ } else {
1516+ let dur = Duration :: from_millis ( t) ;
1517+ let ( new_guard, timeout) = waiter_record. wait_timeout ( guard, dur) ;
1518+ guard = new_guard;
1519+ if timeout. timed_out ( ) {
1520+ guard. remove_from_list ( byte_index_in_buffer, waiter_record) ;
1521+
1522+ // 31. Perform LeaveCriticalSection(WL).
1523+ // 32. If mode is sync, return waiterRecord.[[Result]].
1524+ return BUILTIN_STRING_MEMORY . timed_out . into ( ) ;
1525+ }
15191526 }
1527+ // 31. Perform LeaveCriticalSection(WL).
1528+ // 32. If mode is sync, return waiterRecord.[[Result]].
1529+ BUILTIN_STRING_MEMORY . ok . into ( )
15201530 } else {
15211531 let promise_capability = PromiseCapability :: new ( agent, gc) ;
15221532 let promise = Global :: new ( agent, promise_capability. promise . unbind ( ) ) ;
@@ -1623,7 +1633,7 @@ fn create_wait_result_object<'gc>(
16231633#[ derive( Debug ) ]
16241634struct WaitAsyncJobInner {
16251635 promise_to_resolve : Global < Promise < ' static > > ,
1626- join_handle : JoinHandle < Result < ( ) , FutexError > > ,
1636+ join_handle : JoinHandle < WaitResult > ,
16271637 _has_timeout : bool ,
16281638}
16291639
@@ -1662,18 +1672,8 @@ impl WaitAsyncJob {
16621672 // c. Perform LeaveCriticalSection(WL).
16631673 let promise_capability = PromiseCapability :: from_promise ( promise, true ) ;
16641674 let result = match result {
1665- Ok ( _) => BUILTIN_STRING_MEMORY . ok . into ( ) ,
1666- Err ( FutexError :: NotEqual ) => BUILTIN_STRING_MEMORY . ok . into ( ) ,
1667- Err ( FutexError :: Timeout ) => BUILTIN_STRING_MEMORY . timed_out . into ( ) ,
1668- Err ( FutexError :: Unknown ) => {
1669- let error = agent. throw_exception_with_static_message (
1670- ExceptionType :: Error ,
1671- "unknown error occurred" ,
1672- gc,
1673- ) ;
1674- promise_capability. reject ( agent, error. value ( ) , gc) ;
1675- return Ok ( ( ) ) ;
1676- }
1675+ WaitResult :: Ok | WaitResult :: NotEqual => BUILTIN_STRING_MEMORY . ok . into ( ) ,
1676+ WaitResult :: TimedOut => BUILTIN_STRING_MEMORY . timed_out . into ( ) ,
16771677 } ;
16781678 unwrap_try ( promise_capability. try_resolve ( agent, result, gc) ) ;
16791679 // d. Return unused.
@@ -1701,28 +1701,45 @@ fn enqueue_atomics_wait_async_job<const IS_I64: bool>(
17011701 let signal = Arc :: new ( AtomicBool :: new ( false ) ) ;
17021702 let s = signal. clone ( ) ;
17031703 let handle = thread:: spawn ( move || {
1704+ // SAFETY: buffer is a cloned SharedDataBlock; non-dangling.
1705+ let waiters = unsafe { buffer. get_or_init_waiters ( ) } ;
1706+ let waiter_record = WaiterRecord :: new_shared ( ) ;
1707+ let mut guard = waiters. lock ( ) . unwrap ( ) ;
1708+
1709+ // Re-check the value under the critical section.
17041710 let slot = buffer. as_racy_slice ( ) . slice_from ( byte_index_in_buffer) ;
1705- if IS_I64 {
1706- let v = v as u64 ;
1707- // SAFETY: buffer is still live and index was checked.
1711+ let v_not_equal = if IS_I64 {
17081712 let slot = unsafe { slot. as_aligned :: < u64 > ( ) . unwrap_unchecked ( ) } ;
1709- s. store ( true , std:: sync:: atomic:: Ordering :: Release ) ;
1710- if t == u64:: MAX {
1711- slot. wait ( v)
1712- } else {
1713- slot. wait_timeout ( v, Duration :: from_millis ( t) )
1714- }
1713+ v as u64 != slot. load ( Ordering :: SeqCst )
17151714 } else {
1716- let v = v as i32 as u32 ;
1717- // SAFETY: buffer is still live and index was checked.
17181715 let slot = unsafe { slot. as_aligned :: < u32 > ( ) . unwrap_unchecked ( ) } ;
1719- s. store ( true , std:: sync:: atomic:: Ordering :: Release ) ;
1720- if t == u64:: MAX {
1721- slot. wait ( v)
1722- } else {
1723- slot. wait_timeout ( v, Duration :: from_millis ( t) )
1716+ v as i32 as u32 != slot. load ( Ordering :: SeqCst )
1717+ } ;
1718+
1719+ // Signal the main thread that we have the lock and are about to sleep.
1720+ s. store ( true , StdOrdering :: Release ) ;
1721+
1722+ if v_not_equal {
1723+ return WaitResult :: NotEqual ;
1724+ }
1725+
1726+ guard. push_to_list ( byte_index_in_buffer, waiter_record. clone ( ) ) ;
1727+
1728+ if t == u64:: MAX {
1729+ waiter_record. wait ( guard) ;
1730+ } else {
1731+ let dur = Duration :: from_millis ( t) ;
1732+ let ( new_guard, timeout) = waiter_record. wait_timeout ( guard, dur) ;
1733+ guard = new_guard;
1734+ if timeout. timed_out ( ) {
1735+ guard. remove_from_list ( byte_index_in_buffer, waiter_record) ;
1736+
1737+ // 31. Perform LeaveCriticalSection(WL).
1738+ // 32. If mode is sync, return waiterRecord.[[Result]].
1739+ return WaitResult :: TimedOut ;
17241740 }
17251741 }
1742+ WaitResult :: Ok
17261743 } ) ;
17271744 let wait_async_job = Job {
17281745 realm : Some ( Global :: new ( agent, agent. current_realm ( gc) . unbind ( ) ) ) ,
@@ -1732,7 +1749,7 @@ fn enqueue_atomics_wait_async_job<const IS_I64: bool>(
17321749 _has_timeout : t != u64:: MAX ,
17331750 } ) ) ) ,
17341751 } ;
1735- while !signal. load ( std :: sync :: atomic :: Ordering :: Acquire ) {
1752+ while !signal. load ( StdOrdering :: Acquire ) {
17361753 // Wait until the thread has started up and is about to go to sleep.
17371754 }
17381755 // 2. Let now be the time value (UTC) identifying the current time.
0 commit comments