Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
# v-atomics
Low-level atomic operations for V with explicit i386 support (MMX required on i386).
Native atomic primitives for V implemented with inline assembly, without relying on C FFI.
This repository is an experiment in providing low-level atomic operations directly in V,
using V's inline assembly support.
At the moment, all operations provide sequentially consistent semantics.
## Motivation
In the current V ecosystem, atomic operations are implemented via calls into C.
While this approach works, it introduces an additional dependency on the C toolchain
and headers and limits control over the exact machine instructions being emitted.
x.atomics explores an alternative: **native atomic operations implemented directly in V**,
using architecture-specific inline assembly and explicit semantics.
The current focus of this project is:
- correctness of basic atomic primitives;
- predictable and inspectable code generation;
- sequentially consistent behavior for all operations.
In future versions, the set of supported atomic operations will be expanded,
and additional memory orderings will be introduced.
---
## Scope and Guarantees
- atomic operations on integer types implemented in V with inline assembly;
- architecture-specific implementations (per-platform `atomics.<arch>.v` files);
- **sequential consistency** for all exposed operations.
---
## Memory Model
All operations in this library are intended to be **sequentially consistent**:
- operations appear to be globally ordered;
- no weaker semantics (relaxed, acquire, release) are currently implemented;
- when weaker variants are added in the future, they will be explicitly named and documented.
---
## Examples
See the [examples](examples/) directory for complete runnable examples.
### Basic Usage
```v
import x.atomics
fn main() {
mut value := i32(0)
// Atomically store a value
atomics.store_i32(&value, 42)
// Atomically load the value
loaded := atomics.load_i32(&value)
// Atomic add: returns the new value after addition
new_value := atomics.add_i32(&value, 10)
// Atomic swap: returns the old value
old := atomics.swap_i32(&value, 100)
_ = loaded
_ = new_value
_ = old
}
```
### Compare-and-Swap (CAS)
```v
import x.atomics
fn main() {
mut flag := u32(0)
// CAS: if flag == 0, set it to 1; returns true on success
if atomics.cas_u32(&flag, 0, 1) {
println('Successfully changed flag from 0 to 1')
}
}
```
### Bitwise Operations (AND / OR)
```
import x.atomics
fn main() {
mut flags := u32(0xFF)
// Atomically AND: clears lower nibble, returns old value
old := atomics.and_u32(&flags, 0xF0)
println('old: ${old}, new: ${flags}') // old: 255, new: 240
// Atomically OR: sets a bit, returns old value
prev := atomics.or_u32(&flags, 0x01)
println('prev: ${prev}, new: ${flags}') // prev: 240, new: 241
}
```
### Available Operations
| Operation | i32 | i64 | u32 | u64 |
|-----------|-----|-----|-----|-----|
| `load_*` | yes | yes | yes | yes |
| `store_*` | yes | yes | yes | yes |
| `add_*` | yes | yes | yes | yes |
| `swap_*` | yes | yes | yes | yes |
| `cas_*` | yes | yes | yes | yes |
| `and_*` | yes | yes | yes | yes |
| `or_*` | yes | yes | yes | yes |
+772
View File
@@ -0,0 +1,772 @@
module atomics
// add_i32 atomically adds delta to the value at dest and returns the new value.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn add_i32(dest &i32, delta i32) i32 {
mut result := i32(0)
asm volatile amd64 {
mov rdx, dest
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, delta
lock xadd [rdx], eax
add eax, delta
mov result, eax
2:
; =r (result)
; r (dest)
r (delta)
; eax
rdx
memory
}
return result
}
// swap_i32 atomically stores new value at dest and returns the old value.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn swap_i32(dest &i32, new i32) i32 {
mut old := i32(0)
asm volatile amd64 {
mov rdx, dest
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, new
xchg [rdx], eax
mov old, eax
2:
; =r (old)
; r (dest)
r (new)
; eax
rdx
memory
}
return old
}
// store_i32 atomically stores value at dest.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn store_i32(dest &i32, value i32) {
asm volatile amd64 {
mov rdx, dest
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, value
xchg eax, [rdx]
2:
; ; r (dest)
r (value)
; eax
rdx
memory
}
}
// load_i32 atomically loads and returns the value at num.
// The operation is performed with sequential consistency.
// Panics if num is not 4-byte aligned.
pub fn load_i32(num &i32) i32 {
mut out := i32(0)
asm volatile amd64 {
mov rdx, num
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, [rdx]
mov out, eax
2:
; =r (out)
; r (num)
; eax
rdx
memory
}
return out
}
// cas_i32 performs a compare-and-swap operation.
// If the current value at addr equals old, it atomically stores new.
// Returns true if the swap was performed, false otherwise.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn cas_i32(addr &i32, old i32, new i32) bool {
mut swapped := false
asm volatile amd64 {
mov rdx, addr
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, old
mov ecx, new
lock cmpxchg [rdx], ecx
sete al
mov swapped, al
2:
; =r (swapped)
; r (addr)
r (old)
r (new)
; eax
ecx
rdx
memory
}
return swapped
}
// store_i64 atomically stores value at dest.
// The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn store_i64(dest &i64, value i64) {
asm volatile amd64 {
mov rdx, dest
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, value
xchg rax, [rdx]
2:
; ; r (dest)
r (value)
; rax
rdx
memory
}
}
// load_i64 atomically loads and returns the value at num.
// The operation is performed with sequential consistency.
// Panics if num is not 8-byte aligned.
pub fn load_i64(num &i64) i64 {
mut out := i64(0)
asm volatile amd64 {
mov rdx, num
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, [rdx]
mov out, rax
2:
; =r (out)
; r (num)
; rax
rdx
memory
}
return out
}
// add_i64 atomically adds delta to the value at dest and returns the new value.
// The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn add_i64(dest &i64, delta i64) i64 {
mut result := i64(0)
asm volatile amd64 {
mov rdx, dest
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, delta
lock xadd [rdx], rax
add rax, delta
mov result, rax
2:
; =r (result)
; r (delta)
r (dest)
; rax
rdx
memory
}
return result
}
// swap_i64 atomically stores value at dest and returns the old value.
// The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn swap_i64(dest &i64, value i64) i64 {
mut old := i64(0)
asm volatile amd64 {
mov rdx, dest
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, value
xchg rax, [rdx]
mov old, rax
2:
; =r (old)
; r (dest)
r (value)
; rax
rdx
memory
}
return old
}
// cas_i64 performs a compare-and-swap operation.
// If the current value at addr equals old, it atomically stores new.
// Returns true if the swap was performed, false otherwise.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn cas_i64(addr &i64, old i64, new i64) bool {
mut swapped := false
asm volatile amd64 {
mov rdx, addr
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, old
mov rcx, new
lock cmpxchgq [rdx], rcx
sete al
mov swapped, al
2:
; =r (swapped)
; r (addr)
r (old)
r (new)
; rax
rcx
rdx
memory
}
return swapped
}
// add_u32 atomically adds delta to the value at dest and returns the new value.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn add_u32(dest &u32, delta u32) u32 {
mut result := u32(0)
asm volatile amd64 {
mov rdx, dest
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, delta
lock xadd [rdx], eax
add eax, delta
mov result, eax
2:
; =r (result)
; r (dest)
r (delta)
; rax
rdx
memory
}
return result
}
// swap_u32 atomically stores new value at dest and returns the old value.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn swap_u32(dest &u32, new u32) u32 {
mut old := u32(0)
asm volatile amd64 {
mov rdx, dest
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, new
xchg [rdx], eax
mov old, eax
2:
; =r (old)
; r (dest)
r (new)
; eax
rdx
memory
}
return old
}
// store_u32 atomically stores value at dest.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn store_u32(dest &u32, value u32) {
asm volatile amd64 {
mov rdx, dest
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, value
xchg eax, [rdx]
2:
; ; r (dest)
r (value)
; eax
rdx
memory
}
}
// load_u32 atomically loads and returns the value at num.
// The operation is performed with sequential consistency.
// Panics if num is not 4-byte aligned.
pub fn load_u32(num &u32) u32 {
mut out := u32(0)
asm volatile amd64 {
mov rdx, num
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, [rdx]
mov out, eax
2:
; =r (out)
; r (num)
; rax
rdx
}
return out
}
// cas_u32 performs a compare-and-swap operation.
// If the current value at addr equals old, it atomically stores new.
// Returns true if the swap was performed, false otherwise.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn cas_u32(addr &u32, old u32, new u32) bool {
mut swapped := false
asm volatile amd64 {
mov rdx, addr
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, old
mov ecx, new
lock cmpxchg [rdx], ecx
sete al
mov swapped, al
2:
; =r (swapped)
; r (addr)
r (old)
r (new)
; eax
ecx
rdx
memory
}
return swapped
}
// load_u64 atomically loads and returns the value at num.
// The operation is performed with sequential consistency.
// Panics if num is not 8-byte aligned.
pub fn load_u64(num &u64) u64 {
mut out := u64(0)
asm volatile amd64 {
mov rdx, num
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, [rdx]
mov out, rax
2:
; =r (out)
; r (num)
; rax
rdx
memory
}
return out
}
// store_u64 atomically stores value at dest.
// The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn store_u64(dest &u64, value u64) {
asm volatile amd64 {
mov rdx, dest
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, value
xchg rax, [rdx]
2:
; ; r (dest)
r (value)
; rax
rdx
memory
}
}
// add_u64 atomically adds delta to the value at dest and returns the new value.
// The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn add_u64(dest &u64, delta u64) u64 {
mut result := u64(0)
asm volatile amd64 {
mov rdx, dest
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, delta
lock xadd [rdx], rax
add rax, delta
mov result, rax
2:
; =r (result)
; r (dest)
r (delta)
; rax
rdx
memory
}
return result
}
// swap_u64 atomically stores value at dest and returns the old value.
// The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn swap_u64(dest &u64, value u64) u64 {
mut old := u64(0)
asm volatile amd64 {
mov rdx, dest
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, value
xchg [rdx], rax
mov old, rax
2:
; =r (old)
; r (dest)
r (value)
; rax
rdx
memory
}
return old
}
// cas_u64 performs a compare-and-swap operation.
// If the current value at addr equals old, it atomically stores new.
// Returns true if the swap was performed, false otherwise.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn cas_u64(addr &u64, old u64, new u64) bool {
mut swapped := false
asm volatile amd64 {
mov rdx, addr
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov rax, old
mov rcx, new
lock cmpxchgq [rdx], rcx
sete al
mov swapped, al
2:
; =r (swapped)
; r (addr)
r (old)
r (new)
; rax
rcx
rdx
memory
}
return swapped
}
// and_i64 atomically performs a bitwise AND of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn and_i64(addr &i64, mask i64) i64 {
mut old := i64(0)
asm volatile amd64 {
mov rdx, addr
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov rax, [rdx]
mov rcx, rax
and rcx, mask
lock cmpxchg [rdx], rcx
jnz '3b'
mov old, rax
2:
; =r (old)
; r (addr)
r (mask)
; rdx
rax
rcx
memory
}
return old
}
// and_u64 atomically performs a bitwise AND of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn and_u64(addr &u64, mask u64) u64 {
mut old := u64(0)
asm volatile amd64 {
mov rdx, addr
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov rax, [rdx]
mov rcx, rax
and rcx, mask
lock cmpxchg [rdx], rcx
jnz '3b'
mov old, rax
2:
; =r (old)
; r (addr)
r (mask)
; rdx
rax
rcx
memory
}
return old
}
// or_i64 atomically performs a bitwise OR of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn or_i64(addr &i64, mask i64) i64 {
mut old := i64(0)
asm volatile amd64 {
mov rdx, addr
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov rax, [rdx]
mov rcx, rax
or rcx, mask
lock cmpxchg [rdx], rcx
jnz '3b'
mov old, rax
2:
; =r (old)
; r (addr)
r (mask)
; rdx
rax
rcx
memory
}
return old
}
// or_u64 atomically performs a bitwise OR of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn or_u64(addr &u64, mask u64) u64 {
mut old := u64(0)
asm volatile amd64 {
mov rdx, addr
test rdx, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov rax, [rdx]
mov rcx, rax
or rcx, mask
lock cmpxchg [rdx], rcx
jnz '3b'
mov old, rax
2:
; =r (old)
; r (addr)
r (mask)
; rdx
rax
rcx
memory
}
return old
}
// and_i32 atomically performs a bitwise AND of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn and_i32(addr &i32, mask i32) i32 {
mut old := i32(0)
asm volatile amd64 {
mov rdx, addr
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [rdx]
mov ecx, eax
and ecx, mask
lock cmpxchg [rdx], ecx
jnz '3b'
mov old, eax
2:
; =r (old)
; r (addr)
r (mask)
; rdx
eax
ecx
memory
}
return old
}
// and_u32 atomically performs a bitwise AND of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn and_u32(addr &u32, mask u32) u32 {
mut old := u32(0)
asm volatile amd64 {
mov rdx, addr
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [rdx]
mov ecx, eax
and ecx, mask
lock cmpxchg [rdx], ecx
jnz '3b'
mov old, eax
2:
; =r (old)
; r (addr)
r (mask)
; rdx
eax
ecx
memory
}
return old
}
// or_i32 atomically performs a bitwise OR of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn or_i32(addr &i32, mask i32) i32 {
mut old := i32(0)
asm volatile amd64 {
mov rdx, addr
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [rdx]
mov ecx, eax
or ecx, mask
lock cmpxchg [rdx], ecx
jnz '3b'
mov old, eax
2:
; =r (old)
; r (addr)
r (mask)
; rdx
eax
ecx
memory
}
return old
}
// or_u32 atomically performs a bitwise OR of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn or_u32(addr &u32, mask u32) u32 {
mut old := u32(0)
asm volatile amd64 {
mov rdx, addr
test rdx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [rdx]
mov ecx, eax
or ecx, mask
lock cmpxchg [rdx], ecx
jnz '3b'
mov old, eax
2:
; =r (old)
; r (addr)
r (mask)
; rdx
eax
ecx
memory
}
return old
}
+902
View File
@@ -0,0 +1,902 @@
module atomics
// add_i32 atomically adds delta to the value at dest and returns the new value.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn add_i32(dest &i32, delta i32) i32 {
mut result := i32(0)
asm volatile i386 {
mov edx, dest
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, delta
lock xadd [edx], eax
add eax, delta
mov result, eax
2:
; =r (result)
; r (dest)
r (delta)
; eax
edx
memory
}
return result
}
// swap_i32 atomically stores new value at dest and returns the old value.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn swap_i32(dest &i32, new i32) i32 {
mut old := i32(0)
asm volatile i386 {
mov edx, dest
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, new
xchg [edx], eax
mov old, eax
2:
; =m (old)
; r (dest)
r (new)
; eax
edx
memory
}
return old
}
// store_i32 atomically stores value at dest.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn store_i32(dest &i32, value i32) {
asm volatile i386 {
mov edx, dest
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, value
xchg eax, [edx]
2:
; ; r (dest)
r (value)
; eax
edx
memory
}
}
// load_i32 atomically loads and returns the value at num.
// The operation is performed with sequential consistency.
// Panics if num is not 4-byte aligned.
pub fn load_i32(num &i32) i32 {
mut out := i32(0)
asm volatile i386 {
mov edx, num
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, [edx]
mov out, eax
2:
; =r (out)
; r (num)
; eax
edx
memory
}
return out
}
// cas_i32 performs a compare-and-swap operation.
// If the current value at addr equals old, it atomically stores new.
// Returns true if the swap was performed, false otherwise.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn cas_i32(addr &i32, old i32, new i32) bool {
mut swapped := false
asm volatile i386 {
mov edx, addr
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, old
mov ecx, new
lock cmpxchg [edx], ecx
sete al
mov swapped, al
2:
; =m (swapped)
; r (addr)
r (old)
r (new)
; eax
ecx
edx
memory
}
return swapped
}
// store_i64 atomically stores value at dest using MMX instructions.
// The operation is performed with sequential consistency.
// Requires MMX support. Panics if dest is not 8-byte aligned.
pub fn store_i64(dest &i64, value i64) {
asm volatile i386 {
mov esi, dest
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
movq mm0, value
movq [esi], mm0
emms
xor eax, eax
lock xaddl [esp], eax
2:
; ; r (dest)
m (value)
; esi
eax
mm0
memory
}
}
// load_i64 atomically loads and returns the value at num using MMX instructions.
// The operation is performed with sequential consistency.
// Requires MMX support. Panics if num is not 8-byte aligned.
pub fn load_i64(num &i64) i64 {
mut out := i64(0)
asm volatile i386 {
mov esi, num
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
movq mm0, [esi]
movq out, mm0
emms
2:
; =m (out)
; r (num)
; esi
mm0
memory
}
return out
}
// add_i64 atomically adds delta to the value at dest and returns the new value.
// Uses a compare-and-swap loop. The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn add_i64(dest &i64, delta i64) i64 {
mut delta_lo := u32(u64(delta) & 0xFFFF_FFFF)
mut delta_hi := u32(u64(delta) >> 32)
mut res_lo := u32(0)
mut res_hi := u32(0)
asm volatile i386 {
mov esi, dest
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [esi]
mov edx, [esi + 4]
mov ebx, eax
mov ecx, edx
add ebx, delta_lo
adc ecx, delta_hi
lock cmpxchg8b [esi]
jnz '3b'
mov res_lo, ebx
mov res_hi, ecx
2:
; ; r (dest)
m (delta_lo)
m (delta_hi)
m (res_lo)
m (res_hi)
; eax
ebx
ecx
edx
esi
memory
}
return i64(u64(res_lo) | (u64(res_hi) << 32))
}
// swap_i64 atomically stores value at dest and returns the old value.
// Uses a compare-and-swap loop. The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn swap_i64(dest &i64, value i64) i64 {
mut value_lo := u32(u64(value) & 0xFFFF_FFFF)
mut value_hi := u32(u64(value) >> 32)
asm volatile i386 {
mov esi, dest
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [esi]
mov edx, [esi + 4]
mov ebx, value_lo
mov ecx, value_hi
lock cmpxchg8b [esi]
jnz '3b'
mov value_lo, eax
mov value_hi, edx
2:
; ; r (dest)
m (value_lo)
m (value_hi)
; eax
ebx
ecx
edx
esi
memory
}
return i64(u64(value_lo) | (u64(value_hi) << 32))
}
// cas_i64 performs a compare-and-swap operation.
// If the current value at addr equals old, it atomically stores new.
// Returns true if the swap was performed, false otherwise.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn cas_i64(addr &i64, old i64, new i64) bool {
mut swapped := false
mut old_lo := u32(u64(old) & 0xFFFF_FFFF)
mut old_hi := u32(u64(old) >> 32)
mut new_lo := u32(u64(new) & 0xFFFF_FFFF)
mut new_hi := u32(u64(new) >> 32)
asm volatile i386 {
mov esi, addr
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, old_lo
mov edx, old_hi
mov ebx, new_lo
mov ecx, new_hi
lock cmpxchg8b [esi]
sete al
mov swapped, al
2:
; =m (swapped)
; r (addr)
m (old_lo)
m (old_hi)
m (new_lo)
m (new_hi)
; eax
ebx
ecx
edx
esi
memory
}
return swapped
}
// add_u32 atomically adds delta to the value at dest and returns the new value.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn add_u32(dest &u32, delta u32) u32 {
mut result := u32(0)
asm volatile i386 {
mov edx, dest
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, delta
lock xadd [edx], eax
add eax, delta
mov result, eax
2:
; =r (result)
; r (dest)
r (delta)
; eax
edx
memory
}
return result
}
// swap_u32 atomically stores new value at dest and returns the old value.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn swap_u32(dest &u32, new u32) u32 {
mut old := u32(0)
asm volatile i386 {
mov edx, dest
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, new
xchg [edx], eax
mov old, eax
2:
; =m (old)
; r (dest)
r (new)
; eax
edx
memory
}
return old
}
// store_u32 atomically stores value at dest.
// The operation is performed with sequential consistency.
// Panics if dest is not 4-byte aligned.
pub fn store_u32(dest &u32, value u32) {
asm volatile i386 {
mov edx, dest
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, value
xchg eax, [edx]
2:
; ; r (dest)
r (value)
; eax
edx
memory
}
}
// load_u32 atomically loads and returns the value at num.
// The operation is performed with sequential consistency.
// Panics if num is not 4-byte aligned.
pub fn load_u32(num &u32) u32 {
mut out := u32(0)
asm volatile i386 {
mov edx, num
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, [edx]
mov out, eax
2:
; =r (out)
; r (num)
; eax
edx
memory
}
return out
}
// cas_u32 performs a compare-and-swap operation.
// If the current value at addr equals old, it atomically stores new.
// Returns true if the swap was performed, false otherwise.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn cas_u32(addr &u32, old u32, new u32) bool {
mut swapped := false
asm volatile i386 {
mov edx, addr
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, old
mov ecx, new
lock cmpxchg [edx], ecx
sete al
mov swapped, al
2:
; =m (swapped)
; r (addr)
r (old)
r (new)
; eax
ecx
edx
memory
}
return swapped
}
// load_u64 atomically loads and returns the value at num using MMX instructions.
// The operation is performed with sequential consistency.
// Requires MMX support. Panics if num is not 8-byte aligned.
pub fn load_u64(num &u64) u64 {
mut out := u64(0)
asm volatile i386 {
mov esi, num
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
movq mm0, [esi]
movq out, mm0
emms
2:
; =m (out)
; r (num)
; esi
mm0
memory
}
return out
}
// store_u64 atomically stores value at dest using MMX instructions.
// The operation is performed with sequential consistency.
// Requires MMX support. Panics if dest is not 8-byte aligned.
pub fn store_u64(dest &u64, value u64) {
asm volatile i386 {
mov esi, dest
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
movq mm0, value
movq [esi], mm0
emms
xor eax, eax
lock xaddl [esp], eax
2:
; ; r (dest)
m (value)
; eax
mm0
memory
}
}
// add_u64 atomically adds delta to the value at dest and returns the new value.
// Uses a compare-and-swap loop. The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn add_u64(dest &u64, delta u64) u64 {
mut delta_lo := u32(delta & 0xFFFF_FFFF)
mut delta_hi := u32(delta >> 32)
mut res_lo := u32(0)
mut res_hi := u32(0)
asm volatile i386 {
mov esi, dest
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [esi]
mov edx, [esi + 4]
mov ebx, eax
mov ecx, edx
add ebx, delta_lo
adc ecx, delta_hi
lock cmpxchg8b [esi]
jnz '3b'
mov res_lo, ebx
mov res_hi, ecx
2:
; ; r (dest)
m (delta_lo)
m (delta_hi)
m (res_lo)
m (res_hi)
; eax
ebx
ecx
edx
esi
memory
}
return u64(res_lo) | (u64(res_hi) << 32)
}
// swap_u64 atomically stores value at dest and returns the old value.
// Uses a compare-and-swap loop. The operation is performed with sequential consistency.
// Panics if dest is not 8-byte aligned.
pub fn swap_u64(dest &u64, value u64) u64 {
mut old := u64(0)
mut value_lo := u32(value & 0xFFFF_FFFF)
mut value_hi := u32(value >> 32)
asm volatile i386 {
mov esi, dest
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [esi]
mov edx, [esi + 4]
mov ebx, value_lo
mov ecx, value_hi
lock cmpxchg8b [esi]
jnz '3b'
mov value_lo, eax
mov value_hi, edx
2:
; ; r (dest)
m (value_lo)
m (value_hi)
; eax
ebx
ecx
edx
esi
memory
}
old = u64(value_lo) | (u64(value_hi) << 32)
return old
}
// cas_u64 performs a compare-and-swap operation.
// If the current value at addr equals old, it atomically stores new.
// Returns true if the swap was performed, false otherwise.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn cas_u64(addr &u64, old u64, new u64) bool {
mut swapped := false
mut old_lo := u32(old & 0xFFFF_FFFF)
mut old_hi := u32(old >> 32)
mut new_lo := u32(new & 0xFFFF_FFFF)
mut new_hi := u32(new >> 32)
asm volatile i386 {
mov esi, addr
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, old_lo
mov edx, old_hi
mov ebx, new_lo
mov ecx, new_hi
lock cmpxchg8b [esi]
sete al
mov swapped, al
2:
; =m (swapped)
; r (addr)
m (old_lo)
m (old_hi)
m (new_lo)
m (new_hi)
; eax
ebx
ecx
edx
esi
memory
}
return swapped
}
// and_i64 atomically performs a bitwise AND of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn and_i64(addr &i64, mask i64) i64 {
mask_lo := u32(u64(mask) & 0xFFFF_FFFF)
mask_hi := u32(u64(mask) >> 32)
mut old_lo := u32(0)
mut old_hi := u32(0)
asm volatile i386 {
mov esi, addr
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, [esi]
mov edx, [esi + 4]
3:
mov ebx, eax
and ebx, mask_lo
mov ecx, edx
and ecx, mask_hi
lock cmpxchg8b [esi]
jnz '3b'
mov old_lo, eax
mov old_hi, edx
2:
; =m (old_lo)
=m (old_hi)
; m (mask_lo)
m (mask_hi)
r (addr)
; eax
edx
ecx
ebx
esi
}
return i64(u64(old_lo) | (u64(old_hi) << 32))
}
// and_u64 atomically performs a bitwise AND of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn and_u64(addr &u64, mask u64) u64 {
mask_lo := u32(u64(mask) & 0xFFFF_FFFF)
mask_hi := u32(u64(mask) >> 32)
mut old_lo := u32(0)
mut old_hi := u32(0)
asm volatile i386 {
mov esi, addr
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, [esi]
mov edx, [esi + 4]
3:
mov ebx, eax
and ebx, mask_lo
mov ecx, edx
and ecx, mask_hi
lock cmpxchg8b [esi]
jnz '3b'
mov old_lo, eax
mov old_hi, edx
2:
; =m (old_lo)
=m (old_hi)
; m (mask_lo)
m (mask_hi)
r (addr)
; eax
edx
ecx
ebx
esi
}
return u64(old_lo) | (u64(old_hi) << 32)
}
// or_i64 atomically performs a bitwise OR of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn or_i64(addr &i64, mask i64) i64 {
mask_lo := u32(u64(mask) & 0xFFFF_FFFF)
mask_hi := u32(u64(mask) >> 32)
mut old_lo := u32(0)
mut old_hi := u32(0)
asm volatile i386 {
mov esi, addr
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, [esi]
mov edx, [esi + 4]
3:
mov ebx, eax
or ebx, mask_lo
mov ecx, edx
or ecx, mask_hi
lock cmpxchg8b [esi]
jnz '3b'
mov old_lo, eax
mov old_hi, edx
2:
; =m (old_lo)
=m (old_hi)
; m (mask_lo)
m (mask_hi)
r (addr)
; eax
edx
ecx
ebx
esi
}
return i64(u64(old_lo) | (u64(old_hi) << 32))
}
// or_u64 atomically performs a bitwise OR of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 8-byte aligned.
pub fn or_u64(addr &u64, mask u64) u64 {
mask_lo := u32(u64(mask) & 0xFFFF_FFFF)
mask_hi := u32(u64(mask) >> 32)
mut old_lo := u32(0)
mut old_hi := u32(0)
asm volatile i386 {
mov esi, addr
test esi, 7
jz '1f'
call panicUnaligned
jmp '2f'
1:
mov eax, [esi]
mov edx, [esi + 4]
3:
mov ebx, eax
or ebx, mask_lo
mov ecx, edx
or ecx, mask_hi
lock cmpxchg8b [esi]
jnz '3b'
mov old_lo, eax
mov old_hi, edx
2:
; =m (old_lo)
=m (old_hi)
; m (mask_lo)
m (mask_hi)
r (addr)
; eax
edx
ecx
ebx
esi
}
return u64(old_lo) | (u64(old_hi) << 32)
}
// and_u32 atomically performs a bitwise AND of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn and_u32(addr &u32, mask u32) u32 {
mut old := u32(0)
asm volatile i386 {
mov edx, addr
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [edx]
mov ecx, eax
and ecx, mask
lock cmpxchgl [edx], ecx
jnz '3b'
mov old, eax
2:
; =r (old)
; r (addr)
r (mask)
; edx
eax
ecx
memory
}
return old
}
// and_i32 atomically performs a bitwise AND of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn and_i32(addr &i32, mask i32) i32 {
mut old := i32(0)
asm volatile i386 {
mov edx, addr
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [edx]
mov ecx, eax
and ecx, mask
lock cmpxchgl [edx], ecx
jnz '3b'
mov old, eax
2:
; =r (old)
; r (addr)
r (mask)
; edx
eax
ecx
memory
}
return old
}
// or_i32 atomically performs a bitwise OR of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn or_i32(addr &i32, mask i32) i32 {
mut old := i32(0)
asm volatile i386 {
mov edx, addr
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [edx]
mov ecx, eax
or ecx, mask
lock cmpxchgl [edx], ecx
jnz '3b'
mov old, eax
2:
; =r (old)
; r (addr)
r (mask)
; edx
eax
ecx
memory
}
return old
}
// or_u32 atomically performs a bitwise OR of the value at addr with mask and returns the old value.
// The operation is performed with sequential consistency.
// Panics if addr is not 4-byte aligned.
pub fn or_u32(addr &u32, mask u32) u32 {
mut old := u32(0)
asm volatile i386 {
mov edx, addr
test edx, 3
jz '1f'
call panicUnaligned
jmp '2f'
1:
3:
mov eax, [edx]
mov ecx, eax
or ecx, mask
lock cmpxchgl [edx], ecx
jnz '3b'
mov old, eax
2:
; =r (old)
; r (addr)
r (mask)
; edx
eax
ecx
memory
}
return old
}
+123
View File
@@ -0,0 +1,123 @@
### Environment
- CPU: AMD Ryzen 9 9950X3D (16C / 32T)
- RAM: 64 GiB
- OS: EndeavourOS (Linux, kernel 6.18.6-arch1-1)
- Compiler:
- amd64: v -prod -cc gcc -gc none
- i386: v -keepc -cc i686-linux-gnu-gcc -prod -m32 -arch i386 -cflags -mmmx -w -gc none
### How to run
```bash
# amd64
v -prod -cc gcc -gc none run benchmarks/atomic_benchmark.v
# i386
v -keepc -cc i686-linux-gnu-gcc -prod -m32 -arch i386 -cflags -mmmx -w -gc none run benchmarks/atomic_benchmark.v
```
### Results (ns/op, 100M iterations)
```
AMD64
=====
Command:
v -prod -cc gcc -gc none run atomic_benchmark.v
u64 store std: 3.788 ns/op (total: 378.783ms, iters: 100000000)
u64 store custom: 3.773 ns/op (total: 377.301ms, iters: 100000000)
u64 load std: 1.078 ns/op (total: 107.848ms, iters: 100000000)
u64 load custom: 1.084 ns/op (total: 108.381ms, iters: 100000000)
u64 add std: 3.601 ns/op (total: 360.067ms, iters: 100000000)
u64 add custom: 3.782 ns/op (total: 378.213ms, iters: 100000000)
u64 swap std (exchange): 3.805 ns/op (total: 380.520ms, iters: 100000000)
u64 swap custom: 3.835 ns/op (total: 383.493ms, iters: 100000000)
u64 cas std: 3.824 ns/op (total: 382.391ms, iters: 100000000)
u64 cas custom: 3.783 ns/op (total: 378.264ms, iters: 100000000)
u32 store std: 3.783 ns/op (total: 378.346ms, iters: 100000000)
u32 store custom: 3.822 ns/op (total: 382.245ms, iters: 100000000)
u32 load std: 1.084 ns/op (total: 108.427ms, iters: 100000000)
u32 load custom: 1.085 ns/op (total: 108.536ms, iters: 100000000)
u32 add std: 3.663 ns/op (total: 366.308ms, iters: 100000000)
u32 add custom: 3.857 ns/op (total: 385.722ms, iters: 100000000)
u32 swap std (exchange): 3.855 ns/op (total: 385.503ms, iters: 100000000)
u32 swap custom: 3.859 ns/op (total: 385.892ms, iters: 100000000)
u32 cas std: 3.87 ns/op (total: 387.025ms, iters: 100000000)
u32 cas custom: 3.837 ns/op (total: 383.680ms, iters: 100000000)
i64 store std (via u64): 3.784 ns/op (total: 378.377ms, iters: 100000000)
i64 store custom: 3.79 ns/op (total: 378.993ms, iters: 100000000)
i64 load std (via u64): 0.935 ns/op (total: 93.519ms, iters: 100000000)
i64 load custom: 0.864 ns/op (total: 86.350ms, iters: 100000000)
i64 add std (via u64): 3.608 ns/op (total: 360.752ms, iters: 100000000)
i64 add custom: 3.843 ns/op (total: 384.319ms, iters: 100000000)
i64 swap std (exchange u64): 3.826 ns/op (total: 382.621ms, iters: 100000000)
i64 swap custom: 3.835 ns/op (total: 383.513ms, iters: 100000000)
i64 cas std (via u64): 3.84 ns/op (total: 383.988ms, iters: 100000000)
i64 cas custom: 3.847 ns/op (total: 384.733ms, iters: 100000000)
i32 store std (via u32): 3.84 ns/op (total: 383.983ms, iters: 100000000)
i32 store custom: 3.841 ns/op (total: 384.068ms, iters: 100000000)
i32 load std (via u32): 1.1 ns/op (total: 109.956ms, iters: 100000000)
i32 load custom: 1.1 ns/op (total: 109.978ms, iters: 100000000)
i32 add std (via u32): 3.659 ns/op (total: 365.907ms, iters: 100000000)
i32 add custom: 3.846 ns/op (total: 384.574ms, iters: 100000000)
i32 swap std (exchange u32): 3.848 ns/op (total: 384.830ms, iters: 100000000)
i32 swap custom: 3.836 ns/op (total: 383.562ms, iters: 100000000)
i32 cas std (via u32): 3.837 ns/op (total: 383.690ms, iters: 100000000)
i32 cas custom: 3.815 ns/op (total: 381.453ms, iters: 100000000)
I386
====
Command:
v -keepc -cc i686-linux-gnu-gcc -prod -m32 -arch i386 -cflags -mmmx -w -gc none run benchmarks/atomic_benchmark.v
u64 store std: 9.575 ns/op (total: 957.485ms, iters: 100000000)
u64 store custom: 7.703 ns/op (total: 770.251ms, iters: 100000000)
u64 load std: 1.769 ns/op (total: 176.860ms, iters: 100000000)
u64 load custom: 1.892 ns/op (total: 189.238ms, iters: 100000000)
u64 add std: 5.544 ns/op (total: 554.431ms, iters: 100000000)
u64 add custom: 5.31 ns/op (total: 530.964ms, iters: 100000000)
u64 swap std: 5.32 ns/op (total: 531.988ms, iters: 100000000)
u64 swap custom: 5.242 ns/op (total: 524.175ms, iters: 100000000)
u64 cas std: 4.948 ns/op (total: 494.824ms, iters: 100000000)
u64 cas custom: 5.268 ns/op (total: 526.833ms, iters: 100000000)
u32 store std: 3.896 ns/op (total: 389.574ms, iters: 100000000)
u32 store custom: 4.067 ns/op (total: 406.712ms, iters: 100000000)
u32 load std: 1.132 ns/op (total: 113.242ms, iters: 100000000)
u32 load custom: 1.135 ns/op (total: 113.523ms, iters: 100000000)
u32 add std: 3.951 ns/op (total: 395.090ms, iters: 100000000)
u32 add custom: 4.141 ns/op (total: 414.139ms, iters: 100000000)
u32 swap std: 4.136 ns/op (total: 413.586ms, iters: 100000000)
u32 swap custom: 4.138 ns/op (total: 413.812ms, iters: 100000000)
u32 cas std: 4.136 ns/op (total: 413.644ms, iters: 100000000)
u32 cas custom: 4.705 ns/op (total: 470.505ms, iters: 100000000)
i64 store std: 9.643 ns/op (total: 964.327ms, iters: 100000000)
i64 store custom: 7.373 ns/op (total: 737.327ms, iters: 100000000)
i64 load std: 1.7 ns/op (total: 169.983ms, iters: 100000000)
i64 load custom: 1.824 ns/op (total: 182.396ms, iters: 100000000)
i64 add std: 5.27 ns/op (total: 526.950ms, iters: 100000000)
i64 add custom: 5.268 ns/op (total: 526.833ms, iters: 100000000)
i64 swap std: 4.917 ns/op (total: 491.690ms, iters: 100000000)
i64 swap custom: 5.095 ns/op (total: 509.546ms, iters: 100000000)
i64 cas std: 4.931 ns/op (total: 493.122ms, iters: 100000000)
i64 cas custom: 5.268 ns/op (total: 526.774ms, iters: 100000000)
i32 store std: 4.018 ns/op (total: 401.776ms, iters: 100000000)
i32 store custom: 4.138 ns/op (total: 413.820ms, iters: 100000000)
i32 load std: 1.132 ns/op (total: 113.185ms, iters: 100000000)
i32 load custom: 1.134 ns/op (total: 113.359ms, iters: 100000000)
i32 add std: 3.949 ns/op (total: 394.853ms, iters: 100000000)
i32 add custom: 4.139 ns/op (total: 413.947ms, iters: 100000000)
i32 swap std: 4.135 ns/op (total: 413.485ms, iters: 100000000)
i32 swap custom: 4.136 ns/op (total: 413.623ms, iters: 100000000)
i32 cas std: 4.137 ns/op (total: 413.702ms, iters: 100000000)
i32 cas custom: 4.704 ns/op (total: 470.402ms, iters: 100000000)
```
@@ -0,0 +1,376 @@
module main
import x.atomics
import time
$if windows {
#include "@VEXEROOT/thirdparty/stdatomic/win/atomic.h"
} $else {
#include "@VEXEROOT/thirdparty/stdatomic/nix/atomic.h"
}
fn C.atomic_store_u32(voidptr, u32)
fn C.atomic_load_u32(voidptr) u32
fn C.atomic_fetch_add_u32(voidptr, u32) u32
fn C.atomic_compare_exchange_strong_u32(voidptr, voidptr, u32) bool
fn C.atomic_exchange_u32(voidptr, u32) u32
fn C.atomic_store_u64(voidptr, u64)
fn C.atomic_load_u64(voidptr) u64
fn C.atomic_fetch_add_u64(voidptr, u64) u64
fn C.atomic_compare_exchange_strong_u64(voidptr, voidptr, u64) bool
fn C.atomic_exchange_u64(voidptr, u64) u64
const iterations = 100_000_000
fn keepalive_u64(x u64) {
asm volatile amd64 {
nop
; ; r (x)
}
}
fn keepalive_u32(x u32) {
asm volatile amd64 {
nop
; ; r (x)
}
}
fn keepalive_i64(x i64) {
asm volatile amd64 {
nop
; ; r (x)
}
}
fn keepalive_i32(x i32) {
asm volatile amd64 {
nop
; ; r (x)
}
}
fn bench_u64(name string, f fn (&u64, u64), iters int) {
mut v := u64(0)
for i in 0 .. 100_000 {
f(&v, u64(i))
}
mut sw := time.new_stopwatch()
for i in 0 .. iters {
f(&v, u64(i))
}
elapsed := sw.elapsed()
ns_per_op := f64(elapsed.nanoseconds()) / f64(iters)
keepalive_u64(v)
println('${name:-17s}: ${ns_per_op:6.3f} ns/op (total: ${elapsed:9}, iters: ${iters})')
}
fn bench_u32(name string, f fn (&u32, u32), iters int) {
mut v := u32(0)
for i in 0 .. 100_000 {
f(&v, u32(i))
}
mut sw := time.new_stopwatch()
for i in 0 .. iters {
f(&v, u32(i))
}
elapsed := sw.elapsed()
ns_per_op := f64(elapsed.nanoseconds()) / f64(iters)
keepalive_u32(v)
println('${name:-17s}: ${ns_per_op:6.3f} ns/op (total: ${elapsed:9}, iters: ${iters})')
}
fn bench_i64(name string, f fn (&i64, i64), iters int) {
mut v := i64(0)
for i in 0 .. 100_000 {
f(&v, i64(i))
}
mut sw := time.new_stopwatch()
for i in 0 .. iters {
f(&v, i64(i))
}
elapsed := sw.elapsed()
ns_per_op := f64(elapsed.nanoseconds()) / f64(iters)
keepalive_i64(v)
println('${name:-17s}: ${ns_per_op:6.3f} ns/op (total: ${elapsed:9}, iters: ${iters})')
}
fn bench_i32(name string, f fn (&i32, i32), iters int) {
mut v := i32(0)
for i in 0 .. 100_000 {
f(&v, i32(i))
}
mut sw := time.new_stopwatch()
for i in 0 .. iters {
f(&v, i32(i))
}
elapsed := sw.elapsed()
ns_per_op := f64(elapsed.nanoseconds()) / f64(iters)
keepalive_i32(v)
println('${name:-17s}: ${ns_per_op:6.3f} ns/op (total: ${elapsed:9}, iters: ${iters})')
}
fn std_store_u64(addr &u64, val u64) {
C.atomic_store_u64(voidptr(addr), val)
}
fn custom_store_u64(addr &u64, val u64) {
atomics.store_u64(addr, val)
}
fn std_load_u64(addr &u64, _ u64) {
_ = C.atomic_load_u64(voidptr(addr))
}
fn custom_load_u64(addr &u64, _ u64) {
_ = atomics.load_u64(addr)
}
fn std_add_u64(addr &u64, delta u64) {
_ = C.atomic_fetch_add_u64(voidptr(addr), delta)
}
fn custom_add_u64(addr &u64, delta u64) {
_ = atomics.add_u64(addr, delta)
}
fn std_swap_u64(addr &u64, val u64) {
_ = C.atomic_exchange_u64(voidptr(addr), val)
}
fn custom_swap_u64(addr &u64, val u64) {
_ = atomics.swap_u64(addr, val)
}
fn std_cas_u64(addr &u64, val u64) {
mut expected := u64(0)
_ = C.atomic_compare_exchange_strong_u64(voidptr(addr), voidptr(&expected), val)
}
fn custom_cas_u64(addr &u64, val u64) {
_ = atomics.cas_u64(addr, 0, val)
}
fn std_store_u32(addr &u32, val u32) {
C.atomic_store_u32(voidptr(addr), val)
}
fn custom_store_u32(addr &u32, val u32) {
atomics.store_u32(addr, val)
}
fn std_load_u32(addr &u32, _ u32) {
_ = C.atomic_load_u32(voidptr(addr))
}
fn custom_load_u32(addr &u32, _ u32) {
_ = atomics.load_u32(addr)
}
fn std_add_u32(addr &u32, delta u32) {
_ = C.atomic_fetch_add_u32(voidptr(addr), delta)
}
fn custom_add_u32(addr &u32, delta u32) {
_ = atomics.add_u32(addr, delta)
}
fn std_swap_u32(addr &u32, val u32) {
_ = C.atomic_exchange_u32(voidptr(addr), val)
}
fn custom_swap_u32(addr &u32, val u32) {
_ = atomics.swap_u32(addr, val)
}
fn std_cas_u32(addr &u32, val u32) {
mut expected := u32(0)
_ = C.atomic_compare_exchange_strong_u32(voidptr(addr), voidptr(&expected), val)
}
fn custom_cas_u32(addr &u32, val u32) {
_ = atomics.cas_u32(addr, 0, val)
}
fn std_store_i64(addr &i64, val i64) {
unsafe { C.atomic_store_u64(voidptr(addr), u64(val)) }
}
fn custom_store_i64(addr &i64, val i64) {
atomics.store_i64(addr, val)
}
fn std_load_i64(addr &i64, _ i64) {
unsafe {
_ = C.atomic_load_u64(voidptr(addr))
}
}
fn custom_load_i64(addr &i64, _ i64) {
_ = atomics.load_i64(addr)
}
fn std_add_i64(addr &i64, delta i64) {
unsafe {
_ = C.atomic_fetch_add_u64(voidptr(addr), u64(delta))
}
}
fn custom_add_i64(addr &i64, delta i64) {
_ = atomics.add_i64(addr, delta)
}
fn std_swap_i64(addr &i64, val i64) {
unsafe {
_ = C.atomic_exchange_u64(voidptr(addr), u64(val))
}
}
fn custom_swap_i64(addr &i64, val i64) {
_ = atomics.swap_i64(addr, val)
}
fn std_cas_i64(addr &i64, val i64) {
unsafe {
mut expected := u64(0)
_ = C.atomic_compare_exchange_strong_u64(voidptr(addr), voidptr(&expected), u64(val))
}
}
fn custom_cas_i64(addr &i64, val i64) {
_ = atomics.cas_i64(addr, 0, val)
}
fn std_store_i32(addr &i32, val i32) {
unsafe { C.atomic_store_u32(voidptr(addr), u32(val)) }
}
fn custom_store_i32(addr &i32, val i32) {
atomics.store_i32(addr, val)
}
fn std_load_i32(addr &i32, _ i32) {
unsafe {
_ = C.atomic_load_u32(voidptr(addr))
}
}
fn custom_load_i32(addr &i32, _ i32) {
_ = atomics.load_i32(addr)
}
fn std_add_i32(addr &i32, delta i32) {
unsafe {
_ = C.atomic_fetch_add_u32(voidptr(addr), u32(delta))
}
}
fn custom_add_i32(addr &i32, delta i32) {
_ = atomics.add_i32(addr, delta)
}
fn std_swap_i32(addr &i32, val i32) {
unsafe {
_ = C.atomic_exchange_u32(voidptr(addr), u32(val))
}
}
fn custom_swap_i32(addr &i32, val i32) {
_ = atomics.swap_i32(addr, val)
}
fn std_cas_i32(addr &i32, val i32) {
unsafe {
mut expected := u32(0)
_ = C.atomic_compare_exchange_strong_u32(voidptr(addr), voidptr(&expected), u32(val))
}
}
fn custom_cas_i32(addr &i32, val i32) {
_ = atomics.cas_i32(addr, 0, val)
}
fn main() {
bench_u64('u64 store std', std_store_u64, iterations)
bench_u64('u64 store custom', custom_store_u64, iterations)
bench_u64('u64 load std', std_load_u64, iterations)
bench_u64('u64 load custom', custom_load_u64, iterations)
bench_u64('u64 add std', std_add_u64, iterations)
bench_u64('u64 add custom', custom_add_u64, iterations)
bench_u64('u64 swap std', std_swap_u64, iterations)
bench_u64('u64 swap custom', custom_swap_u64, iterations)
bench_u64('u64 cas std', std_cas_u64, iterations)
bench_u64('u64 cas custom', custom_cas_u64, iterations)
println('')
bench_u32('u32 store std', std_store_u32, iterations)
bench_u32('u32 store custom', custom_store_u32, iterations)
bench_u32('u32 load std', std_load_u32, iterations)
bench_u32('u32 load custom', custom_load_u32, iterations)
bench_u32('u32 add std', std_add_u32, iterations)
bench_u32('u32 add custom', custom_add_u32, iterations)
bench_u32('u32 swap std', std_swap_u32, iterations)
bench_u32('u32 swap custom', custom_swap_u32, iterations)
bench_u32('u32 cas std', std_cas_u32, iterations)
bench_u32('u32 cas custom', custom_cas_u32, iterations)
println('')
bench_i64('i64 store std', std_store_i64, iterations)
bench_i64('i64 store custom', custom_store_i64, iterations)
bench_i64('i64 load std', std_load_i64, iterations)
bench_i64('i64 load custom', custom_load_i64, iterations)
bench_i64('i64 add std', std_add_i64, iterations)
bench_i64('i64 add custom', custom_add_i64, iterations)
bench_i64('i64 swap std', std_swap_i64, iterations)
bench_i64('i64 swap custom', custom_swap_i64, iterations)
bench_i64('i64 cas std', std_cas_i64, iterations)
bench_i64('i64 cas custom', custom_cas_i64, iterations)
println('')
bench_i32('i32 store std', std_store_i32, iterations)
bench_i32('i32 store custom', custom_store_i32, iterations)
bench_i32('i32 load std', std_load_i32, iterations)
bench_i32('i32 load custom', custom_load_i32, iterations)
bench_i32('i32 add std', std_add_i32, iterations)
bench_i32('i32 add custom', custom_add_i32, iterations)
bench_i32('i32 swap std', std_swap_i32, iterations)
bench_i32('i32 swap custom', custom_swap_i32, iterations)
bench_i32('i32 cas std', std_cas_i32, iterations)
bench_i32('i32 cas custom', custom_cas_i32, iterations)
}
+23
View File
@@ -0,0 +1,23 @@
module main
import x.atomics
fn main() {
// Basic atomic load and store operations
mut value := i32(0)
// Atomically store a value
atomics.store_i32(&value, 42)
// Atomically load the value
loaded := atomics.load_i32(&value)
println('Loaded value: ${loaded}') // Output: 42
// Atomic add: returns the new value after addition
new_value := atomics.add_i32(&value, 10)
println('After add: ${new_value}') // Output: 52
// Atomic swap: returns the old value
old := atomics.swap_i32(&value, 100)
println('Old value: ${old}, new value: ${atomics.load_i32(&value)}') // Output: 52, 100
}
+40
View File
@@ -0,0 +1,40 @@
module main
import x.atomics
fn increment(counter &i64) {
atomics.add_i64(counter, 1)
}
fn worker(counter &i64, iterations int) {
for _ in 0 .. iterations {
increment(counter)
}
}
fn main() {
mut counter := i64(0)
num_threads := 4
increments_per_thread := 10000
mut threads := []thread{}
for _ in 0 .. num_threads {
threads << spawn worker(&counter, increments_per_thread)
}
threads.wait()
expected := i64(num_threads * increments_per_thread)
actual := atomics.load_i64(&counter)
println('Expected: ${expected}')
println('Actual: ${actual}')
if actual == expected {
println('Counter is correct')
} else {
println('Counter mismatch - race condition detected')
}
}
@@ -0,0 +1,59 @@
module main
import x.atomics
struct SpinLock {
mut:
state u32 // 0 = unlocked, 1 = locked
}
fn acquire(mut spinlock SpinLock) {
for !atomics.cas_u32(&spinlock.state, 0, 1) {
// Busy-wait
}
}
fn release(mut spinlock SpinLock) {
atomics.store_u32(&spinlock.state, 0)
}
struct SharedData {
mut:
spinlock SpinLock
value int
}
fn worker(mut data SharedData, iterations int) {
for _ in 0 .. iterations {
acquire(mut data.spinlock)
data.value++
release(mut data.spinlock)
}
}
fn main() {
mut data := &SharedData{}
num_threads := 4
iterations_per_thread := 10000
mut threads := []thread{}
for _ in 0 .. num_threads {
threads << spawn worker(mut data, iterations_per_thread)
}
threads.wait()
expected := num_threads * iterations_per_thread
actual := data.value
println('Expected: ${expected}')
println('Actual: ${actual}')
if actual == expected {
println('Spinlock works correctly')
} else {
println('Race condition detected')
}
}
+221
View File
@@ -0,0 +1,221 @@
// vtest build: !(macos || windows)
module atomics
fn test_cas_i32_basic() {
mut x := i32(10)
ok := cas_i32(&x, 10, 20)
assert ok == true
assert x == 20
}
fn test_cas_fail() {
mut x := i32(5)
assert !cas_i32(&x, 10, 42)
assert x == 5
}
fn test_cas_fail_memory_unchanged() {
mut x := i32(7)
cas_i32(&x, 1, 2)
assert x == 7
}
fn test_cas_exact_match() {
mut x := i32(-1)
assert !cas_i32(&x, 0, 999)
assert x == -1
}
fn test_cas_twice() {
mut x := i32(1)
assert cas_i32(&x, 1, 2)
assert cas_i32(&x, 2, 3)
assert x == 3
}
fn test_cas_with_negative() {
mut x := i32(-123)
assert cas_i32(&x, -123, 8)
assert x == 8
}
fn test_add_i32_basic() {
mut x := i32(0)
for _ in 0 .. 1000 {
add_i32(&x, 1)
}
assert x == 1000
}
fn test_add_i32_negative() {
mut x := i32(10)
add_i32(&x, -3)
assert x == 7
}
fn test_add_i32_return_value() {
mut x := i32(5)
r := add_i32(&x, 7)
assert r == 12
assert x == 12
}
fn test_add_i32_overflow_wraps() {
mut x := i32(2147483647)
add_i32(&x, 1)
assert x == -2147483648
}
fn test_swap_i32_basic() {
mut x := i32(5)
old := swap_i32(&x, 99)
assert old == 5
assert x == 99
}
fn test_swap_i32_twice() {
mut x := i32(1)
assert swap_i32(&x, 2) == 1
assert swap_i32(&x, 3) == 2
assert x == 3
}
fn test_swap_i32_with_cas() {
mut x := i32(10)
assert cas_i32(&x, 10, 20)
old := swap_i32(&x, 30)
assert old == 20
assert x == 30
}
fn test_load_i32_basic() {
mut x := i32(123456)
assert load_i32(&x) == 123456
}
fn test_store_i32_basic() {
mut x := i32(5)
store_i32(&x, 777)
assert x == 777
}
fn test_add_i32_concurrent() {
mut x := i32(0)
mut threads := []thread{}
for _ in 0 .. 9 {
threads << spawn fn (ptr &i32) {
for _ in 0 .. 100_000 {
add_i32(ptr, 1)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 900_000
}
fn test_swap_i32_concurrent() {
mut x := i32(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (ptr &i32) {
for _ in 0 .. 50_000 {
swap_i32(ptr, 123)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 123
}
fn test_cas_i32_concurrent() {
mut x := i32(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (ptr &i32) {
for _ in 0 .. 100_000 {
for {
old := load_i32(ptr)
if cas_i32(ptr, old, old + 1) {
break
}
}
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 800_000
}
fn test_load_store_i32_concurrent() {
mut x := i32(0)
mut threads := []thread{}
for _ in 0 .. 4 {
threads << spawn fn (px &i32) {
for _ in 0 .. 50_000 {
add_i32(px, 1)
}
}(&x)
}
for _ in 0 .. 4 {
threads << spawn fn (px &i32) {
for _ in 0 .. 50_000 {
_ = load_i32(px)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 4 * 50_000
}
fn test_and_i32_concurrent() {
mut x := i32(0x7fffffff)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &i32) {
for _ in 0 .. 100_000 {
and_i32(px, 0x0fffffff)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 0x0fffffff
}
fn test_or_i32_concurrent() {
mut x := i32(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &i32) {
for _ in 0 .. 100_000 {
or_i32(px, 0x12345678)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 0x12345678
}
+193
View File
@@ -0,0 +1,193 @@
// vtest build: !(macos || windows)
module atomics
fn test_load_i64_basic() {
mut x := i64(1234567890123)
assert load_i64(&x) == 1234567890123
}
fn test_store_i64_basic() {
mut x := i64(1)
store_i64(&x, 9999999)
assert x == 9999999
}
fn test_swap_i64_basic() {
mut x := i64(50)
old := swap_i64(&x, 777)
assert old == 50
assert x == 777
}
fn test_swap_i64_same_value() {
mut x := i64(-123)
old := swap_i64(&x, -123)
assert old == -123
assert x == -123
}
fn test_add_i64_basic() {
mut x := i64(0)
for _ in 0 .. 1000 {
add_i64(&x, 3)
}
assert x == 3000
}
fn test_add_i64_negative_delta() {
mut x := i64(100)
add_i64(&x, -7)
assert x == 93
}
fn test_add_i64_wraparound_behavior() {
mut x := i64(0x7fffffffffffffff)
add_i64(&x, 1)
assert x == -0x8000000000000000
}
fn test_add_i64_return_value() {
mut x := i64(10)
r := add_i64(&x, 100)
assert r == 110
assert x == 110
}
fn test_cas_i64_basic() {
mut x := i64(111)
assert cas_i64(&x, 111, 222)
assert x == 222
}
fn test_cas_i64_fail() {
mut x := i64(555)
assert !cas_i64(&x, 123, 999)
assert x == 555
}
fn test_cas_i64_nochange_on_fail() {
mut x := i64(-999)
cas_i64(&x, 1, 5)
assert x == -999
}
fn test_cas_i64_negative_values() {
mut x := i64(-123456)
assert cas_i64(&x, -123456, 777)
assert x == 777
}
fn test_add_i64_concurrent() {
mut x := i64(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &i64) {
for _ in 0 .. 100_000 {
add_i64(px, 1)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 800_000
}
fn test_swap_i64_concurrent() {
mut x := i64(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &i64) {
for _ in 0 .. 50_000 {
swap_i64(px, 12345)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 12345
}
fn test_cas_i64_concurrent_inc() {
mut x := i64(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &i64) {
for _ in 0 .. 50_000 {
for {
old := load_i64(px)
if cas_i64(px, old, old + 1) {
break
}
}
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 400_000
}
fn test_cas_i64_contended_flip() {
mut x := i64(0)
mut threads := []thread{}
for _ in 0 .. 4 {
threads << spawn fn (px &i64) {
for _ in 0 .. 200_000 {
cas_i64(px, 0, 1)
cas_i64(px, 1, 0)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 0 || x == 1
}
fn test_and_i64_concurrent() {
mut x := i64(0x7fffffffffffffff)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &i64) {
for _ in 0 .. 100_000 {
and_i64(px, 0x00ffffffffffffff)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 0x00ffffffffffffff
}
fn test_or_i64_concurrent() {
mut x := i64(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &i64) {
for _ in 0 .. 100_000 {
or_i64(px, 0x0123456789abcdef)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 0x0123456789abcdef
}
+10
View File
@@ -0,0 +1,10 @@
module atomics
$if prod && (gcc || clang) {
#flag -Wl,--undefined=panicUnaligned
}
@[export: 'panicUnaligned']
fn panic_unaligned() {
panic('unaligned atomic operation')
}
+210
View File
@@ -0,0 +1,210 @@
// vtest build: !(macos || windows)
module atomics
fn test_add_u32_basic() {
mut x := u32(0)
for _ in 0 .. 1000 {
add_u32(&x, 1)
}
assert x == 1000
}
fn test_add_u32_wraparound() {
mut x := u32(0xffffffff)
add_u32(&x, 1)
assert x == 0
}
fn test_add_u32_large() {
mut x := u32(10)
r := add_u32(&x, 1000)
assert r == 1010
assert x == 1010
}
fn test_swap_u32_basic() {
mut x := u32(123)
old := swap_u32(&x, 999)
assert old == 123
assert x == 999
}
fn test_swap_u32_same() {
mut x := u32(777)
old := swap_u32(&x, 777)
assert old == 777
assert x == 777
}
fn test_store_u32_basic() {
mut x := u32(1)
store_u32(&x, 555)
assert x == 555
}
fn test_load_u32_basic() {
mut x := u32(888)
assert load_u32(&x) == 888
}
fn test_cas_u32_basic() {
mut x := u32(10)
assert cas_u32(&x, 10, 50)
assert x == 50
}
fn test_cas_u32_fail() {
mut x := u32(10)
assert !cas_u32(&x, 5, 999)
assert x == 10
}
fn test_cas_u32_nochange_on_fail() {
mut x := u32(777)
cas_u32(&x, 5, 9)
assert x == 777
}
fn test_cas_u32_boundary() {
mut x := u32(0xffffffff)
assert cas_u32(&x, 0xffffffff, 0)
assert x == 0
}
fn test_add_u32_concurrent() {
mut x := u32(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u32) {
for _ in 0 .. 100_000 {
add_u32(px, 1)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 800_000
}
fn test_swap_u32_concurrent() {
mut x := u32(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u32) {
for _ in 0 .. 50_000 {
swap_u32(px, 123)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 123
}
fn test_cas_u32_concurrent_inc() {
mut x := u32(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u32) {
for _ in 0 .. 50_000 {
for {
old := load_u32(px)
if cas_u32(px, old, old + 1) {
break
}
}
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 400_000
}
fn test_cas_u32_contended_flip() {
mut x := u32(0)
mut threads := []thread{}
for _ in 0 .. 4 {
threads << spawn fn (px &u32) {
for _ in 0 .. 200_000 {
cas_u32(px, 0, 1)
cas_u32(px, 1, 0)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 0 || x == 1
}
fn test_load_store_u32_concurrent() {
mut x := u32(0)
mut threads := []thread{}
for i in 0 .. 8 {
threads << spawn fn (px &u32, id int) {
for _ in 0 .. 50_000 {
if id % 2 == 0 {
store_u32(px, 1)
} else {
_ = load_u32(px)
}
}
}(&x, i)
}
for t in threads {
t.wait()
}
assert x == 1
}
fn test_and_u32_concurrent() {
mut x := u32(0xffffffff)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u32) {
for _ in 0 .. 100_000 {
and_u32(px, 0x0f0f0f0f)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 0x0f0f0f0f
}
fn test_or_u32_concurrent() {
mut x := u32(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u32) {
for _ in 0 .. 100_000 {
or_u32(px, 0xdeadbeef)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == u32(0xdeadbeef)
}
+187
View File
@@ -0,0 +1,187 @@
// vtest build: !(macos || windows)
module atomics
fn test_load_u64_basic() {
mut x := u64(1234567890123)
assert load_u64(&x) == 1234567890123
}
fn test_store_u64_basic() {
mut x := u64(1)
store_u64(&x, 999999)
assert x == 999999
}
fn test_swap_u64_basic() {
mut x := u64(123)
old := swap_u64(&x, 999)
assert old == 123
assert x == 999
}
fn test_swap_u64_same() {
mut x := u64(777777)
old := swap_u64(&x, 777777)
assert old == 777777
assert x == 777777
}
fn test_add_u64_basic() {
mut x := u64(0)
for _ in 0 .. 1000 {
add_u64(&x, 2)
}
assert x == 2000
}
fn test_add_u64_wraparound() {
mut x := u64(0xffffffffffffffff)
add_u64(&x, 1)
assert x == 0
}
fn test_add_u64_return() {
mut x := u64(10)
r := add_u64(&x, 100)
assert r == 110
assert x == 110
}
fn test_cas_u64_basic() {
mut x := u64(111)
assert cas_u64(&x, 111, 222)
assert x == 222
}
fn test_cas_u64_fail() {
mut x := u64(500)
assert !cas_u64(&x, 100, 200)
assert x == 500
}
fn test_cas_u64_nochange_fail() {
mut x := u64(999)
cas_u64(&x, 1, 2)
assert x == 999
}
fn test_cas_u64_boundary() {
mut x := u64(0xffffffffffffffff)
assert cas_u64(&x, 0xffffffffffffffff, 0)
assert x == 0
}
fn test_add_u64_concurrent() {
mut x := u64(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u64) {
for _ in 0 .. 100_000 {
add_u64(px, 1)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 800_000
}
fn test_swap_u64_concurrent() {
mut x := u64(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u64) {
for _ in 0 .. 50_000 {
swap_u64(px, 123456)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 123456
}
fn test_cas_u64_concurrent_inc() {
mut x := u64(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u64) {
for _ in 0 .. 50_000 {
for {
old := load_u64(px)
if cas_u64(px, old, old + 1) {
break
}
}
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 400_000
}
fn test_cas_u64_contended_flip() {
mut x := u64(0)
mut threads := []thread{}
for _ in 0 .. 4 {
threads << spawn fn (px &u64) {
for _ in 0 .. 200_000 {
cas_u64(px, 0, 1)
cas_u64(px, 1, 0)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 0 || x == 1
}
fn test_and_u64_concurrent() {
mut x := u64(0xffffffffffffffff)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u64) {
for _ in 0 .. 100_000 {
and_u64(px, 0x00ff00ff00ff00ff)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == 0x00ff00ff00ff00ff
}
fn test_or_u64_concurrent() {
mut x := u64(0)
mut threads := []thread{}
for _ in 0 .. 8 {
threads << spawn fn (px &u64) {
for _ in 0 .. 100_000 {
or_u64(px, 0xfedcba9876543210)
}
}(&x)
}
for t in threads {
t.wait()
}
assert x == u64(0xfedcba9876543210)
}