25.4 The Atomics Object

The Atomics object:

  • is %Atomics%.
  • is the initial value of the "Atomics" property of the global object.
  • is an ordinary object.
  • has a [[Prototype]] internal slot whose value is %Object.prototype%.
  • does not have a [[Construct]] internal method; it cannot be used as a constructor with the new operator.
  • does not have a [[Call]] internal method; it cannot be invoked as a function.

The Atomics object provides functions that operate indivisibly (atomically) on shared memory array cells as well as functions that let agents wait for and dispatch primitive events. When used with discipline, the Atomics functions allow multi-agent programs that communicate through shared memory to execute in a well-understood order even on parallel CPUs. The rules that govern shared-memory communication are provided by the memory model, defined below.

Note

For informative guidelines for programming and implementing shared memory in ECMAScript, please see the notes at the end of the memory model section.

25.4.1 Abstract Operations for Atomics

25.4.1.1 ValidateIntegerTypedArray ( typedArray [ , waitable ] )

The abstract operation ValidateIntegerTypedArray takes argument typedArray and optional argument waitable (a Boolean). It performs the following steps when called:

  1. If waitable is not present, set waitable to false.
  2. Let buffer be ? ValidateTypedArray(typedArray).
  3. Let typeName be typedArray.[[TypedArrayName]].
  4. Let type be the Element Type value in Table 60 for typeName.
  5. If waitable is true, then
    1. If typeName is not "Int32Array" or "BigInt64Array", throw a TypeError exception.
  6. Else,
    1. If ! IsUnclampedIntegerElementType(type) is false and ! IsBigIntElementType(type) is false, throw a TypeError exception.
  7. Return buffer.

25.4.1.2 ValidateAtomicAccess ( typedArray, requestIndex )

The abstract operation ValidateAtomicAccess takes arguments typedArray and requestIndex. It performs the following steps when called:

  1. Assert: typedArray is an Object that has a [[ViewedArrayBuffer]] internal slot.
  2. Let length be typedArray.[[ArrayLength]].
  3. Let accessIndex be ? ToIndex(requestIndex).
  4. Assert: accessIndex ≥ 0.
  5. If accessIndexlength, throw a RangeError exception.
  6. Let arrayTypeName be typedArray.[[TypedArrayName]].
  7. Let elementSize be the Element Size value specified in Table 60 for arrayTypeName.
  8. Let offset be typedArray.[[ByteOffset]].
  9. Return (accessIndex × elementSize) + offset.

25.4.1.3 GetWaiterList ( block, i )

A WaiterList is a semantic object that contains an ordered list of those agents that are waiting on a location (block, i) in shared memory; block is a Shared Data Block and i a byte offset into the memory of block. A WaiterList object also optionally contains a Synchronize event denoting the previous leaving of its critical section.

Initially a WaiterList object has an empty list and no Synchronize event.

The agent cluster has a store of WaiterList objects; the store is indexed by (block, i). WaiterLists are agent-independent: a lookup in the store of WaiterLists by (block, i) will result in the same WaiterList object in any agent in the agent cluster.

Each WaiterList has a critical section that controls exclusive access to that WaiterList during evaluation. Only a single agent may enter a WaiterList's critical section at one time. Entering and leaving a WaiterList's critical section is controlled by the abstract operations EnterCriticalSection and LeaveCriticalSection. Operations on a WaiterList—adding and removing waiting agents, traversing the list of agents, suspending and notifying agents on the list, setting and retrieving the Synchronize event—may only be performed by agents that have entered the WaiterList's critical section.

The abstract operation GetWaiterList takes arguments block (a Shared Data Block) and i (a non-negative integer). It performs the following steps when called:

  1. Assert: block is a Shared Data Block.
  2. Assert: i and i + 3 are valid byte offsets within the memory of block.
  3. Assert: i is divisible by 4.
  4. Return the WaiterList that is referenced by the pair (block, i).

25.4.1.4 EnterCriticalSection ( WL )

The abstract operation EnterCriticalSection takes argument WL (a WaiterList). It performs the following steps when called:

  1. Assert: The calling agent is not in the critical section for any WaiterList.
  2. Wait until no agent is in the critical section for WL, then enter the critical section for WL (without allowing any other agent to enter).
  3. If WL has a Synchronize event, then
    1. NOTE: A WL whose critical section has been entered at least once has a Synchronize event set by LeaveCriticalSection.
    2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
    3. Let eventsRecord be the Agent Events Record in execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
    4. Let entererEventList be eventsRecord.[[EventList]].
    5. Let enterEvent be a new Synchronize event.
    6. Append enterEvent to entererEventList.
    7. Let leaveEvent be the Synchronize event in WL.
    8. Append (leaveEvent, enterEvent) to eventsRecord.[[AgentSynchronizesWith]].

