Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MemoryAtomicWait32, MemoryAtomicWait64 and MemoryAtomicNotify helper functions #3155

Closed
ptitSeb opened this issue Aug 31, 2022 · 1 comment
Labels
🎉 enhancement New feature! priority-medium Medium priority issue
Milestone

Comments

@ptitSeb
Copy link
Contributor

ptitSeb commented Aug 31, 2022

Motivation

To help adding the memory.atomic.wait32, memory.atomic.wait64 and memory.atomic.notify opcode to all compilers, some helper function that will do all the traitment should be coded, similarly to how the GrowMemory and the other complex opcodes are handled

Proposed solution

Functions are defined in lib/vm/src/libcalls.rs
Expected signature would be

#[no_mangle]
pub unsafe extern "C" fn wasmer_vm_memory32_atomic_wait32(
    vmctx: *mut VMContext,
    memory_index: u32,
    dst: u32,
    val: u32,
    timeout: i64,
) -> u32;

for the memory.atomic.wait32. An alternative for imported_memory might also be needed.

The specs are here: https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md

The relevant part is:

Wait and Notify operators

The notify and wait operators are optimizations over busy-waiting for a value to change. The operators have sequentially consistent ordering.

Both notify and wait operators trap if the effective address of either operator is misaligned or out-of-bounds. Wait operators additionally trap if used on an unshared linear memory. The wait operators require an alignment of their memory access size. The notify operator requires an alignment of 32 bits.

For the web embedding, the agent can also be suspended or woken via the Atomics.wait and Atomics.notify functions respectively. An agent will not be suspended for other reasons, unless all agents in that cluster are also suspended.

An agent suspended via Atomics.wait can be woken by the WebAssembly memory.atomic.notify operator. Similarly, an agent suspended by memory.atomic.wait32 or memory.atomic.wait64 can be woken by Atomics.notify.

Wait

The wait operator take three operands: an address operand, an expected value, and a relative timeout in nanoseconds as an i64. The return value is 0, 1, or 2, returned as an i32.

timeout value Behavior
timeout < 0 Never expires
0 <= timeout <= maximum signed i64 value Expires after timeout nanoseconds

If the linear memory is unshared, the wait operation traps. Otherwise, the wait operation begins by performing an atomic load from the given address. If the loaded value is not equal to the expected value, the operator returns 1 ("not-equal"). If the values are equal, the agent is suspended. If the agent is woken, the wait operator returns 0 ("ok"). If the timeout expires before another agent notifies this one, this operator returns 2 ("timed-out"). Note that when the agent is suspended, it will not be spuriously woken. The agent is only woken by memory.atomic.notify (or Atomics.notify in the web embedding).

When an agent is suspended, if the number of waiters (including this one) is equal to 232, then trap.

  • memory.atomic.wait32: load i32 value, compare to expected (as i32), and wait for notify at same address
  • memory.atomic.wait64: load i64 value, compare to expected (as i64), and wait for notify at same address

For the web embedding, memory.atomic.wait32 is equivalent in behavior to executing the following:

  1. Let memory be a WebAssembly.Memory object for this module.
  2. Let buffer be memory(Get(memory, "buffer")).
  3. Let int32array be Int32Array(buffer).
  4. Let result be Atomics.wait(int32array, address, expected, timeout / 1e6), where address, expected, and timeout are the operands to the wait operator as described above.
  5. Return an i32 value as described in the above table: ("ok" -> 0, "not-equal" -> 1, "timed-out" -> 2).

memory.atomic.wait64 has no equivalent in ECMAScript as it is currently specified, as there is no Int64Array type, and an ECMAScript Number cannot represent all values of a 64-bit integer. That said, the behavior can be approximated as follows:

  1. Let memory be a WebAssembly.Memory object for this module.
  2. Let buffer be memory(Get(memory, "buffer")).
  3. Let int64array be Int64Array, where Int64Array is a typed-array constructor that allows 64-bit integer views with an element size of 8.
  4. Let result be Atomics.wait(int64array, address, expected, timeout / 1e6), where address, expected, and timeout are the operands to the wait operator as described above. The Atomics.wait operation is modified:
    1. ValidateSharedIntegerTypedArray will fail if the typed-array type is not an Int64Array.
    2. value is not converted to an Int32, but kept in a 64-bit integer representation.
    3. indexedPosition is (i x 8) + offset
  5. Return an i32 value as described in the above table: ("ok" -> 0, "not-equal" -> 1, "timed-out" -> 2).

Notify

The notify operator takes two operands: an address operand and a count as an unsigned i32. The operation will notify as many waiters as are waiting on the same effective address, up to the maximum as specified by count. The operator returns the number of waiters that were woken as an unsigned i32. Note that if the notify operator is used with an unshared linear memory, the number of waiters will always be zero.

  • memory.atomic.notify: notify count threads waiting on the given address via memory.atomic.wait32 or memory.atomic.wait64

For the web embedding, memory.atomic.notify is equivalent in behavior to executing the following:

  1. Let memory be a WebAssembly.Memory object for this module.
  2. Let buffer be memory(Get(memory, "buffer")).
  3. Let int32array be Int32Array(buffer).
  4. Let result be Atomics.notify(int32array, address, count).
  5. Return result converted to an i32.
Wait and Notify operators The notify and wait operators are optimizations over busy-waiting for a value to change. The operators have sequentially consistent ordering.

Both notify and wait operators trap if the effective address of either operator is misaligned or out-of-bounds. Wait operators additionally trap if used on an unshared linear memory. The wait operators require an alignment of their memory access size. The notify operator requires an alignment of 32 bits.

For the web embedding, the agent can also be suspended or woken via the Atomics.wait and Atomics.notify functions respectively. An agent will not be suspended for other reasons, unless all agents in that cluster are also suspended.

