package libc

// 7.17 Atomics

import "core:intrinsics"

ATOMIC_BOOL_LOCK_FREE     :: true
ATOMIC_CHAR_LOCK_FREE     :: true
ATOMIC_CHAR16_T_LOCK_FREE :: true
ATOMIC_CHAR32_T_LOCK_FREE :: true
ATOMIC_WCHAR_T_LOCK_FREE  :: true
ATOMIC_SHORT_LOCK_FREE    :: true
ATOMIC_INT_LOCK_FREE      :: true
ATOMIC_LONG_LOCK_FREE     :: true
ATOMIC_LLONG_LOCK_FREE    :: true
ATOMIC_POINTER_LOCK_FREE  :: true

// 7.17.3 Order and consistency
memory_order :: enum int {
	relaxed,
	consume,
	acquire,
	release,
	acq_rel,
	seq_cst,
}

memory_order_relaxed :: memory_order.relaxed
memory_order_consume :: memory_order.consume
memory_order_acquire :: memory_order.acquire
memory_order_release :: memory_order.release
memory_order_acq_rel :: memory_order.acq_rel
memory_order_seq_cst :: memory_order.seq_cst

// 7.17.2 Initialization
ATOMIC_VAR_INIT :: #force_inline proc(value: $T) -> T {
	return value
}

atomic_init :: #force_inline proc(obj: ^$T, value: T) {
	intrinsics.atomic_store(obj, value)
}

kill_dependency :: #force_inline proc(value: $T) -> T {
	return value
}

// 7.17.4 Fences
atomic_thread_fence :: #force_inline proc(order: memory_order) {
	switch (order) {
	case .relaxed:
		return
	case .consume:
		intrinsics.atomic_fence_acq()
	case .acquire:
		intrinsics.atomic_fence_acq()
	case .release:
		intrinsics.atomic_fence_rel()
	case .acq_rel:
		intrinsics.atomic_fence_acqrel()
	case .seq_cst:
		intrinsics.atomic_fence_acqrel()
	}
}

atomic_signal_fence :: #force_inline proc(order: memory_order) {
	atomic_thread_fence(order)
}

// 7.17.5 Lock-free property
atomic_is_lock_free :: #force_inline proc(obj: ^$T) -> bool {
	return size_of(T) <= 8 && (intrinsics.type_is_integer(T) || intrinsics.type_is_pointer(T))
}

// 7.17.6 Atomic integer types
atomic_bool           :: distinct bool
atomic_char           :: distinct char
atomic_schar          :: distinct char
atomic_uchar          :: distinct uchar
atomic_short          :: distinct short
atomic_ushort         :: distinct ushort
atomic_int            :: distinct int
atomic_uint           :: distinct uint
atomic_long           :: distinct long
atomic_ulong          :: distinct ulong
atomic_llong          :: distinct longlong
atomic_ullong         :: distinct ulonglong
atomic_char16_t       :: distinct char16_t
atomic_char32_t       :: distinct char32_t
atomic_wchar_t        :: distinct wchar_t
atomic_int_least8_t   :: distinct int_least8_t
atomic_uint_least8_t  :: distinct uint_least8_t
atomic_int_least16_t  :: distinct int_least16_t
atomic_uint_least16_t :: distinct uint_least16_t
atomic_int_least32_t  :: distinct int_least32_t
atomic_uint_least32_t :: distinct uint_least32_t
atomic_int_least64_t  :: distinct int_least64_t
atomic_uint_least64_t :: distinct uint_least64_t
atomic_int_fast8_t    :: distinct int_fast8_t
atomic_uint_fast8_t   :: distinct uint_fast8_t
atomic_int_fast16_t   :: distinct int_fast16_t
atomic_uint_fast16_t  :: distinct uint_fast16_t
atomic_int_fast32_t   :: distinct int_fast32_t
atomic_uint_fast32_t  :: distinct uint_fast32_t
atomic_int_fast64_t   :: distinct int_fast64_t
atomic_uint_fast64_t  :: distinct uint_fast64_t
atomic_intptr_t       :: distinct intptr_t
atomic_uintptr_t      :: distinct uintptr_t
atomic_size_t         :: distinct size_t
atomic_ptrdiff_t      :: distinct ptrdiff_t
atomic_intmax_t       :: distinct intmax_t
atomic_uintmax_t      :: distinct uintmax_t