EnterCriticalSection has contention when an agent attempting to enter the critical section must wait for another agent to leave it. When there is no contention, FIFO order of EnterCriticalSection calls is observable. When there is contention, an implementation may choose an arbitrary order but may not cause an agent to wait indefinitely.

25.4.1.5 LeaveCriticalSection ( WL )

The abstract operation LeaveCriticalSection takes argument WL (a WaiterList). It performs the following steps when called:

  1. Assert: The calling agent is in the critical section for WL.
  2. Let execution be the [[CandidateExecution]] field of the calling surrounding's Agent Record.
  3. Let eventsRecord be the Agent Events Record in execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
  4. Let leaverEventList be eventsRecord.[[EventList]].
  5. Let leaveEvent be a new Synchronize event.
  6. Append leaveEvent to leaverEventList.
  7. Set the Synchronize event in WL to leaveEvent.
  8. Leave the critical section for WL.

25.4.1.6 AddWaiter ( WL, W )

The abstract operation AddWaiter takes arguments WL (a WaiterList) and W (an agent signifier). It performs the following steps when called:

  1. Assert: The calling agent is in the critical section for WL.
  2. Assert: W is not on the list of waiters in any WaiterList.
  3. Add W to the end of the list of waiters in WL.

25.4.1.7 RemoveWaiter ( WL, W )

The abstract operation RemoveWaiter takes arguments WL (a WaiterList) and W (an agent signifier). It performs the following steps when called:

  1. Assert: The calling agent is in the critical section for WL.
  2. Assert: W is on the list of waiters in WL.
  3. Remove W from the list of waiters in WL.

25.4.1.8 RemoveWaiters ( WL, c )

The abstract operation RemoveWaiters takes arguments WL (a WaiterList) and c (a non-negative integer or +∞). It performs the following steps when called:

  1. Assert: The calling agent is in the critical section for WL.
  2. Let L be a new empty List.
  3. Let S be a reference to the list of waiters in WL.
  4. Repeat, while c > 0 and S is not an empty List,
    1. Let W be the first waiter in S.
    2. Add W to the end of L.
    3. Remove W from S.
    4. If c is finite, set c to c - 1.
  5. Return L.

25.4.1.9 SuspendAgent ( WL, W, timeout )

The abstract operation SuspendAgent takes arguments WL (a WaiterList), W (an agent signifier), and timeout (a non-negative integer). It performs the following steps when called:

  1. Assert: The calling agent is in the critical section for WL.
  2. Assert: W is equivalent to AgentSignifier().
  3. Assert: W is on the list of waiters in WL.
  4. Assert: AgentCanSuspend() is true.
  5. Perform LeaveCriticalSection(WL) and suspend W for up to timeout milliseconds, performing the combined operation in such a way that a notification that arrives after the critical section is exited but before the suspension takes effect is not lost. W can notify either because the timeout expired or because it was notified explicitly by another agent calling NotifyWaiter(WL, W), and not for any other reasons at all.
  6. Perform EnterCriticalSection(WL).
  7. If W was notified explicitly by another agent calling NotifyWaiter(WL, W), return true.
  8. Return false.

25.4.1.10 NotifyWaiter ( WL, W )

The abstract operation NotifyWaiter takes arguments WL (a WaiterList) and W (an agent signifier). It performs the following steps when called:

  1. Assert: The calling agent is in the critical section for WL.
  2. Notify the agent W.
Note

The embedding may delay notifying W, e.g. for resource management reasons, but W must eventually be notified in order to guarantee forward progress.

25.4.1.11 AtomicReadModifyWrite ( typedArray, index, value, op )

The abstract operation AtomicReadModifyWrite takes arguments typedArray, index, value, and op (a read-modify-write modification function). 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 result of the combination. It returns the loaded value. It performs the following steps when called:

  1. Let buffer be ? ValidateIntegerTypedArray(typedArray).
  2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
  3. Let arrayTypeName be typedArray.[[TypedArrayName]].
  4. If typedArray.[[ContentType]] is BigInt, let v be ? ToBigInt(value).
  5. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)).
  6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
  7. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call to ToBigInt or ToIntegerOrInfinity on the preceding lines can have arbitrary side effects, which could cause the buffer to become detached.
  8. Let elementType be the Element Type value in Table 60 for arrayTypeName.
  9. Return GetModifySetValueInBuffer(buffer, indexedPosition, elementType, v, op).