An agent suspended via Atomics.wait can be woken by the WebAssembly memory.atomic.notify operator. Similarly, an agent suspended by memory.atomic.wait32 or memory.atomic.wait64 can be woken by Atomics.notify.

Wait
The wait operator take three operands: an address operand, an expected value, and a relative timeout in nanoseconds as an i64. The return value is 0, 1, or 2, returned as an i32.

timeout value Behavior
timeout < 0 Never expires
0 <= timeout <= maximum signed i64 value Expires after timeout nanoseconds
Return value Description
0 "ok", woken by another agent in the cluster
1 "not-equal", the loaded value did not match the expected value
2 "timed-out", not woken before timeout expired
If the linear memory is unshared, the wait operation traps. Otherwise, the wait operation begins by performing an atomic load from the given address. If the loaded value is not equal to the expected value, the operator returns 1 ("not-equal"). If the values are equal, the agent is suspended. If the agent is woken, the wait operator returns 0 ("ok"). If the timeout expires before another agent notifies this one, this operator returns 2 ("timed-out"). Note that when the agent is suspended, it will not be spuriously woken. The agent is only woken by memory.atomic.notify (or Atomics.notify in the web embedding).

When an agent is suspended, if the number of waiters (including this one) is equal to 232, then trap.

memory.atomic.wait32: load i32 value, compare to expected (as i32), and wait for notify at same address
memory.atomic.wait64: load i64 value, compare to expected (as i64), and wait for notify at same address
For the web embedding, memory.atomic.wait32 is equivalent in behavior to executing the following:

Let memory be a WebAssembly.Memory object for this module.
Let buffer be memory(Get(memory, "buffer")).
Let int32array be Int32Array(buffer).
Let result be Atomics.wait(int32array, address, expected, timeout / 1e6), where address, expected, and timeout are the operands to the wait operator as described above.
Return an i32 value as described in the above table: ("ok" -> 0, "not-equal" -> 1, "timed-out" -> 2).
memory.atomic.wait64 has no equivalent in ECMAScript as it is currently specified, as there is no Int64Array type, and an ECMAScript Number cannot represent all values of a 64-bit integer. That said, the behavior can be approximated as follows:

Let memory be a WebAssembly.Memory object for this module.
Let buffer be memory(Get(memory, "buffer")).
Let int64array be Int64Array, where Int64Array is a typed-array constructor that allows 64-bit integer views with an element size of 8.
Let result be Atomics.wait(int64array, address, expected, timeout / 1e6), where address, expected, and timeout are the operands to the wait operator as described above. The Atomics.wait operation is modified:
ValidateSharedIntegerTypedArray will fail if the typed-array type is not an Int64Array.
value is not converted to an Int32, but kept in a 64-bit integer representation.
indexedPosition is (i x 8) + offset
Return an i32 value as described in the above table: ("ok" -> 0, "not-equal" -> 1, "timed-out" -> 2).
Notify
The notify operator takes two operands: an address operand and a count as an unsigned i32. The operation will notify as many waiters as are waiting on the same effective address, up to the maximum as specified by count. The operator returns the number of waiters that were woken as an unsigned i32. Note that if the notify operator is used with an unshared linear memory, the number of waiters will always be zero.

memory.atomic.notify: notify count threads waiting on the given address via memory.atomic.wait32 or memory.atomic.wait64
For the web embedding, memory.atomic.notify is equivalent in behavior to executing the following:

Let memory be a WebAssembly.Memory object for this module.
Let buffer be memory(Get(memory, "buffer")).
Let int32array be Int32Array(buffer).
Let result be Atomics.notify(int32array, address, count).
Return result converted to an i32.

@ptitSeb ptitSeb added the 🎉 enhancement New feature! label Aug 31, 2022
@ptitSeb ptitSeb added this to the v3.x milestone Aug 31, 2022
@syrusakbary syrusakbary added the priority-medium Medium priority issue label Sep 6, 2022
@ptitSeb ptitSeb modified the milestones: v3.x, v3.1 Nov 16, 2022
bors bot added a commit that referenced this issue Nov 29, 2022
3153: SharedMemory & Atomics r=ptitSeb a=ptitSeb

# Description
Enabled SharedMemory and the Atomics extension proposal

- [x] Enable Atomic extension by default
- [x] Fix "imports" tests #3154
- [x] Add function for memory.atomic.wait32, memory.atomic.wait64 and memory.atomic.notify opcodes #3155
- [x] Add support for the new wait/notify opcodes in Cranelift compiler #3156 
- [x] Add support for the new wait/notify opcodes in LLVM compiler #3157
- [x] Add support for atomic access opcodes in AArch64/Singlepass compiler #3159
- [x] Add support for the new wait/notify opcodes in Singlepass compiler #3158
- [x] Fix Atomic issues on x86_64 Singlepass compiler not related to Wait/Notify opcodes #3161
- [x] Fix Atomic issues on Cranelift compiler not related to Wait/Notify opcodes #3162
- [x] Fix Atomic issues on LLVM compiler not related to Wait/Notify opcodes #3163
- [x] Fix the ticket #3167 on Cranelift

For #3304 

Co-authored-by: John Sharratt's Shared Account <johnathan.sharratt@gmail.com>
Co-authored-by: ptitSeb <sebastien.chev@gmail.com>
Co-authored-by: Syrus Akbary <me@syrusakbary.com>
@ptitSeb
Copy link
Contributor Author

ptitSeb commented Dec 6, 2022

Merged

@ptitSeb ptitSeb closed this as completed Dec 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🎉 enhancement New feature! priority-medium Medium priority issue
Projects
None yet
Development

No branches or pull requests

2 participants