// 7.17.7 Operations on atomic types
atomic_store :: #force_inline proc(object: ^$T, desired: T) {
	intrinsics.atomic_store(object, desired)
}

atomic_store_explicit :: #force_inline proc(object: ^$T, desired: T, order: memory_order) {
	assert(order != .consume)
	assert(order != .acquire)
	assert(order != .acq_rel)

	#partial switch (order) {
	case .relaxed:
		intrinsics.atomic_store_relaxed(object, desired)
	case .release:
		intrinsics.atomic_store_rel(object, desired)
	case .seq_cst:
		intrinsics.atomic_store(object, desired)
	}
}

atomic_load :: #force_inline proc(object: ^$T) -> T {
	return intrinsics.atomic_load(object)
}

atomic_load_explicit :: #force_inline proc(object: ^$T, order: memory_order) {
	assert(order != .release)
	assert(order != .acq_rel)

	#partial switch (order) {
	case .relaxed:
		return intrinsics.atomic_load_relaxed(object)
	case .consume:
		return intrinsics.atomic_load_acq(object)
	case .acquire:
		return intrinsics.atomic_load_acq(object)
	case .seq_cst:
		return intrinsics.atomic_load(object)
	}
}

atomic_exchange :: #force_inline proc(object: ^$T, desired: T) -> T {
	return intrinsics.atomic_xchg(object, desired)
}

atomic_exchange_explicit :: #force_inline proc(object: ^$T, desired: T, order: memory_order) -> T {
	switch (order) {
	case .relaxed:
		return intrinsics.atomic_xchg_relaxed(object, desired)
	case .consume:
		return intrinsics.atomic_xchg_acq(object, desired)
	case .acquire:
		return intrinsics.atomic_xchg_acq(object, desired)
	case .release:
		return intrinsics.atomic_xchg_rel(object, desired)
	case .acq_rel:
		return intrinsics.atomic_xchg_acqrel(object, desired)
	case .seq_cst:
		return intrinsics.atomic_xchg(object, desired)
	}
	return false
}

// C does not allow failure memory order to be order_release or acq_rel.
// Similarly, it does not allow the failure order to be stronger than success
// order. Since consume and acquire are both monotonic, we can count them as
// one, for a total of three memory orders that are relevant in compare exchange.
// 	relaxed, acquire (consume), seq_cst.
// The requirement that the failure order cannot be stronger than success limits
// the valid combinations for the failure order to this table:
// 	[success = seq_cst, failure = seq_cst] => _
// 	[success = acquire, failure = seq_cst] => acq
// 	[success = release, failure = seq_cst] => rel
// 	[success = acq_rel, failure = seq_cst] => acqrel
// 	[success = relaxed, failure = relaxed] => relaxed
// 	[success = seq_cst, failure = relaxed] => failrelaxed
// 	[success = seq_cst, failure = acquire] => failacq
// 	[success = acquire, failure = relaxed] => acq_failrelaxed
// 	[success = acq_rel, failure = relaxed] => acqrel_failrelaxed
atomic_compare_exchange_strong :: #force_inline proc(object, expected: ^$T, desired: T) {
	value, ok := intrinsics.atomic_cxchg(object, expected^, desired)
	if !ok { expected^ = value } 
	return ok
}

