module stdatomic // Implement the atomic operations. For now TCC does not support the atomic // versions on nix so it uses locks to simulate the same behavior. // // On windows tcc can simulate with other atomic operations. // // Note: this implementations should be regarded as alpha stage and be tested // much more. // add_u64 adds provided delta as an atomic operation @[inline] pub fn add_u64(ptr &u64, delta int) u64 { C.atomic_fetch_add_u64(voidptr(ptr), delta) return *ptr } // sub_u64 subtracts provided delta as an atomic operation @[inline] pub fn sub_u64(ptr &u64, delta int) u64 { C.atomic_fetch_sub_u64(voidptr(ptr), delta) return *ptr } // add_i64 adds provided delta as an atomic operation @[inline] pub fn add_i64(ptr &i64, delta int) i64 { C.atomic_fetch_add_u64(voidptr(ptr), delta) return *ptr } // add_i64 subtracts provided delta as an atomic operation @[inline] pub fn sub_i64(ptr &i64, delta int) i64 { C.atomic_fetch_sub_u64(voidptr(ptr), delta) return *ptr } // atomic store/load operations have to be used when there might be another concurrent access // atomicall set a value @[inline] pub fn store_u64(ptr &u64, val u64) { C.atomic_store_u64(voidptr(ptr), val) } // atomicall get a value @[inline] pub fn load_u64(ptr &u64) u64 { return C.atomic_load_u64(voidptr(ptr)) } // atomicall set a value @[inline] pub fn store_i64(ptr &i64, val i64) { C.atomic_store_u64(voidptr(ptr), val) } // atomicall get a value @[inline] pub fn load_i64(ptr &i64) i64 { return i64(C.atomic_load_u64(voidptr(ptr))) } @[heap] pub struct AtomicVal[T] { val T } // new_atomic creates a new atomic value of `T` type @[inline] pub fn new_atomic[T](val T) &AtomicVal[T] { $if T is $int { return &AtomicVal[T]{ val: val } } $else $if T is bool { return &AtomicVal[T]{ val: val } } $else $if T is voidptr { return &AtomicVal[T]{ val: val } } $else { $compile_error('atomic: only support number, bool, and voidptr types') } return unsafe { nil } } // load returns current value of the atomic value @[inline] pub fn (mut a AtomicVal[T]) load() T { $if T is bool { return C.atomic_load_byte(voidptr(&a.val)) != 0 } $else $if T is u8 || T is i8 { return T(C.atomic_load_byte(voidptr(&a.val))) } $else $if T is u16 || T is i16 { return T(C.atomic_load_u16(voidptr(&a.val))) } $else $if T is u32 || T is i32 { return T(C.atomic_load_u32(voidptr(&a.val))) } $else $if T is u64 || T is i64 { return T(C.atomic_load_u64(voidptr(&a.val))) } $else $if T is int { if sizeof(int) == 4 { return int(C.atomic_load_u32(voidptr(&a.val))) } else { return int(C.atomic_load_u64(voidptr(&a.val))) } } $else $if T is isize || T is usize { if sizeof(isize) == 4 { return T(C.atomic_load_u32(voidptr(&a.val))) } else { return T(C.atomic_load_u64(voidptr(&a.val))) } } $else $if T is voidptr { // TODO: this should be $if sizeof(T) == 4 $if x32 { return T(C.atomic_load_u32(voidptr(&a.val))) } $else { return T(C.atomic_load_u64(voidptr(&a.val))) } } return a.val } // store updates the atomic value with `val` @[inline] pub fn (mut a AtomicVal[T]) store(val T) { $if T is bool { C.atomic_store_byte(voidptr(&a.val), u8(val)) } $else $if T is u8 || T is i8 { C.atomic_store_byte(voidptr(&a.val), u8(val)) } $else $if T is u16 || T is i16 { C.atomic_store_u16(voidptr(&a.val), u16(val)) } $else $if T is u32 || T is i32 { C.atomic_store_u32(voidptr(&a.val), u32(val)) } $else $if T is u64 || T is i64 { C.atomic_store_u64(voidptr(&a.val), u64(val)) } $else $if T is int { if sizeof(int) == 4 { C.atomic_store_u32(voidptr(&a.val), u32(val)) } else { C.atomic_store_u64(voidptr(&a.val), u64(val)) } } $else $if T is isize || T is usize { if sizeof(isize) == 4 { C.atomic_store_u32(voidptr(&a.val), u32(val)) } else { C.atomic_store_u64(voidptr(&a.val), u64(val)) } } $else $if T is voidptr { // TODO: this should be $if sizeof(T) == 4 $if x32 { C.atomic_store_u32(voidptr(&a.val), u32(val)) } $else { C.atomic_store_u64(voidptr(&a.val), u64(val)) } } } // add adds the atomic value with `delta` and returns the previous value @[inline] pub fn (mut a AtomicVal[T]) add(delta T) T { $if T is bool { panic('atomic: add() not supported for bool type') } $else $if T is voidptr { panic('atomic: add() not supported for voidptr type') } $else $if T is u8 || T is i8 { old := C.atomic_fetch_add_byte(voidptr(&a.val), u8(delta)) return T(old) } $else $if T is u16 || T is i16 { old := C.atomic_fetch_add_u16(voidptr(&a.val), u16(delta)) return T(old) } $else $if T is u32 || T is i32 { old := C.atomic_fetch_add_u32(voidptr(&a.val), u32(delta)) return T(old) } $else $if T is u64 || T is i64 { old := C.atomic_fetch_add_u64(voidptr(&a.val), u64(delta)) return T(old) } $else $if T is int { if sizeof(int) == 4 { old := C.atomic_fetch_add_u32(voidptr(&a.val), u32(delta)) return T(old) } else { old := C.atomic_fetch_add_u64(voidptr(&a.val), u64(delta)) return T(old) } } $else $if T is isize || T is usize { if sizeof(isize) == 4 { old := C.atomic_fetch_add_u32(voidptr(&a.val), u32(delta)) return T(old) } else { old := C.atomic_fetch_add_u64(voidptr(&a.val), u64(delta)) return T(old) } } panic('unreachable') } // sub subtracts the atomic value with `delta` and returns the previous value @[inline] pub fn (mut a AtomicVal[T]) sub(delta T) T { $if T is bool { panic('atomic: sub() not supported for bool type') } $else $if T is voidptr { panic('atomic: sub() not supported for voidptr type') } $else $if T is u8 || T is i8 { old := C.atomic_fetch_sub_byte(voidptr(&a.val), u8(delta)) return T(old) } $else $if T is u16 || T is i16 { old := C.atomic_fetch_sub_u16(voidptr(&a.val), u16(delta)) return T(old) } $else $if T is u32 || T is i32 { old := C.atomic_fetch_sub_u32(voidptr(&a.val), u32(delta)) return T(old) } $else $if T is u64 || T is i64 { old := C.atomic_fetch_sub_u64(voidptr(&a.val), u64(delta)) return T(old) } $else $if T is int { if sizeof(int) == 4 { old := C.atomic_fetch_sub_u32(voidptr(&a.val), u32(delta)) return T(old) } else { old := C.atomic_fetch_sub_u64(voidptr(&a.val), u64(delta)) return T(old) } } $else $if T is isize || T is usize { if sizeof(isize) == 4 { old := C.atomic_fetch_sub_u32(voidptr(&a.val), u32(delta)) return T(old) } else { old := C.atomic_fetch_sub_u64(voidptr(&a.val), u64(delta)) return T(old) } } panic('unreachable') } // swap sets the `new` value and returns the previous value @[inline] pub fn (mut a AtomicVal[T]) swap(new T) T { $if T is bool { old := C.atomic_exchange_byte(voidptr(&a.val), u8(new)) return old != 0 } $else $if T is u8 || T is i8 { old := C.atomic_exchange_byte(voidptr(&a.val), u8(new)) return T(old) } $else $if T is u16 || T is i16 { old := C.atomic_exchange_u16(voidptr(&a.val), u16(new)) return T(old) } $else $if T is u32 || T is i32 { old := C.atomic_exchange_u32(voidptr(&a.val), u32(new)) return T(old) } $else $if T is u64 || T is i64 { old := C.atomic_exchange_u64(voidptr(&a.val), u64(new)) return T(old) } $else $if T is int { if sizeof(int) == 4 { old := C.atomic_exchange_u32(voidptr(&a.val), u32(new)) return T(old) } else { old := C.atomic_exchange_u64(voidptr(&a.val), u64(new)) return T(old) } } $else $if T is isize || T is usize { if sizeof(isize) == 4 { old := C.atomic_exchange_u32(voidptr(&a.val), u32(new)) return T(old) } else { old := C.atomic_exchange_u64(voidptr(&a.val), u64(new)) return T(old) } } $else $if T is voidptr { // TODO: this should be $if sizeof(T) == 4 $if x32 { old := C.atomic_exchange_u32(voidptr(&a.val), u32(new)) return T(old) } $else { old := C.atomic_exchange_u64(voidptr(&a.val), u64(new)) return T(old) } } panic('unreachable') } // compare_and_swap executes the compare-and-swap(CAS) operation // if atomic value == `expected`, then it will be set to `new`, and return true // else return false, and the atomic value remains unchanged @[inline] pub fn (mut a AtomicVal[T]) compare_and_swap(expected T, new T) bool { $if T is bool { mut exp := u8(expected) return C.atomic_compare_exchange_strong_byte(voidptr(&a.val), &exp, u8(new)) } $else $if T is u8 || T is i8 { mut exp := u8(expected) return C.atomic_compare_exchange_strong_byte(voidptr(&a.val), &exp, u8(new)) } $else $if T is u16 || T is i16 { mut exp := u16(expected) return C.atomic_compare_exchange_strong_u16(voidptr(&a.val), &exp, u16(new)) } $else $if T is u32 || T is i32 { mut exp := u32(expected) return C.atomic_compare_exchange_strong_u32(voidptr(&a.val), &exp, u32(new)) } $else $if T is u64 || T is i64 { mut exp := u64(expected) return C.atomic_compare_exchange_strong_u64(voidptr(&a.val), &exp, u64(new)) } $else $if T is int { if sizeof(int) == 4 { mut exp := u32(expected) return C.atomic_compare_exchange_strong_u32(voidptr(&a.val), &exp, u32(new)) } else { mut exp := u64(expected) return C.atomic_compare_exchange_strong_u64(voidptr(&a.val), &exp, u64(new)) } } $else $if T is isize || T is usize { if sizeof(isize) == 4 { mut exp := u32(expected) return C.atomic_compare_exchange_strong_u32(voidptr(&a.val), &exp, u32(new)) } else { mut exp := u64(expected) return C.atomic_compare_exchange_strong_u64(voidptr(&a.val), &exp, u64(new)) } } $else $if T is voidptr { // TODO: this should be $if sizeof(T) == 4 $if x32 { mut exp := u32(expected) return C.atomic_compare_exchange_strong_u32(voidptr(&a.val), &exp, u32(new)) } $else { mut exp := u64(expected) return C.atomic_compare_exchange_strong_u64(voidptr(&a.val), &exp, u64(new)) } } panic('unreachable') }