25.4.1.12 ByteListBitwiseOp ( op, xBytes, yBytes )

The abstract operation ByteListBitwiseOp takes arguments op (a sequence of Unicode code points), xBytes (a List of byte values), and yBytes (a List of byte values). The operation atomically performs a bitwise operation on all byte values of the arguments and returns a List of byte values. It performs the following steps when called:

  1. Assert: op is &, ^, or |.
  2. Assert: xBytes and yBytes have the same number of elements.
  3. Let result be a new empty List.
  4. Let i be 0.
  5. For each element xByte of xBytes, do
    1. Let yByte be yBytes[i].
    2. If op is &, let resultByte be the result of applying the bitwise AND operation to xByte and yByte.
    3. Else if op is ^, let resultByte be the result of applying the bitwise exclusive OR (XOR) operation to xByte and yByte.
    4. Else, op is |. Let resultByte be the result of applying the bitwise inclusive OR operation to xByte and yByte.
    5. Set i to i + 1.
    6. Append resultByte to the end of result.
  6. Return result.

25.4.1.13 ByteListEqual ( xBytes, yBytes )

The abstract operation ByteListEqual takes arguments xBytes (a List of byte values) and yBytes (a List of byte values). It performs the following steps when called:

  1. If xBytes and yBytes do not have the same number of elements, return false.
  2. Let i be 0.
  3. For each element xByte of xBytes, do
    1. Let yByte be yBytes[i].
    2. If xByteyByte, return false.
    3. Set i to i + 1.
  4. Return true.

25.4.2 Atomics.add ( typedArray, index, value )

The following steps are taken:

  1. Let type be the Element Type value in Table 60 for typedArray.[[TypedArrayName]].
  2. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
  3. Let add be a new read-modify-write modification function with parameters (xBytes, yBytes) that captures type and isLittleEndian and performs the following steps atomically when called:
    1. Let x be RawBytesToNumeric(type, xBytes, isLittleEndian).
    2. Let y be RawBytesToNumeric(type, yBytes, isLittleEndian).
    3. Let T be Type(x).
    4. Let sum be T::add(x, y).
    5. Let sumBytes be NumericToRawBytes(type, sum, isLittleEndian).
    6. Assert: sumBytes, xBytes, and yBytes have the same number of elements.
    7. Return sumBytes.
  4. Return ? AtomicReadModifyWrite(typedArray, index, value, add).

25.4.3 Atomics.and ( typedArray, index, value )

The following steps are taken:

  1. Let and be a new read-modify-write modification function with parameters (xBytes, yBytes) that captures nothing and performs the following steps atomically when called:
    1. Return ByteListBitwiseOp(&, xBytes, yBytes).
  2. Return ? AtomicReadModifyWrite(typedArray, index, value, and).

25.4.4 Atomics.compareExchange ( typedArray, index, expectedValue, replacementValue )