atomic_compare_exchange_strong_explicit :: #force_inline proc(object, expected: ^$T, desired: T, success, failure: memory_order) {
	assert(failure != .release)
	assert(failure != .acq_rel)

	value: T; ok: bool
	#partial switch (failure) {
	case .seq_cst:
		assert(success != .relaxed)
		#partial switch (success) {
		case .seq_cst:
			value, ok := intrinsics.atomic_cxchg(object, expected^, desired)
		case .acquire:
			value, ok := intrinsics.atomic_cxchg_acq(object, expected^, desired)
		case .consume:
			value, ok := intrinsics.atomic_cxchg_acq(object, expected^, desired)
		case .release:
			value, ok := intrinsics.atomic_cxchg_rel(object, expected^, desired)
		case .acq_rel:
			value, ok := intrinsics.atomic_cxchg_acqrel(object, expected^, desired)
		}
	case .relaxed:
		assert(success != .release)
		#partial switch (success) {
		case .relaxed:
			value, ok := intrinsics.atomic_cxchg_relaxed(object, expected^, desired)
		case .seq_cst:
			value, ok := intrinsics.atomic_cxchg_failrelaxed(object, expected^, desired)
		case .acquire:
			value, ok := intrinsics.atomic_cxchg_acq_failrelaxed(object, expected^, desired)
		case .consume:
			value, ok := intrinsics.atomic_cxchg_acq_failrelaxed(object, expected^, desired)
		case .acq_rel:
			value, ok := intrinsics.atomic_cxchg_acqrel_failrelaxed(object, expected^, desired)
		}
	case .consume:
		fallthrough
	case .acquire:
		assert(success == .seq_cst)
		value, ok := intrinsics.atomic_cxchg_failacq(object, expected^, desired)

	}
	if !ok { expected^ = value }
	return ok
}

atomic_compare_exchange_weak :: #force_inline proc(object, expected: ^$T, desired: T) {
	value, ok := intrinsics.atomic_cxchgweak(object, expected^, desired)
	if !ok { expected^ = value }
	return ok
}

atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desited: T, success, failure: memory_order) {
	assert(failure != .release)
	assert(failure != .acq_rel)

	value: T; ok: bool
	#partial switch (failure) {
	case .seq_cst:
		assert(success != .relaxed)
		#partial switch (success) {
		case .seq_cst:
			value, ok := intrinsics.atomic_cxchgweak(object, expected^, desired)
		case .acquire:
			value, ok := intrinsics.atomic_cxchgweak_acq(object, expected^, desired)
		case .consume:
			value, ok := intrinsics.atomic_cxchgweak_acq(object, expected^, desired)
		case .release:
			value, ok := intrinsics.atomic_cxchgweak_rel(object, expected^, desired)
		case .acq_rel:
			value, ok := intrinsics.atomic_cxchgweak_acqrel(object, expected^, desired)
		}
	case .relaxed:
		assert(success != .release)
		#partial switch (success) {
		case .relaxed:
			value, ok := intrinsics.atomic_cxchgweak_relaxed(object, expected^, desired)
		case .seq_cst:
			value, ok := intrinsics.atomic_cxchgweak_failrelaxed(object, expected^, desired)
		case .acquire:
			value, ok := intrinsics.atomic_cxchgweak_acq_failrelaxed(object, expected^, desired)
		case .consume:
			value, ok := intrinsics.atomic_cxchgweak_acq_failrelaxed(object, expected^, desired)
		case .acq_rel:
			value, ok := intrinsics.atomic_cxchgweak_acqrel_failrelaxed(object, expected^, desired)
		}
	case .consume:
		fallthrough
	case .acquire:
		assert(success == .seq_cst)
		value, ok := intrinsics.atomic_cxchgweak_failacq(object, expected^, desired)

	}
	if !ok { expected^ = value }
	return ok
}

// 7.17.7.5 The atomic_fetch and modify generic functions
atomic_fetch_add :: #force_inline proc(object: ^$T, operand: T) -> T {
	return intrinsics.atomic_add(object, operand)
}

atomic_fetch_add_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
	switch (order) {
	case .relaxed:
		return intrinsics.atomic_add_relaxed(object, operand)
	case .consume:
		return intrinsics.atomic_add_acq(object, operand)
	case .acquire:
		return intrinsics.atomic_add_acq(object, operand)
	case .release:
		return intrinsics.atomic_add_rel(object, operand)
	case .acq_rel:
		return intrinsics.atomic_add_acqrel(object, operand)
	case .seq_cst:
		return intrinsics.atomic_add(object, operand)
	}
}

atomic_fetch_sub :: #force_inline proc(object: ^$T, operand: T) -> T {
	return intrinsics.atomic_sub(object, operand)
}

atomic_fetch_sub_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
	switch (order) {
	case .relaxed:
		return intrinsics.atomic_sub_relaxed(object, operand)
	case .consume:
		return intrinsics.atomic_sub_acq(object, operand)
	case .acquire:
		return intrinsics.atomic_sub_acq(object, operand)
	case .release:
		return intrinsics.atomic_sub_rel(object, operand)
	case .acq_rel:
		return intrinsics.atomic_sub_acqrel(object, operand)
	case .seq_cst:
		return intrinsics.atomic_sub(object, operand)
	}
}

atomic_fetch_or :: #force_inline proc(object: ^$T, operand: T) -> T {
	return intrinsics.atomic_or(object, operand)
}

atomic_fetch_or_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
	switch (order) {
	case .relaxed:
		return intrinsics.atomic_or_relaxed(object, operand)
	case .consume:
		return intrinsics.atomic_or_acq(object, operand)
	case .acquire:
		return intrinsics.atomic_or_acq(object, operand)
	case .release:
		return intrinsics.atomic_or_rel(object, operand)
	case .acq_rel:
		return intrinsics.atomic_or_acqrel(object, operand)
	case .seq_cst:
		return intrinsics.atomic_or(object, operand)
	}
}

atomic_fetch_xor :: #force_inline proc(object: ^$T, operand: T) -> T {
	return intrinsics.atomic_xor(object, operand)
}

atomic_fetch_xor_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
	switch (order) {
	case .relaxed:
		return intrinsics.atomic_xor_relaxed(object, operand)
	case .consume:
		return intrinsics.atomic_xor_acq(object, operand)
	case .acquire:
		return intrinsics.atomic_xor_acq(object, operand)
	case .release:
		return intrinsics.atomic_xor_rel(object, operand)
	case .acq_rel:
		return intrinsics.atomic_xor_acqrel(object, operand)
	case .seq_cst:
		return intrinsics.atomic_xor(object, operand)
	}
}

atomic_fetch_and :: #force_inline proc(object: ^$T, operand: T) -> T {
	return intrinsics.atomic_and(object, operand)
}
atomic_fetch_and_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T {
	switch (order) {
	case .relaxed:
		return intrinsics.atomic_and_relaxed(object, operand)
	case .consume:
		return intrinsics.atomic_and_acq(object, operand)
	case .acquire:
		return intrinsics.atomic_and_acq(object, operand)
	case .release:
		return intrinsics.atomic_and_rel(object, operand)
	case .acq_rel:
		return intrinsics.atomic_and_acqrel(object, operand)
	case .seq_cst:
		return intrinsics.atomic_and(object, operand)
	}
}

// 7.17.8 Atomic flag type and operations
atomic_flag :: distinct atomic_bool

atomic_flag_test_and_set :: #force_inline proc(flag: ^atomic_flag) -> bool {
	return bool(atomic_exchange(flag, atomic_flag(true)))
}

atomic_flag_test_and_set_explicit :: #force_inline proc(flag: ^atomic_flag, order: memory_order) -> bool {
	return bool(atomic_exchange_explicit(flag, atomic_flag(true), order))
}

atomic_flag_clear :: #force_inline proc(flag: ^atomic_flag) {
	atomic_store(flag, atomic_flag(false))
}

atomic_flag_clear_explicit :: #force_inline proc(flag: ^atomic_flag, order: memory_order) {
	atomic_store_explicit(flag, atomic_flag(false), order)
}