The following steps are taken:

  1. Let buffer be ? ValidateIntegerTypedArray(typedArray).
  2. Let block be buffer.[[ArrayBufferData]].
  3. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
  4. Let arrayTypeName be typedArray.[[TypedArrayName]].
  5. If typedArray.[[ContentType]] is BigInt, then
    1. Let expected be ? ToBigInt(expectedValue).
    2. Let replacement be ? ToBigInt(replacementValue).
  6. Else,
    1. Let expected be 𝔽(? ToIntegerOrInfinity(expectedValue)).
    2. Let replacement be 𝔽(? ToIntegerOrInfinity(replacementValue)).
  7. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
  8. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call to ToBigInt or ToIntegerOrInfinity on the preceding lines can have arbitrary side effects, which could cause the buffer to become detached.
  9. Let elementType be the Element Type value in Table 60 for arrayTypeName.
  10. Let elementSize be the Element Size value specified in Table 60 for Element Type elementType.
  11. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
  12. Let expectedBytes be NumericToRawBytes(elementType, expected, isLittleEndian).
  13. Let replacementBytes be NumericToRawBytes(elementType, replacement, isLittleEndian).
  14. If IsSharedArrayBuffer(buffer) is true, then
    1. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
    2. Let eventList be the [[EventList]] field of the element in execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
    3. Let rawBytesRead be a List of length elementSize whose elements are nondeterministically chosen byte values.
    4. 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.
    5. NOTE: The comparison of the expected value and the read value is performed outside of the read-modify-write modification function to avoid needlessly strong synchronization when the expected value is not equal to the read value.
    6. If ByteListEqual(rawBytesRead, expectedBytes) is true, then
      1. Let second be a new read-modify-write modification function with parameters (oldBytes, newBytes) that captures nothing and performs the following steps atomically when called:
        1. Return newBytes.
      2. Let event be ReadModifyWriteSharedMemory { [[Order]]: SeqCst, [[NoTear]]: true, [[Block]]: block, [[ByteIndex]]: indexedPosition, [[ElementSize]]: elementSize, [[Payload]]: replacementBytes, [[ModifyOp]]: second }.
    7. Else,
      1. Let event be ReadSharedMemory { [[Order]]: SeqCst, [[NoTear]]: true, [[Block]]: block, [[ByteIndex]]: indexedPosition, [[ElementSize]]: elementSize }.
    8. Append event to eventList.
    9. Append Chosen Value Record { [[Event]]: event, [[ChosenValue]]: rawBytesRead } to execution.[[ChosenValues]].
  15. Else,
    1. Let rawBytesRead be a List of length elementSize whose elements are the sequence of elementSize bytes starting with block[indexedPosition].
    2. If ByteListEqual(rawBytesRead, expectedBytes) is true, then
      1. Store the individual bytes of replacementBytes into block, starting at block[indexedPosition].
  16. Return RawBytesToNumeric(elementType, rawBytesRead, isLittleEndian).

25.4.5 Atomics.exchange ( typedArray, index, value )

The following steps are taken:

  1. Let second be a new read-modify-write modification function with parameters (oldBytes, newBytes) that captures nothing and performs the following steps atomically when called:
    1. Return newBytes.
  2. Return ? AtomicReadModifyWrite(typedArray, index, value, second).

25.4.6 Atomics.isLockFree ( size )

The following steps are taken:

  1. Let n be ? ToIntegerOrInfinity(size).
  2. Let AR be the Agent Record of the surrounding agent.
  3. If n = 1, return AR.[[IsLockFree1]].
  4. If n = 2, return AR.[[IsLockFree2]].
  5. If n = 4, return true.
  6. If n = 8, return AR.[[IsLockFree8]].
  7. Return false.
Note

Atomics.isLockFree() is an optimization primitive. The intuition is that if the atomic step of an atomic primitive (compareExchange, load, store, add, sub, and, or, xor, or exchange) on a datum of size n bytes will be performed without the calling agent acquiring a lock outside the n bytes comprising the datum, then Atomics.isLockFree(n) will return true. High-performance algorithms will use Atomics.isLockFree to determine whether to use locks or atomic operations in critical sections. If an atomic primitive is not lock-free then it is often more efficient for an algorithm to provide its own locking.

Atomics.isLockFree(4) always returns true as that can be supported on all known relevant hardware. Being able to assume this will generally simplify programs.

Regardless of the value of Atomics.isLockFree, all atomic operations are guaranteed to be atomic. For example, they will never have a visible operation take place in the middle of the operation (e.g., "tearing").

25.4.7 Atomics.load ( typedArray, index )

The following steps are taken:

  1. Let buffer be ? ValidateIntegerTypedArray(typedArray).
  2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
  3. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
  4. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call to ValidateAtomicAccess on the preceding line can have arbitrary side effects, which could cause the buffer to become detached.
  5. Let arrayTypeName be typedArray.[[TypedArrayName]].
  6. Let elementType be the Element Type value in Table 60 for arrayTypeName.
  7. Return GetValueFromBuffer(buffer, indexedPosition, elementType, true, SeqCst).

25.4.8 Atomics.or ( typedArray, index, value )

The following steps are taken:

  1. Let or be a new read-modify-write modification function with parameters (xBytes, yBytes) that captures nothing and performs the following steps atomically when called:
    1. Return ByteListBitwiseOp(|, xBytes, yBytes).
  2. Return ? AtomicReadModifyWrite(typedArray, index, value, or).

25.4.9 Atomics.store ( typedArray, index, value )

The following steps are taken:

  1. Let buffer be ? ValidateIntegerTypedArray(typedArray).
  2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
  3. Let arrayTypeName be typedArray.[[TypedArrayName]].
  4. If arrayTypeName is "BigUint64Array" or "BigInt64Array", let v be ? ToBigInt(value).
  5. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)).
  6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
  7. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call to ToBigInt or ToIntegerOrInfinity on the preceding lines can have arbitrary side effects, which could cause the buffer to become detached.
  8. Let elementType be the Element Type value in Table 60 for arrayTypeName.
  9. Perform SetValueInBuffer(buffer, indexedPosition, elementType, v, true, SeqCst).
  10. Return v.

25.4.10 Atomics.sub ( typedArray, index, value )

The following steps are taken:

  1. Let type be the Element Type value in Table 60 for typedArray.[[TypedArrayName]].
  2. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
  3. Let subtract be a new read-modify-write modification function with parameters (xBytes, yBytes) that captures type and isLittleEndian and performs the following steps atomically when called:
    1. Let x be RawBytesToNumeric(type, xBytes, isLittleEndian).
    2. Let y be RawBytesToNumeric(type, yBytes, isLittleEndian).
    3. Let T be Type(x).
    4. Let difference be T::subtract(x, y).
    5. Let differenceBytes be NumericToRawBytes(type, difference, isLittleEndian).
    6. Assert: differenceBytes, xBytes, and yBytes have the same number of elements.
    7. Return differenceBytes.
  4. Return ? AtomicReadModifyWrite(typedArray, index, value, subtract).

25.4.11 Atomics.wait ( typedArray, index, value, timeout )

Atomics.wait puts the calling agent in a wait queue and puts it to sleep until it is notified or the sleep times out. The following steps are taken:

  1. Let buffer be ? ValidateIntegerTypedArray(typedArray, true).
  2. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception.
  3. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
  4. Let arrayTypeName be typedArray.[[TypedArrayName]].
  5. If arrayTypeName is "BigInt64Array", let v be ? ToBigInt64(value).
  6. Otherwise, let v be ? ToInt32(value).
  7. Let q be ? ToNumber(timeout).
  8. If q is NaN or +∞𝔽, let t be +∞; else if q is -∞𝔽, let t be 0; else let t be max((q), 0).
  9. Let B be AgentCanSuspend().
  10. If B is false, throw a TypeError exception.
  11. Let block be buffer.[[ArrayBufferData]].
  12. Let WL be GetWaiterList(block, indexedPosition).
  13. Perform EnterCriticalSection(WL).
  14. Let elementType be the Element Type value in Table 60 for arrayTypeName.
  15. Let w be ! GetValueFromBuffer(buffer, indexedPosition, elementType, true, SeqCst).
  16. If vw, then
    1. Perform LeaveCriticalSection(WL).
    2. Return the String "not-equal".
  17. Let W be AgentSignifier().
  18. Perform AddWaiter(WL, W).
  19. Let notified be SuspendAgent(WL, W, t).
  20. If notified is true, then
    1. Assert: W is not on the list of waiters in WL.
  21. Else,
    1. Perform RemoveWaiter(WL, W).
  22. Perform LeaveCriticalSection(WL).
  23. If notified is true, return the String "ok".
  24. Return the String "timed-out".

25.4.12 Atomics.notify ( typedArray, index, count )

Atomics.notify notifies some agents that are sleeping in the wait queue. The following steps are taken:

  1. Let buffer be ? ValidateIntegerTypedArray(typedArray, true).
  2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
  3. If count is undefined, let c be +∞.
  4. Else,
    1. Let intCount be ? ToIntegerOrInfinity(count).
    2. Let c be max(intCount, 0).
  5. Let block be buffer.[[ArrayBufferData]].
  6. Let arrayTypeName be typedArray.[[TypedArrayName]].
  7. If IsSharedArrayBuffer(buffer) is false, return +0𝔽.
  8. Let WL be GetWaiterList(block, indexedPosition).
  9. Let n be 0.
  10. Perform EnterCriticalSection(WL).
  11. Let S be RemoveWaiters(WL, c).
  12. Repeat, while S is not an empty List,
    1. Let W be the first agent in S.
    2. Remove W from the front of S.
    3. Perform NotifyWaiter(WL, W).
    4. Set n to n + 1.
  13. Perform LeaveCriticalSection(WL).
  14. Return 𝔽(n).

25.4.13 Atomics.xor ( typedArray, index, value )

The following steps are taken:

  1. Let xor be a new read-modify-write modification function with parameters (xBytes, yBytes) that captures nothing and performs the following steps atomically when called:
    1. Return ByteListBitwiseOp(^, xBytes, yBytes).
  2. Return ? AtomicReadModifyWrite(typedArray, index, value, xor).

25.4.14 Atomics [ @@toStringTag ]

The initial value of the @@toStringTag property is the String value "Atomics".

This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.