diff --git a/Utilities/Atomic.h b/Utilities/Atomic.h index 49948c2744..b7591b556a 100644 --- a/Utilities/Atomic.h +++ b/Utilities/Atomic.h @@ -1,696 +1,979 @@ #pragma once -#if defined(__GNUG__) +#include "types.h" -template inline std::enable_if_t sync_val_compare_and_swap(volatile T* dest, T2 comp, T2 exch) +// Helper class, provides access to compiler-specific atomic intrinsics +template +struct atomic_storage { - return __sync_val_compare_and_swap(dest, comp, exch); -} + static_assert(sizeof(T) <= 16 && sizeof(T) == alignof(T), "atomic_storage<> error: invalid type"); -template inline std::enable_if_t sync_bool_compare_and_swap(volatile T* dest, T2 comp, T2 exch) -{ - return __sync_bool_compare_and_swap(dest, comp, exch); -} + /* First part: Non-MSVC intrinsics */ -template inline std::enable_if_t sync_lock_test_and_set(volatile T* dest, T2 value) -{ - return __sync_lock_test_and_set(dest, value); -} - -template inline std::enable_if_t sync_fetch_and_add(volatile T* dest, T2 value) -{ - return __sync_fetch_and_add(dest, value); -} - -template inline std::enable_if_t sync_fetch_and_sub(volatile T* dest, T2 value) -{ - return __sync_fetch_and_sub(dest, value); -} - -template inline std::enable_if_t sync_fetch_and_or(volatile T* dest, T2 value) -{ - return __sync_fetch_and_or(dest, value); -} - -template inline std::enable_if_t sync_fetch_and_and(volatile T* dest, T2 value) -{ - return __sync_fetch_and_and(dest, value); -} - -template inline std::enable_if_t sync_fetch_and_xor(volatile T* dest, T2 value) -{ - return __sync_fetch_and_xor(dest, value); -} - -#elif defined(_MSC_VER) - -// atomic compare and swap functions - -inline u8 sync_val_compare_and_swap(volatile u8* dest, u8 comp, u8 exch) -{ - return _InterlockedCompareExchange8((volatile char*)dest, exch, comp); -} - -inline u16 sync_val_compare_and_swap(volatile u16* dest, u16 comp, u16 exch) -{ - return _InterlockedCompareExchange16((volatile short*)dest, exch, comp); -} - -inline u32 sync_val_compare_and_swap(volatile u32* dest, u32 comp, u32 exch) -{ - return _InterlockedCompareExchange((volatile long*)dest, exch, comp); -} - -inline u64 sync_val_compare_and_swap(volatile u64* dest, u64 comp, u64 exch) -{ - return _InterlockedCompareExchange64((volatile long long*)dest, exch, comp); -} - -inline u128 sync_val_compare_and_swap(volatile u128* dest, u128 comp, u128 exch) -{ - _InterlockedCompareExchange128((volatile long long*)dest, exch.hi, exch.lo, (long long*)&comp); - return comp; -} - -inline bool sync_bool_compare_and_swap(volatile u8* dest, u8 comp, u8 exch) -{ - return (u8)_InterlockedCompareExchange8((volatile char*)dest, exch, comp) == comp; -} - -inline bool sync_bool_compare_and_swap(volatile u16* dest, u16 comp, u16 exch) -{ - return (u16)_InterlockedCompareExchange16((volatile short*)dest, exch, comp) == comp; -} - -inline bool sync_bool_compare_and_swap(volatile u32* dest, u32 comp, u32 exch) -{ - return (u32)_InterlockedCompareExchange((volatile long*)dest, exch, comp) == comp; -} - -inline bool sync_bool_compare_and_swap(volatile u64* dest, u64 comp, u64 exch) -{ - return (u64)_InterlockedCompareExchange64((volatile long long*)dest, exch, comp) == comp; -} - -inline bool sync_bool_compare_and_swap(volatile u128* dest, u128 comp, u128 exch) -{ - return _InterlockedCompareExchange128((volatile long long*)dest, exch.hi, exch.lo, (long long*)&comp) != 0; -} - -// atomic exchange functions - -inline u8 sync_lock_test_and_set(volatile u8* dest, u8 value) -{ - return _InterlockedExchange8((volatile char*)dest, value); -} - -inline u16 sync_lock_test_and_set(volatile u16* dest, u16 value) -{ - return _InterlockedExchange16((volatile short*)dest, value); -} - -inline u32 sync_lock_test_and_set(volatile u32* dest, u32 value) -{ - return _InterlockedExchange((volatile long*)dest, value); -} - -inline u64 sync_lock_test_and_set(volatile u64* dest, u64 value) -{ - return _InterlockedExchange64((volatile long long*)dest, value); -} - -inline u128 sync_lock_test_and_set(volatile u128* dest, u128 value) -{ - while (true) - { - u128 old; - old.lo = dest->lo; - old.hi = dest->hi; - - if (sync_bool_compare_and_swap(dest, old, value)) return old; - } -} - -// atomic add functions - -inline u8 sync_fetch_and_add(volatile u8* dest, u8 value) -{ - return _InterlockedExchangeAdd8((volatile char*)dest, value); -} - -inline u16 sync_fetch_and_add(volatile u16* dest, u16 value) -{ - return _InterlockedExchangeAdd16((volatile short*)dest, value); -} - -inline u32 sync_fetch_and_add(volatile u32* dest, u32 value) -{ - return _InterlockedExchangeAdd((volatile long*)dest, value); -} - -inline u64 sync_fetch_and_add(volatile u64* dest, u64 value) -{ - return _InterlockedExchangeAdd64((volatile long long*)dest, value); -} - -inline u128 sync_fetch_and_add(volatile u128* dest, u128 value) -{ - while (true) - { - u128 old; - old.lo = dest->lo; - old.hi = dest->hi; - - if (sync_bool_compare_and_swap(dest, old, old + value)) return old; - } -} - -// atomic sub functions - -inline u8 sync_fetch_and_sub(volatile u8* dest, u8 value) -{ - return _InterlockedExchangeAdd8((volatile char*)dest, -(char)value); -} - -inline u16 sync_fetch_and_sub(volatile u16* dest, u16 value) -{ - return _InterlockedExchangeAdd16((volatile short*)dest, -(short)value); -} - -inline u32 sync_fetch_and_sub(volatile u32* dest, u32 value) -{ - return _InterlockedExchangeAdd((volatile long*)dest, -(long)value); -} - -inline u64 sync_fetch_and_sub(volatile u64* dest, u64 value) -{ - return _InterlockedExchangeAdd64((volatile long long*)dest, -(long long)value); -} - -inline u128 sync_fetch_and_sub(volatile u128* dest, u128 value) -{ - while (true) - { - u128 old; - old.lo = dest->lo; - old.hi = dest->hi; - - if (sync_bool_compare_and_swap(dest, old, old - value)) return old; - } -} - -// atomic `bitwise or` functions - -inline u8 sync_fetch_and_or(volatile u8* dest, u8 value) -{ - return _InterlockedOr8((volatile char*)dest, value); -} - -inline u16 sync_fetch_and_or(volatile u16* dest, u16 value) -{ - return _InterlockedOr16((volatile short*)dest, value); -} - -inline u32 sync_fetch_and_or(volatile u32* dest, u32 value) -{ - return _InterlockedOr((volatile long*)dest, value); -} - -inline u64 sync_fetch_and_or(volatile u64* dest, u64 value) -{ - return _InterlockedOr64((volatile long long*)dest, value); -} - -inline u128 sync_fetch_and_or(volatile u128* dest, u128 value) -{ - while (true) - { - u128 old; - old.lo = dest->lo; - old.hi = dest->hi; - - if (sync_bool_compare_and_swap(dest, old, old | value)) return old; - } -} - -// atomic `bitwise and` functions - -inline u8 sync_fetch_and_and(volatile u8* dest, u8 value) -{ - return _InterlockedAnd8((volatile char*)dest, value); -} - -inline u16 sync_fetch_and_and(volatile u16* dest, u16 value) -{ - return _InterlockedAnd16((volatile short*)dest, value); -} - -inline u32 sync_fetch_and_and(volatile u32* dest, u32 value) -{ - return _InterlockedAnd((volatile long*)dest, value); -} - -inline u64 sync_fetch_and_and(volatile u64* dest, u64 value) -{ - return _InterlockedAnd64((volatile long long*)dest, value); -} - -inline u128 sync_fetch_and_and(volatile u128* dest, u128 value) -{ - while (true) - { - u128 old; - old.lo = dest->lo; - old.hi = dest->hi; - - if (sync_bool_compare_and_swap(dest, old, old & value)) return old; - } -} - -// atomic `bitwise xor` functions - -inline u8 sync_fetch_and_xor(volatile u8* dest, u8 value) -{ - return _InterlockedXor8((volatile char*)dest, value); -} - -inline u16 sync_fetch_and_xor(volatile u16* dest, u16 value) -{ - return _InterlockedXor16((volatile short*)dest, value); -} - -inline u32 sync_fetch_and_xor(volatile u32* dest, u32 value) -{ - return _InterlockedXor((volatile long*)dest, value); -} - -inline u64 sync_fetch_and_xor(volatile u64* dest, u64 value) -{ - return _InterlockedXor64((volatile long long*)dest, value); -} - -inline u128 sync_fetch_and_xor(volatile u128* dest, u128 value) -{ - while (true) - { - u128 old; - old.lo = dest->lo; - old.hi = dest->hi; - - if (sync_bool_compare_and_swap(dest, old, old ^ value)) return old; - } -} - -#endif /* _MSC_VER */ - -template struct atomic_storage -{ - static_assert(!Size, "Invalid atomic type"); -}; - -template struct atomic_storage -{ - using type = u8; -}; - -template struct atomic_storage -{ - using type = u16; -}; - -template struct atomic_storage -{ - using type = u32; -}; - -template struct atomic_storage -{ - using type = u64; -}; - -template struct atomic_storage -{ - using type = u128; -}; - -template using atomic_storage_t = typename atomic_storage::type; - -// atomic result wrapper; implements special behaviour for void result type -template struct atomic_op_result_t -{ - RT result; - - template atomic_op_result_t(T func, VT& var, Args&&... args) - : result(std::move(func(var, std::forward(args)...))) +#ifndef _MSC_VER + static inline bool compare_exchange(T& dest, T& comp, T exch) { + return __atomic_compare_exchange(&dest, &comp, &exch, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } - RT move() + static inline T load(const T& dest) { - return std::move(result); + T result; + __atomic_load(&dest, &result, __ATOMIC_SEQ_CST); + return result; + } + + static inline void store(T& dest, T value) + { + __atomic_store(&dest, &value, __ATOMIC_SEQ_CST); + } + + static inline T exchange(T& dest, T value) + { + T result; + __atomic_exchange(&dest, &value, &result, __ATOMIC_SEQ_CST); + return result; + } + + static inline T fetch_add(T& dest, T value) + { + return __atomic_fetch_add(&dest, value, __ATOMIC_SEQ_CST); + } + + static inline T add_fetch(T& dest, T value) + { + return __atomic_add_fetch(&dest, value, __ATOMIC_SEQ_CST); + } + + static inline T fetch_sub(T& dest, T value) + { + return __atomic_fetch_sub(&dest, value, __ATOMIC_SEQ_CST); + } + + static inline T sub_fetch(T& dest, T value) + { + return __atomic_sub_fetch(&dest, value, __ATOMIC_SEQ_CST); + } + + static inline T fetch_and(T& dest, T value) + { + return __atomic_fetch_and(&dest, value, __ATOMIC_SEQ_CST); + } + + static inline T and_fetch(T& dest, T value) + { + return __atomic_and_fetch(&dest, value, __ATOMIC_SEQ_CST); + } + + static inline T fetch_xor(T& dest, T value) + { + return __atomic_fetch_xor(&dest, value, __ATOMIC_SEQ_CST); + } + + static inline T xor_fetch(T& dest, T value) + { + return __atomic_xor_fetch(&dest, value, __ATOMIC_SEQ_CST); + } + + static inline T fetch_or(T& dest, T value) + { + return __atomic_fetch_or(&dest, value, __ATOMIC_SEQ_CST); + } + + static inline T or_fetch(T& dest, T value) + { + return __atomic_or_fetch(&dest, value, __ATOMIC_SEQ_CST); + } +#endif + + /* Second part: MSVC-specific */ + +#ifdef _MSC_VER + static inline T add_fetch(T& dest, T value) + { + return atomic_storage::fetch_add(dest, value) + value; + } + + static inline T fetch_sub(T& dest, T value) + { + return atomic_storage::fetch_add(dest, 0 - value); + } + + static inline T sub_fetch(T& dest, T value) + { + return atomic_storage::fetch_add(dest, 0 - value) - value; + } + + static inline T and_fetch(T& dest, T value) + { + return atomic_storage::fetch_and(dest, value) & value; + } + + static inline T or_fetch(T& dest, T value) + { + return atomic_storage::fetch_or(dest, value) | value; + } + + static inline T xor_fetch(T& dest, T value) + { + return atomic_storage::fetch_xor(dest, value) ^ value; + } +#endif + + /* Third part: fallbacks, may be hidden by subsequent atomic_storage<> specializations */ + + static inline T fetch_inc(T& dest) + { + return atomic_storage::fetch_add(dest, 1); + } + + static inline T inc_fetch(T& dest) + { + return atomic_storage::add_fetch(dest, 1); + } + + static inline T fetch_dec(T& dest) + { + return atomic_storage::fetch_sub(dest, 1); + } + + static inline T dec_fetch(T& dest) + { + return atomic_storage::sub_fetch(dest, 1); + } + + static inline bool bts(T& dest, uint bit) + { + const T mask = static_cast(1) << bit; + return (atomic_storage::fetch_or(dest, mask) & mask) != 0; + } + + static inline bool btr(T& dest, uint bit) + { + const T mask = static_cast(1) << bit; + return (atomic_storage::fetch_and(dest, ~mask) & mask) != 0; + } + + static inline bool btc(T& dest, uint bit) + { + const T mask = static_cast(1) << bit; + return (atomic_storage::fetch_xor(dest, mask) & mask) != 0; } }; -// void specialization: result is the initial value of the first arg -template struct atomic_op_result_t -{ - VT result; +/* The rest: ugly MSVC intrinsics + possibly __asm__ implementations (TODO) */ - template atomic_op_result_t(T func, VT& var, Args&&... args) - : result(var) +template +struct atomic_storage : atomic_storage +{ +#ifdef _MSC_VER + static inline bool compare_exchange(T& dest, T& comp, T exch) { - func(var, std::forward(args)...); + char v = *(char*)∁ + char r = _InterlockedCompareExchange8((volatile char*)&dest, (char&)exch, v); + comp = (T&)r; + return r == v; } - VT move() + static inline T load(const T& dest) { - return std::move(result); + char value = *(const volatile char*)&dest; + _ReadWriteBarrier(); + return (T&)value; + } + + static inline void store(T& dest, T value) + { + _InterlockedExchange8((volatile char*)&dest, (char&)value); + } + + static inline T exchange(T& dest, T value) + { + char r = _InterlockedExchange8((volatile char*)&dest, (char&)value); + return (T&)r; + } + + static inline T fetch_add(T& dest, T value) + { + char r = _InterlockedExchangeAdd8((volatile char*)&dest, (char&)value); + return (T&)r; + } + + static inline T fetch_and(T& dest, T value) + { + char r = _InterlockedAnd8((volatile char*)&dest, (char&)value); + return (T&)r; + } + + static inline T fetch_or(T& dest, T value) + { + char r = _InterlockedOr8((volatile char*)&dest, (char&)value); + return (T&)r; + } + + static inline T fetch_xor(T& dest, T value) + { + char r = _InterlockedXor8((volatile char*)&dest, (char&)value); + return (T&)r; + } +#endif +}; + +template +struct atomic_storage : atomic_storage +{ +#ifdef _MSC_VER + static inline bool compare_exchange(T& dest, T& comp, T exch) + { + short v = *(short*)∁ + short r = _InterlockedCompareExchange16((volatile short*)&dest, (short&)exch, v); + comp = (T&)r; + return r == v; + } + + static inline T load(const T& dest) + { + short value = *(const volatile short*)&dest; + _ReadWriteBarrier(); + return (T&)value; + } + + static inline void store(T& dest, T value) + { + _InterlockedExchange16((volatile short*)&dest, (short&)value); + } + + static inline T exchange(T& dest, T value) + { + short r = _InterlockedExchange16((volatile short*)&dest, (short&)value); + return (T&)r; + } + + static inline T fetch_add(T& dest, T value) + { + short r = _InterlockedExchangeAdd16((volatile short*)&dest, (short&)value); + return (T&)r; + } + + static inline T fetch_and(T& dest, T value) + { + short r = _InterlockedAnd16((volatile short*)&dest, (short&)value); + return (T&)r; + } + + static inline T fetch_or(T& dest, T value) + { + short r = _InterlockedOr16((volatile short*)&dest, (short&)value); + return (T&)r; + } + + static inline T fetch_xor(T& dest, T value) + { + short r = _InterlockedXor16((volatile short*)&dest, (short&)value); + return (T&)r; + } + + static inline T inc_fetch(T& dest) + { + short r = _InterlockedIncrement16((volatile short*)&dest); + return (T&)r; + } + + static inline T dec_fetch(T& dest) + { + short r = _InterlockedDecrement16((volatile short*)&dest); + return (T&)r; + } +#endif +}; + +template +struct atomic_storage : atomic_storage +{ +#ifdef _MSC_VER + static inline bool compare_exchange(T& dest, T& comp, T exch) + { + long v = *(long*)∁ + long r = _InterlockedCompareExchange((volatile long*)&dest, (long&)exch, v); + comp = (T&)r; + return r == v; + } + + static inline T load(const T& dest) + { + long value = *(const volatile long*)&dest; + _ReadWriteBarrier(); + return (T&)value; + } + + static inline void store(T& dest, T value) + { + _InterlockedExchange((volatile long*)&dest, (long&)value); + } + + static inline T exchange(T& dest, T value) + { + long r = _InterlockedExchange((volatile long*)&dest, (long&)value); + return (T&)r; + } + + static inline T fetch_add(T& dest, T value) + { + long r = _InterlockedExchangeAdd((volatile long*)&dest, (long&)value); + return (T&)r; + } + + static inline T fetch_and(T& dest, T value) + { + long r = _InterlockedAnd((volatile long*)&dest, (long&)value); + return (T&)r; + } + + static inline T fetch_or(T& dest, T value) + { + long r = _InterlockedOr((volatile long*)&dest, (long&)value); + return (T&)r; + } + + static inline T fetch_xor(T& dest, T value) + { + long r = _InterlockedXor((volatile long*)&dest, (long&)value); + return (T&)r; + } + + static inline T inc_fetch(T& dest) + { + long r = _InterlockedIncrement((volatile long*)&dest); + return (T&)r; + } + + static inline T dec_fetch(T& dest) + { + long r = _InterlockedDecrement((volatile long*)&dest); + return (T&)r; + } + + static inline bool bts(T& dest, uint bit) + { + return _interlockedbittestandset((volatile long*)&dest, bit) != 0; + } + + static inline bool btr(T& dest, uint bit) + { + return _interlockedbittestandreset((volatile long*)&dest, bit) != 0; + } +#endif +}; + +template +struct atomic_storage : atomic_storage +{ +#ifdef _MSC_VER + static inline bool compare_exchange(T& dest, T& comp, T exch) + { + llong v = *(llong*)∁ + llong r = _InterlockedCompareExchange64((volatile llong*)&dest, (llong&)exch, v); + comp = (T&)r; + return r == v; + } + + static inline T load(const T& dest) + { + llong value = *(const volatile llong*)&dest; + _ReadWriteBarrier(); + return (T&)value; + } + + static inline void store(T& dest, T value) + { + _InterlockedExchange64((volatile llong*)&dest, (llong&)value); + } + + static inline T exchange(T& dest, T value) + { + llong r = _InterlockedExchange64((volatile llong*)&dest, (llong&)value); + return (T&)r; + } + + static inline T fetch_add(T& dest, T value) + { + llong r = _InterlockedExchangeAdd64((volatile llong*)&dest, (llong&)value); + return (T&)r; + } + + static inline T fetch_and(T& dest, T value) + { + llong r = _InterlockedAnd64((volatile llong*)&dest, (llong&)value); + return (T&)r; + } + + static inline T fetch_or(T& dest, T value) + { + llong r = _InterlockedOr64((volatile llong*)&dest, (llong&)value); + return (T&)r; + } + + static inline T fetch_xor(T& dest, T value) + { + llong r = _InterlockedXor64((volatile llong*)&dest, (llong&)value); + return (T&)r; + } + + static inline T inc_fetch(T& dest) + { + llong r = _InterlockedIncrement64((volatile llong*)&dest); + return (T&)r; + } + + static inline T dec_fetch(T& dest) + { + llong r = _InterlockedDecrement64((volatile llong*)&dest); + return (T&)r; + } + + static inline bool bts(T& dest, uint bit) + { + return _interlockedbittestandset64((volatile llong*)&dest, bit) != 0; + } + + static inline bool btr(T& dest, uint bit) + { + return _interlockedbittestandreset64((volatile llong*)&dest, bit) != 0; + } +#endif +}; + +template +struct atomic_storage : atomic_storage +{ +#ifdef _MSC_VER + static inline bool compare_exchange(T& dest, T& comp, T exch) + { + llong* _exch = (llong*)&exch; + return _InterlockedCompareExchange128((volatile llong*)&dest, _exch[1], _exch[0], (llong*)&comp) != 0; + } + + static inline T load(const T& dest) + { + llong result[2]; + _InterlockedCompareExchange128((volatile llong*)&dest, 0, 0, result); + return *(T*)+result; + } + + static inline void store(T& dest, T value) + { + llong lo = *(llong*)&value; + llong hi = *((llong*)&value + 1); + llong cmp[2]{ *(volatile llong*)&dest, *((volatile llong*)&dest + 1) }; + while (!_InterlockedCompareExchange128((volatile llong*)&dest, hi, lo, cmp)); + } + + static inline T exchange(T& dest, T value) + { + llong lo = *(llong*)&value; + llong hi = *((llong*)&value + 1); + llong cmp[2]{ *(volatile llong*)&dest, *((volatile llong*)&dest + 1) }; + while (!_InterlockedCompareExchange128((volatile llong*)&dest, hi, lo, cmp)); + return *(T*)+cmp; + } +#endif +}; + +template +struct atomic_add +{ + auto operator()(T1& lhs, const T2& rhs) const + { + return lhs += rhs; } }; -// member function specialization -template struct atomic_op_result_t +template +struct atomic_add::value && std::is_convertible::value>> { - RT result; + static constexpr auto fetch_op = &atomic_storage::fetch_add; + static constexpr auto op_fetch = &atomic_storage::add_fetch; + static constexpr auto atomic_op = &atomic_storage::add_fetch; +}; - template atomic_op_result_t(RT(CT::*func)(FArgs...), VT& var, Args&&... args) - : result(std::move((var.*func)(std::forward(args)...))) +template +struct atomic_sub +{ + auto operator()(T1& lhs, const T2& rhs) const { - } - - RT move() - { - return std::move(result); + return lhs -= rhs; } }; -// member function void specialization -template struct atomic_op_result_t +template +struct atomic_sub::value && std::is_convertible::value>> { - VT result; + static constexpr auto fetch_op = &atomic_storage::fetch_sub; + static constexpr auto op_fetch = &atomic_storage::sub_fetch; + static constexpr auto atomic_op = &atomic_storage::sub_fetch; +}; - template atomic_op_result_t(void(CT::*func)(FArgs...), VT& var, Args&&... args) - : result(var) +template +struct atomic_pre_inc +{ + auto operator()(T& v) const { - (var.*func)(std::forward(args)...); + return ++v; + } +}; + +template +struct atomic_pre_inc::value>> +{ + static constexpr auto atomic_op = &atomic_storage::inc_fetch; +}; + +template +struct atomic_post_inc +{ + auto operator()(T& v) const + { + return v++; + } +}; + +template +struct atomic_post_inc::value>> +{ + static constexpr auto atomic_op = &atomic_storage::fetch_inc; +}; + +template +struct atomic_pre_dec +{ + auto operator()(T& v) const + { + return --v; + } +}; + +template +struct atomic_pre_dec::value>> +{ + static constexpr auto atomic_op = &atomic_storage::dec_fetch; +}; + +template +struct atomic_post_dec +{ + auto operator()(T& v) const + { + return v--; + } +}; + +template +struct atomic_post_dec::value>> +{ + static constexpr auto atomic_op = &atomic_storage::fetch_dec; +}; + +template +struct atomic_and +{ + auto operator()(T1& lhs, const T2& rhs) const + { + return lhs &= rhs; + } +}; + +template +struct atomic_and::value && std::is_convertible::value>> +{ + static constexpr auto fetch_op = &atomic_storage::fetch_and; + static constexpr auto op_fetch = &atomic_storage::and_fetch; + static constexpr auto atomic_op = &atomic_storage::and_fetch; +}; + +template +struct atomic_or +{ + auto operator()(T1& lhs, const T2& rhs) const + { + return lhs |= rhs; + } +}; + +template +struct atomic_or::value && std::is_convertible::value>> +{ + static constexpr auto fetch_op = &atomic_storage::fetch_or; + static constexpr auto op_fetch = &atomic_storage::or_fetch; + static constexpr auto atomic_op = &atomic_storage::or_fetch; +}; + +template +struct atomic_xor +{ + auto operator()(T1& lhs, const T2& rhs) const + { + return lhs ^= rhs; + } +}; + +template +struct atomic_xor::value && std::is_convertible::value>> +{ + static constexpr auto fetch_op = &atomic_storage::fetch_xor; + static constexpr auto op_fetch = &atomic_storage::xor_fetch; + static constexpr auto atomic_op = &atomic_storage::xor_fetch; +}; + +template +struct atomic_test_and_set +{ + bool operator()(T1& lhs, const T2& rhs) const + { + return lhs.test_and_set(rhs); + } +}; + +template +struct atomic_test_and_set::value && std::is_convertible::value>> +{ + static inline bool op(T1& lhs, const T2& rhs) + { + return (atomic_storage::fetch_or(lhs, rhs) & rhs) != 0; } - VT move() + static constexpr auto fetch_op = &op; + static constexpr auto op_fetch = &op; + static constexpr auto atomic_op = &op; +}; + +template +struct atomic_test_and_reset +{ + bool operator()(T1& lhs, const T2& rhs) const { - return std::move(result); + return lhs.test_and_reset(rhs); } }; +template +struct atomic_test_and_reset::value && std::is_convertible::value>> +{ + static inline bool op(T1& lhs, const T2& rhs) + { + return (atomic_storage::fetch_and(lhs, ~rhs) & rhs) != 0; + } + + static constexpr auto fetch_op = &op; + static constexpr auto op_fetch = &op; + static constexpr auto atomic_op = &op; +}; + +template +struct atomic_test_and_complement +{ + bool operator()(T1& lhs, const T2& rhs) const + { + return lhs.test_and_complement(rhs); + } +}; + +template +struct atomic_test_and_complement::value && std::is_convertible::value>> +{ + static inline bool op(T1& lhs, const T2& rhs) + { + return (atomic_storage::fetch_xor(lhs, rhs) & rhs) != 0; + } + + static constexpr auto fetch_op = &op; + static constexpr auto op_fetch = &op; + static constexpr auto atomic_op = &op; +}; + // Atomic type with lock-free and standard layout guarantees (and appropriate limitations) -template class atomic_t +template +class atomic_t { - using type = std::remove_cv_t; - using stype = atomic_storage_t; - using storage = atomic_storage; + using type = typename std::remove_cv::type; - static_assert(alignof(type) <= alignof(stype), "atomic_t<> error: unexpected alignment"); + static_assert(alignof(type) == sizeof(type), "atomic_t<> error: unexpected alignment, use alignas() if necessary"); - stype m_data; - - template static inline void write_relaxed(volatile T2& data, const T2& value) - { - data = value; - } - - static inline void write_relaxed(volatile u128& data, const u128& value) - { - sync_lock_test_and_set(&data, value); - } - - template static inline T2 read_relaxed(const volatile T2& data) - { - return data; - } - - static inline u128 read_relaxed(const volatile u128& value) - { - return sync_val_compare_and_swap(const_cast(&value), u128{0}, u128{0}); - } + type m_data; public: - static inline const stype to_subtype(const type& value) - { - return reinterpret_cast(value); - } - - static inline const type from_subtype(const stype value) - { - return reinterpret_cast(value); - } - atomic_t() = default; atomic_t(const atomic_t&) = delete; - atomic_t(type value) - : m_data(to_subtype(value)) - { - } - atomic_t& operator =(const atomic_t&) = delete; - atomic_t& operator =(type value) - { - return write_relaxed(m_data, to_subtype(value)), *this; - } + // Define simple type + using simple_type = simple_t; - operator type() const volatile + explicit constexpr atomic_t(const type& value) + : m_data(value) { - return from_subtype(read_relaxed(m_data)); - } - - // Unsafe direct access - stype* raw_data() - { - return reinterpret_cast(&m_data); } // Unsafe direct access type& raw() { - return reinterpret_cast(m_data); + return m_data; } // Atomically compare data with cmp, replace with exch if equal, return previous data value anyway - type compare_and_swap(const type& cmp, const type& exch) volatile + simple_type compare_and_swap(const type& cmp, const type& exch) { - return from_subtype(sync_val_compare_and_swap(&m_data, to_subtype(cmp), to_subtype(exch))); + type old = cmp; + atomic_storage::compare_exchange(m_data, old, exch); + return old; } // Atomically compare data with cmp, replace with exch if equal, return true if data was replaced - bool compare_and_swap_test(const type& cmp, const type& exch) volatile + bool compare_and_swap_test(const type& cmp, const type& exch) { - return sync_bool_compare_and_swap(&m_data, to_subtype(cmp), to_subtype(exch)); + type old = cmp; + return atomic_storage::compare_exchange(m_data, old, exch); } - // Atomically replace data with exch, return previous data value - type exchange(const type& exch) volatile + // Atomic operation; returns old value, discards function result value + template> + type fetch_op(F&& func, const Args&... args) { - return from_subtype(sync_lock_test_and_set(&m_data, to_subtype(exch))); - } + type _new, old = atomic_storage::load(m_data); - // Atomically read data, possibly without memory barrier (not for 128 bit) - type load() const volatile - { - return from_subtype(read_relaxed(m_data)); - } - - // Atomically write data, possibly without memory barrier (not for 128 bit) - void store(const type& value) volatile - { - write_relaxed(m_data, to_subtype(value)); - } - - // Perform an atomic operation on data (func is either pointer to member function or callable object with a T& first arg); - // Returns the result of the callable object call or previous (old) value of the atomic variable if the return type is void - template> auto atomic_op(F func, Args&&... args) volatile -> decltype(atomic_op_result_t::result) - { while (true) { - // Read the old value from memory - const stype old = read_relaxed(m_data); + func(_new = old, args...); - // Copy the old value - stype _new = old; - - // Call atomic op for the local copy of the old value and save the return value of the function - atomic_op_result_t result(func, reinterpret_cast(_new), args...); - - // Atomically compare value with `old`, replace with `_new` and return on success - if (sync_bool_compare_and_swap(&m_data, old, _new)) return result.move(); + if (atomic_storage::compare_exchange(m_data, old, _new)) return old; } } - // Atomic bitwise OR, returns previous data - type _or(const type& right) volatile + // Helper overload for calling optimized implementation + template> + type fetch_op(F&&, const Args&... args) { - return from_subtype(sync_fetch_and_or(&m_data, to_subtype(right))); + return F::fetch_op(m_data, args...); } - // Atomic bitwise AND, returns previous data - type _and(const type& right) volatile + // Atomic operation; returns new value, discards function result value + template> + type op_fetch(F&& func, const Args&... args) { - return from_subtype(sync_fetch_and_and(&m_data, to_subtype(right))); + type _new, old = atomic_storage::load(m_data); + + while (true) + { + func(_new = old, args...); + + if (atomic_storage::compare_exchange(m_data, old, _new)) return _new; + } } - // Atomic bitwise AND NOT (inverts right argument), returns previous data - type _and_not(const type& right) volatile + // Helper overload for calling optimized implementation + template> + type op_fetch(F&&, const Args&... args) { - return from_subtype(sync_fetch_and_and(&m_data, ~to_subtype(right))); + return F::op_fetch(m_data, args...); } - // Atomic bitwise XOR, returns previous data - type _xor(const type& right) volatile + // Atomic operation; returns function result value + template, typename = std::enable_if_t::value>> + RT atomic_op(F&& func, const Args&... args) { - return from_subtype(sync_fetch_and_xor(&m_data, to_subtype(right))); + type _new, old = atomic_storage::load(m_data); + + while (true) + { + RT&& result = func(_new = old, args...); + + if (atomic_storage::compare_exchange(m_data, old, _new)) return std::move(result); + } } - type operator |=(const type& right) volatile + // Overload for void return type + template, typename = std::enable_if_t::value>> + void atomic_op(F&& func, const Args&... args) { - return from_subtype(sync_fetch_and_or(&m_data, to_subtype(right)) | to_subtype(right)); + type _new, old = atomic_storage::load(m_data); + + while (true) + { + func(_new = old, args...); + + if (atomic_storage::compare_exchange(m_data, old, _new)) return; + } } - type operator &=(const type& right) volatile + // Helper overload for calling optimized implementation + template> + auto atomic_op(F&&, const Args&... args) { - return from_subtype(sync_fetch_and_and(&m_data, to_subtype(right)) & to_subtype(right)); + return F::atomic_op(m_data, args...); } - type operator ^=(const type& right) volatile + // Atomically read data + type load() const { - return from_subtype(sync_fetch_and_xor(&m_data, to_subtype(right)) ^ to_subtype(right)); + return atomic_storage::load(m_data); + } + + // Atomically read data + operator simple_type() const + { + return atomic_storage::load(m_data); + } + + // Atomically write data + void store(const type& rhs) + { + atomic_storage::store(m_data, rhs); + } + + type operator =(const type& rhs) + { + atomic_storage::store(m_data, rhs); + return rhs; + } + + // Atomically replace data with value, return previous data value + type exchange(const type& rhs) + { + return atomic_storage::exchange(m_data, rhs); + } + + template + type fetch_add(const T2& rhs) + { + return fetch_op(atomic_add{}, rhs); + } + + template + type add_fetch(const T2& rhs) + { + return op_fetch(atomic_add{}, rhs); + } + + template + auto operator +=(const T2& rhs) + { + return atomic_op(atomic_add{}, rhs); + } + + template + type fetch_sub(const T2& rhs) + { + return fetch_op(atomic_sub{}, rhs); + } + + template + type sub_fetch(const T2& rhs) + { + return op_fetch(atomic_sub{}, rhs); + } + + template + auto operator -=(const T2& rhs) + { + return atomic_op(atomic_sub{}, rhs); + } + + template + type fetch_and(const T2& rhs) + { + return fetch_op(atomic_and{}, rhs); + } + + template + type and_fetch(const T2& rhs) + { + return op_fetch(atomic_and{}, rhs); + } + + template + auto operator &=(const T2& rhs) + { + return atomic_op(atomic_and{}, rhs); + } + + template + type fetch_or(const T2& rhs) + { + return fetch_op(atomic_or{}, rhs); + } + + template + type or_fetch(const T2& rhs) + { + return op_fetch(atomic_or{}, rhs); + } + + template + auto operator |=(const T2& rhs) + { + return atomic_op(atomic_or{}, rhs); + } + + template + type fetch_xor(const T2& rhs) + { + return fetch_op(atomic_xor{}, rhs); + } + + template + type xor_fetch(const T2& rhs) + { + return op_fetch(atomic_xor{}, rhs); + } + + template + auto operator ^=(const T2& rhs) + { + return atomic_op(atomic_xor{}, rhs); + } + + auto operator ++() + { + return atomic_op(atomic_pre_inc{}); + } + + auto operator --() + { + return atomic_op(atomic_pre_dec{}); + } + + auto operator ++(int) + { + return atomic_op(atomic_post_inc{}); + } + + auto operator --(int) + { + return atomic_op(atomic_post_dec{}); + } + + template + auto test(const T2& rhs) const + { + return load().test(rhs); + } + + template + auto test_and_set(const T2& rhs) + { + return atomic_op(atomic_test_and_set{}, rhs); + } + + template + auto test_and_reset(const T2& rhs) + { + return atomic_op(atomic_test_and_reset{}, rhs); + } + + template + auto test_and_complement(const T2& rhs) + { + return atomic_op(atomic_test_and_complement{}, rhs); } }; - -template inline std::enable_if_t operator ++(atomic_t& left) -{ - return left.from_subtype(sync_fetch_and_add(left.raw_data(), 1) + 1); -} - -template inline std::enable_if_t operator --(atomic_t& left) -{ - return left.from_subtype(sync_fetch_and_sub(left.raw_data(), 1) - 1); -} - -template inline std::enable_if_t operator ++(atomic_t& left, int) -{ - return left.from_subtype(sync_fetch_and_add(left.raw_data(), 1)); -} - -template inline std::enable_if_t operator --(atomic_t& left, int) -{ - return left.from_subtype(sync_fetch_and_sub(left.raw_data(), 1)); -} - -template inline std::enable_if_t::value, T> operator +=(atomic_t& left, const T2& right) -{ - return left.from_subtype(sync_fetch_and_add(left.raw_data(), right) + right); -} - -template inline std::enable_if_t::value, T> operator -=(atomic_t& left, const T2& right) -{ - return left.from_subtype(sync_fetch_and_sub(left.raw_data(), right) - right); -} - -template inline std::enable_if_t> operator ++(atomic_t>& left) -{ - return left.from_subtype(sync_fetch_and_add(left.raw_data(), 1) + 1); -} - -template inline std::enable_if_t> operator --(atomic_t>& left) -{ - return left.from_subtype(sync_fetch_and_sub(left.raw_data(), 1) - 1); -} - -template inline std::enable_if_t> operator ++(atomic_t>& left, int) -{ - return left.from_subtype(sync_fetch_and_add(left.raw_data(), 1)); -} - -template inline std::enable_if_t> operator --(atomic_t>& left, int) -{ - return left.from_subtype(sync_fetch_and_sub(left.raw_data(), 1)); -} - -template inline std::enable_if_t::value, nse_t> operator +=(atomic_t>& left, const T2& right) -{ - return left.from_subtype(sync_fetch_and_add(left.raw_data(), right) + right); -} - -template inline std::enable_if_t::value, nse_t> operator -=(atomic_t>& left, const T2& right) -{ - return left.from_subtype(sync_fetch_and_sub(left.raw_data(), right) - right); -} - -template inline std::enable_if_t> operator ++(atomic_t>& left) -{ - return left.atomic_op([](se_t& value) -> se_t - { - return ++value; - }); -} - -template inline std::enable_if_t> operator --(atomic_t>& left) -{ - return left.atomic_op([](se_t& value) -> se_t - { - return --value; - }); -} - -template inline std::enable_if_t> operator ++(atomic_t>& left, int) -{ - return left.atomic_op([](se_t& value) -> se_t - { - return value++; - }); -} - -template inline std::enable_if_t> operator --(atomic_t>& left, int) -{ - return left.atomic_op([](se_t& value) -> se_t - { - return value--; - }); -} - -template inline std::enable_if_t::value, se_t> operator +=(atomic_t>& left, const T2& right) -{ - return left.atomic_op([&](se_t& value) -> se_t - { - return value += right; - }); -} - -template inline std::enable_if_t::value, se_t> operator -=(atomic_t>& left, const T2& right) -{ - return left.atomic_op([&](se_t& value) -> se_t - { - return value -= right; - }); -} - -// Atomic BE Type (for PS3 virtual memory) -template using atomic_be_t = atomic_t>; - -// Atomic LE Type (for PSV virtual memory) -template using atomic_le_t = atomic_t>; - -// Algorithm for std::atomic; similar to atomic_t::atomic_op() -template> auto atomic_op(std::atomic& var, F func, Args&&... args) -> decltype(atomic_op_result_t::result) -{ - auto old = var.load(); - - while (true) - { - auto _new = old; - - atomic_op_result_t result(func, _new, args...); - - if (var.compare_exchange_strong(old, _new)) return result.move(); - } -} diff --git a/Utilities/AutoPause.cpp b/Utilities/AutoPause.cpp index 35608d3300..5fc5125975 100644 --- a/Utilities/AutoPause.cpp +++ b/Utilities/AutoPause.cpp @@ -1,57 +1,29 @@ #include "stdafx.h" -#include "AutoPause.h" -#include "Utilities/Log.h" -#include "Utilities/File.h" +#include "Config.h" #include "Emu/System.h" -#include "Emu/state.h" +#include "AutoPause.h" -using namespace Debug; +cfg::bool_entry g_cfg_debug_autopause_syscall(cfg::root.misc, "Auto Pause at System Call"); +cfg::bool_entry g_cfg_debug_autopause_func_call(cfg::root.misc, "Auto Pause at Function Call"); -std::unique_ptr g_autopause; - -AutoPause& AutoPause::getInstance(void) +debug::autopause& debug::autopause::get_instance() { - if (!g_autopause) + // Use magic static + static autopause instance; + return instance; +} + +// Load Auto Pause Configuration from file "pause.bin" +void debug::autopause::reload(void) +{ + auto& instance = get_instance(); + + instance.m_pause_function.clear(); + instance.m_pause_syscall.clear(); + + // TODO: better format, possibly a config entry + if (fs::file list{ fs::get_config_dir() + "pause.bin" }) { - g_autopause.reset(new AutoPause); - } - - return *g_autopause; -} - -//Still use binary format. Default Setting should be "disable all auto pause". -AutoPause::AutoPause(void) -{ - m_pause_function.reserve(16); - m_pause_syscall.reserve(16); - initialized = false; - //Reload(false, false); - Reload(); -} - -//Notice: I would not allow to write the binary to file in this command. -AutoPause::~AutoPause(void) -{ - initialized = false; - m_pause_function.clear(); - m_pause_syscall.clear(); - m_pause_function_enable = false; - m_pause_syscall_enable = false; -} - -//Load Auto Pause Configuration from file "pause.bin" -//This would be able to create in a GUI window. -void AutoPause::Reload(void) -{ - if (fs::is_file(fs::get_config_dir() + "pause.bin")) - { - m_pause_function.clear(); - m_pause_function.reserve(16); - m_pause_syscall.clear(); - m_pause_syscall.reserve(16); - - fs::file list(fs::get_config_dir() + "pause.bin"); - //System calls ID and Function calls ID are all u32 iirc. u32 num; size_t fmax = list.size(); size_t fcur = 0; @@ -64,60 +36,38 @@ void AutoPause::Reload(void) if (num < 1024) { - //Less than 1024 - be regarded as a system call. - //emplace_back may not cause reductant move/copy operation. - m_pause_syscall.emplace_back(num); - LOG_WARNING(HLE, "Auto Pause: Find System Call ID 0x%x", num); + instance.m_pause_syscall.emplace(num); + LOG_WARNING(HLE, "Set autopause at syscall %lld", num); } else { - m_pause_function.emplace_back(num); - LOG_WARNING(HLE, "Auto Pause: Find Function Call ID 0x%x", num); + instance.m_pause_function.emplace(num); + LOG_WARNING(HLE, "Set autopause at function 0x%08x", num); } } } - - m_pause_syscall_enable = rpcs3::config.misc.debug.auto_pause_syscall.value(); - m_pause_function_enable = rpcs3::config.misc.debug.auto_pause_func_call.value(); - initialized = true; } -void AutoPause::TryPause(u32 code) +bool debug::autopause::pause_syscall(u64 code) { - if (code < 1024) + if (g_cfg_debug_autopause_syscall && get_instance().m_pause_syscall.count(code) != 0) { - //Would first check Enable setting. Then the list length. - if ((!m_pause_syscall_enable) - || (m_pause_syscall.size() <= 0)) - { - return; - } - - for (u32 i = 0; i < m_pause_syscall.size(); ++i) - { - if (code == m_pause_syscall[i]) - { - Emu.Pause(); - LOG_ERROR(HLE, "Auto Pause Triggered: System call 0x%x", code); // Used Error - } - } + Emu.Pause(); + LOG_SUCCESS(HLE, "Autopause triggered at syscall %lld", code); + return true; } - else - { - //Well similiar.. Seperate the list caused by possible setting difference. - if ((!m_pause_function_enable) - || (m_pause_function.size() <= 0)) - { - return; - } - for (u32 i = 0; i < m_pause_function.size(); ++i) - { - if (code == m_pause_function[i]) - { - Emu.Pause(); - LOG_ERROR(HLE, "Auto Pause Triggered: Function call 0x%x", code); // Used Error - } - } - } + return false; +} + +bool debug::autopause::pause_function(u32 code) +{ + if (g_cfg_debug_autopause_func_call && get_instance().m_pause_function.count(code) != 0) + { + Emu.Pause(); + LOG_SUCCESS(HLE, "Autopause triggered at function 0x%08x", code); + return true; + } + + return false; } diff --git a/Utilities/AutoPause.h b/Utilities/AutoPause.h index cf871b46cb..13a2f0c133 100644 --- a/Utilities/AutoPause.h +++ b/Utilities/AutoPause.h @@ -1,24 +1,20 @@ #pragma once -//Regarded as a Debugger Enchantment -namespace Debug { - //To store the pause function/call id, and let those pause there. - //Would be with a GUI to configure those. - struct AutoPause +// Regarded as a Debugger Enchantment +namespace debug +{ + // To store the pause function/call id, and let those pause there. + // Would be with a GUI to configure those. + class autopause { - std::vector m_pause_syscall; - std::vector m_pause_function; - bool initialized; - bool m_pause_syscall_enable; - bool m_pause_function_enable; + std::unordered_set m_pause_syscall; + std::unordered_set m_pause_function; - AutoPause(); - ~AutoPause(); + static autopause& get_instance(); public: - static AutoPause& getInstance(void); - void Reload(void); - - void TryPause(u32 code); + static void reload(); + static bool pause_syscall(u64 code); + static bool pause_function(u32 code); }; -} \ No newline at end of file +} diff --git a/Utilities/BEType.h b/Utilities/BEType.h index 80c8256a2f..d5b51b99c7 100644 --- a/Utilities/BEType.h +++ b/Utilities/BEType.h @@ -1,16 +1,14 @@ #pragma once -#ifdef _MSC_VER -#include -#else -#include -#endif +#include "types.h" +#include "Platform.h" -#define IS_LE_MACHINE // only draft - -union v128 +union alignas(16) v128 { - template class masked_array_t // array type accessed as (index ^ M) + char _bytes[16]; + + template + struct masked_array_t // array type accessed as (index ^ M) { T m_data[N]; @@ -24,24 +22,11 @@ union v128 { return m_data[index ^ M]; } - - T& at(std::size_t index) - { - return (index ^ M) < N ? m_data[index ^ M] : throw std::out_of_range(__FUNCTION__); - } - - const T& at(std::size_t index) const - { - return (index ^ M) < N ? m_data[index ^ M] : throw std::out_of_range(__FUNCTION__); - } }; -#ifdef IS_LE_MACHINE +#if IS_LE_MACHINE == 1 template using normal_array_t = masked_array_t; template using reversed_array_t = masked_array_t; -#else - template using normal_array_t = masked_array_t; - template using reversed_array_t = masked_array_t; #endif normal_array_t _u64; @@ -73,7 +58,7 @@ union v128 __m128i vi; __m128d vd; - class bit_array_128 + struct bit_array_128 { u64 m_data[2]; @@ -125,36 +110,18 @@ union v128 // Index 0 returns the MSB and index 127 returns the LSB bit_element operator [](u32 index) { -#ifdef IS_LE_MACHINE +#if IS_LE_MACHINE == 1 return bit_element(m_data[1 - (index >> 6)], 0x8000000000000000ull >> (index & 0x3F)); -#else - return bit_element(m_data[index >> 6], 0x8000000000000000ull >> (index & 0x3F)); #endif } // Index 0 returns the MSB and index 127 returns the LSB bool operator [](u32 index) const { -#ifdef IS_LE_MACHINE +#if IS_LE_MACHINE == 1 return (m_data[1 - (index >> 6)] & (0x8000000000000000ull >> (index & 0x3F))) != 0; -#else - return (m_data[index >> 6] & (0x8000000000000000ull >> (index & 0x3F))) != 0; #endif } - - bit_element at(u32 index) - { - if (index >= 128) throw std::out_of_range(__FUNCTION__); - - return operator[](index); - } - - bool at(u32 index) const - { - if (index >= 128) throw std::out_of_range(__FUNCTION__); - - return operator[](index); - } } _bit; @@ -320,16 +287,6 @@ union v128 return _u64[0] != right._u64[0] || _u64[1] != right._u64[1]; } - bool is_any_1() const // check if any bit is 1 - { - return _u64[0] || _u64[1]; - } - - bool is_any_0() const // check if any bit is 0 - { - return ~_u64[0] || ~_u64[1]; - } - // result = (~left) & (right) static inline v128 andnot(const v128& left, const v128& right) { @@ -345,15 +302,8 @@ union v128 std::string to_hex() const; std::string to_xyzw() const; - - static inline v128 byteswap(const v128 val) - { - return fromV(_mm_shuffle_epi8(val.vi, _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15))); - } }; -CHECK_SIZE_ALIGN(v128, 16, 16); - inline v128 operator |(const v128& left, const v128& right) { return v128::fromV(_mm_or_si128(left.vi, right.vi)); @@ -374,21 +324,21 @@ inline v128 operator ~(const v128& other) return v128::from64(~other._u64[0], ~other._u64[1]); } -template struct se_storage +#define IS_INTEGER(t) (std::is_integral::value || std::is_enum::value) +#define IS_BINARY_COMPARABLE(t1, t2) (IS_INTEGER(t1) && IS_INTEGER(t2) && sizeof(t1) == sizeof(t2)) + +template +struct se_storage { static_assert(!Size, "Bad se_storage<> type"); }; -template struct se_storage +template +struct se_storage { using type = u16; - [[deprecated]] static constexpr u16 _swap(u16 src) // for reference - { - return (src >> 8) | (src << 8); - } - - static inline u16 swap(u16 src) + static constexpr u16 swap(u16 src) { #if defined(__GNUG__) return __builtin_bswap16(src); @@ -409,16 +359,12 @@ template struct se_storage } }; -template struct se_storage +template +struct se_storage { using type = u32; - [[deprecated]] static constexpr u32 _swap(u32 src) // for reference - { - return (src >> 24) | (src << 24) | ((src >> 8) & 0x0000ff00) | ((src << 8) & 0x00ff0000); - } - - static inline u32 swap(u32 src) + static constexpr u32 swap(u32 src) { #if defined(__GNUG__) return __builtin_bswap32(src); @@ -439,22 +385,12 @@ template struct se_storage } }; -template struct se_storage +template +struct se_storage { using type = u64; - [[deprecated]] static constexpr u64 _swap(u64 src) // for reference - { - return (src >> 56) | (src << 56) | - ((src >> 40) & 0x000000000000ff00) | - ((src >> 24) & 0x0000000000ff0000) | - ((src >> 8) & 0x00000000ff000000) | - ((src << 8) & 0x000000ff00000000) | - ((src << 24) & 0x0000ff0000000000) | - ((src << 40) & 0x00ff000000000000); - } - - static inline u64 swap(u64 src) + static constexpr u64 swap(u64 src) { #if defined(__GNUG__) return __builtin_bswap64(src); @@ -475,25 +411,32 @@ template struct se_storage } }; -template struct se_storage +template +struct se_storage { using type = v128; + static inline v128 swap(const v128& src) + { + return v128::fromV(_mm_shuffle_epi8(src.vi, _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15))); + } + static inline v128 to(const T& src) { - return v128::byteswap(reinterpret_cast(src)); + return swap(reinterpret_cast(src)); } static inline T from(const v128& src) { - const v128 result = v128::byteswap(src); + const v128 result = swap(src); return reinterpret_cast(result); } }; template using se_storage_t = typename se_storage::type; -template struct se_convert +template +struct se_convert { using type_from = std::remove_cv_t; using type_to = std::remove_cv_t; @@ -515,10 +458,12 @@ template struct se_convert static struct se_raw_tag_t {} constexpr se_raw{}; -template class se_t; +template +class se_t; -// se_t with switched endianness -template class se_t +// Switched endianness +template +class se_t { using type = typename std::remove_cv::type; using stype = se_storage_t; @@ -526,14 +471,13 @@ template class se_t stype m_data; - static_assert(!std::is_union::value && !std::is_class::value || std::is_same::value || std::is_same::value, "se_t<> error: invalid type (struct or union)"); static_assert(!std::is_pointer::value, "se_t<> error: invalid type (pointer)"); static_assert(!std::is_reference::value, "se_t<> error: invalid type (reference)"); static_assert(!std::is_array::value, "se_t<> error: invalid type (array)"); - //static_assert(!std::is_enum::value, "se_t<> error: invalid type (enumeration), use integral type instead"); - static_assert(alignof(type) == alignof(stype), "se_t<> error: unexpected alignment"); + static_assert(sizeof(type) == alignof(type), "se_t<> error: unexpected alignment"); - template struct bool_converter + template + struct bool_converter { static inline bool to_bool(const se_t& value) { @@ -541,7 +485,8 @@ template class se_t } }; - template struct bool_converter::value>> + template + struct bool_converter::value>> { static inline bool to_bool(const se_t& value) { @@ -559,7 +504,7 @@ public: { } - // construct directly from raw data (don't use) + // Construct directly from raw data (don't use) constexpr se_t(const stype& raw_value, const se_raw_tag_t&) : m_data(raw_value) { @@ -570,7 +515,7 @@ public: return storage::from(m_data); } - // access underlying raw data (don't use) + // Access underlying raw data (don't use) constexpr const stype& raw_data() const noexcept { return m_data; @@ -583,78 +528,96 @@ public: return m_data = storage::to(value), *this; } + using simple_type = simple_t; + operator type() const { return storage::from(m_data); } - // optimization + // Optimization explicit operator bool() const { return bool_converter::to_bool(*this); } - // optimization - template std::enable_if_t operator &=(const se_t& right) + // Optimization + template + std::enable_if_t operator &=(const se_t& right) { return m_data &= right.raw_data(), *this; } - // optimization - template std::enable_if_t::value, se_t&> operator &=(CT right) + // Optimization + template + std::enable_if_t::value && std::is_convertible::value, se_t&> operator &=(CT right) { return m_data &= storage::to(right), *this; } - // optimization - template std::enable_if_t operator |=(const se_t& right) + // Optimization + template + std::enable_if_t operator |=(const se_t& right) { return m_data |= right.raw_data(), *this; } - // optimization - template std::enable_if_t::value, se_t&> operator |=(CT right) + // Optimization + template + std::enable_if_t::value && std::is_convertible::value, se_t&> operator |=(CT right) { return m_data |= storage::to(right), *this; } - // optimization - template std::enable_if_t operator ^=(const se_t& right) + // Optimization + template + std::enable_if_t operator ^=(const se_t& right) { return m_data ^= right.raw_data(), *this; } - // optimization - template std::enable_if_t::value, se_t&> operator ^=(CT right) + // Optimization + template + std::enable_if_t::value && std::is_convertible::value, se_t&> operator ^=(CT right) { return m_data ^= storage::to(right), *this; } }; -// se_t with native endianness -template class se_t +// Native endianness +template +class se_t { using type = typename std::remove_cv::type; - type m_data; - - static_assert(!std::is_union::value && !std::is_class::value || std::is_same::value || std::is_same::value, "se_t<> error: invalid type (struct or union)"); static_assert(!std::is_pointer::value, "se_t<> error: invalid type (pointer)"); static_assert(!std::is_reference::value, "se_t<> error: invalid type (reference)"); static_assert(!std::is_array::value, "se_t<> error: invalid type (array)"); - //static_assert(!std::is_enum::value, "se_t<> error: invalid type (enumeration), use integral type instead"); + static_assert(sizeof(type) == alignof(type), "se_t<> error: unexpected alignment"); + + type m_data; public: se_t() = default; - se_t(const se_t&) = default; - constexpr se_t(type value) : m_data(value) { } - type value() const + // Construct directly from raw data (don't use) + constexpr se_t(const type& raw_value, const se_raw_tag_t&) + : m_data(raw_value) + { + } + + constexpr type value() const + { + return m_data; + } + + // Access underlying raw data (don't use) + constexpr const type& raw_data() const noexcept { return m_data; } @@ -666,22 +629,27 @@ public: return m_data = value, *this; } - operator type() const + using simple_type = simple_t; + + constexpr operator type() const { return m_data; } - template std::enable_if_t::value, se_t&> operator &=(const CT& right) + template + std::enable_if_t::value && std::is_convertible::value, se_t&> operator &=(const CT& right) { return m_data &= right, *this; } - template std::enable_if_t::value, se_t&> operator |=(const CT& right) + template + std::enable_if_t::value && std::is_convertible::value, se_t&> operator |=(const CT& right) { return m_data |= right, *this; } - template std::enable_if_t::value, se_t&> operator ^=(const CT& right) + template + std::enable_if_t::value && std::is_convertible::value, se_t&> operator ^=(const CT& right) { return m_data ^= right, *this; } @@ -690,49 +658,57 @@ public: // se_t with native endianness (alias) template using nse_t = se_t; -template inline se_t& operator +=(se_t& left, const T1& right) +template +inline se_t& operator +=(se_t& left, const T1& right) { auto value = left.value(); return left = (value += right); } -template inline se_t& operator -=(se_t& left, const T1& right) +template +inline se_t& operator -=(se_t& left, const T1& right) { auto value = left.value(); return left = (value -= right); } -template inline se_t& operator *=(se_t& left, const T1& right) +template +inline se_t& operator *=(se_t& left, const T1& right) { auto value = left.value(); return left = (value *= right); } -template inline se_t& operator /=(se_t& left, const T1& right) +template +inline se_t& operator /=(se_t& left, const T1& right) { auto value = left.value(); return left = (value /= right); } -template inline se_t& operator %=(se_t& left, const T1& right) +template +inline se_t& operator %=(se_t& left, const T1& right) { auto value = left.value(); return left = (value %= right); } -template inline se_t& operator <<=(se_t& left, const T1& right) +template +inline se_t& operator <<=(se_t& left, const T1& right) { auto value = left.value(); return left = (value <<= right); } -template inline se_t& operator >>=(se_t& left, const T1& right) +template +inline se_t& operator >>=(se_t& left, const T1& right) { auto value = left.value(); return left = (value >>= right); } -template inline se_t operator ++(se_t& left, int) +template +inline se_t operator ++(se_t& left, int) { auto value = left.value(); auto result = value++; @@ -740,7 +716,8 @@ template inline se_t operator ++(se_t& left, return result; } -template inline se_t operator --(se_t& left, int) +template +inline se_t operator --(se_t& left, int) { auto value = left.value(); auto result = value--; @@ -748,193 +725,205 @@ template inline se_t operator --(se_t& left, return result; } -template inline se_t& operator ++(se_t& right) +template +inline se_t& operator ++(se_t& right) { auto value = right.value(); return right = ++value; } -template inline se_t& operator --(se_t& right) +template +inline se_t& operator --(se_t& right) { auto value = right.value(); return right = --value; } -// optimization -template inline std::enable_if_t operator ==(const se_t& left, const se_t& right) +// Optimization +template +inline std::enable_if_t operator ==(const se_t& left, const se_t& right) { return left.raw_data() == right.raw_data(); } -// optimization -template inline std::enable_if_t= sizeof(T2), bool> operator ==(const se_t& left, T2 right) +// Optimization +template +inline std::enable_if_t::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2), bool> operator ==(const se_t& left, T2 right) { return left.raw_data() == se_storage::to(right); } -// optimization -template inline std::enable_if_t operator ==(T1 left, const se_t& right) +// Optimization +template +inline std::enable_if_t::value && sizeof(T1) <= sizeof(T2), bool> operator ==(T1 left, const se_t& right) { return se_storage::to(left) == right.raw_data(); } -// optimization -template inline std::enable_if_t operator !=(const se_t& left, const se_t& right) +// Optimization +template +inline std::enable_if_t operator !=(const se_t& left, const se_t& right) { return left.raw_data() != right.raw_data(); } -// optimization -template inline std::enable_if_t= sizeof(T2), bool> operator !=(const se_t& left, T2 right) +// Optimization +template +inline std::enable_if_t::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2), bool> operator !=(const se_t& left, T2 right) { return left.raw_data() != se_storage::to(right); } -// optimization -template inline std::enable_if_t operator !=(T1 left, const se_t& right) +// Optimization +template +inline std::enable_if_t::value && sizeof(T1) <= sizeof(T2), bool> operator !=(T1 left, const se_t& right) { return se_storage::to(left) != right.raw_data(); } -// optimization -template inline std::enable_if_t= 4, se_t> operator &(const se_t& left, const se_t& right) +// Optimization +template +inline std::enable_if_t= 4, se_t> operator &(const se_t& left, const se_t& right) { return{ left.raw_data() & right.raw_data(), se_raw }; } -// optimization -template inline std::enable_if_t= sizeof(T2) && sizeof(T1) >= 4, se_t> operator &(const se_t& left, T2 right) +// Optimization +template +inline std::enable_if_t::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2) && sizeof(T1) >= 4, se_t> operator &(const se_t& left, T2 right) { return{ left.raw_data() & se_storage::to(right), se_raw }; } -// optimization -template inline std::enable_if_t= 4, se_t> operator &(T1 left, const se_t& right) +// Optimization +template +inline std::enable_if_t::value && sizeof(T1) <= sizeof(T2) && sizeof(T2) >= 4, se_t> operator &(T1 left, const se_t& right) { return{ se_storage::to(left) & right.raw_data(), se_raw }; } -// optimization -template inline std::enable_if_t= 4, se_t> operator |(const se_t& left, const se_t& right) +// Optimization +template +inline std::enable_if_t= 4, se_t> operator |(const se_t& left, const se_t& right) { return{ left.raw_data() | right.raw_data(), se_raw }; } -// optimization -template inline std::enable_if_t= sizeof(T2) && sizeof(T1) >= 4, se_t> operator |(const se_t& left, T2 right) +// Optimization +template +inline std::enable_if_t::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2) && sizeof(T1) >= 4, se_t> operator |(const se_t& left, T2 right) { return{ left.raw_data() | se_storage::to(right), se_raw }; } -// optimization -template inline std::enable_if_t= 4, se_t> operator |(T1 left, const se_t& right) +// Optimization +template +inline std::enable_if_t::value && sizeof(T1) <= sizeof(T2) && sizeof(T2) >= 4, se_t> operator |(T1 left, const se_t& right) { return{ se_storage::to(left) | right.raw_data(), se_raw }; } -// optimization -template inline std::enable_if_t= 4, se_t> operator ^(const se_t& left, const se_t& right) +// Optimization +template +inline std::enable_if_t= 4, se_t> operator ^(const se_t& left, const se_t& right) { return{ left.raw_data() ^ right.raw_data(), se_raw }; } -// optimization -template inline std::enable_if_t= sizeof(T2) && sizeof(T1) >= 4, se_t> operator ^(const se_t& left, T2 right) +// Optimization +template +inline std::enable_if_t::value && IS_INTEGER(T2) && sizeof(T1) >= sizeof(T2) && sizeof(T1) >= 4, se_t> operator ^(const se_t& left, T2 right) { return{ left.raw_data() ^ se_storage::to(right), se_raw }; } -// optimization -template inline std::enable_if_t= 4, se_t> operator ^(T1 left, const se_t& right) +// Optimization +template +inline std::enable_if_t::value && sizeof(T1) <= sizeof(T2) && sizeof(T2) >= 4, se_t> operator ^(T1 left, const se_t& right) { return{ se_storage::to(left) ^ right.raw_data(), se_raw }; } -// optimization -template inline std::enable_if_t= 4, se_t> operator ~(const se_t& right) +// Optimization +template +inline std::enable_if_t::value && sizeof(T) >= 4, se_t> operator ~(const se_t& right) { return{ ~right.raw_data(), se_raw }; } -#ifdef IS_LE_MACHINE +#if IS_LE_MACHINE == 1 template using be_t = se_t; template using le_t = se_t; -#else -template using be_t = se_t; -template using le_t = se_t; #endif - -template struct to_se +// Type converter: converts native endianness arithmetic/enum types to appropriate se_t<> type +template +struct to_se { + // Convert arithmetic and enum types using type = typename std::conditional::value || std::is_enum::value, se_t, T>::type; }; -template struct to_se::value>> // move const qualifier -{ - using type = const typename to_se::type; -}; - -template struct to_se::value && !std::is_const::value>> // move volatile qualifier -{ - using type = volatile typename to_se::type; -}; - -template struct to_se -{ - using type = typename to_se::type[]; -}; - -template struct to_se -{ - using type = typename to_se::type[N]; -}; - -template struct to_se { using type = se_t; }; template struct to_se { using type = se_t; }; template struct to_se { using type = bool; }; template struct to_se { using type = char; }; template struct to_se { using type = u8; }; template struct to_se { using type = s8; }; -#ifdef IS_LE_MACHINE +template +struct to_se::value>> +{ + // Move const qualifier + using type = const typename to_se::type; +}; + +template +struct to_se::value && !std::is_const::value>> +{ + // Move volatile qualifier + using type = volatile typename to_se::type; +}; + +template +struct to_se +{ + // Move array qualifier + using type = typename to_se::type[]; +}; + +template +struct to_se +{ + // Move array qualifier + using type = typename to_se::type[N]; +}; + +// BE/LE aliases for to_se<> +#if IS_LE_MACHINE == 1 template using to_be_t = typename to_se::type; template using to_le_t = typename to_se::type; -#else -template using to_be_t = typename to_se::type; -template using to_le_t = typename to_se::type; #endif +// BE/LE aliases for atomic_t +#if IS_LE_MACHINE == 1 +template using atomic_be_t = atomic_t>; +template using atomic_le_t = atomic_t>; +#endif -template struct to_ne +namespace fmt { - using type = T; -}; + // Formatting for BE/LE data + template + struct unveil, void> + { + using result_type = typename unveil::result_type; -template struct to_ne> -{ - using type = typename std::remove_cv::type; -}; + static inline result_type get_value(const se_t& arg) + { + return unveil::get_value(arg); + } + }; +} -template struct to_ne::value>> // move const qualifier -{ - using type = const typename to_ne::type; -}; - -template struct to_ne::value && !std::is_const::value>> // move volatile qualifier -{ - using type = volatile typename to_ne::type; -}; - -template struct to_ne -{ - using type = typename to_ne::type[]; -}; - -template struct to_ne -{ - using type = typename to_ne::type[N]; -}; - -// restore native endianness for T: returns T for be_t or le_t, T otherwise -template using to_ne_t = typename to_ne::type; +#undef IS_BINARY_COMPARABLE +#undef IS_INTEGER diff --git a/Utilities/BitField.h b/Utilities/BitField.h index 7c7ad0704f..ef03e0ab8a 100644 --- a/Utilities/BitField.h +++ b/Utilities/BitField.h @@ -1,73 +1,106 @@ #pragma once -// BitField access helper class (N bits from I position), intended to be put in union -template class bf_t +#include "types.h" + +template +struct bf_base { - // Checks - static_assert(I < sizeof(T) * 8, "bf_t<> error: I out of bounds"); - static_assert(N < sizeof(T) * 8, "bf_t<> error: N out of bounds"); - static_assert(I + N <= sizeof(T) * 8, "bf_t<> error: values out of bounds"); + using type = T; + using vtype = simple_t; - // Underlying data type - using type = typename std::remove_cv::type; + // Datatype bitsize + static constexpr uint bitmax = sizeof(T) * CHAR_BIT; static_assert(N - 1 < bitmax, "bf_base<> error: N out of bounds"); + + // Field bitsize + static constexpr uint bitsize = N; - // Underlying value type (native endianness) - using vtype = typename to_ne::type; + // Value mask + static constexpr vtype vmask = static_cast(~std::make_unsigned_t{} >> (bitmax - bitsize)); - // Mask of size N - constexpr static vtype s_mask = (static_cast(1) << N) - 1; - - // Underlying data member +protected: type m_data; +}; - // Conversion operator helper (uses SFINAE) - template struct converter {}; +// Bitfield accessor (N bits from I position, 0 is LSB) +template +struct bf_t : bf_base +{ + using type = typename bf_t::type; + using vtype = typename bf_t::vtype; - template struct converter::value>> + // Field offset + static constexpr uint bitpos = I; static_assert(bitpos + N <= bf_t::bitmax, "bf_t<> error: I out of bounds"); + + // Get bitmask of size N, at I pos + static constexpr vtype data_mask() + { + return bf_t::vmask << bitpos; + } + + // Bitfield extraction helper + template + struct extract_impl + { + static_assert(!sizeof(T2), "bf_t<> error: Invalid type"); + }; + + template + struct extract_impl::value>> { // Load unsigned value - static inline T2 convert(const type& data) + static constexpr T2 extract(const T& data) { - return (data >> I) & s_mask; + return (data >> bitpos) & bf_t::vmask; } }; - template struct converter::value>> + template + struct extract_impl::value>> { // Load signed value (sign-extended) - static inline T2 convert(const type& data) + static constexpr T2 extract(const T& data) { - return data << (sizeof(T) * 8 - I - N) >> (sizeof(T) * 8 - N); + return data << (bf_t::bitmax - bitpos - N) >> (bf_t::bitmax - N); } }; -public: - // Assignment operator (store bitfield value) - bf_t& operator =(vtype value) + // Bitfield extraction + static constexpr vtype extract(const T& data) { - m_data = (m_data & ~(s_mask << I)) | (value & s_mask) << I; - return *this; + return extract_impl::extract(data); } - // Conversion operator (load bitfield value) - operator vtype() const + // Bitfield insertion + static constexpr vtype insert(vtype value) { - return converter::convert(m_data); + return (value & bf_t::vmask) << bitpos; } - // Get raw data with mask applied - type unshifted() const + // Load bitfield value + constexpr operator vtype() const { - return (m_data & (s_mask << I)); + return extract(this->m_data); } - // Optimized bool conversion - explicit operator bool() const + // Load raw data with mask applied + constexpr T unshifted() const + { + return this->m_data & data_mask(); + } + + // Optimized bool conversion (must be removed if inappropriate) + explicit constexpr operator bool() const { return unshifted() != 0; } - // Postfix increment operator + // Store bitfield value + bf_t& operator =(vtype value) + { + this->m_data = (this->m_data & ~data_mask()) | insert(value); + return *this; + } + vtype operator ++(int) { vtype result = *this; @@ -75,13 +108,11 @@ public: return result; } - // Prefix increment operator bf_t& operator ++() { return *this = *this + 1; } - // Postfix decrement operator vtype operator --(int) { vtype result = *this; @@ -89,52 +120,125 @@ public: return result; } - // Prefix decrement operator bf_t& operator --() { return *this = *this - 1; } - // Addition assignment operator bf_t& operator +=(vtype right) { return *this = *this + right; } - // Subtraction assignment operator bf_t& operator -=(vtype right) { return *this = *this - right; } - // Multiplication assignment operator bf_t& operator *=(vtype right) { return *this = *this * right; } - // Bitwise AND assignment operator bf_t& operator &=(vtype right) { - m_data &= (right & s_mask) << I; + this->m_data &= (right & bf_t::vmask) << bitpos; return *this; } - // Bitwise OR assignment operator bf_t& operator |=(vtype right) { - m_data |= (right & s_mask) << I; + this->m_data |= (right & bf_t::vmask) << bitpos; return *this; } - // Bitwise XOR assignment operator bf_t& operator ^=(vtype right) { - m_data ^= (right & s_mask) << I; + this->m_data ^= (right & bf_t::vmask) << bitpos; return *this; } }; -template using bf_be_t = bf_t, I, N>; +// Field pack (concatenated from left to right) +template +struct cf_t : bf_base::bitsize> +{ + using type = typename cf_t::type; + using vtype = typename cf_t::vtype; -template using bf_le_t = bf_t, I, N>; + // Get disjunction of all "data" masks of concatenated values + static constexpr vtype data_mask() + { + return F::data_mask() | cf_t::data_mask(); + } + + // Extract all bitfields and concatenate + static constexpr vtype extract(const type& data) + { + return F::extract(data) << cf_t::bitsize | cf_t::extract(data); + } + + // Split bitfields and insert them + static constexpr vtype insert(vtype value) + { + return F::insert(value >> cf_t::bitsize) | cf_t::insert(value); + } + + // Load value + constexpr operator vtype() const + { + return extract(this->m_data); + } + + // Store value + cf_t& operator =(vtype value) + { + this->m_data = (this->m_data & ~data_mask()) | insert(value); + return *this; + } +}; + +// Empty field pack (recursion terminator) +template<> +struct cf_t +{ + static constexpr uint bitsize = 0; + + static constexpr uint data_mask() + { + return 0; + } + + template + static constexpr auto extract(const T& data) -> decltype(+T()) + { + return 0; + } + + template + static constexpr T insert(T value) + { + return 0; + } +}; + +// Fixed field (provides constant values in field pack) +template +struct ff_t : bf_base +{ + using type = typename ff_t::type; + using vtype = typename ff_t::vtype; + + // Return constant value + static constexpr vtype extract(const type& data) + { + static_assert((V & ff_t::vmask) == V, "ff_t<> error: V out of bounds"); + return V; + } + + // Get value + operator vtype() const + { + return V; + } +}; diff --git a/Utilities/Config.cpp b/Utilities/Config.cpp new file mode 100644 index 0000000000..e7368683c3 --- /dev/null +++ b/Utilities/Config.cpp @@ -0,0 +1,203 @@ +#include "stdafx.h" +#include "Config.h" + +#include "yaml-cpp/yaml.h" + +namespace cfg +{ + _log::channel cfg("CFG", _log::level::notice); + + entry_base::entry_base(type _type) + : m_type(_type) + { + if (_type != type::node) + { + throw std::logic_error("Invalid root node"); + } + } + + entry_base::entry_base(type _type, node& owner, const std::string& name) + : m_type(_type) + { + if (!owner.m_nodes.emplace(name, this).second) + { + throw std::logic_error("Node already exists"); + } + } + + entry_base& entry_base::operator[](const std::string& name) const + { + if (m_type == type::node) + { + return *static_cast(*this).m_nodes.at(name); + } + + throw std::logic_error("Invalid node type"); + } + + entry_base& entry_base::operator[](const char* name) const + { + if (m_type == type::node) + { + return *static_cast(*this).m_nodes.at(name); + } + + throw std::logic_error("Invalid node type"); + } + + // Emit YAML + static void encode(YAML::Emitter& out, const class entry_base& rhs); + + // Incrementally load config entries from YAML::Node. + // The config value is preserved if the corresponding YAML node doesn't exist. + static void decode(const YAML::Node& data, class entry_base& rhs); +} + +bool cfg::try_to_int64(s64* out, const std::string& value, s64 min, s64 max) +{ + // TODO: this could be rewritten without exceptions (but it should be as safe as possible and provide logs) + s64 result; + std::size_t pos; + + try + { + result = std::stoll(value, &pos, 0 /* Auto-detect numeric base */); + } + catch (const std::exception& e) + { + if (out) cfg.error("cfg::try_to_int('%s'): exception: %s", value, e.what()); + return false; + } + + if (pos != value.size()) + { + if (out) cfg.error("cfg::try_to_int('%s'): unexpected characters (pos=%zu)", value, pos); + return false; + } + + if (result < min || result > max) + { + if (out) cfg.error("cfg::try_to_int('%s'): out of bounds (%lld..%lld)", value, min, max); + return false; + } + + if (out) *out = result; + return true; +} + +void cfg::encode(YAML::Emitter& out, const cfg::entry_base& rhs) +{ + switch (rhs.get_type()) + { + case type::node: + { + out << YAML::BeginMap; + for (const auto& np : static_cast(rhs).get_nodes()) + { + out << YAML::Key << np.first; + out << YAML::Value; encode(out, *np.second); + } + + out << YAML::EndMap; + return; + } + case type::set: + { + out << YAML::BeginSeq; + for (const auto& str : static_cast(rhs).get_set()) + { + out << str; + } + + out << YAML::EndSeq; + return; + } + } + + out << rhs.to_string(); +} + +void cfg::decode(const YAML::Node& data, cfg::entry_base& rhs) +{ + switch (rhs.get_type()) + { + case type::node: + { + if (data.IsScalar() || data.IsSequence()) + { + return; // ??? + } + + for (const auto& pair : data) + { + if (!pair.first.IsScalar()) continue; + + // Find the key among existing nodes + const auto name = pair.first.Scalar(); + const auto found = static_cast(rhs).get_nodes().find(name); + + if (found != static_cast(rhs).get_nodes().cend()) + { + decode(pair.second, *found->second); + } + else + { + // ??? + } + } + + break; + } + case type::set: + { + std::vector values; + + if (YAML::convert::decode(data, values)) + { + rhs.from_list(std::move(values)); + } + + break; + } + default: + { + std::string value; + + if (YAML::convert::decode(data, value)) + { + rhs.from_string(value); + } + + break; // ??? + } + } +} + +std::string cfg::node::to_string() const +{ + YAML::Emitter out; + cfg::encode(out, *this); + + return{ out.c_str(), out.size() }; +} + +bool cfg::node::from_string(const std::string& value) +{ + cfg::decode(YAML::Load(value), *this); + return true; +} + +void cfg::node::from_default() +{ + for (auto& node : m_nodes) + { + node.second->from_default(); + } +} + +cfg::root_node& cfg::get_root() +{ + // Magic static + static root_node root; + return root; +} diff --git a/Utilities/Config.h b/Utilities/Config.h new file mode 100644 index 0000000000..203e959a30 --- /dev/null +++ b/Utilities/Config.h @@ -0,0 +1,523 @@ +#pragma once + +#include "Utilities/Atomic.h" +#include +#include + +namespace cfg +{ + // Convert string to signed integer + bool try_to_int64(s64* out, const std::string& value, s64 min, s64 max); + + // Config tree entry type. + enum class type : uint + { + node = 0, // cfg::node type + boolean, // cfg::bool_entry type + fixed_map, // cfg::map_entry type + enumeration, // cfg::enum_entry type + integer, // cfg::int_entry type + string, // cfg::string_entry type + set, // cfg::set_entry type + }; + + // Config tree entry abstract base class + class entry_base + { + const type m_type; + + protected: + // Ownerless entry constructor + entry_base(type _type); + + // Owned entry constructor + entry_base(type _type, class node& owner, const std::string& name); + + public: + // Disallow copy/move constructors and assignments + entry_base(const entry_base&) = delete; + + // Get type + type get_type() const { return m_type; } + + // Access child node (must exist) + entry_base& operator [](const std::string& name) const; entry_base& operator [](const char* name) const; + + // Reset defaults + virtual void from_default() = 0; + + // Convert to string (optional) + virtual std::string to_string() const + { + return{}; + } + + // Try to convert from string (optional) + virtual bool from_string(const std::string&) + { + throw std::logic_error("from_string() not specified"); + } + + // Get string list (optional) + virtual std::vector to_list() const + { + return{}; + } + + // Set multiple values. Implementation-specific, optional. + virtual bool from_list(std::vector&&) + { + throw std::logic_error("from_list() not specified"); + } + }; + + // Config tree node which contains another nodes + class node : public entry_base + { + std::map m_nodes; + + friend class entry_base; + + public: + // Root node constructor + node() + : entry_base(type::node) + { + } + + // Registered node constructor + node(node& owner, const std::string& name) + : entry_base(type::node, owner, name) + { + } + + // Get child nodes + const std::map& get_nodes() const + { + return m_nodes; + } + + // Serialize node + std::string to_string() const override; + + // Deserialize node + bool from_string(const std::string& value) override; + + // Set default values + void from_default() override; + }; + + struct bool_entry final : public entry_base + { + atomic_t value; + + const bool def; + + bool_entry(node& owner, const std::string& name, bool def = false) + : entry_base(type::boolean, owner, name) + , value(def) + , def(def) + { + } + + explicit operator bool() const + { + return value.load(); + } + + bool_entry& operator =(bool value) + { + value = value; + return *this; + } + + void from_default() override + { + value = def; + } + + std::string to_string() const override + { + return value.load() ? "true" : "false"; + } + + bool from_string(const std::string& value) override + { + if (value == "false") + this->value = false; + else if (value == "true") + this->value = true; + else + return false; + + return true; + } + }; + + // Value node with fixed set of possible values, each maps to a value of type T. + template + struct map_entry final : public entry_base + { + using init_type = std::initializer_list>; + using map_type = std::unordered_map; + using list_type = std::vector; + using value_type = typename map_type::value_type; + + static map_type make_map(init_type init) + { + map_type map(init.size()); + + for (const auto& v : init) + { + // Ensure elements are unique + ASSERT(map.emplace(v.first, v.second).second); + } + + return map; + } + + static list_type make_list(init_type init) + { + list_type list; list.reserve(init.size()); + + for (const auto& v : init) + { + list.emplace_back(v.first); + } + + return list; + } + + public: + const map_type map; + const list_type list; // Element list sorted in original order + const value_type& def; // Pointer to the default value + + private: + atomic_t m_value; + + public: + map_entry(node& owner, const std::string& name, const std::string& def, init_type init) + : entry_base(type::fixed_map, owner, name) + , map(make_map(init)) + , list(make_list(init)) + , def(*map.find(def)) + , m_value(&this->def) + { + } + + map_entry(node& owner, const std::string& name, std::size_t def_index, init_type init) + : map_entry(owner, name, def_index < init.size() ? (init.begin() + def_index)->first : throw std::logic_error("Invalid default value index"), init) + { + } + + map_entry(node& owner, const std::string& name, init_type init) + : map_entry(owner, name, 0, init) + { + } + + const T& get() const + { + return m_value.load()->second; + } + + void from_default() override + { + m_value = &def; + } + + std::string to_string() const override + { + return m_value.load()->first; + } + + bool from_string(const std::string& value) override + { + const auto found = map.find(value); + + if (found == map.end()) + { + return false; + } + else + { + m_value = &*found; + return true; + } + } + + std::vector to_list() const override + { + return list; + } + }; + + // Value node with fixed set of possible values, each maps to an enum value of type T. + template + class enum_entry final : public entry_base + { + // Value or reference + std::conditional_t&, atomic_t> m_value; + + public: + const T def; + + enum_entry(node& owner, const std::string& name, std::conditional_t&, T> value) + : entry_base(type::enumeration, owner, name) + , m_value(value) + , def(value) + { + } + + operator T() const + { + return m_value.load(); + } + + enum_entry& operator =(T value) + { + m_value = value; + return *this; + } + + void from_default() override + { + m_value = def; + } + + std::string to_string() const override + { + for (const auto& pair : bijective::map) + { + if (pair.first == m_value) + { + return pair.second; + } + } + + return{}; // TODO: ??? + } + + bool from_string(const std::string& value) override + { + for (const auto& pair : bijective::map) + { + if (pair.second == value) + { + m_value = pair.first; + return true; + } + } + + return false; + } + + std::vector to_list() const override + { + std::vector result; + + for (const auto& pair : bijective::map) + { + result.emplace_back(pair.second); + } + + return result; + } + }; + + // Signed 32/64-bit integer entry with custom Min/Max range. + template + class int_entry final : public entry_base + { + static_assert(Min < Max, "Invalid cfg::int_entry range"); + + // Prefer 32 bit type if possible + using int_type = std::conditional_t= INT32_MIN && Max <= INT32_MAX, s32, s64>; + + atomic_t m_value; + + public: + const int_type def; + + int_entry(node& owner, const std::string& name, int_type def = std::min(Max, std::max(Min, 0))) + : entry_base(type::integer, owner, name) + , m_value(def) + , def(def) + { + } + + operator int_type() const + { + return m_value.load(); + } + + int_entry& operator =(int_type value) + { + if (value < Min || value > Max) + { + throw fmt::exception("Value out of the valid range: %lld" HERE, s64{ value }); + } + + m_value = value; + return *this; + } + + void from_default() override + { + m_value = def; + } + + std::string to_string() const override + { + return std::to_string(m_value.load()); + } + + bool from_string(const std::string& value) override + { + s64 result; + if (try_to_int64(&result, value, Min, Max)) + { + m_value = static_cast(result); + return true; + } + + return false; + } + }; + + // Alias for 32 bit int + using int32_entry = int_entry; + + // Alias for 64 bit int + using int64_entry = int_entry; + + // Simple string entry with mutex + class string_entry final : public entry_base + { + mutable std::mutex m_mutex; + std::string m_value; + + public: + const std::string def; + + string_entry(node& owner, const std::string& name, const std::string& def = {}) + : entry_base(type::string, owner, name) + , m_value(def) + , def(def) + { + } + + operator std::string() const + { + std::lock_guard lock(m_mutex); + return m_value; + } + + std::string get() const + { + return *this; + } + + string_entry& operator =(const std::string& value) + { + std::lock_guard lock(m_mutex); + m_value = value; + return *this; + } + + std::size_t size() const + { + std::lock_guard lock(m_mutex); + return m_value.size(); + } + + void from_default() override + { + *this = def; + } + + std::string to_string() const override + { + std::lock_guard lock(m_mutex); + return m_value; + } + + bool from_string(const std::string& value) override + { + *this = value; + return true; + } + }; + + // Simple set entry with mutex (TODO: template for various types) + class set_entry final : public entry_base + { + mutable std::mutex m_mutex; + + std::set m_set; + + public: + // Default value is empty list in current implementation + set_entry(node& owner, const std::string& name) + : entry_base(type::set, owner, name) + { + } + + std::set get_set() const + { + std::lock_guard lock(m_mutex); + + return m_set; + } + + void set_set(std::set&& set) + { + std::lock_guard lock(m_mutex); + m_set = std::move(set); + } + + void from_default() override + { + std::lock_guard lock(m_mutex); + m_set = {}; + } + + std::vector to_list() const override + { + std::lock_guard lock(m_mutex); + + return{ m_set.begin(), m_set.end() }; + } + + bool from_list(std::vector&& list) override + { + std::lock_guard lock(m_mutex); + m_set = { std::make_move_iterator(list.begin()), std::make_move_iterator(list.end()) }; + + return true; + } + }; + + // Root type with some predefined nodes. Don't change it, this is not mandatory for adding nodes. + struct root_node : node + { + node core { *this, "Core" }; + node vfs { *this, "VFS" }; + node log { *this, "Log" }; + node video { *this, "Video" }; + node audio { *this, "Audio" }; + node io { *this, "Input/Output" }; + node sys { *this, "System" }; + node net { *this, "Net" }; + node misc { *this, "Miscellaneous" }; + }; + + // Get global configuration root instance + extern root_node& get_root(); + + // Global configuration root instance (cached reference) + static root_node& root = get_root(); +} + +// Registered log channel +#define LOG_CHANNEL(name) _log::channel name(#name, _log::level::notice); namespace _log { cfg::enum_entry<_log::level, true> name(cfg::root.log, #name, ::name.enabled); } diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 9d3d4571e3..7581ccbde4 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1,5 +1,8 @@ -#include "stdafx.h" #include "File.h" +#include "StrFmt.h" +#include "Macro.h" +#include "SharedMutex.h" +#include #ifdef _WIN32 @@ -12,13 +15,13 @@ static std::unique_ptr to_wchar(const std::string& source) { const auto buf_size = source.size() + 1; // size + null terminator - const int size = source.size() < INT_MAX ? static_cast(buf_size) : throw EXCEPTION("Invalid source length (0x%llx)", source.size()); + const int size = source.size() < INT_MAX ? static_cast(buf_size) : throw fmt::exception("to_wchar(): invalid source length (0x%llx)", source.size()); std::unique_ptr buffer(new wchar_t[buf_size]); // allocate buffer assuming that length is the max possible size if (!MultiByteToWideChar(CP_UTF8, 0, source.c_str(), size, buffer.get(), size)) { - throw EXCEPTION("MultiByteToWideChar() failed (0x%x).", GetLastError()); + throw fmt::exception("to_wchar(): MultiByteToWideChar() failed: error %u.", GetLastError()); } return buffer; @@ -28,7 +31,7 @@ static void to_utf8(std::string& result, const wchar_t* source) { const auto length = std::wcslen(source); - const int buf_size = length <= INT_MAX / 3 ? static_cast(length) * 3 + 1 : throw EXCEPTION("Invalid source length (0x%llx)", length); + const int buf_size = length <= INT_MAX / 3 ? static_cast(length) * 3 + 1 : throw fmt::exception("to_utf8(): invalid source length (0x%llx)", length); result.resize(buf_size); // set max possible length for utf-8 + null terminator @@ -38,7 +41,7 @@ static void to_utf8(std::string& result, const wchar_t* source) } else { - throw EXCEPTION("WideCharToMultiByte() failed (0x%x).", GetLastError()); + throw fmt::exception("to_utf8(): WideCharToMultiByte() failed: error %u.", GetLastError()); } } @@ -85,6 +88,68 @@ static time_t to_time(const FILETIME& ft) #endif +namespace fs +{ + class device_manager final + { + mutable shared_mutex m_mutex; + + std::unordered_map> m_map; + + public: + std::shared_ptr get_device(const std::string& name); + std::shared_ptr set_device(const std::string& name, const std::shared_ptr&); + }; + + static device_manager& get_device_manager() + { + // Use magic static + static device_manager instance; + return instance; + } +} + +safe_buffers std::shared_ptr fs::device_manager::get_device(const std::string& name) +{ + reader_lock lock(m_mutex); + + const auto found = m_map.find(name); + + if (found == m_map.end()) + { + return nullptr; + } + + return found->second; +} + +safe_buffers std::shared_ptr fs::device_manager::set_device(const std::string& name, const std::shared_ptr& device) +{ + std::lock_guard lock(m_mutex); + + return m_map[name] = device; +} + +safe_buffers std::shared_ptr fs::get_virtual_device(const std::string& path) +{ + // Every virtual device path must have "//" at the beginning + if (path.size() > 2 && reinterpret_cast(path.front()) == '//') + { + return get_device_manager().get_device(path.substr(0, path.find_first_of('/', 2))); + } + + return nullptr; +} + +safe_buffers std::shared_ptr fs::set_virtual_device(const std::string& name, const std::shared_ptr& device) +{ + Expects(name.size() > 2); + Expects(name[0] == '/'); + Expects(name[1] == '/'); + + return get_device_manager().set_device(name, device); +} + std::string fs::get_parent_dir(const std::string& path) { // Search upper bound (set to the last character, npos for empty string) @@ -112,32 +177,37 @@ std::string fs::get_parent_dir(const std::string& path) static const auto test_get_parent_dir = []() -> bool { // Success: - CHECK_ASSERTION(fs::get_parent_dir("/x/y///") == "/x"); - CHECK_ASSERTION(fs::get_parent_dir("/x/y/") == "/x"); - CHECK_ASSERTION(fs::get_parent_dir("/x/y") == "/x"); - CHECK_ASSERTION(fs::get_parent_dir("x:/y") == "x:"); - CHECK_ASSERTION(fs::get_parent_dir("//x/y") == "//x"); + ASSERT(fs::get_parent_dir("/x/y///") == "/x"); + ASSERT(fs::get_parent_dir("/x/y/") == "/x"); + ASSERT(fs::get_parent_dir("/x/y") == "/x"); + ASSERT(fs::get_parent_dir("x:/y") == "x:"); + ASSERT(fs::get_parent_dir("//x/y") == "//x"); // Failure: - CHECK_ASSERTION(fs::get_parent_dir("").empty()); - CHECK_ASSERTION(fs::get_parent_dir("x/").empty()); - CHECK_ASSERTION(fs::get_parent_dir("x").empty()); - CHECK_ASSERTION(fs::get_parent_dir("x///").empty()); - CHECK_ASSERTION(fs::get_parent_dir("/x/").empty()); - CHECK_ASSERTION(fs::get_parent_dir("/x").empty()); - CHECK_ASSERTION(fs::get_parent_dir("/").empty()); - CHECK_ASSERTION(fs::get_parent_dir("//").empty()); - CHECK_ASSERTION(fs::get_parent_dir("//x").empty()); - CHECK_ASSERTION(fs::get_parent_dir("//x/").empty()); - CHECK_ASSERTION(fs::get_parent_dir("///").empty()); - CHECK_ASSERTION(fs::get_parent_dir("///x").empty()); - CHECK_ASSERTION(fs::get_parent_dir("///x/").empty()); + ASSERT(fs::get_parent_dir("").empty()); + ASSERT(fs::get_parent_dir("x/").empty()); + ASSERT(fs::get_parent_dir("x").empty()); + ASSERT(fs::get_parent_dir("x///").empty()); + ASSERT(fs::get_parent_dir("/x/").empty()); + ASSERT(fs::get_parent_dir("/x").empty()); + ASSERT(fs::get_parent_dir("/").empty()); + ASSERT(fs::get_parent_dir("//").empty()); + ASSERT(fs::get_parent_dir("//x").empty()); + ASSERT(fs::get_parent_dir("//x/").empty()); + ASSERT(fs::get_parent_dir("///").empty()); + ASSERT(fs::get_parent_dir("///x").empty()); + ASSERT(fs::get_parent_dir("///x/").empty()); return false; }(); bool fs::stat(const std::string& path, stat_t& info) { + if (auto device = get_virtual_device(path)) + { + return device->stat(path, info); + } + #ifdef _WIN32 WIN32_FILE_ATTRIBUTE_DATA attrs; if (!GetFileAttributesExW(to_wchar(path).get(), GetFileExInfoStandard, &attrs)) @@ -147,7 +217,7 @@ bool fs::stat(const std::string& path, stat_t& info) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -179,6 +249,12 @@ bool fs::stat(const std::string& path, stat_t& info) bool fs::exists(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + stat_t info; + return device->stat(path, info); + } + #ifdef _WIN32 if (GetFileAttributesW(to_wchar(path).get()) == INVALID_FILE_ATTRIBUTES) { @@ -187,7 +263,7 @@ bool fs::exists(const std::string& path) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -202,6 +278,23 @@ bool fs::exists(const std::string& path) bool fs::is_file(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + stat_t info; + if (!device->stat(path, info)) + { + return false; + } + + if (info.is_directory) + { + errno = EEXIST; + return false; + } + + return true; + } + #ifdef _WIN32 const DWORD attrs = GetFileAttributesW(to_wchar(path).get()); if (attrs == INVALID_FILE_ATTRIBUTES) @@ -211,7 +304,7 @@ bool fs::is_file(const std::string& path) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -240,6 +333,23 @@ bool fs::is_file(const std::string& path) bool fs::is_dir(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + stat_t info; + if (!device->stat(path, info)) + { + return false; + } + + if (info.is_directory == false) + { + errno = EEXIST; + return false; + } + + return true; + } + #ifdef _WIN32 const DWORD attrs = GetFileAttributesW(to_wchar(path).get()); if (attrs == INVALID_FILE_ATTRIBUTES) @@ -249,7 +359,7 @@ bool fs::is_dir(const std::string& path) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -277,6 +387,11 @@ bool fs::is_dir(const std::string& path) bool fs::create_dir(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + return device->create_dir(path); + } + #ifdef _WIN32 if (!CreateDirectoryW(to_wchar(path).get(), NULL)) { @@ -285,7 +400,7 @@ bool fs::create_dir(const std::string& path) { case ERROR_ALREADY_EXISTS: errno = EEXIST; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -311,6 +426,11 @@ bool fs::create_path(const std::string& path) bool fs::remove_dir(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + return device->remove_dir(path); + } + #ifdef _WIN32 if (!RemoveDirectoryW(to_wchar(path).get())) { @@ -318,7 +438,7 @@ bool fs::remove_dir(const std::string& path) switch (DWORD error = GetLastError()) { case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -332,6 +452,18 @@ bool fs::remove_dir(const std::string& path) bool fs::rename(const std::string& from, const std::string& to) { + const auto device = get_virtual_device(from); + + if (device != get_virtual_device(to)) + { + throw fmt::exception("fs::rename() between different devices not implemented.\nFrom: %s\nTo: %s", from, to); + } + + if (device) + { + return device->rename(from, to); + } + #ifdef _WIN32 if (!MoveFileW(to_wchar(from).get(), to_wchar(to).get())) { @@ -339,7 +471,7 @@ bool fs::rename(const std::string& from, const std::string& to) switch (DWORD error = GetLastError()) { case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.\nFrom: %s\nTo: %s", error, from, to); + default: throw fmt::exception("Unknown Win32 error: %u.\nFrom: %s\nTo: %s" HERE, error, from, to); } return false; @@ -353,6 +485,13 @@ bool fs::rename(const std::string& from, const std::string& to) bool fs::copy_file(const std::string& from, const std::string& to, bool overwrite) { + const auto device = get_virtual_device(from); + + if (device != get_virtual_device(to) || device) // TODO + { + throw fmt::exception("fs::copy_file() for virtual devices not implemented.\nFrom: %s\nTo: %s", from, to); + } + #ifdef _WIN32 if (!CopyFileW(to_wchar(from).get(), to_wchar(to).get(), !overwrite)) { @@ -360,7 +499,7 @@ bool fs::copy_file(const std::string& from, const std::string& to, bool overwrit switch (DWORD error = GetLastError()) { case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.\nFrom: %s\nTo: %s", error, from, to); + default: throw fmt::exception("Unknown Win32 error: %u.\nFrom: %s\nTo: %s" HERE, error, from, to); } return false; @@ -413,6 +552,11 @@ bool fs::copy_file(const std::string& from, const std::string& to, bool overwrit bool fs::remove_file(const std::string& path) { + if (auto device = get_virtual_device(path)) + { + return device->remove(path); + } + #ifdef _WIN32 if (!DeleteFileW(to_wchar(path).get())) { @@ -421,7 +565,7 @@ bool fs::remove_file(const std::string& path) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -435,6 +579,11 @@ bool fs::remove_file(const std::string& path) bool fs::truncate_file(const std::string& path, u64 length) { + if (auto device = get_virtual_device(path)) + { + return device->trunc(path, length); + } + #ifdef _WIN32 // Open the file const auto handle = CreateFileW(to_wchar(path).get(), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); @@ -445,7 +594,7 @@ bool fs::truncate_file(const std::string& path, u64 length) { case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; @@ -461,7 +610,7 @@ bool fs::truncate_file(const std::string& path, u64 length) switch (DWORD error = GetLastError()) { case ERROR_NEGATIVE_SEEK: errno = EINVAL; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (length=0x%llx).", error, length); + default: throw fmt::exception("Unknown Win32 error: %u (length=0x%llx)." HERE, error, length); } CloseHandle(handle); @@ -475,51 +624,55 @@ bool fs::truncate_file(const std::string& path, u64 length) #endif } -fs::file::~file() +void fs::file::xnull() const { - if (m_fd != null) - { -#ifdef _WIN32 - CloseHandle((HANDLE)m_fd); -#else - ::close(m_fd); -#endif - } + throw std::logic_error("fs::file is null"); } -bool fs::file::open(const std::string& path, u32 mode) +void fs::file::xfail() const { - this->close(); + throw fmt::exception("Unexpected fs::file error %d", errno); +} -#ifdef _WIN32 - DWORD access = 0; - switch (mode & (fom::read | fom::write | fom::append)) +bool fs::file::open(const std::string& path, mset mode) +{ + if (auto device = get_virtual_device(path)) { - case fom::read: access |= GENERIC_READ; break; - case fom::read | fom::append: access |= GENERIC_READ; break; - case fom::write: access |= GENERIC_WRITE; break; - case fom::write | fom::append: access |= FILE_APPEND_DATA; break; - case fom::read | fom::write: access |= GENERIC_READ | GENERIC_WRITE; break; - case fom::read | fom::write | fom::append: access |= GENERIC_READ | FILE_APPEND_DATA; break; - } + if (auto&& _file = device->open(path, mode)) + { + m_file = std::move(_file); + return true; + } - DWORD disp = 0; - switch (mode & (fom::create | fom::trunc | fom::excl)) - { - case 0: disp = OPEN_EXISTING; break; - case fom::create: disp = OPEN_ALWAYS; break; - case fom::trunc: disp = TRUNCATE_EXISTING; break; - case fom::create | fom::trunc: disp = CREATE_ALWAYS; break; - case fom::create | fom::excl: disp = CREATE_NEW; break; - case fom::create | fom::excl | fom::trunc: disp = CREATE_NEW; break; - default: - errno = EINVAL; return false; } - m_fd = (std::intptr_t)CreateFileW(to_wchar(path).get(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, disp, FILE_ATTRIBUTE_NORMAL, NULL); +#ifdef _WIN32 + DWORD access = 0; + if (mode & fs::read) access |= GENERIC_READ; + if (mode & fs::write) access |= mode & fs::append ? FILE_APPEND_DATA : GENERIC_WRITE; - if (m_fd == null) + DWORD disp = 0; + if (mode & fs::create) + { + disp = + mode & fs::excl ? CREATE_NEW : + mode & fs::trunc ? CREATE_ALWAYS : OPEN_ALWAYS; + } + else + { + if (mode & fs::excl) + { + errno = EINVAL; + return false; + } + + disp = mode & fs::trunc ? TRUNCATE_EXISTING : OPEN_EXISTING; + } + + const HANDLE handle = CreateFileW(to_wchar(path).get(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, disp, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle == INVALID_HANDLE_VALUE) { // TODO: convert Win32 error code to errno switch (DWORD error = GetLastError()) @@ -527,457 +680,639 @@ bool fs::file::open(const std::string& path, u32 mode) case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; case ERROR_FILE_EXISTS: errno = EEXIST; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x (%s).", error, path); + default: throw fmt::exception("Unknown Win32 error: %u (%s)." HERE, error, path); } return false; } - return true; + class windows_file final : public file_base + { + const HANDLE m_handle; + + public: + windows_file(HANDLE handle) + : m_handle(handle) + { + } + + ~windows_file() override + { + CloseHandle(m_handle); + } + + stat_t stat() override + { + FILE_BASIC_INFO basic_info; + if (!GetFileInformationByHandleEx(m_handle, FileBasicInfo, &basic_info, sizeof(FILE_BASIC_INFO))) + { + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + stat_t info; + info.is_directory = (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + info.is_writable = (basic_info.FileAttributes & FILE_ATTRIBUTE_READONLY) == 0; + info.size = this->size(); + info.atime = to_time(basic_info.LastAccessTime); + info.mtime = to_time(basic_info.ChangeTime); + info.ctime = to_time(basic_info.CreationTime); + + return info; + } + + bool trunc(u64 length) override + { + LARGE_INTEGER old, pos; + + pos.QuadPart = 0; + if (!SetFilePointerEx(m_handle, pos, &old, FILE_CURRENT)) // get old position + { + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + + return false; + } + + pos.QuadPart = length; + if (!SetFilePointerEx(m_handle, pos, NULL, FILE_BEGIN)) // set new position + { + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case ERROR_NEGATIVE_SEEK: errno = EINVAL; break; + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + + return false; + } + + const BOOL result = SetEndOfFile(m_handle); // change file size + + if (!result) + { + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + } + + if (!SetFilePointerEx(m_handle, old, NULL, FILE_BEGIN) && result) // restore position + { + if (DWORD error = GetLastError()) + { + throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return result != FALSE; + } + + u64 read(void* buffer, u64 count) override + { + // TODO (call ReadFile multiple times if count is too big) + const int size = ::narrow(count, "Too big count" HERE); + Expects(size >= 0); + + DWORD nread; + if (!ReadFile(m_handle, buffer, size, &nread, NULL)) + { + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return nread; + } + + u64 write(const void* buffer, u64 count) override + { + // TODO (call WriteFile multiple times if count is too big) + const int size = ::narrow(count, "Too big count" HERE); + Expects(size >= 0); + + DWORD nwritten; + if (!WriteFile(m_handle, buffer, size, &nwritten, NULL)) + { + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return nwritten; + } + + u64 seek(s64 offset, seek_mode whence) override + { + LARGE_INTEGER pos; + pos.QuadPart = offset; + + const DWORD mode = + whence == seek_set ? FILE_BEGIN : + whence == seek_cur ? FILE_CURRENT : + whence == seek_end ? FILE_END : + throw fmt::exception("Invalid whence (0x%x)" HERE, whence); + + if (!SetFilePointerEx(m_handle, pos, &pos, mode)) + { + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return pos.QuadPart; + } + + u64 size() override + { + LARGE_INTEGER size; + if (!GetFileSizeEx(m_handle, &size)) + { + switch (DWORD error = GetLastError()) + { + case 0: + default: throw fmt::exception("Win32 error: %u." HERE, error); + } + } + + return size.QuadPart; + } + }; + + m_file = std::make_unique(handle); #else int flags = 0; - switch (mode & (fom::read | fom::write)) + if (mode & fs::read && mode & fs::write) flags |= O_RDWR; + else if (mode & fs::read) flags |= O_RDONLY; + else if (mode & fs::write) flags |= O_WRONLY; + + if (mode & fs::append) flags |= O_APPEND; + if (mode & fs::create) flags |= O_CREAT; + if (mode & fs::trunc) flags |= O_TRUNC; + if (mode & fs::excl) flags |= O_EXCL; + + const int fd = ::open(path.c_str(), flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (fd == -1) { - case fom::read: flags |= O_RDONLY; break; - case fom::write: flags |= O_WRONLY; break; - case fom::read | fom::write: flags |= O_RDWR; break; - } - - if (mode & fom::append) flags |= O_APPEND; - if (mode & fom::create) flags |= O_CREAT; - if (mode & fom::trunc) flags |= O_TRUNC; - if (mode & fom::excl) flags |= O_EXCL; - - m_fd = ::open(path.c_str(), flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - - return m_fd != null; -#endif -} - -bool fs::file::trunc(u64 size) const -{ -#ifdef _WIN32 - LARGE_INTEGER old, pos; - - pos.QuadPart = 0; - if (!SetFilePointerEx((HANDLE)m_fd, pos, &old, FILE_CURRENT)) // get old position - { - switch (DWORD error = GetLastError()) - { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); - } - + // TODO: errno return false; } - pos.QuadPart = size; - if (!SetFilePointerEx((HANDLE)m_fd, pos, NULL, FILE_BEGIN)) // set new position + class unix_file final : public file_base { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + const int m_fd; + + public: + unix_file(int fd) + : m_fd(fd) { - case ERROR_NEGATIVE_SEEK: errno = EINVAL; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); } - return false; - } - - const BOOL result = SetEndOfFile((HANDLE)m_fd); // change file size - - if (!result) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + ~unix_file() override { - case 0: - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); - } - } - - if (!SetFilePointerEx((HANDLE)m_fd, old, NULL, FILE_BEGIN) && result) // restore position - { - if (DWORD error = GetLastError()) - { - throw EXCEPTION("Unknown Win32 error: 0x%x.", error); - } - } - - return result != FALSE; -#else - return !::ftruncate(m_fd, size); -#endif -} - -bool fs::file::stat(stat_t& info) const -{ -#ifdef _WIN32 - FILE_BASIC_INFO basic_info; - if (!GetFileInformationByHandleEx((HANDLE)m_fd, FileBasicInfo, &basic_info, sizeof(FILE_BASIC_INFO))) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) - { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + ::close(m_fd); } - return false; - } - - info.is_directory = (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - info.is_writable = (basic_info.FileAttributes & FILE_ATTRIBUTE_READONLY) == 0; - info.size = this->size(); - info.atime = to_time(basic_info.LastAccessTime); - info.mtime = to_time(basic_info.ChangeTime); - info.ctime = to_time(basic_info.CreationTime); -#else - struct ::stat file_info; - if (::fstat(m_fd, &file_info) < 0) - { - return false; - } - - info.is_directory = S_ISDIR(file_info.st_mode); - info.is_writable = file_info.st_mode & 0200; // HACK: approximation - info.size = file_info.st_size; - info.atime = file_info.st_atime; - info.mtime = file_info.st_mtime; - info.ctime = file_info.st_ctime; -#endif - - return true; -} - -void fs::file::close() -{ - if (m_fd == null) - { - return /*true*/; - } - - const auto fd = m_fd; - m_fd = null; - -#ifdef _WIN32 - if (!CloseHandle((HANDLE)fd)) - { - throw EXCEPTION("CloseHandle() failed (fd=0x%llx, 0x%x)", fd, GetLastError()); - } -#else - if (::close(fd) != 0) - { - throw EXCEPTION("close() failed (fd=0x%llx, errno=%d)", fd, errno); - } -#endif -} - -u64 fs::file::read(void* buffer, u64 count) const -{ - // TODO (call ReadFile multiple times if count is too big) - const int size = count <= INT_MAX ? static_cast(count) : throw EXCEPTION("Invalid count (0x%llx)", count); - -#ifdef _WIN32 - DWORD nread; - if (!ReadFile((HANDLE)m_fd, buffer, size, &nread, NULL)) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + stat_t stat() override { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + struct ::stat file_info; + if (::fstat(m_fd, &file_info) != 0) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + stat_t info; + info.is_directory = S_ISDIR(file_info.st_mode); + info.is_writable = file_info.st_mode & 0200; // HACK: approximation + info.size = file_info.st_size; + info.atime = file_info.st_atime; + info.mtime = file_info.st_mtime; + info.ctime = file_info.st_ctime; + + return info; } - return -1; - } - - return nread; -#else - return ::read(m_fd, buffer, size); -#endif -} - -u64 fs::file::write(const void* buffer, u64 count) const -{ - // TODO (call WriteFile multiple times if count is too big) - const int size = count <= INT_MAX ? static_cast(count) : throw EXCEPTION("Invalid count (0x%llx)", count); - -#ifdef _WIN32 - DWORD nwritten; - if (!WriteFile((HANDLE)m_fd, buffer, size, &nwritten, NULL)) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) + bool trunc(u64 length) override { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); - } + if (::ftruncate(m_fd, length) != 0) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } - return -1; - } + return false; + } - return nwritten; -#else - return ::write(m_fd, buffer, size); -#endif -} - -u64 fs::file::seek(s64 offset, seek_mode whence) const -{ -#ifdef _WIN32 - LARGE_INTEGER pos; - pos.QuadPart = offset; - - DWORD mode; - switch (whence) - { - case seek_set: mode = FILE_BEGIN; break; - case seek_cur: mode = FILE_CURRENT; break; - case seek_end: mode = FILE_END; break; - default: - { - errno = EINVAL; - return -1; - } - } - - if (!SetFilePointerEx((HANDLE)m_fd, pos, &pos, mode)) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) - { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); - } - - return -1; - } - - return pos.QuadPart; -#else - int mode; - switch (whence) - { - case seek_set: mode = SEEK_SET; break; - case seek_cur: mode = SEEK_CUR; break; - case seek_end: mode = SEEK_END; break; - default: - { - errno = EINVAL; - return -1; - } - } - - return ::lseek(m_fd, offset, mode); -#endif -} - -u64 fs::file::size() const -{ -#ifdef _WIN32 - LARGE_INTEGER size; - if (!GetFileSizeEx((HANDLE)m_fd, &size)) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) - { - case ERROR_INVALID_HANDLE: errno = EBADF; break; - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); - } - - return -1; - } - - return size.QuadPart; -#else - struct ::stat file_info; - if (::fstat(m_fd, &file_info) != 0) - { - return -1; - } - - return file_info.st_size; -#endif -} - -fs::dir::~dir() -{ - if (m_path) - { -#ifdef _WIN32 - if (m_dd != -1) FindClose((HANDLE)m_dd); -#else - ::closedir((DIR*)m_dd); -#endif - } -} - -void fs::file_read_map::reset(const file& f) -{ - reset(); - - if (f) - { -#ifdef _WIN32 - const HANDLE handle = ::CreateFileMapping((HANDLE)f.m_fd, NULL, PAGE_READONLY, 0, 0, NULL); - m_ptr = (char*)::MapViewOfFile(handle, FILE_MAP_READ, 0, 0, 0); - m_size = f.size(); - ::CloseHandle(handle); -#else - m_ptr = (char*)::mmap(nullptr, m_size = f.size(), PROT_READ, MAP_SHARED, f.m_fd, 0); - if (m_ptr == (void*)-1) m_ptr = nullptr; -#endif - } -} - -void fs::file_read_map::reset() -{ - if (m_ptr) - { -#ifdef _WIN32 - ::UnmapViewOfFile(m_ptr); -#else - ::munmap(m_ptr, m_size); -#endif - } -} - -bool fs::dir::open(const std::string& dirname) -{ - this->close(); - -#ifdef _WIN32 - if (!is_dir(dirname)) - { - return false; - } - - m_dd = -1; -#else - const auto ptr = ::opendir(dirname.c_str()); - if (!ptr) - { - return false; - } - - m_dd = reinterpret_cast(ptr); -#endif - - m_path.reset(new char[dirname.size() + 1]); - std::memcpy(m_path.get(), dirname.c_str(), dirname.size() + 1); - - return true; -} - -void fs::dir::close() -{ - if (!m_path) - { - return /*true*/; - } - - m_path.reset(); - -#ifdef _WIN32 - CHECK_ASSERTION(m_dd == -1 || FindClose((HANDLE)m_dd)); -#else - CHECK_ASSERTION(!::closedir((DIR*)m_dd)); -#endif -} - -bool fs::dir::read(std::string& name, stat_t& info) -{ - if (!m_path) - { - errno = EINVAL; - return false; - } - -#ifdef _WIN32 - const bool is_first = m_dd == -1; - - WIN32_FIND_DATAW found; - - if (is_first) - { - m_dd = (std::intptr_t)FindFirstFileW(to_wchar(m_path.get() + "/*"s).get(), &found); - } - - if (is_first && m_dd == -1 || !is_first && !FindNextFileW((HANDLE)m_dd, &found)) - { - // TODO: convert Win32 error code to errno - switch (DWORD error = GetLastError()) - { - case ERROR_NO_MORE_FILES: - { - name.clear(); return true; } - default: throw EXCEPTION("Unknown Win32 error: 0x%x.", error); + u64 read(void* buffer, u64 count) override + { + const auto result = ::read(m_fd, buffer, count); + if (result == -1) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + return result; } - return false; - } + u64 write(const void* buffer, u64 count) override + { + const auto result = ::write(m_fd, buffer, count); + if (result == -1) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } - to_utf8(name, found.cFileName); + return result; + } - info.is_directory = (found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - info.is_writable = (found.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0; - info.size = ((u64)found.nFileSizeHigh << 32) | (u64)found.nFileSizeLow; - info.atime = to_time(found.ftLastAccessTime); - info.mtime = to_time(found.ftLastWriteTime); - info.ctime = to_time(found.ftCreationTime); -#else - const auto found = ::readdir((DIR*)m_dd); - if (!found) - { - name.clear(); - return true; - } + u64 seek(s64 offset, seek_mode whence) override + { + const int mode = + whence == seek_set ? SEEK_SET : + whence == seek_cur ? SEEK_CUR : + whence == seek_end ? SEEK_END : + throw fmt::exception("Invalid whence (0x%x)" HERE, whence); - struct ::stat file_info; - if (::fstatat(::dirfd((DIR*)m_dd), found->d_name, &file_info, 0) != 0) - { - return false; - } + const auto result = ::lseek(m_fd, offset, mode); + if (result == -1) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } - name = found->d_name; + return result; + } - info.is_directory = S_ISDIR(file_info.st_mode); - info.is_writable = file_info.st_mode & 0200; // HACK: approximation - info.size = file_info.st_size; - info.atime = file_info.st_atime; - info.mtime = file_info.st_mtime; - info.ctime = file_info.st_ctime; + u64 size() override + { + struct ::stat file_info; + if (::fstat(m_fd, &file_info) != 0) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + return file_info.st_size; + } + }; + + m_file = std::make_unique(fd); #endif return true; } -bool fs::dir::first(std::string& name, stat_t& info) +fs::file::file(const void* ptr, std::size_t size) { + class memory_stream final : public file_base + { + u64 m_pos = 0; + u64 m_size; + + public: + const char* const ptr; + + memory_stream(const void* ptr, std::size_t size) + : m_size(size) + , ptr(static_cast(ptr)) + { + } + + fs::stat_t stat() override + { + throw std::logic_error("memory_stream doesn't support stat()"); + } + + bool trunc(u64 length) override + { + throw std::logic_error("memory_stream doesn't support trunc()"); + } + + u64 read(void* buffer, u64 count) override + { + const u64 start = m_pos; + const u64 end = seek(count, fs::seek_cur); + const u64 read_size = end >= start ? end - start : throw std::logic_error("memory_stream::read(): overflow"); + std::memcpy(buffer, ptr + start, read_size); + return read_size; + } + + u64 write(const void* buffer, u64 count) override + { + throw std::logic_error("memory_stream is not writable"); + } + + u64 seek(s64 offset, fs::seek_mode whence) override + { + return m_pos = + whence == fs::seek_set ? std::min(offset, m_size) : + whence == fs::seek_cur ? std::min(offset + m_pos, m_size) : + whence == fs::seek_end ? std::min(offset + m_size, m_size) : + throw std::logic_error("memory_stream::seek(): invalid whence"); + } + + u64 size() override + { + return m_size; + } + }; + + m_file = std::make_unique(ptr, size); +} + +fs::file::file(std::vector& vec) +{ + class vector_stream final : public file_base + { + u64 m_pos = 0; + + public: + std::vector& vec; + + vector_stream(std::vector& vec) + : vec(vec) + { + } + + fs::stat_t stat() override + { + throw std::logic_error("vector_stream doesn't support stat()"); + } + + bool trunc(u64 length) override + { + vec.resize(length); + return true; + } + + u64 read(void* buffer, u64 count) override + { + const u64 start = m_pos; + const u64 end = seek(count, fs::seek_cur); + const u64 read_size = end >= start ? end - start : throw std::logic_error("vector_stream::read(): overflow"); + std::memcpy(buffer, vec.data() + start, read_size); + return read_size; + } + + u64 write(const void* buffer, u64 count) override + { + throw std::logic_error("TODO: vector_stream doesn't support write()"); + } + + u64 seek(s64 offset, fs::seek_mode whence) override + { + return m_pos = + whence == fs::seek_set ? std::min(offset, vec.size()) : + whence == fs::seek_cur ? std::min(offset + m_pos, vec.size()) : + whence == fs::seek_end ? std::min(offset + vec.size(), vec.size()) : + throw std::logic_error("vector_stream::seek(): invalid whence"); + } + + u64 size() override + { + return vec.size(); + } + }; + + m_file = std::make_unique(vec); +} + +//void fs::file_read_map::reset(const file& f) +//{ +// reset(); +// +// if (f) +// { +//#ifdef _WIN32 +// const HANDLE handle = ::CreateFileMapping((HANDLE)f.m_fd, NULL, PAGE_READONLY, 0, 0, NULL); +// m_ptr = (char*)::MapViewOfFile(handle, FILE_MAP_READ, 0, 0, 0); +// m_size = f.size(); +// ::CloseHandle(handle); +//#else +// m_ptr = (char*)::mmap(nullptr, m_size = f.size(), PROT_READ, MAP_SHARED, f.m_fd, 0); +// if (m_ptr == (void*)-1) m_ptr = nullptr; +//#endif +// } +//} +// +//void fs::file_read_map::reset() +//{ +// if (m_ptr) +// { +//#ifdef _WIN32 +// ::UnmapViewOfFile(m_ptr); +//#else +// ::munmap(m_ptr, m_size); +//#endif +// } +//} + +void fs::dir::xnull() const +{ + throw std::logic_error("fs::dir is null"); +} + +bool fs::dir::open(const std::string& path) +{ + if (auto device = get_virtual_device(path)) + { + if (auto&& _dir = device->open_dir(path)) + { + m_dir = std::move(_dir); + return true; + } + + return false; + } + #ifdef _WIN32 - if (m_path && m_dd != -1) + WIN32_FIND_DATAW found; + const auto handle = FindFirstFileW(to_wchar(path + "/*").get(), &found); + + if (handle == INVALID_HANDLE_VALUE) { - CHECK_ASSERTION(FindClose((HANDLE)m_dd)); - m_dd = -1; + // TODO: convert Win32 error code to errno + switch (DWORD error = GetLastError()) + { + case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; + case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + + return false; } + + class windows_dir final : public dir_base + { + const HANDLE m_handle; + + std::vector m_entries; + std::size_t m_pos = 0; + + void add_entry(const WIN32_FIND_DATAW& found) + { + dir_entry info; + + to_utf8(info.name, found.cFileName); + info.is_directory = (found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + info.is_writable = (found.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0; + info.size = ((u64)found.nFileSizeHigh << 32) | (u64)found.nFileSizeLow; + info.atime = to_time(found.ftLastAccessTime); + info.mtime = to_time(found.ftLastWriteTime); + info.ctime = to_time(found.ftCreationTime); + + m_entries.emplace_back(std::move(info)); + } + + public: + windows_dir(HANDLE handle, const WIN32_FIND_DATAW& found) + : m_handle(handle) + { + add_entry(found); + } + + ~windows_dir() + { + FindClose(m_handle); + } + + bool read(dir_entry& out) override + { + if (m_pos == m_entries.size()) + { + WIN32_FIND_DATAW found; + if (!FindNextFileW(m_handle, &found)) + { + switch (DWORD error = GetLastError()) + { + case ERROR_NO_MORE_FILES: return false; + default: throw fmt::exception("Unknown Win32 error: %u." HERE, error); + } + } + + add_entry(found); + } + + out = m_entries[m_pos++]; + return true; + } + + void rewind() override + { + m_pos = 0; + } + }; + + m_dir = std::make_unique(handle, found); #else - if (m_path) + ::DIR* const ptr = ::opendir(path.c_str()); + + if (!ptr) { - ::rewinddir((DIR*)m_dd); + // TODO: errno + return false; } + + class unix_dir final : public dir_base + { + ::DIR* m_dd; + + public: + unix_dir(::DIR* dd) + : m_dd(dd) + { + } + + ~unix_dir() override + { + ::closedir(m_dd); + } + + bool read(dir_entry& info) override + { + const auto found = ::readdir(m_dd); + if (!found) + { + return false; + } + + struct ::stat file_info; + if (::fstatat(::dirfd(m_dd), found->d_name, &file_info, 0) != 0) + { + switch (int error = errno) + { + case 0: + default: throw fmt::exception("Unknown error: %d." HERE, error); + } + } + + info.name = found->d_name; + info.is_directory = S_ISDIR(file_info.st_mode); + info.is_writable = file_info.st_mode & 0200; // HACK: approximation + info.size = file_info.st_size; + info.atime = file_info.st_atime; + info.mtime = file_info.st_mtime; + info.ctime = file_info.st_ctime; + + return true; + } + + void rewind() override + { + ::rewinddir(m_dd); + } + }; + + m_dir = std::make_unique(ptr); #endif - return read(name, info); + return true; } const std::string& fs::get_config_dir() { - // Use magic static for dir initialization + // Use magic static static const std::string s_dir = [] { #ifdef _WIN32 @@ -1009,7 +1344,7 @@ const std::string& fs::get_config_dir() const std::string& fs::get_executable_dir() { - // Use magic static for dir initialization + // Use magic static static const std::string s_dir = [] { std::string dir; @@ -1018,7 +1353,7 @@ const std::string& fs::get_executable_dir() wchar_t buf[2048]; if (GetModuleFileName(NULL, buf, ::size32(buf)) - 1 >= ::size32(buf) - 1) { - MessageBoxA(0, fmt::format("GetModuleFileName() failed (0x%x).", GetLastError()).c_str(), "fs::get_config_dir()", MB_ICONERROR); + MessageBoxA(0, fmt::format("GetModuleFileName() failed: error %u.", GetLastError()).c_str(), "fs::get_config_dir()", MB_ICONERROR); return dir; // empty } @@ -1055,3 +1390,51 @@ const std::string& fs::get_executable_dir() return s_dir; } + +void fs::remove_all(const std::string& path) +{ + for (const auto& entry : dir(path)) + { + if (entry.name == "." || entry.name == "..") + { + continue; + } + + if (entry.is_directory == false) + { + remove_file(path + '/' + entry.name); + } + + if (entry.is_directory == true) + { + remove_all(path + '/' + entry.name); + } + } + + remove_dir(path); +} + +u64 fs::get_dir_size(const std::string& path) +{ + u64 result = 0; + + for (const auto entry : dir(path)) + { + if (entry.name == "." || entry.name == "..") + { + continue; + } + + if (entry.is_directory == false) + { + result += entry.size; + } + + if (entry.is_directory == true) + { + result += get_dir_size(path + '/' + entry.name); + } + } + + return result; +} diff --git a/Utilities/File.h b/Utilities/File.h index 36af62704f..7a0605b872 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -1,29 +1,47 @@ #pragma once -namespace fom // file open mode -{ - enum open_mode : u32 - { - read = 1 << 0, // enable reading - write = 1 << 1, // enable writing - append = 1 << 2, // enable appending (always write to the end of file) - create = 1 << 3, // create file if it doesn't exist - trunc = 1 << 4, // clear opened file if it's not empty - excl = 1 << 5, // failure if the file already exists (used with `create`) +#include +#include +#include +#include - rewrite = write | create | trunc, - }; -}; +#include "types.h" namespace fs { - enum seek_mode : u32 // file seek mode + // File open mode flags + enum struct open_mode : u32 + { + read, + write, + append, + create, + trunc, + excl, + }; + + constexpr mset read = open_mode::read; // Enable reading + constexpr mset write = open_mode::write; // Enable writing + constexpr mset append = open_mode::append; // Always append to the end of the file + constexpr mset create = open_mode::create; // Create file if it doesn't exist + constexpr mset trunc = open_mode::trunc; // Clear opened file if it's not empty + constexpr mset excl = open_mode::excl; // Failure if the file already exists (used with `create`) + + constexpr mset rewrite = write + create + trunc; + + // File seek mode + enum class seek_mode : u32 { seek_set, seek_cur, seek_end, }; + constexpr auto seek_set = seek_mode::seek_set; // From beginning + constexpr auto seek_cur = seek_mode::seek_cur; // From current position + constexpr auto seek_end = seek_mode::seek_end; // From end + + // File attributes (TODO) struct stat_t { bool is_directory; @@ -34,7 +52,57 @@ namespace fs s64 ctime; }; - // Get parent directory for the path (returns empty string on failure) + // File handle base + struct file_base + { + virtual ~file_base() = default; + + virtual stat_t stat() = 0; + virtual bool trunc(u64 length) = 0; + virtual u64 read(void* buffer, u64 size) = 0; + virtual u64 write(const void* buffer, u64 size) = 0; + virtual u64 seek(s64 offset, seek_mode whence) = 0; + virtual u64 size() = 0; + }; + + // Directory entry (TODO) + struct dir_entry : stat_t + { + std::string name; + }; + + // Directory handle base + struct dir_base + { + virtual ~dir_base() = default; + + virtual bool read(dir_entry&) = 0; + virtual void rewind() = 0; + }; + + // Virtual device + struct device_base + { + virtual ~device_base() = default; + + virtual bool stat(const std::string& path, stat_t& info) = 0; + virtual bool remove_dir(const std::string& path) = 0; + virtual bool create_dir(const std::string& path) = 0; + virtual bool rename(const std::string& from, const std::string& to) = 0; + virtual bool remove(const std::string& path) = 0; + virtual bool trunc(const std::string& path, u64 length) = 0; + + virtual std::unique_ptr open(const std::string& path, mset mode) = 0; + virtual std::unique_ptr open_dir(const std::string& path) = 0; + }; + + // Get virtual device for specified path (nullptr for real path) + std::shared_ptr get_virtual_device(const std::string& path); + + // Set virtual device with specified name (nullptr for deletion) + std::shared_ptr set_virtual_device(const std::string& root_name, const std::shared_ptr&); + + // Try to get parent directory (returns empty string on failure) std::string get_parent_dir(const std::string& path); // Get file information @@ -72,77 +140,105 @@ namespace fs class file final { - using handle_type = std::intptr_t; + std::unique_ptr m_file; - constexpr static handle_type null = -1; - - handle_type m_fd = null; - - friend class file_read_map; - friend class file_write_map; + [[noreturn]] void xnull() const; + [[noreturn]] void xfail() const; public: + // Default constructor file() = default; - explicit file(const std::string& path, u32 mode = fom::read) + // Open file with specified mode + explicit file(const std::string& path, mset mode = ::fs::read) { open(path, mode); } - file(file&& other) - : m_fd(other.m_fd) - { - other.m_fd = null; - } + // Open file with specified mode + bool open(const std::string& path, mset mode = ::fs::read); - file& operator =(file&& right) - { - std::swap(m_fd, right.m_fd); - return *this; - } + // Open memory for read + explicit file(const void* ptr, std::size_t size); - ~file(); - - // Check whether the handle is valid (opened file) - bool is_opened() const - { - return m_fd != null; - } + // Open vector + explicit file(std::vector& vec); // Check whether the handle is valid (opened file) explicit operator bool() const { - return is_opened(); + return m_file.operator bool(); } - // Open specified file with specified mode - bool open(const std::string& path, u32 mode = fom::read); + // Close the file explicitly + void close() + { + m_file.reset(); + } + + void reset(std::unique_ptr&& ptr) + { + m_file = std::move(ptr); + } + + std::unique_ptr release() + { + return std::move(m_file); + } // Change file size (possibly appending zero bytes) - bool trunc(u64 size) const; + bool trunc(u64 length) const + { + if (!m_file) xnull(); + return m_file->trunc(length); + } // Get file information - bool stat(stat_t& info) const; - - // Close the file explicitly (destructor automatically closes the file) - void close(); + stat_t stat() const + { + if (!m_file) xnull(); + return m_file->stat(); + } // Read the data from the file and return the amount of data written in buffer - u64 read(void* buffer, u64 count) const; + u64 read(void* buffer, u64 count) const + { + if (!m_file) xnull(); + return m_file->read(buffer, count); + } // Write the data to the file and return the amount of data actually written - u64 write(const void* buffer, u64 count) const; + u64 write(const void* buffer, u64 count) const + { + if (!m_file) xnull(); + return m_file->write(buffer, count); + } - // Move file pointer - u64 seek(s64 offset, seek_mode whence = seek_set) const; + // Change current position, returns previous position + u64 seek(s64 offset, seek_mode whence = seek_set) const + { + if (!m_file) xnull(); + return m_file->seek(offset, whence); + } // Get file size - u64 size() const; + u64 size() const + { + if (!m_file) xnull(); + return m_file->size(); + } + + // Get current position + u64 pos() const + { + if (!m_file) xnull(); + return m_file->seek(0, seek_cur); + } // Write std::string unconditionally const file& write(const std::string& str) const { - CHECK_ASSERTION(write(str.data(), str.size()) == str.size()); + if (write(str.data(), str.size()) != str.size()) xfail(); return *this; } @@ -150,7 +246,7 @@ namespace fs template std::enable_if_t::value && !std::is_pointer::value, const file&> write(const T& data) const { - CHECK_ASSERTION(write(std::addressof(data), sizeof(T)) == sizeof(T)); + if (write(std::addressof(data), sizeof(T)) != sizeof(T)) xfail(); return *this; } @@ -158,7 +254,7 @@ namespace fs template std::enable_if_t::value && !std::is_pointer::value, const file&> write(const std::vector& vec) const { - CHECK_ASSERTION(write(vec.data(), vec.size() * sizeof(T)) == vec.size() * sizeof(T)); + if (write(vec.data(), vec.size() * sizeof(T)) != vec.size() * sizeof(T)) xfail(); return *this; } @@ -187,7 +283,7 @@ namespace fs std::enable_if_t::value && !std::is_pointer::value, T> read() const { T result; - CHECK_ASSERTION(read(result)); + if (!read(result)) xfail(); return result; } @@ -196,7 +292,7 @@ namespace fs { std::string result; result.resize(size()); - CHECK_ASSERTION(seek(0) != -1 && read(result)); + if (seek(0), !read(result)) xfail(); return result; } @@ -206,164 +302,69 @@ namespace fs { std::vector result; result.resize(size() / sizeof(T)); - CHECK_ASSERTION(seek(0) != -1 && read(result)); + if (seek(0), !read(result)) xfail(); return result; } }; - // TODO - class file_read_map final - { - char* m_ptr = nullptr; - u64 m_size; - - public: - file_read_map() = default; - - file_read_map(file_read_map&& right) - : m_ptr(right.m_ptr) - , m_size(right.m_size) - { - right.m_ptr = 0; - } - - file_read_map& operator =(file_read_map&& right) - { - std::swap(m_ptr, right.m_ptr); - std::swap(m_size, right.m_size); - return *this; - } - - file_read_map(const file& f) - { - reset(f); - } - - ~file_read_map() - { - reset(); - } - - // Open file mapping - void reset(const file& f); - - // Close file mapping - void reset(); - - // Get pointer - operator const char*() const - { - return m_ptr; - } - }; - - // TODO - class file_write_map final - { - char* m_ptr = nullptr; - u64 m_size; - - public: - file_write_map() = default; - - file_write_map(file_write_map&& right) - : m_ptr(right.m_ptr) - , m_size(right.m_size) - { - right.m_ptr = 0; - } - - file_write_map& operator =(file_write_map&& right) - { - std::swap(m_ptr, right.m_ptr); - std::swap(m_size, right.m_size); - return *this; - } - - file_write_map(const file& f) - { - reset(f); - } - - ~file_write_map() - { - reset(); - } - - // Open file mapping - void reset(const file& f); - - // Close file mapping - void reset(); - - // Get pointer - operator char*() const - { - return m_ptr; - } - }; - class dir final { - std::unique_ptr m_path; - std::intptr_t m_dd; // handle (aux) + std::unique_ptr m_dir; + + [[noreturn]] void xnull() const; public: dir() = default; - explicit dir(const std::string& dirname) + // Open dir handle + explicit dir(const std::string& path) { - open(dirname); + open(path); } - dir(dir&& other) - : m_dd(other.m_dd) - , m_path(std::move(other.m_path)) - { - } - - dir& operator =(dir&& right) - { - std::swap(m_dd, right.m_dd); - std::swap(m_path, right.m_path); - return *this; - } - - ~dir(); - - // Check whether the handle is valid (opened directory) - bool is_opened() const - { - return m_path.operator bool(); - } + // Open specified directory + bool open(const std::string& path); // Check whether the handle is valid (opened directory) explicit operator bool() const { - return is_opened(); + return m_dir.operator bool(); } - // Open specified directory - bool open(const std::string& dirname); - - // Close the directory explicitly (destructor automatically closes the directory) - void close(); - - // Get next directory entry (UTF-8 name and file stat) - bool read(std::string& name, stat_t& info); - - bool first(std::string& name, stat_t& info); - - struct entry + // Close the directory explicitly + void close() { - std::string name; - stat_t info; - }; + m_dir.reset(); + } + + void reset(std::unique_ptr&& ptr) + { + m_dir = std::move(ptr); + } + + std::unique_ptr release() + { + return std::move(m_dir); + } + + // Get next directory entry + bool read(dir_entry& out) const + { + if (!m_dir) xnull(); + return m_dir->read(out); + } + + // Reset to the beginning + void rewind() const + { + if (!m_dir) xnull(); + return m_dir->rewind(); + } class iterator { - entry m_entry; dir* m_parent; + dir_entry m_entry; public: enum class mode @@ -382,20 +383,16 @@ namespace fs if (mode_ == mode::from_first) { - m_parent->first(m_entry.name, m_entry.info); - } - else - { - m_parent->read(m_entry.name, m_entry.info); + m_parent->rewind(); } - if (m_entry.name.empty()) + if (!m_parent->read(m_entry)) { m_parent = nullptr; } } - entry& operator *() + dir_entry& operator *() { return m_entry; } @@ -414,7 +411,7 @@ namespace fs iterator begin() { - return{ this }; + return{ m_dir ? this : nullptr }; } iterator end() @@ -428,4 +425,10 @@ namespace fs // Get executable directory const std::string& get_executable_dir(); + + // Delete directory and all its contents recursively + void remove_all(const std::string& path); + + // Get size of all files recursively + u64 get_dir_size(const std::string& path); } diff --git a/Utilities/Log.cpp b/Utilities/Log.cpp index d86e235c62..08d6d7197e 100644 --- a/Utilities/Log.cpp +++ b/Utilities/Log.cpp @@ -1,26 +1,17 @@ -#include "stdafx.h" -#include "Thread.h" -#include "File.h" -#include "Log.h" - -#ifdef _WIN32 -#include -#endif +#include "Log.h" namespace _log { - logger& get_logger() + static file_listener& get_logger() { - // Use magic static for global logger instance - static logger instance; - return instance; + // Use magic static + static file_listener logger("RPCS3.log"); + return logger; } - file_listener g_log_file(_PRGNAME_ ".log"); - file_writer g_tty_file("TTY.log"); - channel GENERAL("", level::notice); + channel GENERAL(nullptr, level::notice); channel LOADER("LDR", level::notice); channel MEMORY("MEM", level::notice); channel RSX("RSX", level::notice); @@ -28,72 +19,29 @@ namespace _log channel PPU("PPU", level::notice); channel SPU("SPU", level::notice); channel ARMv7("ARMv7"); -} -_log::listener::listener() -{ - // Register self - get_logger().add_listener(this); -} - -_log::listener::~listener() -{ - // Unregister self - get_logger().remove_listener(this); -} - -_log::channel::channel(const std::string& name, _log::level init_level) - : name{ name } - , enabled{ init_level } -{ - // TODO: register config property "name" associated with "enabled" member -} - -void _log::logger::add_listener(_log::listener* listener) -{ - std::lock_guard lock(m_mutex); - - m_listeners.emplace(listener); -} - -void _log::logger::remove_listener(_log::listener* listener) -{ - std::lock_guard lock(m_mutex); - - m_listeners.erase(listener); -} - -void _log::logger::broadcast(const _log::channel& ch, _log::level sev, const std::string& text) const -{ - reader_lock lock(m_mutex); - - for (auto listener : m_listeners) - { - listener->log(ch, sev, text); - } + thread_local std::string(*g_tls_make_prefix)(const channel&, level, const std::string&) = nullptr; } void _log::broadcast(const _log::channel& ch, _log::level sev, const std::string& text) { - get_logger().broadcast(ch, sev, text); + get_logger().log(ch, sev, text); } +[[noreturn]] extern void catch_all_exceptions(); + _log::file_writer::file_writer(const std::string& name) { try { - if (!m_file.open(fs::get_config_dir() + name, fom::rewrite | fom::append)) + if (!m_file.open(fs::get_config_dir() + name, fs::rewrite + fs::append)) { - throw EXCEPTION("Can't create log file %s (error %d)", name, errno); + throw fmt::exception("Can't create log file %s (error %d)", name, errno); } } - catch (const fmt::exception& e) + catch (...) { -#ifdef _WIN32 - MessageBoxA(0, e.what(), "_log::file_writer() failed", MB_ICONERROR); -#else - std::printf("_log::file_writer() failed: %s\n", e.what()); -#endif + catch_all_exceptions(); } } @@ -104,7 +52,7 @@ void _log::file_writer::log(const std::string& text) std::size_t _log::file_writer::size() const { - return m_file.seek(0, fs::seek_cur); + return m_file.pos(); } void _log::file_listener::log(const _log::channel& ch, _log::level sev, const std::string& text) @@ -126,14 +74,14 @@ void _log::file_listener::log(const _log::channel& ch, _log::level sev, const st // TODO: print time? - if (auto t = thread_ctrl::get_current()) + if (auto func = g_tls_make_prefix) { msg += '{'; - msg += t->get_name(); + msg += func(ch, sev, text); msg += "} "; } - - if (ch.name.size()) + + if (ch.name) { msg += ch.name; msg += sev == level::todo ? " TODO: " : ": "; diff --git a/Utilities/Log.h b/Utilities/Log.h index 11ef6c6c21..5f319ae480 100644 --- a/Utilities/Log.h +++ b/Utilities/Log.h @@ -1,6 +1,9 @@ #pragma once -#include "SharedMutex.h" +#include "types.h" +#include "Atomic.h" +#include "File.h" +#include "StrFmt.h" namespace _log { @@ -19,40 +22,24 @@ namespace _log struct channel; struct listener; - // Log manager - class logger final - { - mutable shared_mutex m_mutex; - - std::set m_listeners; - - public: - // Register listener - void add_listener(listener* listener); - - // Unregister listener - void remove_listener(listener* listener); - - // Send log message to all listeners - void broadcast(const channel& ch, level sev, const std::string& text) const; - }; - // Send log message to global logger instance void broadcast(const channel& ch, level sev, const std::string& text); - // Log channel (source) + // Log channel struct channel { - // Channel prefix (also used for identification) - const std::string name; + // Channel prefix (added to every log message) + const char* const name; // The lowest logging level enabled for this channel (used for early filtering) - std::atomic enabled; + atomic_t enabled; - // Initialization (max level enabled by default) - channel(const std::string& name, level = level::trace); - - virtual ~channel() = default; + // Constant initialization: name and initial log level + constexpr channel(const char* name, level enabled = level::trace) + : name{ name } + , enabled{ enabled } + { + } // Log without formatting force_inline void log(level sev, const std::string& text) const @@ -71,7 +58,7 @@ namespace _log #define GEN_LOG_METHOD(_sev)\ template\ - force_inline void _sev(const char* fmt, const Args&... args)\ + force_inline void _sev(const char* fmt, const Args&... args) const\ {\ return format(level::_sev, fmt, args...);\ } @@ -90,9 +77,9 @@ namespace _log // Log listener (destination) struct listener { - listener(); + listener() = default; - virtual ~listener(); + virtual ~listener() = default; virtual void log(const channel& ch, level sev, const std::string& text) = 0; }; @@ -126,9 +113,6 @@ namespace _log virtual void log(const channel& ch, level sev, const std::string& text) override; }; - // Global variable for RPCS3.log - extern file_listener g_log_file; - // Global variable for TTY.log extern file_writer g_tty_file; @@ -142,8 +126,26 @@ namespace _log extern channel PPU; extern channel SPU; extern channel ARMv7; + + extern thread_local std::string(*g_tls_make_prefix)(const channel&, level, const std::string&); } +template<> +struct bijective<_log::level, const char*> +{ + static constexpr std::pair<_log::level, const char*> map[] + { + { _log::level::always, "Nothing" }, + { _log::level::fatal, "Fatal" }, + { _log::level::error, "Error" }, + { _log::level::todo, "TODO" }, + { _log::level::success, "Success" }, + { _log::level::warning, "Warning" }, + { _log::level::notice, "Notice" }, + { _log::level::trace, "Trace" }, + }; +}; + // Legacy: #define LOG_SUCCESS(ch, fmt, ...) _log::ch.success(fmt, ##__VA_ARGS__) diff --git a/Utilities/Macro.h b/Utilities/Macro.h new file mode 100644 index 0000000000..d8fdc67f92 --- /dev/null +++ b/Utilities/Macro.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include + +template::value>> +constexpr T align(const T& value, std::uint64_t align) +{ + return static_cast((value + (align - 1)) & ~(align - 1)); +} + +template +constexpr To narrow_impl(const To& result, const From& value, const char* message) +{ + return static_cast(result) != value ? throw std::runtime_error(message) : result; +} + +// Narrow cast (similar to gsl::narrow) with fixed message +template +constexpr auto narrow(const From& value, const char* fixed_msg = "::narrow() failed") -> decltype(static_cast(static_cast(std::declval()))) +{ + return narrow_impl(static_cast(value), value, fixed_msg); +} + +// Return 32 bit .size() for container +template +constexpr auto size32(const CT& container, const char* fixed_msg = "::size32() failed") -> decltype(static_cast(container.size())) +{ + return narrow(container.size(), fixed_msg); +} + +// Return 32 bit size for an array +template +constexpr std::uint32_t size32(const T(&)[Size]) +{ + static_assert(Size <= UINT32_MAX, "size32() error: too big"); + return static_cast(Size); +} + +#define CHECK_SIZE(type, size) static_assert(sizeof(type) == size, "Invalid " #type " type size") +#define CHECK_ALIGN(type, align) static_assert(alignof(type) == align, "Invalid " #type " type alignment") +#define CHECK_MAX_SIZE(type, size) static_assert(sizeof(type) <= size, #type " type size is too big") +#define CHECK_SIZE_ALIGN(type, size, align) CHECK_SIZE(type, size); CHECK_ALIGN(type, align) + +// Return 32 bit sizeof() to avoid widening/narrowing conversions with size_t +#define SIZE_32(type) static_cast(sizeof(type)) + +// Return 32 bit alignof() to avoid widening/narrowing conversions with size_t +#define ALIGN_32(type) static_cast(alignof(type)) + +// Return 32 bit custom offsetof() +#define OFFSET_32(type, x) static_cast(reinterpret_cast(&reinterpret_cast(reinterpret_cast(0ull)->x))) + +// Sometimes to avoid writing std::remove_cv_t<>, example: std::is_same +#define CV const volatile + +#define CONCATENATE_DETAIL(x, y) x ## y +#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y) + +#define STRINGIZE_DETAIL(x) #x +#define STRINGIZE(x) STRINGIZE_DETAIL(x) + +// Macro set, allows to hide "return" in simple lambda expressions. +#define WRAP_EXPR(expr, ...) [&](__VA_ARGS__) { return expr; } +#define COPY_EXPR(expr, ...) [=](__VA_ARGS__) { return expr; } +#define PURE_EXPR(expr, ...) [] (__VA_ARGS__) { return expr; } + +#define HERE "\n(in file " __FILE__ ":" STRINGIZE(__LINE__) ")" + +// Ensure that the expression is evaluated to true. Always evaluated and allowed to have side effects (unlike assert() macro). +#define ASSERT(expr) if (!(expr)) throw std::runtime_error("Assertion failed: " #expr HERE) + +// Expects() and Ensures() are intended to check function arguments and results. +// Expressions are not guaranteed to evaluate. Redefinition with ASSERT macro for better unification. +#define Expects ASSERT +#define Ensures ASSERT + +#define DECLARE(static_member) decltype(static_member) static_member +#define STR_CASE(value) case value: return #value diff --git a/Utilities/GNU.cpp b/Utilities/Platform.cpp similarity index 98% rename from Utilities/GNU.cpp rename to Utilities/Platform.cpp index 29467b205f..9f2672997e 100644 --- a/Utilities/GNU.cpp +++ b/Utilities/Platform.cpp @@ -1,4 +1,4 @@ -#include "GNU.h" +#include "Platform.h" #ifdef __APPLE__ #include diff --git a/Utilities/GNU.h b/Utilities/Platform.h similarity index 58% rename from Utilities/GNU.h rename to Utilities/Platform.h index 268318e568..18d9297e24 100644 --- a/Utilities/GNU.h +++ b/Utilities/Platform.h @@ -1,17 +1,22 @@ #pragma once +#include +#include #include -#if defined(_MSC_VER) && _MSC_VER <= 1800 -#define thread_local __declspec(thread) -#elif __APPLE__ -#define thread_local __thread +#define IS_LE_MACHINE 1 +#define IS_BE_MACHINE 0 + +#ifdef _MSC_VER +#include +#else +#include #endif -#if defined(_MSC_VER) -#define never_inline __declspec(noinline) -#else -#define never_inline __attribute__((noinline)) +// Some platforms don't support thread_local well yet. +#ifndef _MSC_VER +#define thread_local __thread +#define __assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0) #endif #if defined(_MSC_VER) @@ -20,20 +25,21 @@ #define safe_buffers #endif +#if defined(_MSC_VER) +#define never_inline __declspec(noinline) +#else +#define never_inline __attribute__((noinline)) +#endif + #if defined(_MSC_VER) #define force_inline __forceinline #else #define force_inline __attribute__((always_inline)) inline #endif -#if defined(_MSC_VER) && _MSC_VER <= 1800 -#define alignas(x) _CRT_ALIGN(x) -#endif - #if defined(__GNUG__) #include -#include #define _fpclass(x) std::fpclassify(x) #define INFINITE 0xFFFFFFFF @@ -59,170 +65,6 @@ int clock_gettime(clockid_t clk_id, struct timespec *tp); #endif /* __APPLE__ */ #endif /* __GNUG__ */ -#if defined(_MSC_VER) - -// Unsigned 128-bit integer implementation -struct alignas(16) u128 -{ - std::uint64_t lo, hi; - - u128() = default; - - u128(const u128&) = default; - - u128(std::uint64_t l) - : lo(l) - , hi(0) - { - } - - u128 operator +(const u128& r) const - { - u128 value; - _addcarry_u64(_addcarry_u64(0, r.lo, lo, &value.lo), r.hi, hi, &value.hi); - return value; - } - - friend u128 operator +(const u128& l, std::uint64_t r) - { - u128 value; - _addcarry_u64(_addcarry_u64(0, r, l.lo, &value.lo), l.hi, 0, &value.hi); - return value; - } - - friend u128 operator +(std::uint64_t l, const u128& r) - { - u128 value; - _addcarry_u64(_addcarry_u64(0, r.lo, l, &value.lo), 0, r.hi, &value.hi); - return value; - } - - u128 operator -(const u128& r) const - { - u128 value; - _subborrow_u64(_subborrow_u64(0, r.lo, lo, &value.lo), r.hi, hi, &value.hi); - return value; - } - - friend u128 operator -(const u128& l, std::uint64_t r) - { - u128 value; - _subborrow_u64(_subborrow_u64(0, r, l.lo, &value.lo), 0, l.hi, &value.hi); - return value; - } - - friend u128 operator -(std::uint64_t l, const u128& r) - { - u128 value; - _subborrow_u64(_subborrow_u64(0, r.lo, l, &value.lo), r.hi, 0, &value.hi); - return value; - } - - u128 operator +() const - { - return *this; - } - - u128 operator -() const - { - u128 value; - _subborrow_u64(_subborrow_u64(0, lo, 0, &value.lo), hi, 0, &value.hi); - return value; - } - - u128& operator ++() - { - _addcarry_u64(_addcarry_u64(0, 1, lo, &lo), 0, hi, &hi); - return *this; - } - - u128 operator ++(int) - { - u128 value = *this; - _addcarry_u64(_addcarry_u64(0, 1, lo, &lo), 0, hi, &hi); - return value; - } - - u128& operator --() - { - _subborrow_u64(_subborrow_u64(0, 1, lo, &lo), 0, hi, &hi); - return *this; - } - - u128 operator --(int) - { - u128 value = *this; - _subborrow_u64(_subborrow_u64(0, 1, lo, &lo), 0, hi, &hi); - return value; - } - - u128 operator ~() const - { - u128 value; - value.lo = ~lo; - value.hi = ~hi; - return value; - } - - u128 operator &(const u128& r) const - { - u128 value; - value.lo = lo & r.lo; - value.hi = hi & r.hi; - return value; - } - - u128 operator |(const u128& r) const - { - u128 value; - value.lo = lo | r.lo; - value.hi = hi | r.hi; - return value; - } - - u128 operator ^(const u128& r) const - { - u128 value; - value.lo = lo ^ r.lo; - value.hi = hi ^ r.hi; - return value; - } - - u128& operator +=(const u128& r) - { - _addcarry_u64(_addcarry_u64(0, r.lo, lo, &lo), r.hi, hi, &hi); - return *this; - } - - u128& operator +=(uint64_t r) - { - _addcarry_u64(_addcarry_u64(0, r, lo, &lo), 0, hi, &hi); - return *this; - } - - u128& operator &=(const u128& r) - { - lo &= r.lo; - hi &= r.hi; - return *this; - } - - u128& operator |=(const u128& r) - { - lo |= r.lo; - hi |= r.hi; - return *this; - } - - u128& operator ^=(const u128& r) - { - lo ^= r.lo; - hi ^= r.hi; - return *this; - } -}; -#endif - inline std::uint32_t cntlz32(std::uint32_t arg) { #if defined(_MSC_VER) @@ -243,7 +85,67 @@ inline std::uint64_t cntlz64(std::uint64_t arg) #endif } -// compare 16 packed unsigned bytes (greater than) +template +struct add_flags_result_t +{ + T result; + bool carry; + //bool overflow; + bool zero; + bool sign; + + add_flags_result_t() = default; + + // Straighforward ADD with flags + add_flags_result_t(T a, T b) + : result(a + b) + , carry(result < a) + //, overflow((result ^ ~(a ^ b)) >> (sizeof(T) * 8 - 1) != 0) + , zero(result == 0) + , sign(result >> (sizeof(T) * 8 - 1) != 0) + { + } + + // Straighforward ADC with flags + add_flags_result_t(T a, T b, bool c) + : add_flags_result_t(a, b) + { + add_flags_result_t r(result, c); + result = r.result; + carry |= r.carry; + //overflow |= r.overflow; + zero = r.zero; + sign = r.sign; + } +}; + +inline add_flags_result_t add32_flags(std::uint32_t a, std::uint32_t b) +{ + //add_flags_result_t r; + //r.carry = _addcarry_u32(0, a, b, &r.result) != 0; + //r.zero = r.result == 0; + //r.sign = r.result >> 31; + //return r; + + return{ a, b }; +} + +inline add_flags_result_t add32_flags(std::uint32_t a, std::uint32_t b, bool c) +{ + return{ a, b, c }; +} + +inline add_flags_result_t add64_flags(std::uint64_t a, std::uint64_t b) +{ + return{ a, b }; +} + +inline add_flags_result_t add64_flags(std::uint64_t a, std::uint64_t b, bool c) +{ + return{ a, b, c }; +} + +// Compare 16 packed unsigned bytes (greater than) inline __m128i sse_cmpgt_epu8(__m128i A, __m128i B) { // (A xor 0x80) > (B xor 0x80) @@ -290,3 +192,36 @@ inline __m128 sse_log2_ps(__m128 A) const auto x8 = _mm_cvtepi32_ps(_mm_sub_epi32(_mm_srli_epi32(_mm_castps_si128(x0), 23), _mm_set1_epi32(127))); return _mm_add_ps(_mm_mul_ps(_mm_mul_ps(_mm_mul_ps(_mm_mul_ps(x5, x6), x7), x4), _c), _mm_add_ps(_mm_mul_ps(x4, _c), x8)); } + +// Helper function, used by ""_u16, ""_u32, ""_u64 +constexpr std::uint8_t to_u8(char c) +{ + return static_cast(c); +} + +// Convert 2-byte string to u16 value like reinterpret_cast does +constexpr std::uint16_t operator""_u16(const char* s, std::size_t length) +{ + return length != 2 ? throw s : +#if IS_LE_MACHINE == 1 + to_u8(s[1]) << 8 | to_u8(s[0]); +#endif +} + +// Convert 4-byte string to u32 value like reinterpret_cast does +constexpr std::uint32_t operator""_u32(const char* s, std::size_t length) +{ + return length != 4 ? throw s : +#if IS_LE_MACHINE == 1 + to_u8(s[3]) << 24 | to_u8(s[2]) << 16 | to_u8(s[1]) << 8 | to_u8(s[0]); +#endif +} + +// Convert 8-byte string to u64 value like reinterpret_cast does +constexpr std::uint64_t operator""_u64(const char* s, std::size_t length) +{ + return length != 8 ? throw s : +#if IS_LE_MACHINE == 1 + static_cast(to_u8(s[7]) << 24 | to_u8(s[6]) << 16 | to_u8(s[5]) << 8 | to_u8(s[4])) << 32 | to_u8(s[3]) << 24 | to_u8(s[2]) << 16 | to_u8(s[1]) << 8 | to_u8(s[0]); +#endif +} diff --git a/Utilities/Semaphore.cpp b/Utilities/Semaphore.cpp index ed3d419e12..e6ea21b123 100644 --- a/Utilities/Semaphore.cpp +++ b/Utilities/Semaphore.cpp @@ -10,7 +10,7 @@ bool semaphore_t::try_wait() } // try to decrement m_value atomically - const auto old = m_var.atomic_op([](sync_var_t& var) + const auto old = m_var.fetch_op([](sync_var_t& var) { if (var.value) { @@ -36,7 +36,7 @@ bool semaphore_t::try_post() } // try to increment m_value atomically - const auto old = m_var.atomic_op([&](sync_var_t& var) + const auto old = m_var.fetch_op([&](sync_var_t& var) { if (var.value < max_value) { diff --git a/Utilities/Semaphore.h b/Utilities/Semaphore.h index 8ed96f4275..ea3c2c4e5c 100644 --- a/Utilities/Semaphore.h +++ b/Utilities/Semaphore.h @@ -8,7 +8,7 @@ class semaphore_t // semaphore condition variable std::condition_variable m_cv; - struct sync_var_t + struct alignas(8) sync_var_t { u32 value; // current semaphore value u32 waiters; // current amount of waiters diff --git a/Utilities/SharedMutex.cpp b/Utilities/SharedMutex.cpp deleted file mode 100644 index 728e4e4437..0000000000 --- a/Utilities/SharedMutex.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "stdafx.h" -#include "SharedMutex.h" - -void shared_mutex::impl_lock_shared(u32 old_value) -{ - // Throw if reader count breaks the "second" limit (it should be impossible) - CHECK_ASSERTION((old_value & SM_READER_COUNT) != SM_READER_COUNT); - - std::unique_lock lock(m_mutex); - - // Notify non-zero reader queue size - m_ctrl |= SM_READER_QUEUE; - - // Compensate incorrectly increased reader count - if ((--m_ctrl & SM_READER_COUNT) == 0 && m_wq_size) - { - // Notify current exclusive owner (condition passed) - m_ocv.notify_one(); - } - - CHECK_ASSERTION(++m_rq_size); - - // Obtain the reader lock - while (!atomic_op(m_ctrl, op_lock_shared)) - { - m_rcv.wait(lock); - } - - CHECK_ASSERTION(m_rq_size--); - - if (m_rq_size == 0) - { - m_ctrl &= ~SM_READER_QUEUE; - } -} - -void shared_mutex::impl_unlock_shared(u32 new_value) -{ - // Throw if reader count was zero - CHECK_ASSERTION((new_value & SM_READER_COUNT) != SM_READER_COUNT); - - // Mutex cannot be unlocked before notification because m_ctrl has been changed outside - std::lock_guard lock(m_mutex); - - if (m_wq_size && (new_value & SM_READER_COUNT) == 0) - { - // Notify current exclusive owner that the latest reader is gone - m_ocv.notify_one(); - } - else if (m_rq_size) - { - m_rcv.notify_one(); - } -} - -void shared_mutex::impl_lock_excl(u32 value) -{ - std::unique_lock lock(m_mutex); - - // Notify non-zero writer queue size - m_ctrl |= SM_WRITER_QUEUE; - - CHECK_ASSERTION(++m_wq_size); - - // Obtain the writer lock - while (!atomic_op(m_ctrl, op_lock_excl)) - { - m_wcv.wait(lock); - } - - // Wait for remaining readers - while ((m_ctrl & SM_READER_COUNT) != 0) - { - m_ocv.wait(lock); - } - - CHECK_ASSERTION(m_wq_size--); - - if (m_wq_size == 0) - { - m_ctrl &= ~SM_WRITER_QUEUE; - } -} - -void shared_mutex::impl_unlock_excl(u32 value) -{ - // Throw if was not locked exclusively - CHECK_ASSERTION(value & SM_WRITER_LOCK); - - // Mutex cannot be unlocked before notification because m_ctrl has been changed outside - std::lock_guard lock(m_mutex); - - if (m_wq_size) - { - // Notify next exclusive owner - m_wcv.notify_one(); - } - else if (m_rq_size) - { - // Notify all readers - m_rcv.notify_all(); - } -} diff --git a/Utilities/SharedMutex.h b/Utilities/SharedMutex.h index 961ef1dd23..361f94f2ee 100644 --- a/Utilities/SharedMutex.h +++ b/Utilities/SharedMutex.h @@ -1,49 +1,156 @@ #pragma once +#include +#include +#include +#include +#include + +#include "Atomic.h" + //! An attempt to create effective implementation of "shared mutex", lock-free in optimistic case. //! All locking and unlocking may be done by single LOCK XADD or LOCK CMPXCHG instructions. //! MSVC implementation of std::shared_timed_mutex seems suboptimal. //! std::shared_mutex is not available until C++17. class shared_mutex final { - enum : u32 - { - SM_WRITER_LOCK = 1u << 31, // Exclusive lock flag, must be MSB - SM_WRITER_QUEUE = 1u << 30, // Flag set if m_wq_size != 0 - SM_READER_QUEUE = 1u << 29, // Flag set if m_rq_size != 0 + using ctrl_type = u32; - SM_READER_COUNT = SM_READER_QUEUE - 1, // Valid reader count bit mask - SM_READER_MAX = 1u << 24, // Max reader count + enum : ctrl_type + { + SM_WRITER_LOCK = 1u << 31, // Exclusive lock flag, must be MSB + SM_WAITERS_BIT = 1u << 30, // Flag set if m_wq_size or m_rq_size is non-zero + SM_INVALID_BIT = 1u << 29, // Unreachable reader count bit (may be set by incorrect unlock_shared() call) + + SM_READER_MASK = SM_WAITERS_BIT - 1, // Valid reader count bit mask + SM_READER_MAX = 1u << 24, // Max reader count }; - std::atomic m_ctrl{}; // Control atomic variable: reader count | SM_* flags - std::thread::id m_owner{}; // Current exclusive owner (TODO: implement only for debug mode?) + atomic_t m_ctrl{}; // Control atomic variable: reader count | SM_* flags std::mutex m_mutex; - u32 m_rq_size{}; // Reader queue size (threads waiting on m_rcv) - u32 m_wq_size{}; // Writer queue size (threads waiting on m_wcv+m_ocv) + std::size_t m_rq_size{}; // Reader queue size (threads waiting on m_rcv) + std::size_t m_wq_size{}; // Writer queue size (threads waiting on m_wcv and m_ocv) std::condition_variable m_rcv; // Reader queue std::condition_variable m_wcv; // Writer queue std::condition_variable m_ocv; // For current exclusive owner - static bool op_lock_shared(u32& ctrl) + void lock_shared_hard() { - // Check writer flags and reader limit - return (ctrl & ~SM_READER_QUEUE) < SM_READER_MAX ? ctrl++, true : false; + std::unique_lock lock(m_mutex); + + // Validate + if ((m_ctrl & SM_INVALID_BIT) != 0) throw std::runtime_error("shared_mutex::lock_shared(): Invalid bit"); + if ((m_ctrl & SM_READER_MASK) == 0) throw std::runtime_error("shared_mutex::lock_shared(): No readers"); + + // Notify non-zero reader queue size + m_ctrl |= SM_WAITERS_BIT, m_rq_size++; + + // Fix excess reader count + if ((--m_ctrl & SM_READER_MASK) == 0 && m_wq_size) + { + // Notify exclusive owner + m_ocv.notify_one(); + } + + // Obtain the reader lock + while (true) + { + const auto ctrl = m_ctrl.load(); + + // Check writers and reader limit + if (m_wq_size || (ctrl & ~SM_WAITERS_BIT) >= SM_READER_MAX) + { + m_rcv.wait(lock); + continue; + } + + if (m_ctrl.compare_and_swap_test(ctrl, ctrl + 1)) + { + break; + } + } + + if (!--m_rq_size && !m_wq_size) + { + m_ctrl &= ~SM_WAITERS_BIT; + } } - static bool op_lock_excl(u32& ctrl) + void unlock_shared_notify() { - // Test and set writer lock - return (ctrl & SM_WRITER_LOCK) == 0 ? ctrl |= SM_WRITER_LOCK, true : false; + // Mutex is locked for reliable notification because m_ctrl has been changed outside + std::lock_guard lock(m_mutex); + + if ((m_ctrl & SM_READER_MASK) == 0 && m_wq_size) + { + // Notify exclusive owner + m_ocv.notify_one(); + } + else if (m_rq_size) + { + // Notify other readers + m_rcv.notify_one(); + } } - void impl_lock_shared(u32 old_ctrl); - void impl_unlock_shared(u32 new_ctrl); - void impl_lock_excl(u32 ctrl); - void impl_unlock_excl(u32 ctrl); + void lock_hard() + { + std::unique_lock lock(m_mutex); + + // Validate + if ((m_ctrl & SM_INVALID_BIT) != 0) throw std::runtime_error("shared_mutex::lock(): Invalid bit"); + + // Notify non-zero writer queue size + m_ctrl |= SM_WAITERS_BIT, m_wq_size++; + + // Obtain the writer lock + while (true) + { + const auto ctrl = m_ctrl.load(); + + if (ctrl & SM_WRITER_LOCK) + { + m_wcv.wait(lock); + continue; + } + + if (m_ctrl.compare_and_swap_test(ctrl, ctrl | SM_WRITER_LOCK)) + { + break; + } + } + + // Wait for remaining readers + while ((m_ctrl & SM_READER_MASK) != 0) + { + m_ocv.wait(lock); + } + + if (!--m_wq_size && !m_rq_size) + { + m_ctrl &= ~SM_WAITERS_BIT; + } + } + + void unlock_notify() + { + // Mutex is locked for reliable notification because m_ctrl has been changed outside + std::lock_guard lock(m_mutex); + + if (m_wq_size) + { + // Notify next exclusive owner + m_wcv.notify_one(); + } + else if (m_rq_size) + { + // Notify all readers + m_rcv.notify_all(); + } + } public: shared_mutex() = default; @@ -51,65 +158,49 @@ public: // Lock in shared mode void lock_shared() { - const u32 old_ctrl = m_ctrl++; - - // Check flags and reader limit - if (old_ctrl >= SM_READER_MAX) + if (m_ctrl++ >= SM_READER_MAX) { - impl_lock_shared(old_ctrl); + lock_shared_hard(); } } // Try to lock in shared mode bool try_lock_shared() { - return atomic_op(m_ctrl, [](u32& ctrl) - { - // Check flags and reader limit - return ctrl < SM_READER_MAX ? ctrl++, true : false; - }); + auto ctrl = m_ctrl.load(); + + return ctrl < SM_READER_MAX && m_ctrl.compare_and_swap_test(ctrl, ctrl + 1); } // Unlock in shared mode void unlock_shared() { - const u32 new_ctrl = --m_ctrl; - - // Check if notification required - if (new_ctrl >= SM_READER_MAX) + if (m_ctrl-- >= SM_READER_MAX) { - impl_unlock_shared(new_ctrl); - } - } - - // Lock exclusively - void lock() - { - u32 value = 0; - - if (!m_ctrl.compare_exchange_strong(value, SM_WRITER_LOCK)) - { - impl_lock_excl(value); + unlock_shared_notify(); } } // Try to lock exclusively bool try_lock() { - u32 value = 0; + return m_ctrl.compare_and_swap_test(0, SM_WRITER_LOCK); + } - return m_ctrl.compare_exchange_strong(value, SM_WRITER_LOCK); + // Lock exclusively + void lock() + { + if (m_ctrl.compare_and_swap_test(0, SM_WRITER_LOCK)) return; + + lock_hard(); } // Unlock exclusively void unlock() { - const u32 value = m_ctrl.fetch_add(SM_WRITER_LOCK); - - // Check if notification required - if (value != SM_WRITER_LOCK) + if (m_ctrl.fetch_sub(SM_WRITER_LOCK) != SM_WRITER_LOCK) { - impl_unlock_excl(value); + unlock_notify(); } } }; diff --git a/Utilities/SleepQueue.cpp b/Utilities/SleepQueue.cpp deleted file mode 100644 index d7f432cea6..0000000000 --- a/Utilities/SleepQueue.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "stdafx.h" -#include "Emu/CPU/CPUThread.h" - -#include "SleepQueue.h" - -void sleep_queue_entry_t::add_entry() -{ - m_queue.emplace_back(std::static_pointer_cast(m_thread.shared_from_this())); -} - -void sleep_queue_entry_t::remove_entry() -{ - for (auto it = m_queue.begin(); it != m_queue.end(); it++) - { - if (it->get() == &m_thread) - { - m_queue.erase(it); - return; - } - } -} - -bool sleep_queue_entry_t::find() const -{ - for (auto it = m_queue.begin(); it != m_queue.end(); it++) - { - if (it->get() == &m_thread) - { - return true; - } - } - - return false; -} - -sleep_queue_entry_t::sleep_queue_entry_t(sleep_entry_t& cpu, sleep_queue_t& queue) - : m_thread(cpu) - , m_queue(queue) -{ - add_entry(); - cpu.sleep(); -} - -sleep_queue_entry_t::sleep_queue_entry_t(sleep_entry_t& cpu, sleep_queue_t& queue, const defer_sleep_t&) - : m_thread(cpu) - , m_queue(queue) -{ - cpu.sleep(); -} - -sleep_queue_entry_t::~sleep_queue_entry_t() -{ - remove_entry(); - m_thread.awake(); -} diff --git a/Utilities/SleepQueue.h b/Utilities/SleepQueue.h index e66a284c65..fc97baad52 100644 --- a/Utilities/SleepQueue.h +++ b/Utilities/SleepQueue.h @@ -1,45 +1,75 @@ #pragma once -using sleep_entry_t = class CPUThread; -using sleep_queue_t = std::deque>; +#include -static struct defer_sleep_t {} const defer_sleep{}; +// Tag used in sleep_entry<> constructor +static struct defer_sleep_tag {} constexpr defer_sleep{}; -// automatic object handling a thread entry in the sleep queue -class sleep_queue_entry_t final +// Define sleep queue as std::deque with T* pointers, T - thread type +template using sleep_queue = std::deque; + +// Automatic object handling a thread pointer (T*) in the sleep queue +// Sleep is called in the constructor (if not null) +// Awake is called in the destructor (if not null) +// Sleep queue is actually std::deque with pointers, be careful about the lifetime +template +class sleep_entry final { - sleep_entry_t& m_thread; - sleep_queue_t& m_queue; - - void add_entry(); - void remove_entry(); - bool find() const; + sleep_queue& m_queue; + T& m_thread; public: - // add specified thread to the sleep queue - sleep_queue_entry_t(sleep_entry_t& entry, sleep_queue_t& queue); + // Constructor; enter() not called + sleep_entry(sleep_queue& queue, T& entry, const defer_sleep_tag&) + : m_queue(queue) + , m_thread(entry) + { + if (Sleep) (m_thread.*Sleep)(); + } - // don't add specified thread to the sleep queue - sleep_queue_entry_t(sleep_entry_t& entry, sleep_queue_t& queue, const defer_sleep_t&); + // Constructor; calls enter() + sleep_entry(sleep_queue& queue, T& entry) + : sleep_entry(queue, entry, defer_sleep) + { + enter(); + } - // removes specified thread from the sleep queue if added - ~sleep_queue_entry_t(); + // Destructor; calls leave() + ~sleep_entry() + { + leave(); + if (Awake) (m_thread.*Awake)(); + } - // add thread to the sleep queue + // Add thread to the sleep queue void enter() { - add_entry(); + for (auto t : m_queue) + { + if (t == &m_thread) + { + // Already exists, is it an error? + return; + } + } + + m_queue.emplace_back(&m_thread); } - // remove thread from the sleep queue + // Remove thread from the sleep queue void leave() { - remove_entry(); + auto it = std::find(m_queue.begin(), m_queue.end(), &m_thread); + + if (it != m_queue.end()) + { + m_queue.erase(it); + } } - // check whether the thread exists in the sleep queue + // Check whether the thread exists in the sleep queue explicit operator bool() const { - return find(); + return std::find(m_queue.begin(), m_queue.end(), &m_thread) != m_queue.end(); } }; diff --git a/Utilities/StrFmt.cpp b/Utilities/StrFmt.cpp index 8e67616579..1b7df25a4d 100644 --- a/Utilities/StrFmt.cpp +++ b/Utilities/StrFmt.cpp @@ -1,9 +1,5 @@ -#include "stdafx.h" -#pragma warning(push) -#pragma message("TODO: remove wx dependency: ") -#pragma warning(disable : 4996) -#include -#pragma warning(pop) +#include "StrFmt.h" +#include "BEType.h" std::string v128::to_hex() const { @@ -19,7 +15,7 @@ std::string fmt::to_hex(u64 value, u64 count) { if (count - 1 >= 16) { - throw EXCEPTION("Invalid count: 0x%llx", count); + throw exception("fmt::to_hex(): invalid count: 0x%llx", count); } count = std::max(count, 16 - cntlz64(value) / 4); @@ -78,8 +74,6 @@ std::string fmt::to_sdec(s64 svalue) return std::string(&res[first], sizeof(res) - first); } -//extern const std::string fmt::placeholder = "???"; - std::string fmt::replace_first(const std::string& src, const std::string& from, const std::string& to) { auto pos = src.find(from); @@ -104,83 +98,6 @@ std::string fmt::replace_all(const std::string &src, const std::string& from, co return target; } -//TODO: move this wx Stuff somewhere else -//convert a wxString to a std::string encoded in utf8 -//CAUTION, only use this to interface with wxWidgets classes -std::string fmt::ToUTF8(const wxString& right) -{ - auto ret = std::string(((const char *)right.utf8_str())); - return ret; -} - -//convert a std::string encoded in utf8 to a wxString -//CAUTION, only use this to interface with wxWidgets classes -wxString fmt::FromUTF8(const std::string& right) -{ - auto ret = wxString::FromUTF8(right.c_str()); - return ret; -} - -//TODO: remove this after every snippet that uses it is gone -//WARNING: not fully compatible with CmpNoCase from wxString -int fmt::CmpNoCase(const std::string& a, const std::string& b) -{ - if (a.length() != b.length()) - { - return -1; - } - else - { - return std::equal(a.begin(), - a.end(), - b.begin(), - [](const char& a, const char& b){return ::tolower(a) == ::tolower(b); }) - ? 0 : -1; - } -} - -//TODO: remove this after every snippet that uses it is gone -//WARNING: not fully compatible with CmpNoCase from wxString -void fmt::Replace(std::string &str, const std::string &searchterm, const std::string& replaceterm) -{ - size_t cursor = 0; - do - { - cursor = str.find(searchterm, cursor); - if (cursor != std::string::npos) - { - str.replace(cursor, searchterm.size(), replaceterm); - cursor += replaceterm.size(); - } - else - { - break; - } - } while (true); -} - -std::vector fmt::rSplit(const std::string& source, const std::string& delim) -{ - std::vector ret; - size_t cursor = 0; - do - { - size_t prevcurs = cursor; - cursor = source.find(delim, cursor); - if (cursor != std::string::npos) - { - ret.push_back(source.substr(prevcurs,cursor-prevcurs)); - cursor += delim.size(); - } - else - { - ret.push_back(source.substr(prevcurs)); - break; - } - } while (true); - return ret; -} - std::vector fmt::split(const std::string& source, std::initializer_list separators, bool is_skip_empty) { std::vector result; @@ -222,21 +139,7 @@ std::string fmt::trim(const std::string& source, const std::string& values) return source.substr(begin, source.find_last_not_of(values) + 1); } -std::string fmt::tolower(std::string source) -{ - std::transform(source.begin(), source.end(), source.begin(), ::tolower); - - return source; -} - -std::string fmt::toupper(std::string source) -{ - std::transform(source.begin(), source.end(), source.begin(), ::toupper); - - return source; -} - -std::string fmt::escape(std::string source) +std::string fmt::escape(const std::string& source, std::initializer_list more) { const std::pair escape_list[] = { @@ -244,20 +147,66 @@ std::string fmt::escape(std::string source) { "\a", "\\a" }, { "\b", "\\b" }, { "\f", "\\f" }, - { "\n", "\\n\n" }, + { "\n", "\\n" }, { "\r", "\\r" }, { "\t", "\\t" }, { "\v", "\\v" }, }; - source = fmt::replace_all(source, escape_list); + std::string result = fmt::replace_all(source, escape_list); for (char c = 0; c < 32; c++) { - if (c != '\n') source = fmt::replace_all(source, std::string(1, c), fmt::format("\\x%02X", c)); + result = fmt::replace_all(result, std::string(1, c), fmt::format("\\x%02X", c)); } - return source; + for (char c : more) + { + result = fmt::replace_all(result, std::string(1, c), fmt::format("\\x%02X", c)); + } + + return result; +} + +std::string fmt::unescape(const std::string& source) +{ + std::string result; + + for (auto it = source.begin(); it != source.end();) + { + const char bs = *it++; + + if (bs == '\\' && it != source.end()) + { + switch (const char code = *it++) + { + case 'a': result += '\a'; break; + case 'b': result += '\b'; break; + case 'f': result += '\f'; break; + case 'n': result += '\n'; break; + case 'r': result += '\r'; break; + case 't': result += '\t'; break; + case 'v': result += '\v'; break; + case 'x': + { + // Detect hexadecimal character code (TODO) + if (source.end() - it >= 2) + { + result += std::stoi(std::string{ *it++, *it++ }, 0, 16); + } + + } + // Octal/unicode not supported + default: result += code; + } + } + else + { + result += bs; + } + } + + return result; } bool fmt::match(const std::string &source, const std::string &mask) diff --git a/Utilities/StrFmt.h b/Utilities/StrFmt.h index 02f544a58b..d88a4a7cef 100644 --- a/Utilities/StrFmt.h +++ b/Utilities/StrFmt.h @@ -1,95 +1,38 @@ #pragma once -class wxString; +#include +#include +#include +#include +#include + +#include "Platform.h" +#include "types.h" #if defined(_MSC_VER) && _MSC_VER <= 1800 #define snprintf _snprintf #endif +// Copy null-terminated string from std::string to char array with truncation +template +inline void strcpy_trunc(char(&dst)[N], const std::string& src) +{ + const std::size_t count = src.size() >= N ? N - 1 : src.size(); + std::memcpy(dst, src.c_str(), count); + dst[count] = '\0'; +} + +// Copy null-terminated string from char array to another char array with truncation +template +inline void strcpy_trunc(char(&dst)[N], const char(&src)[N2]) +{ + const std::size_t count = N2 >= N ? N - 1 : N2; + std::memcpy(dst, src, count); + dst[count] = '\0'; +} + namespace fmt { - //struct empty_t{}; - - //extern const std::string placeholder; - - template - std::string AfterLast(const std::string& source, T searchstr) - { - size_t search_pos = source.rfind(searchstr); - search_pos = search_pos == std::string::npos ? 0 : search_pos; - return source.substr(search_pos); - } - - template - std::string BeforeLast(const std::string& source, T searchstr) - { - size_t search_pos = source.rfind(searchstr); - search_pos = search_pos == std::string::npos ? 0 : search_pos; - return source.substr(0, search_pos); - } - - template - std::string AfterFirst(const std::string& source, T searchstr) - { - size_t search_pos = source.find(searchstr); - search_pos = search_pos == std::string::npos ? 0 : search_pos; - return source.substr(search_pos); - } - - template - std::string BeforeFirst(const std::string& source, T searchstr) - { - size_t search_pos = source.find(searchstr); - search_pos = search_pos == std::string::npos ? 0 : search_pos; - return source.substr(0, search_pos); - } - - // write `fmt` from `pos` to the first occurence of `fmt::placeholder` to - // the stream `os`. Then write `arg` to to the stream. If there's no - // `fmt::placeholder` after `pos` everything in `fmt` after pos is written - // to `os`. Then `arg` is written to `os` after appending a space character - //template - //empty_t write(const std::string &fmt, std::ostream &os, std::string::size_type &pos, T &&arg) - //{ - // std::string::size_type ins = fmt.find(placeholder, pos); - - // if (ins == std::string::npos) - // { - // os.write(fmt.data() + pos, fmt.size() - pos); - // os << ' ' << arg; - - // pos = fmt.size(); - // } - // else - // { - // os.write(fmt.data() + pos, ins - pos); - // os << arg; - - // pos = ins + placeholder.size(); - // } - // return{}; - //} - - // typesafe version of a sprintf-like function. Returns the printed to - // string. To mark positions where the arguments are supposed to be - // inserted use `fmt::placeholder`. If there's not enough placeholders - // the rest of the arguments are appended at the end, seperated by spaces - //template - //std::string SFormat(const std::string &fmt, Args&& ... parameters) - //{ - // std::ostringstream os; - // std::string::size_type pos = 0; - // std::initializer_list { write(fmt, os, pos, parameters)... }; - - // if (!fmt.empty()) - // { - // os.write(fmt.data() + pos, fmt.size() - pos); - // } - - // std::string result = os.str(); - // return result; - //} - std::string replace_first(const std::string& src, const std::string& from, const std::string& to); std::string replace_all(const std::string &src, const std::string& from, const std::string& to); @@ -145,7 +88,8 @@ namespace fmt std::string to_udec(u64 value); std::string to_sdec(s64 value); - template::value> struct unveil + template + struct unveil { using result_type = T; @@ -155,37 +99,41 @@ namespace fmt } }; - template<> struct unveil + template<> + struct unveil { using result_type = const char* const; - force_inline static result_type get_value(const char* const& arg) + static result_type get_value(const char* const& arg) { return arg; } }; - template struct unveil + template + struct unveil { using result_type = const char* const; - force_inline static result_type get_value(const char(&arg)[N]) + static result_type get_value(const char(&arg)[N]) { return arg; } }; - template<> struct unveil + template<> + struct unveil { using result_type = const char*; - force_inline static result_type get_value(const std::string& arg) + static result_type get_value(const std::string& arg) { return arg.c_str(); } }; - template struct unveil + template + struct unveil::value>> { using result_type = std::underlying_type_t; @@ -195,31 +143,13 @@ namespace fmt } }; - template struct unveil, false> - { - using result_type = typename unveil::result_type; - - force_inline static result_type get_value(const se_t& arg) - { - return unveil::get_value(arg); - } - }; - template force_inline typename unveil::result_type do_unveil(const T& arg) { return unveil::get_value(arg); } - // Formatting function with special functionality: - // - // std::string is forced to .c_str() - // be_t<> is forced to .value() (fmt::do_unveil reverts byte order automatically) - // - // External specializations for fmt::do_unveil (can be found in another headers): - // vm::ptr, vm::bptr, ... (fmt::do_unveil) (vm_ptr.h) (with appropriate address type, using .addr() can be avoided) - // vm::ref, vm::bref, ... (fmt::do_unveil) (vm_ref.h) - // + // Formatting function with special functionality (fmt::unveil) template safe_buffers std::string format(const char* fmt, const Args&... args) { @@ -256,51 +186,28 @@ namespace fmt } } - struct exception : public std::exception + // Create exception of type T (std::runtime_error by default) with formatting + template + never_inline safe_buffers T exception(const char* fmt, const Args&... args) noexcept(noexcept(T{ fmt })) { - std::unique_ptr message; + return T{ format(fmt, do_unveil(args)...).c_str() }; + } - template never_inline safe_buffers exception(const char* file, int line, const char* func, const char* text, Args... args) noexcept - { - const std::string data = format(text, args...) + format("\n(in file %s:%d, in function %s)", file, line, func); + // Create exception of type T (std::runtime_error by default) without formatting + template + safe_buffers T exception(const char* msg) noexcept(noexcept(T{ msg })) + { + return T{ msg }; + } - message.reset(new char[data.size() + 1]); - - std::memcpy(message.get(), data.c_str(), data.size() + 1); - } - - exception(const exception& other) noexcept - { - const std::size_t size = std::strlen(other.message.get()); - - message.reset(new char[size + 1]); - - std::memcpy(message.get(), other.message.get(), size + 1); - } - - virtual const char* what() const noexcept override - { - return message.get(); - } - }; - - //convert a wxString to a std::string encoded in utf8 - //CAUTION, only use this to interface with wxWidgets classes - std::string ToUTF8(const wxString& right); - - //convert a std::string encoded in utf8 to a wxString - //CAUTION, only use this to interface with wxWidgets classes - wxString FromUTF8(const std::string& right); - - //TODO: remove this after every snippet that uses it is gone - //WARNING: not fully compatible with CmpNoCase from wxString - int CmpNoCase(const std::string& a, const std::string& b); - - //TODO: remove this after every snippet that uses it is gone - //WARNING: not fully compatible with Replace from wxString - void Replace(std::string &str, const std::string &searchterm, const std::string& replaceterm); - - std::vector rSplit(const std::string& source, const std::string& delim); + // Narrow cast (similar to gsl::narrow) with exception message formatting + template + inline auto narrow(const char* format_str, const From& value, const Args&... args) -> decltype(static_cast(static_cast(std::declval()))) + { + const auto result = static_cast(value); + if (static_cast(result) != value) throw fmt::exception(format_str, fmt::do_unveil(value), fmt::do_unveil(args)...); + return result; + } std::vector split(const std::string& source, std::initializer_list separators, bool is_skip_empty = true); std::string trim(const std::string& source, const std::string& values = " \t"); @@ -352,8 +259,35 @@ namespace fmt return result; } - std::string tolower(std::string source); - std::string toupper(std::string source); - std::string escape(std::string source); + template + std::string to_lower(IT _begin, IT _end) + { + std::string result; result.resize(_end - _begin); + std::transform(_begin, _end, result.begin(), ::tolower); + return result; + } + + template + std::string to_lower(const T& string) + { + return to_lower(std::begin(string), std::end(string)); + } + + template + std::string to_upper(IT _begin, IT _end) + { + std::string result; result.resize(_end - _begin); + std::transform(_begin, _end, result.begin(), ::toupper); + return result; + } + + template + std::string to_upper(const T& string) + { + return to_upper(std::begin(string), std::end(string)); + } + + std::string escape(const std::string& source, std::initializer_list more = {}); + std::string unescape(const std::string& source); bool match(const std::string &source, const std::string &mask); } diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 61619466a3..1fd83ba5b0 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -1,11 +1,8 @@ #include "stdafx.h" -#include "Log.h" +#include "Emu/Memory/Memory.h" #include "Emu/System.h" -#include "Emu/state.h" -#include "Emu/CPU/CPUThreadManager.h" -#include "Emu/CPU/CPUThread.h" +#include "Emu/IdManager.h" #include "Emu/Cell/RawSPUThread.h" -#include "Emu/SysCalls/SysCalls.h" #include "Thread.h" #ifdef _WIN32 @@ -21,10 +18,19 @@ static void report_fatal_error(const std::string& msg) { + std::string _msg = msg + "\n" + "HOW TO REPORT ERRORS:\n" + "1) Check the FAQ, readme, other sources. Please ensure that your hardware and software configuration is compliant.\n" + "2) You must provide FULL information: how to reproduce the error (your actions), RPCS3.log file, other *.log files whenever requested.\n" + "3) Please ensure that your software (game) is 'Playable' or close. Please note that 'Non-playable' games will be ignored.\n" + "4) If the software (game) is not 'Playable', please ensure that this error is unexpected, i.e. it didn't happen before or similar.\n" + "Please, don't send incorrect reports. Thanks for understanding.\n"; + #ifdef _WIN32 - MessageBoxA(0, msg.c_str(), "Fatal error", MB_ICONERROR); // TODO: unicode message + _msg += "Press (Ctrl+C) to copy this message."; + MessageBoxA(0, _msg.c_str(), "Fatal error", MB_ICONERROR); // TODO: unicode message #else - std::printf("Fatal error: %s\nPlease report this error to the developers.\n", msg.c_str()); + std::printf("Fatal error: \n%s", _msg.c_str()); #endif } @@ -34,9 +40,9 @@ static void report_fatal_error(const std::string& msg) { throw; } - catch (const std::exception& ex) + catch (const std::exception& e) { - report_fatal_error("Unhandled exception: "s + ex.what()); + report_fatal_error("Unhandled exception of type '"s + typeid(e).name() + "': "s + e.what()); } catch (...) { @@ -775,8 +781,7 @@ size_t get_x64_access_size(x64_context* context, x64_op_t op, x64_reg_t reg, siz if (op == X64OP_CMPXCHG) { - // detect whether this instruction can't actually modify memory to avoid breaking reservation; - // this may theoretically cause endless loop, but it shouldn't be a problem if only load_sync() generates such instruction + // Detect whether the instruction can't actually modify memory to avoid breaking reservation u64 cmp, exch; if (!get_x64_reg_value(context, reg, d_size, i_size, cmp) || !get_x64_reg_value(context, X64R_RAX, d_size, i_size, exch)) { @@ -843,7 +848,7 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context) // check if address is RawSPU MMIO register if (addr - RAW_SPU_BASE_ADDR < (6 * RAW_SPU_OFFSET) && (addr % RAW_SPU_OFFSET) >= RAW_SPU_PROB_OFFSET) { - auto thread = Emu.GetCPU().GetRawSPUThread((addr - RAW_SPU_BASE_ADDR) / RAW_SPU_OFFSET); + auto thread = idm::get((addr - RAW_SPU_BASE_ADDR) / RAW_SPU_OFFSET); if (!thread) { @@ -1051,10 +1056,10 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context) switch (d_size) { - case 1: reg_value = sync_lock_test_and_set((u8*)vm::base_priv(addr), (u8)reg_value); break; - case 2: reg_value = sync_lock_test_and_set((u16*)vm::base_priv(addr), (u16)reg_value); break; - case 4: reg_value = sync_lock_test_and_set((u32*)vm::base_priv(addr), (u32)reg_value); break; - case 8: reg_value = sync_lock_test_and_set((u64*)vm::base_priv(addr), (u64)reg_value); break; + case 1: reg_value = ((atomic_t*)vm::base_priv(addr))->exchange((u8)reg_value); break; + case 2: reg_value = ((atomic_t*)vm::base_priv(addr))->exchange((u16)reg_value); break; + case 4: reg_value = ((atomic_t*)vm::base_priv(addr))->exchange((u32)reg_value); break; + case 8: reg_value = ((atomic_t*)vm::base_priv(addr))->exchange((u64)reg_value); break; default: return false; } @@ -1074,10 +1079,10 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context) switch (d_size) { - case 1: old_value = sync_val_compare_and_swap((u8*)vm::base_priv(addr), (u8)cmp_value, (u8)reg_value); break; - case 2: old_value = sync_val_compare_and_swap((u16*)vm::base_priv(addr), (u16)cmp_value, (u16)reg_value); break; - case 4: old_value = sync_val_compare_and_swap((u32*)vm::base_priv(addr), (u32)cmp_value, (u32)reg_value); break; - case 8: old_value = sync_val_compare_and_swap((u64*)vm::base_priv(addr), (u64)cmp_value, (u64)reg_value); break; + case 1: old_value = ((atomic_t*)vm::base_priv(addr))->compare_and_swap((u8)cmp_value, (u8)reg_value); break; + case 2: old_value = ((atomic_t*)vm::base_priv(addr))->compare_and_swap((u16)cmp_value, (u16)reg_value); break; + case 4: old_value = ((atomic_t*)vm::base_priv(addr))->compare_and_swap((u32)cmp_value, (u32)reg_value); break; + case 8: old_value = ((atomic_t*)vm::base_priv(addr))->compare_and_swap((u64)cmp_value, (u64)reg_value); break; default: return false; } @@ -1097,10 +1102,10 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context) switch (d_size) { - case 1: value &= sync_fetch_and_and((u8*)vm::base_priv(addr), (u8)value); break; - case 2: value &= sync_fetch_and_and((u16*)vm::base_priv(addr), (u16)value); break; - case 4: value &= sync_fetch_and_and((u32*)vm::base_priv(addr), (u32)value); break; - case 8: value &= sync_fetch_and_and((u64*)vm::base_priv(addr), (u64)value); break; + case 1: value = *(atomic_t*)vm::base_priv(addr) &= (u8)value; break; + case 2: value = *(atomic_t*)vm::base_priv(addr) &= (u16)value; break; + case 4: value = *(atomic_t*)vm::base_priv(addr) &= (u32)value; break; + case 8: value = *(atomic_t*)vm::base_priv(addr) &= (u64)value; break; default: return false; } @@ -1126,14 +1131,14 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context) // TODO: allow recovering from a page fault as a feature of PS3 virtual memory } -// Throw virtual memory access violation exception -[[noreturn]] void throw_access_violation(const char* cause, u32 address) // Don't change function definition +[[noreturn]] static void throw_access_violation(const char* cause, u64 addr) { - throw EXCEPTION("Access violation %s location 0x%08x", cause, address); + vm::throw_access_violation(addr, cause); + std::abort(); } // Modify context in order to convert hardware exception to C++ exception -void prepare_throw_access_violation(x64_context* context, const char* cause, u32 address) +static void prepare_throw_access_violation(x64_context* context, const char* cause, u32 address) { // Set throw_access_violation() call args (old register values are lost) ARG1(context) = (u64)cause; @@ -1151,14 +1156,17 @@ static LONG exception_handler(PEXCEPTION_POINTERS pExp) const u64 addr64 = pExp->ExceptionRecord->ExceptionInformation[1] - (u64)vm::base(0); const bool is_writing = pExp->ExceptionRecord->ExceptionInformation[0] != 0; - if (pExp->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && addr64 < 0x100000000ull && thread_ctrl::get_current() && handle_access_violation((u32)addr64, is_writing, pExp->ContextRecord)) + if (pExp->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && addr64 < 0x100000000ull) { - return EXCEPTION_CONTINUE_EXECUTION; - } - else - { - return EXCEPTION_CONTINUE_SEARCH; + vm::g_tls_fault_count++; + + if (thread_ctrl::get_current() && handle_access_violation((u32)addr64, is_writing, pExp->ContextRecord)) + { + return EXCEPTION_CONTINUE_EXECUTION; + } } + + return EXCEPTION_CONTINUE_SEARCH; } static LONG exception_filter(PEXCEPTION_POINTERS pExp) @@ -1170,8 +1178,9 @@ static LONG exception_filter(PEXCEPTION_POINTERS pExp) const u64 addr64 = pExp->ExceptionRecord->ExceptionInformation[1] - (u64)vm::base(0); const auto cause = pExp->ExceptionRecord->ExceptionInformation[0] != 0 ? "writing" : "reading"; - if (addr64 < 0x100000000ull) + if (!(vm::g_tls_fault_count & (1ull << 63)) && addr64 < 0x100000000ull) { + vm::g_tls_fault_count |= (1ull << 63); // Setup throw_access_violation() call on the context prepare_throw_access_violation(pExp->ContextRecord, cause, (u32)addr64); return EXCEPTION_CONTINUE_EXECUTION; @@ -1190,33 +1199,23 @@ static LONG exception_filter(PEXCEPTION_POINTERS pExp) } msg += fmt::format("Instruction address: %p.\n", pExp->ContextRecord->Rip); - msg += fmt::format("Image base: %p.", GetModuleHandle(NULL)); + msg += fmt::format("Image base: %p.\n", GetModuleHandle(NULL)); + + if (pExp->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) + { + msg += "\n" + "Illegal instruction exception occured.\n" + "Note that your CPU must support SSSE3 extension.\n"; + } // TODO: print registers and the callstack - // Exception specific messages - if (pExp->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) - { - msg += "\n\nAn internal access violation has occured." - "\nPlease only report this error to the developers, if you're an advanced user and have obtained a stack trace."; - } - else if (pExp->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) - { - msg += "\n\nAn internal illegal instruction exception has occured." - "\nRPCS3 requires a modern x86-64 CPU that supports SSSE3 (and SSE4.1 for PPU LLVM recompiler)." - "\nPlease make sure that your CPU supports these extensions."; - } - else - { - msg += "\n\nAn unknown internal exception has occured. Please report this to the developers.\nYou can press (Ctrl+C) to copy this message."; - } - // Report fatal error report_fatal_error(msg); return EXCEPTION_CONTINUE_SEARCH; } -const bool g_exception_handler_set = []() -> bool +const bool s_exception_handler_set = []() -> bool { if (!AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)exception_handler)) { @@ -1248,12 +1247,12 @@ static void signal_handler(int sig, siginfo_t* info, void* uct) const u64 addr64 = (u64)info->si_addr - (u64)vm::base(0); const auto cause = is_writing ? "writing" : "reading"; - // TODO: Exception specific informative messages - - if (addr64 < 0x100000000ull && thread_ctrl::get_current()) + if (addr64 < 0x100000000ull) { + vm::g_tls_fault_count++; + // Try to process access violation - if (!handle_access_violation((u32)addr64, is_writing, context)) + if (!thread_ctrl::get_current() || !handle_access_violation((u32)addr64, is_writing, context)) { // Setup throw_access_violation() call on the context prepare_throw_access_violation(context, cause, (u32)addr64); @@ -1267,7 +1266,7 @@ static void signal_handler(int sig, siginfo_t* info, void* uct) } } -const bool g_exception_handler_set = []() -> bool +const bool s_exception_handler_set = []() -> bool { struct ::sigaction sa; sa.sa_flags = SA_SIGINFO; @@ -1285,17 +1284,33 @@ const bool g_exception_handler_set = []() -> bool #endif -thread_local thread_ctrl* thread_ctrl::g_tls_this_thread = nullptr; +const bool s_self_test = []() -> bool +{ + // Find ret instruction + if ((*(u8*)throw_access_violation & 0xF6) == 0xC2) + { + std::abort(); + } + + return true; +}(); + +thread_local DECLARE(thread_ctrl::g_tls_this_thread) = nullptr; // TODO -std::atomic g_thread_count{ 0 }; +atomic_t g_thread_count{ 0 }; void thread_ctrl::initialize() { - SetCurrentThreadDebugName(g_tls_this_thread->m_name().c_str()); + SetCurrentThreadDebugName(g_tls_this_thread->m_name.c_str()); + + _log::g_tls_make_prefix = [](const auto&, auto, const auto&) + { + return g_tls_this_thread->m_name; + }; // TODO - g_thread_count++; + ++g_thread_count; } void thread_ctrl::finalize() noexcept @@ -1304,79 +1319,36 @@ void thread_ctrl::finalize() noexcept vm::reservation_free(); // TODO - g_thread_count--; + --g_thread_count; // Call atexit functions - for (const auto& func : decltype(m_atexit)(std::move(g_tls_this_thread->m_atexit))) - { - func(); - } + g_tls_this_thread->m_atexit.exec(); } -thread_ctrl::~thread_ctrl() -{ - m_thread.detach(); - - if (m_future.valid()) - { - try - { - m_future.get(); - } - catch (...) - { - catch_all_exceptions(); - } - } -} - -std::string thread_ctrl::get_name() const -{ - CHECK_ASSERTION(m_name); - - return m_name(); -} - -std::string named_thread_t::get_name() const +std::string named_thread::get_name() const { return fmt::format("('%s') Unnamed Thread", typeid(*this).name()); } -void named_thread_t::start() +void named_thread::start() { - CHECK_ASSERTION(!m_thread); + Expects(!m_thread); // Get shared_ptr instance (will throw if called from the constructor or the object has been created incorrectly) - auto ptr = shared_from_this(); - - // Make name getter - auto name = [wptr = std::weak_ptr(ptr), type = &typeid(*this)]() - { - // Return actual name if available - if (const auto ptr = wptr.lock()) - { - return ptr->get_name(); - } - else - { - return fmt::format("('%s') Deleted Thread", type->name()); - } - }; + auto&& ptr = shared_from_this(); // Run thread - m_thread = thread_ctrl::spawn(std::move(name), [thread = std::move(ptr)]() + m_thread = thread_ctrl::spawn(get_name(), [thread = std::move(ptr)]() { try { LOG_TRACE(GENERAL, "Thread started"); - thread->on_task(); - LOG_TRACE(GENERAL, "Thread ended"); } catch (const std::exception& e) { - LOG_FATAL(GENERAL, "Exception: %s", e.what()); + LOG_FATAL(GENERAL, "%s thrown: %s", typeid(e).name(), e.what()); Emu.Pause(); } catch (EmulationStopped) @@ -1388,9 +1360,9 @@ void named_thread_t::start() }); } -void named_thread_t::join() +void named_thread::join() { - CHECK_ASSERTION(m_thread != nullptr); + Expects(m_thread); try { @@ -1403,11 +1375,3 @@ void named_thread_t::join() throw; } } - -const std::function SQUEUE_ALWAYS_EXIT = [](){ return true; }; -const std::function SQUEUE_NEVER_EXIT = [](){ return false; }; - -bool squeue_test_exit() -{ - return Emu.IsStopped(); -} diff --git a/Utilities/Thread.h b/Utilities/Thread.h index 557a83d7a0..f8d098dfda 100644 --- a/Utilities/Thread.h +++ b/Utilities/Thread.h @@ -1,24 +1,96 @@ #pragma once +#include +#include +#include +#include +#include +#include + +#include "Platform.h" + // Will report exception and call std::abort() if put in catch(...) [[noreturn]] void catch_all_exceptions(); +// Simple list of void() functors +class task_stack +{ + struct task_base + { + std::unique_ptr next; + + virtual ~task_base() = default; + + virtual void exec() + { + if (next) + { + next->exec(); + } + } + }; + + std::unique_ptr m_stack; + + never_inline void push(std::unique_ptr task) + { + m_stack.swap(task->next); + m_stack.swap(task); + } + +public: + template + void push(F&& func) + { + struct task_t : task_base + { + std::remove_reference_t func; + + task_t(F&& func) + : func(std::forward(func)) + { + } + + void exec() override + { + func(); + task_base::exec(); + } + }; + + return push(std::unique_ptr{ new task_t(std::forward(func)) }); + } + + void reset() + { + m_stack.reset(); + } + + void exec() const + { + if (m_stack) + { + m_stack->exec(); + } + } +}; + // Thread control class class thread_ctrl final { static thread_local thread_ctrl* g_tls_this_thread; - // Name getter - std::function m_name; + // Fixed name + std::string m_name; // Thread handle (be careful) std::thread m_thread; - // Thread result - std::future m_future; + // Thread result (exception) + std::exception_ptr m_exception; // Functions scheduled at thread exit - std::deque> m_atexit; + task_stack m_atexit; // Called at the thread start static void initialize(); @@ -27,24 +99,41 @@ class thread_ctrl final static void finalize() noexcept; public: - template - thread_ctrl(T&& name) - : m_name(std::forward(name)) + template + thread_ctrl(N&& name) + : m_name(std::forward(name)) { } // Disable copy/move constructors and operators thread_ctrl(const thread_ctrl&) = delete; - ~thread_ctrl(); + ~thread_ctrl() + { + if (m_thread.joinable()) + { + m_thread.detach(); + } + } // Get thread name - std::string get_name() const; + const std::string& get_name() const + { + return m_name; + } - // Get future result (may throw) + // Get thread result (may throw) void join() { - return m_future.get(); + if (m_thread.joinable()) + { + m_thread.join(); + } + + if (auto&& e = std::move(m_exception)) + { + std::rethrow_exception(e); + } } // Get current thread (may be nullptr) @@ -54,12 +143,10 @@ public: } // Register function at thread exit (for the current thread) - template - static inline void at_exit(T&& func) + template + static inline void at_exit(F&& func) { - CHECK_ASSERTION(g_tls_this_thread); - - g_tls_this_thread->m_atexit.emplace_front(std::forward(func)); + return g_tls_this_thread->m_atexit.push(std::forward(func)); } // Named thread factory @@ -68,12 +155,9 @@ public: { auto ctrl = std::make_shared(std::forward(name)); - std::promise promise; - - ctrl->m_future = promise.get_future(); - - ctrl->m_thread = std::thread([ctrl, task = std::forward(func)](std::promise promise) + ctrl->m_thread = std::thread([ctrl, task = std::forward(func)]() { + // Initialize TLS variable g_tls_this_thread = ctrl.get(); try @@ -81,21 +165,21 @@ public: initialize(); task(); finalize(); - promise.set_value(); } catch (...) { finalize(); - promise.set_exception(std::current_exception()); - } - }, std::move(promise)); + // Set exception + ctrl->m_exception = std::current_exception(); + } + }); return ctrl; } }; -class named_thread_t : public std::enable_shared_from_this +class named_thread : public std::enable_shared_from_this { // Pointer to managed resource (shared with actual thread) std::shared_ptr m_thread; @@ -107,6 +191,27 @@ public: // Thread mutex for external use (can be used with `cv`) std::mutex mutex; + // Lock mutex, notify condition variable + void safe_notify() + { + // Lock for reliable notification, condition is assumed to be changed externally + std::unique_lock lock(mutex); + + cv.notify_one(); + } + + // ID initialization + virtual void on_init() + { + start(); + } + + // ID finalization + virtual void on_stop() + { + join(); + } + protected: // Thread task (called in the thread) virtual void on_task() = 0; @@ -114,19 +219,13 @@ protected: // Thread finalization (called after on_task) virtual void on_exit() {} - // ID initialization (called through id_aux_initialize) - virtual void on_id_aux_initialize() { start(); } - - // ID finalization (called through id_aux_finalize) - virtual void on_id_aux_finalize() { join(); } - public: - named_thread_t() = default; + named_thread() = default; - virtual ~named_thread_t() = default; + virtual ~named_thread() = default; // Deleted copy/move constructors + copy/move operators - named_thread_t(const named_thread_t&) = delete; + named_thread(const named_thread&) = delete; // Get thread name virtual std::string get_name() const; @@ -134,377 +233,40 @@ public: // Start thread (cannot be called from the constructor: should throw bad_weak_ptr in such case) void start(); - // Join thread (get future result) + // Join thread (get thread result) void join(); - // Check whether the thread is not in "empty state" - bool is_started() const { return m_thread.operator bool(); } + // Get thread_ctrl + const thread_ctrl* get_thread_ctrl() const + { + return m_thread.get(); + } // Compare with the current thread - bool is_current() const { CHECK_ASSERTION(m_thread); return thread_ctrl::get_current() == m_thread.get(); } - - // Get thread_ctrl - const thread_ctrl* get_thread_ctrl() const { return m_thread.get(); } - - friend void id_aux_initialize(named_thread_t* ptr) { ptr->on_id_aux_initialize(); } - friend void id_aux_finalize(named_thread_t* ptr) { ptr->on_id_aux_finalize(); } + bool is_current() const + { + return m_thread && thread_ctrl::get_current() == m_thread.get(); + } }; // Wrapper for named thread, joins automatically in the destructor, can only be used in function scope -class scope_thread_t final +class scope_thread final { std::shared_ptr m_thread; public: template - scope_thread_t(N&& name, F&& func) + scope_thread(N&& name, F&& func) : m_thread(thread_ctrl::spawn(std::forward(name), std::forward(func))) { } // Deleted copy/move constructors + copy/move operators - scope_thread_t(const scope_thread_t&) = delete; + scope_thread(const scope_thread&) = delete; // Destructor with exceptions allowed - ~scope_thread_t() noexcept(false) + ~scope_thread() noexcept(false) { m_thread->join(); } }; - -extern const std::function SQUEUE_ALWAYS_EXIT; -extern const std::function SQUEUE_NEVER_EXIT; - -bool squeue_test_exit(); - -template -class squeue_t -{ - struct squeue_sync_var_t - { - struct - { - u32 position : 31; - u32 pop_lock : 1; - }; - struct - { - u32 count : 31; - u32 push_lock : 1; - }; - }; - - atomic_t m_sync; - - mutable std::mutex m_rcv_mutex; - mutable std::mutex m_wcv_mutex; - mutable std::condition_variable m_rcv; - mutable std::condition_variable m_wcv; - - T m_data[sq_size]; - - enum squeue_sync_var_result : u32 - { - SQSVR_OK = 0, - SQSVR_LOCKED = 1, - SQSVR_FAILED = 2, - }; - -public: - squeue_t() - : m_sync(squeue_sync_var_t{}) - { - } - - u32 get_max_size() const - { - return sq_size; - } - - bool is_full() const - { - return m_sync.load().count == sq_size; - } - - bool push(const T& data, const std::function& test_exit) - { - u32 pos = 0; - - while (u32 res = m_sync.atomic_op([&pos](squeue_sync_var_t& sync) -> u32 - { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - - if (sync.push_lock) - { - return SQSVR_LOCKED; - } - if (sync.count == sq_size) - { - return SQSVR_FAILED; - } - - sync.push_lock = 1; - pos = sync.position + sync.count; - return SQSVR_OK; - })) - { - if (res == SQSVR_FAILED && (test_exit() || squeue_test_exit())) - { - return false; - } - - std::unique_lock wcv_lock(m_wcv_mutex); - m_wcv.wait_for(wcv_lock, std::chrono::milliseconds(1)); - } - - m_data[pos >= sq_size ? pos - sq_size : pos] = data; - - m_sync.atomic_op([](squeue_sync_var_t& sync) - { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - assert(sync.push_lock); - sync.push_lock = 0; - sync.count++; - }); - - m_rcv.notify_one(); - m_wcv.notify_one(); - return true; - } - - bool push(const T& data, const volatile bool* do_exit) - { - return push(data, [do_exit](){ return do_exit && *do_exit; }); - } - - force_inline bool push(const T& data) - { - return push(data, SQUEUE_NEVER_EXIT); - } - - force_inline bool try_push(const T& data) - { - return push(data, SQUEUE_ALWAYS_EXIT); - } - - bool pop(T& data, const std::function& test_exit) - { - u32 pos = 0; - - while (u32 res = m_sync.atomic_op([&pos](squeue_sync_var_t& sync) -> u32 - { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - - if (!sync.count) - { - return SQSVR_FAILED; - } - if (sync.pop_lock) - { - return SQSVR_LOCKED; - } - - sync.pop_lock = 1; - pos = sync.position; - return SQSVR_OK; - })) - { - if (res == SQSVR_FAILED && (test_exit() || squeue_test_exit())) - { - return false; - } - - std::unique_lock rcv_lock(m_rcv_mutex); - m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1)); - } - - data = m_data[pos]; - - m_sync.atomic_op([](squeue_sync_var_t& sync) - { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - assert(sync.pop_lock); - sync.pop_lock = 0; - sync.position++; - sync.count--; - if (sync.position == sq_size) - { - sync.position = 0; - } - }); - - m_rcv.notify_one(); - m_wcv.notify_one(); - return true; - } - - bool pop(T& data, const volatile bool* do_exit) - { - return pop(data, [do_exit](){ return do_exit && *do_exit; }); - } - - force_inline bool pop(T& data) - { - return pop(data, SQUEUE_NEVER_EXIT); - } - - force_inline bool try_pop(T& data) - { - return pop(data, SQUEUE_ALWAYS_EXIT); - } - - bool peek(T& data, u32 start_pos, const std::function& test_exit) - { - assert(start_pos < sq_size); - u32 pos = 0; - - while (u32 res = m_sync.atomic_op([&pos, start_pos](squeue_sync_var_t& sync) -> u32 - { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - - if (sync.count <= start_pos) - { - return SQSVR_FAILED; - } - if (sync.pop_lock) - { - return SQSVR_LOCKED; - } - - sync.pop_lock = 1; - pos = sync.position + start_pos; - return SQSVR_OK; - })) - { - if (res == SQSVR_FAILED && (test_exit() || squeue_test_exit())) - { - return false; - } - - std::unique_lock rcv_lock(m_rcv_mutex); - m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1)); - } - - data = m_data[pos >= sq_size ? pos - sq_size : pos]; - - m_sync.atomic_op([](squeue_sync_var_t& sync) - { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - assert(sync.pop_lock); - sync.pop_lock = 0; - }); - - m_rcv.notify_one(); - return true; - } - - bool peek(T& data, u32 start_pos, const volatile bool* do_exit) - { - return peek(data, start_pos, [do_exit](){ return do_exit && *do_exit; }); - } - - force_inline bool peek(T& data, u32 start_pos = 0) - { - return peek(data, start_pos, SQUEUE_NEVER_EXIT); - } - - force_inline bool try_peek(T& data, u32 start_pos = 0) - { - return peek(data, start_pos, SQUEUE_ALWAYS_EXIT); - } - - class squeue_data_t - { - T* const m_data; - const u32 m_pos; - const u32 m_count; - - squeue_data_t(T* data, u32 pos, u32 count) - : m_data(data) - , m_pos(pos) - , m_count(count) - { - } - - public: - T& operator [] (u32 index) - { - assert(index < m_count); - index += m_pos; - index = index < sq_size ? index : index - sq_size; - return m_data[index]; - } - }; - - void process(void(*proc)(squeue_data_t data)) - { - u32 pos, count; - - while (m_sync.atomic_op([&pos, &count](squeue_sync_var_t& sync) -> u32 - { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - - if (sync.pop_lock || sync.push_lock) - { - return SQSVR_LOCKED; - } - - pos = sync.position; - count = sync.count; - sync.pop_lock = 1; - sync.push_lock = 1; - return SQSVR_OK; - })) - { - std::unique_lock rcv_lock(m_rcv_mutex); - m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1)); - } - - proc(squeue_data_t(m_data, pos, count)); - - m_sync.atomic_op([](squeue_sync_var_t& sync) - { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - assert(sync.pop_lock && sync.push_lock); - sync.pop_lock = 0; - sync.push_lock = 0; - }); - - m_wcv.notify_one(); - m_rcv.notify_one(); - } - - void clear() - { - while (m_sync.atomic_op([](squeue_sync_var_t& sync) -> u32 - { - assert(sync.count <= sq_size); - assert(sync.position < sq_size); - - if (sync.pop_lock || sync.push_lock) - { - return SQSVR_LOCKED; - } - - sync.pop_lock = 1; - sync.push_lock = 1; - return SQSVR_OK; - })) - { - std::unique_lock rcv_lock(m_rcv_mutex); - m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1)); - } - - m_sync.exchange({}); - m_wcv.notify_one(); - m_rcv.notify_one(); - } -}; diff --git a/Utilities/VirtualMemory.cpp b/Utilities/VirtualMemory.cpp index 5a2d00e901..e0b2b2ed61 100644 --- a/Utilities/VirtualMemory.cpp +++ b/Utilities/VirtualMemory.cpp @@ -17,10 +17,10 @@ namespace memory_helper { #ifdef _WIN32 void* ret = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS); - CHECK_ASSERTION(ret != NULL); + Ensures(ret != NULL); #else void* ret = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); - CHECK_ASSERTION(ret != 0); + Ensures(ret != 0); #endif return ret; } @@ -28,18 +28,18 @@ namespace memory_helper void commit_page_memory(void* pointer, size_t page_size) { #ifdef _WIN32 - CHECK_ASSERTION(VirtualAlloc((u8*)pointer, page_size, MEM_COMMIT, PAGE_READWRITE) != NULL); + ASSERT(VirtualAlloc((u8*)pointer, page_size, MEM_COMMIT, PAGE_READWRITE) != NULL); #else - CHECK_ASSERTION(mprotect((u8*)pointer, page_size, PROT_READ | PROT_WRITE) != -1); + ASSERT(mprotect((u8*)pointer, page_size, PROT_READ | PROT_WRITE) != -1); #endif } void free_reserved_memory(void* pointer, size_t size) { #ifdef _WIN32 - CHECK_ASSERTION(VirtualFree(pointer, 0, MEM_RELEASE) != 0); + ASSERT(VirtualFree(pointer, 0, MEM_RELEASE) != 0); #else - CHECK_ASSERTION(munmap(pointer, size) == 0); + ASSERT(munmap(pointer, size) == 0); #endif } } diff --git a/Utilities/config_context.cpp b/Utilities/config_context.cpp deleted file mode 100644 index 69b953d19d..0000000000 --- a/Utilities/config_context.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "stdafx.h" -#include "config_context.h" -#include "StrFmt.h" -#include -#include - -void config_context_t::group::init() -{ - if(!m_cfg->m_groups[full_name()]) - m_cfg->m_groups[full_name()] = this; -} - -config_context_t::group::group(config_context_t* cfg, const std::string& name) - : m_cfg(cfg) - , m_name(name) - , m_parent(nullptr) -{ - init(); -} - -config_context_t::group::group(group* parent, const std::string& name) - : m_cfg(parent->m_cfg) - , m_name(name) - , m_parent(parent) -{ - init(); -} - -void config_context_t::group::set_parent(config_context_t* cfg) -{ - m_cfg = cfg; - init(); -} - -std::string config_context_t::group::name() const -{ - return m_name; -} - -std::string config_context_t::group::full_name() const -{ - if (m_parent) - return m_parent->full_name() + "/" + m_name; - - return m_name; -} - -void config_context_t::assign(const config_context_t& rhs) -{ - for (auto &rhs_g : rhs.m_groups) - { - auto g = m_groups.at(rhs_g.first); - - for (auto rhs_e : rhs_g.second->entries) - { - if (g->entries[rhs_e.first]) - g->entries[rhs_e.first]->value_from(rhs_e.second); - else - g->add_entry(rhs_e.first, rhs_e.second->string_value()); - } - } -} - -void config_context_t::deserialize(std::istream& stream) -{ - set_defaults(); - - uint line_index = 0; - std::string line; - group *current_group = nullptr; - - while (std::getline(stream, line)) - { - ++line_index; - line = fmt::trim(line); - - if (line.empty()) - continue; - - if (line.front() == '[' && line.back() == ']') - { - std::string group_name = line.substr(1, line.length() - 2); - - auto found = m_groups.find(group_name); - - if (found == m_groups.end()) - { - std::cerr << line_index << ": group '" << group_name << "' not exists. ignored" << std::endl; - current_group = nullptr; - continue; - } - - current_group = found->second; - continue; - } - - if (current_group == nullptr) - { - std::cerr << line_index << ": line '" << line << "' ignored, no group." << std::endl; - continue; - } - - auto name_value = fmt::split(line, { "=" }); - switch (name_value.size()) - { - case 1: - { - if (current_group->entries[fmt::trim(name_value[0])]) - current_group->entries[fmt::trim(name_value[0])]->string_value({}); - - else - current_group->add_entry(fmt::trim(name_value[0]), std::string{}); - } - break; - - default: - std::cerr << line_index << ": line '" << line << "' has more than one symbol '='. used only first" << std::endl; - case 2: - { - if (current_group->entries[fmt::trim(name_value[0])]) - current_group->entries[fmt::trim(name_value[0])]->string_value(fmt::trim(name_value[1])); - - else - current_group->add_entry(fmt::trim(name_value[0]), fmt::trim(name_value[1])); - } - break; - - } - } -} - -void config_context_t::serialize(std::ostream& stream) const -{ - for (auto &g : m_groups) - { - stream << "[" + g.first + "]" << std::endl; - - for (auto &e : g.second->entries) - { - stream << e.first << "=" << e.second->string_value() << std::endl; - } - - stream << std::endl; - } -} - -void config_context_t::set_defaults() -{ - for (auto &g : m_groups) - { - for (auto &e : g.second->entries) - { - e.second->to_default(); - } - } -} - -std::string config_context_t::to_string() const -{ - std::ostringstream result; - - serialize(result); - - return result.str(); -} - -void config_context_t::from_string(const std::string& str) -{ - std::istringstream source(str); - - deserialize(source); -} diff --git a/Utilities/config_context.h b/Utilities/config_context.h deleted file mode 100644 index 3263a38532..0000000000 --- a/Utilities/config_context.h +++ /dev/null @@ -1,164 +0,0 @@ -#pragma once -#include -#include -#include "convert.h" - -class config_context_t -{ -public: - class entry_base; - -protected: - class group - { - group* m_parent; - config_context_t* m_cfg; - std::string m_name; - std::vector> m_entries; - - void init(); - - public: - std::unordered_map entries; - - group(config_context_t* cfg, const std::string& name); - group(group* parent, const std::string& name); - void set_parent(config_context_t* cfg); - - std::string name() const; - std::string full_name() const; - - template - void add_entry(const std::string& name, const T& def_value) - { - m_entries.emplace_back(std::make_unique>(this, name, def_value)); - } - - template - T get_entry_value(const std::string& name, const T& def_value) - { - if (!entries[name]) - add_entry(name, def_value); - - return convert::to(entries[name]->string_value()); - } - - template - void set_entry_value(const std::string& name, const T& value) - { - if (entries[name]) - entries[name]->string_value(convert::to(value)); - - else - add_entry(name, value); - } - - friend config_context_t; - }; - -public: - class entry_base - { - public: - virtual ~entry_base() = default; - virtual std::string name() = 0; - virtual void to_default() = 0; - virtual std::string string_value() = 0; - virtual void string_value(const std::string& value) = 0; - virtual void value_from(const entry_base* rhs) = 0; - }; - - template - class entry : public entry_base - { - T m_default_value; - T m_value; - group* m_parent; - std::string m_name; - - public: - entry(group* parent, const std::string& name, const T& default_value) - : m_parent(parent) - , m_name(name) - , m_default_value(default_value) - , m_value(default_value) - { - if(!parent->entries[name]) - parent->entries[name] = this; - } - - T default_value() const - { - return m_default_value; - } - - T value() const - { - return m_value; - } - - void value(const T& new_value) - { - m_value = new_value; - } - - std::string name() override - { - return m_name; - } - - void to_default() override - { - value(default_value()); - } - - std::string string_value() override - { - return convert::to(value()); - } - - void string_value(const std::string &new_value) override - { - value(convert::to(new_value)); - } - - void value_from(const entry_base* rhs) override - { - value(static_cast(rhs)->value()); - } - - entry& operator = (const T& new_value) - { - value(new_value); - return *this; - } - - template - entry& operator = (const T2& new_value) - { - value(static_cast(new_value)); - return *this; - } - - explicit operator const T&() const - { - return m_value; - } - }; - -private: - std::unordered_map m_groups; - -public: - config_context_t() = default; - - void assign(const config_context_t& rhs); - - void serialize(std::ostream& stream) const; - void deserialize(std::istream& stream); - - void set_defaults(); - - std::string to_string() const; - void from_string(const std::string&); -}; diff --git a/Utilities/convert.h b/Utilities/convert.h deleted file mode 100644 index b190737fcb..0000000000 --- a/Utilities/convert.h +++ /dev/null @@ -1,279 +0,0 @@ -#pragma once -#include -#include "types.h" - -namespace convert -{ - template - struct to_impl_t; - - template - struct to_impl_t - { - static Type func(const Type& value) - { - return value; - } - }; - - template<> - struct to_impl_t - { - static std::string func(bool value) - { - return value ? "true" : "false"; - } - }; - - template<> - struct to_impl_t - { - static bool func(const std::string& value) - { - return value == "true" ? true : value == "false" ? false : throw std::invalid_argument(__FUNCTION__); - } - }; - - template<> - struct to_impl_t - { - static std::string func(signed char value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(unsigned char value) - { - return std::to_string(value); - } - }; - - - template<> - struct to_impl_t - { - static std::string func(short value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(unsigned short value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(int value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(unsigned int value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(long value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(unsigned long value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(long long value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(unsigned long long value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(float value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(double value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(long double value) - { - return std::to_string(value); - } - }; - - template<> - struct to_impl_t - { - static std::string func(size2i value) - { - return std::to_string(value.width) + "x" + std::to_string(value.height); - } - }; - - template<> - struct to_impl_t - { - static std::string func(position2i value) - { - return std::to_string(value.x) + ":" + std::to_string(value.y); - } - }; - - template<> - struct to_impl_t - { - static int func(const std::string& value) - { - return std::stoi(value); - } - }; - - template<> - struct to_impl_t - { - static unsigned int func(const std::string& value) - { - return (unsigned long)std::stoul(value); - } - }; - - template<> - struct to_impl_t - { - static long func(const std::string& value) - { - return std::stol(value); - } - }; - - template<> - struct to_impl_t - { - static unsigned long func(const std::string& value) - { - return std::stoul(value); - } - }; - - template<> - struct to_impl_t - { - static long long func(const std::string& value) - { - return std::stoll(value); - } - }; - - template<> - struct to_impl_t - { - static unsigned long long func(const std::string& value) - { - return std::stoull(value); - } - }; - - template<> - struct to_impl_t - { - static float func(const std::string& value) - { - return std::stof(value); - } - }; - - template<> - struct to_impl_t - { - static double func(const std::string& value) - { - return std::stod(value); - } - }; - - template<> - struct to_impl_t - { - static long double func(const std::string& value) - { - return std::stold(value); - } - }; - - template<> - struct to_impl_t - { - static size2i func(const std::string& value) - { - const auto& data = fmt::split(value, { "x" }); - return { std::stoi(data[0]), std::stoi(data[1]) }; - } - }; - - template<> - struct to_impl_t - { - static position2i func(const std::string& value) - { - const auto& data = fmt::split(value, { ":" }); - return { std::stoi(data[0]), std::stoi(data[1]) }; - } - }; - - template - ReturnType to(FromType value) - { - return to_impl_t, std::remove_all_extents_t>::func(value); - } -} diff --git a/Utilities/rPlatform.cpp b/Utilities/rPlatform.cpp deleted file mode 100644 index 53f7e783d7..0000000000 --- a/Utilities/rPlatform.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "stdafx.h" -#include "restore_new.h" -#include "Utilities/Log.h" -#pragma warning(push) -#pragma message("TODO: remove wx dependency: ") -#pragma warning(disable : 4996) -#include -#pragma warning(pop) -#include "define_new_memleakdetect.h" - -#ifndef _WIN32 -#include -#endif - -#include "rPlatform.h" - -rImage::rImage() -{ - handle = static_cast(new wxImage()); -} - -rImage::~rImage() -{ - delete static_cast(handle); -} - -void rImage::Create(int width, int height, void *data, void *alpha) -{ - static_cast(handle)->Create(width, height, static_cast(data), static_cast(alpha)); -} -void rImage::SaveFile(const std::string& name, rImageType type) -{ - if (type == rBITMAP_TYPE_PNG) - { - static_cast(handle)->SaveFile(fmt::FromUTF8(name),wxBITMAP_TYPE_PNG); - } - else - { - throw EXCEPTION("unsupported type"); - } -} diff --git a/Utilities/rPlatform.h b/Utilities/rPlatform.h deleted file mode 100644 index 09308edb53..0000000000 --- a/Utilities/rPlatform.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -/********************************************************************** -*********** RSX Debugger -************************************************************************/ - -struct RSXDebuggerProgram -{ - u32 id; - u32 vp_id; - u32 fp_id; - std::string vp_shader; - std::string fp_shader; - bool modified; - - RSXDebuggerProgram() - : modified(false) - { - } -}; - -extern std::vector m_debug_programs; - -/********************************************************************** -*********** Image stuff -************************************************************************/ -enum rImageType -{ - rBITMAP_TYPE_PNG -}; -struct rImage -{ - rImage(); - rImage(const rImage &) = delete; - ~rImage(); - void Create(int width , int height, void *data, void *alpha); - void SaveFile(const std::string& name, rImageType type); - - void *handle; -}; diff --git a/Utilities/rTime.cpp b/Utilities/rTime.cpp deleted file mode 100644 index 9687141302..0000000000 --- a/Utilities/rTime.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "stdafx.h" -#include "rTime.h" -#pragma warning(push) -#pragma message("TODO: remove wx dependency: ") -#pragma warning(disable : 4996) -#include -#pragma warning(pop) - -std::string rDefaultDateTimeFormat = "%c"; - -rTimeSpan::rTimeSpan() -{ - handle = static_cast(new wxTimeSpan()); -} - -rTimeSpan::~rTimeSpan() -{ - delete static_cast(handle); -} - -rTimeSpan::rTimeSpan(const rTimeSpan& other) - -{ - handle = static_cast(new wxTimeSpan(*static_cast(other.handle))); - -} - -rTimeSpan::rTimeSpan(int a, int b , int c, int d) -{ - handle = static_cast(new wxTimeSpan(a,b,c,d)); -} - - -rDateSpan::rDateSpan() -{ - handle = static_cast(new wxDateSpan()); -} - -rDateSpan::~rDateSpan() -{ - delete static_cast(handle); -} - -rDateSpan::rDateSpan(const rDateSpan& other) -{ - handle = static_cast(new wxDateSpan(*static_cast(other.handle))); -} - -rDateSpan::rDateSpan(int a, int b, int c, int d) -{ - handle = static_cast(new wxDateSpan(a,b,c,d)); -} - -rDateTime::rDateTime() -{ - handle = static_cast(new wxDateTime()); -} - -rDateTime::~rDateTime() -{ - delete static_cast(handle); -} - -rDateTime::rDateTime(const rDateTime& other) -{ - handle = static_cast(new wxDateTime(*static_cast(other.handle))); -} - -rDateTime::rDateTime(const time_t& time) -{ - handle = static_cast(new wxDateTime(time)); -} - -rDateTime::rDateTime(u16 day, rDateTime::Month month, u16 year, u16 hour, u16 minute, u16 second, u32 millisecond) -{ - handle = static_cast(new wxDateTime(day,(wxDateTime::Month)month,year,hour,minute,second,millisecond)); -} - -rDateTime rDateTime::UNow() -{ - rDateTime time; - delete static_cast(time.handle); - time.handle = static_cast(new wxDateTime(wxDateTime::UNow())); - - return time; -} - -rDateTime rDateTime::FromUTC(bool val) -{ - rDateTime time(*this); - void *temp = time.handle; - - time.handle = static_cast(new wxDateTime(static_cast(temp)->FromTimezone(wxDateTime::GMT0, val))); - delete static_cast(temp); - - return time; -} - -rDateTime rDateTime::ToUTC(bool val) -{ - rDateTime time(*this); - void *temp = time.handle; - - time.handle = static_cast(new wxDateTime(static_cast(temp)->ToTimezone(wxDateTime::GMT0, val))); - delete static_cast(temp); - - return time; -} - -time_t rDateTime::GetTicks() -{ - return static_cast(handle)->GetTicks(); -} - -void rDateTime::Add(const rTimeSpan& span) -{ - static_cast(handle)->Add(*static_cast(span.handle)); -} - -void rDateTime::Add(const rDateSpan& span) -{ - static_cast(handle)->Add(*static_cast(span.handle)); -} - -wxDateTime::TimeZone convertTZ(rDateTime::rTimeZone tz) -{ - switch (tz) - { - case rDateTime::Local: - return wxDateTime::Local; - case rDateTime::GMT0: - return wxDateTime::GMT0; - case rDateTime::UTC: - return wxDateTime::UTC; - default: - throw EXCEPTION("WRONG DATETIME"); - } -} - -std::string rDateTime::Format(const std::string &format, const rTimeZone &tz) const -{ - return fmt::ToUTF8(static_cast(handle)->Format(fmt::FromUTF8(format),convertTZ(tz))); -} - -void rDateTime::ParseDateTime(const char* format) -{ - static_cast(handle)->ParseDateTime(format); -} - -u32 rDateTime::GetAsDOS() -{ - return static_cast(handle)->GetAsDOS(); -} - -rDateTime &rDateTime::SetFromDOS(u32 fromdos) -{ - static_cast(handle)->SetFromDOS(fromdos); - return *this; -} - -bool rDateTime::IsLeapYear(int year, rDateTime::Calender cal) -{ - if (cal == Gregorian) - { - return wxDateTime::IsLeapYear(year, wxDateTime::Gregorian); - } - else - { - return wxDateTime::IsLeapYear(year, wxDateTime::Julian); - } -} - -int rDateTime::GetNumberOfDays(rDateTime::Month month, int year, rDateTime::Calender cal) -{ - if (cal == Gregorian) - { - return wxDateTime::GetNumberOfDays(static_cast(month), year, wxDateTime::Gregorian); - } - else - { - return wxDateTime::GetNumberOfDays(static_cast(month), year, wxDateTime::Julian); - } -} - -void rDateTime::SetToWeekDay(rDateTime::WeekDay day, int n, rDateTime::Month month, int year) -{ - static_cast(handle)->SetToWeekDay( - static_cast(day) - , n - , static_cast(month) - , year - ); -} - -int rDateTime::GetWeekDay() -{ - return static_cast(handle)->GetWeekDay(); -} - -u16 rDateTime::GetYear(rDateTime::TZ timezone) -{ - return static_cast(handle)->GetYear(convertTZ(timezone)); -} - -u16 rDateTime::GetMonth(rDateTime::TZ timezone) -{ - return static_cast(handle)->GetMonth(convertTZ(timezone)); -} - -u16 rDateTime::GetDay(rDateTime::TZ timezone) -{ - return static_cast(handle)->GetDay(convertTZ(timezone)); -} - -u16 rDateTime::GetHour(rDateTime::TZ timezone) -{ - return static_cast(handle)->GetHour(convertTZ(timezone)); -} - -u16 rDateTime::GetMinute(rDateTime::TZ timezone) -{ - return static_cast(handle)->GetMinute(convertTZ(timezone)); -} - -u16 rDateTime::GetSecond(rDateTime::TZ timezone) -{ - return static_cast(handle)->GetSecond(convertTZ(timezone)); -} - -u32 rDateTime::GetMillisecond(rDateTime::TZ timezone) -{ - return static_cast(handle)->GetMillisecond(convertTZ(timezone)); -} - - diff --git a/Utilities/rTime.h b/Utilities/rTime.h deleted file mode 100644 index 9c39c46b30..0000000000 --- a/Utilities/rTime.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -extern std::string rDefaultDateTimeFormat; - - -struct rTimeSpan -{ - rTimeSpan(); - ~rTimeSpan(); - rTimeSpan(const rTimeSpan& other); - rTimeSpan(int, int, int, int); - - void *handle; -}; - -struct rDateSpan -{ - rDateSpan(); - ~rDateSpan(); - rDateSpan(const rDateSpan& other); - rDateSpan(int, int, int, int); - - void *handle; -}; - -struct rDateTime -{ - enum TZ - { - Local, GMT0,UTC - }; - enum Calender - { - Gregorian, Julian - }; - - using rTimeZone = TZ; - - enum WeekDay - { - Sun = 0, - Mon, - Tue, - Wed, - Thu, - Fri, - Sat, - Inv_WeekDay - }; - - enum Month { - Jan = 0, - Feb = 1, - Mar = 2, - Apr = 3, - May = 4, - Jun = 5, - Jul = 6, - Aug = 7, - Sep = 8, - Oct = 9, - Nov = 10, - Dec = 11, - Inv_Month = 12 - }; - - rDateTime(); - ~rDateTime(); - rDateTime(const rDateTime& other); - rDateTime(const time_t &time); - rDateTime(u16 day, rDateTime::Month month, u16 year, u16 hour, u16 minute, u16 second, u32 millisecond); - - static rDateTime UNow(); - rDateTime FromUTC(bool val); - rDateTime ToUTC(bool val); - time_t GetTicks(); - void Add(const rTimeSpan& span); - void Add(const rDateSpan& span); - void Close(); - std::string Format(const std::string &format = rDefaultDateTimeFormat, const rTimeZone &tz = Local) const; - - void ParseDateTime(const char* format); - - u32 GetAsDOS(); - rDateTime &SetFromDOS(u32 fromdos); - - static bool IsLeapYear(int year, rDateTime::Calender cal); - static int GetNumberOfDays(rDateTime::Month month, int year, rDateTime::Calender cal); - void SetToWeekDay(rDateTime::WeekDay day, int n, rDateTime::Month month, int year); - int GetWeekDay(); - - u16 GetYear( rDateTime::TZ timezone); - u16 GetMonth(rDateTime::TZ timezone); - u16 GetDay(rDateTime::TZ timezone); - u16 GetHour(rDateTime::TZ timezone); - u16 GetMinute(rDateTime::TZ timezone); - u16 GetSecond(rDateTime::TZ timezone); - u32 GetMillisecond(rDateTime::TZ timezone); - - void *handle; -}; - diff --git a/Utilities/types.h b/Utilities/types.h index ab011c2e3c..dd2f76e0a3 100644 --- a/Utilities/types.h +++ b/Utilities/types.h @@ -1,16 +1,21 @@ #pragma once +#include #include +#include +#include +#include #include #include -#include +#include "Platform.h" + +using schar = signed char; using uchar = unsigned char; using ushort = unsigned short; using uint = unsigned int; using ulong = unsigned long; using ullong = unsigned long long; - using llong = long long; using u8 = std::uint8_t; @@ -23,6 +28,350 @@ using s16 = std::int16_t; using s32 = std::int32_t; using s64 = std::int64_t; +// Specialization with static constexpr pair map[] member expected +template +struct bijective; + +template +struct atomic_storage; + +template +struct atomic_add; + +template +struct atomic_sub; + +template +struct atomic_and; + +template +struct atomic_or; + +template +struct atomic_xor; + +template +struct atomic_pre_inc; + +template +struct atomic_post_inc; + +template +struct atomic_pre_dec; + +template +struct atomic_post_dec; + +template +struct atomic_test_and_set; + +template +struct atomic_test_and_reset; + +template +struct atomic_test_and_complement; + +template +class atomic_t; + +namespace fmt +{ + template + struct unveil; +} + +// TODO: replace with std::void_t when available +namespace void_details +{ + template + struct make_void + { + using type = void; + }; +} + +template using void_t = typename void_details::make_void::type; + +// Extract T::simple_type if available, remove cv qualifiers +template +struct simple_type_helper +{ + using type = typename std::remove_cv::type; +}; + +template +struct simple_type_helper> +{ + using type = typename T::simple_type; +}; + +template using simple_t = typename simple_type_helper::type; + +// Bool type equivalent +class b8 +{ + std::uint8_t m_value; + +public: + b8() = default; + + constexpr b8(bool value) + : m_value(value) + { + } + + constexpr operator bool() const + { + return m_value != 0; + } +}; + +// Bool wrapper for restricting bool result conversions +struct explicit_bool_t +{ + const bool value; + + constexpr explicit_bool_t(bool value) + : value(value) + { + } + + explicit constexpr operator bool() const + { + return value; + } +}; + +#ifndef _MSC_VER +using u128 = __uint128_t; +using s128 = __int128_t; +#else + +#include "intrin.h" + +// Unsigned 128-bit integer implementation (TODO) +struct alignas(16) u128 +{ + std::uint64_t lo, hi; + + u128() = default; + + constexpr u128(std::uint64_t l) + : lo(l) + , hi(0) + { + } + + friend u128 operator +(const u128& l, const u128& r) + { + u128 value; + _addcarry_u64(_addcarry_u64(0, r.lo, l.lo, &value.lo), r.hi, l.hi, &value.hi); + return value; + } + + friend u128 operator +(const u128& l, std::uint64_t r) + { + u128 value; + _addcarry_u64(_addcarry_u64(0, r, l.lo, &value.lo), l.hi, 0, &value.hi); + return value; + } + + friend u128 operator +(std::uint64_t l, const u128& r) + { + u128 value; + _addcarry_u64(_addcarry_u64(0, r.lo, l, &value.lo), 0, r.hi, &value.hi); + return value; + } + + friend u128 operator -(const u128& l, const u128& r) + { + u128 value; + _subborrow_u64(_subborrow_u64(0, r.lo, l.lo, &value.lo), r.hi, l.hi, &value.hi); + return value; + } + + friend u128 operator -(const u128& l, std::uint64_t r) + { + u128 value; + _subborrow_u64(_subborrow_u64(0, r, l.lo, &value.lo), 0, l.hi, &value.hi); + return value; + } + + friend u128 operator -(std::uint64_t l, const u128& r) + { + u128 value; + _subborrow_u64(_subborrow_u64(0, r.lo, l, &value.lo), r.hi, 0, &value.hi); + return value; + } + + u128 operator +() const + { + return *this; + } + + u128 operator -() const + { + u128 value; + _subborrow_u64(_subborrow_u64(0, lo, 0, &value.lo), hi, 0, &value.hi); + return value; + } + + u128& operator ++() + { + _addcarry_u64(_addcarry_u64(0, 1, lo, &lo), 0, hi, &hi); + return *this; + } + + u128 operator ++(int) + { + u128 value = *this; + _addcarry_u64(_addcarry_u64(0, 1, lo, &lo), 0, hi, &hi); + return value; + } + + u128& operator --() + { + _subborrow_u64(_subborrow_u64(0, 1, lo, &lo), 0, hi, &hi); + return *this; + } + + u128 operator --(int) + { + u128 value = *this; + _subborrow_u64(_subborrow_u64(0, 1, lo, &lo), 0, hi, &hi); + return value; + } + + u128 operator ~() const + { + u128 value; + value.lo = ~lo; + value.hi = ~hi; + return value; + } + + friend u128 operator &(const u128& l, const u128& r) + { + u128 value; + value.lo = l.lo & r.lo; + value.hi = l.hi & r.hi; + return value; + } + + friend u128 operator |(const u128& l, const u128& r) + { + u128 value; + value.lo = l.lo | r.lo; + value.hi = l.hi | r.hi; + return value; + } + + friend u128 operator ^(const u128& l, const u128& r) + { + u128 value; + value.lo = l.lo ^ r.lo; + value.hi = l.hi ^ r.hi; + return value; + } + + u128& operator +=(const u128& r) + { + _addcarry_u64(_addcarry_u64(0, r.lo, lo, &lo), r.hi, hi, &hi); + return *this; + } + + u128& operator +=(uint64_t r) + { + _addcarry_u64(_addcarry_u64(0, r, lo, &lo), 0, hi, &hi); + return *this; + } + + u128& operator &=(const u128& r) + { + lo &= r.lo; + hi &= r.hi; + return *this; + } + + u128& operator |=(const u128& r) + { + lo |= r.lo; + hi |= r.hi; + return *this; + } + + u128& operator ^=(const u128& r) + { + lo ^= r.lo; + hi ^= r.hi; + return *this; + } +}; + +// Signed 128-bit integer implementation (TODO) +struct alignas(16) s128 +{ + std::uint64_t lo; + std::int64_t hi; + + s128() = default; + + constexpr s128(std::int64_t l) + : hi(l >> 63) + , lo(l) + { + } + + constexpr s128(std::uint64_t l) + : hi(0) + , lo(l) + { + } +}; +#endif + +namespace std +{ + /* Let's hack. */ + + template<> + struct is_integral : true_type + { + }; + + template<> + struct is_integral : true_type + { + }; + + template<> + struct make_unsigned + { + using type = u128; + }; + + template<> + struct make_unsigned + { + using type = u128; + }; + + template<> + struct make_signed + { + using type = s128; + }; + + template<> + struct make_signed + { + using type = s128; + }; +} + +static_assert(std::is_arithmetic::value && std::is_integral::value && alignof(u128) == 16 && sizeof(u128) == 16, "Wrong u128 implementation"); +static_assert(std::is_arithmetic::value && std::is_integral::value && alignof(s128) == 16 && sizeof(s128) == 16, "Wrong s128 implementation"); + union alignas(2) f16 { u16 _u16; @@ -55,6 +404,313 @@ struct ignore } }; +// Allows to define integer convertible to multiple enum types +template +struct multicast : multicast +{ + static_assert(std::is_enum::value, "multicast<> error: invalid conversion type (enum type expected)"); + + multicast() = default; + + template + constexpr multicast(const UT& value) + : multicast(value) + , m_value{ value } // Forbid narrowing + { + } + + constexpr operator T() const + { + // Cast to enum type + return static_cast(m_value); + } + +private: + std::underlying_type_t m_value; +}; + +// Recursion terminator +template<> +struct multicast +{ + multicast() = default; + + template + constexpr multicast(const UT& value) + { + } +}; + +// Small bitset for enum class types with available values [0, bitsize). +// T must be either enum type or convertible to (registered with via simple_t). +// Internal representation is single value of type T. +template +struct mset +{ + using type = simple_t; + using under = std::underlying_type_t; + + static constexpr auto bitsize = sizeof(type) * CHAR_BIT; + + mset() = default; + + constexpr mset(type _enum_const) + : m_value(static_cast(shift(_enum_const))) + { + } + + constexpr mset(under raw_value, const std::nothrow_t&) + : m_value(static_cast(raw_value)) + { + } + + // Get underlying value + constexpr under _value() const + { + return static_cast(m_value); + } + + explicit constexpr operator bool() const + { + return _value() ? true : false; + } + + mset& operator +=(mset rhs) + { + return *this = { _value() | rhs._value(), std::nothrow }; + } + + mset& operator -=(mset rhs) + { + return *this = { _value() & ~rhs._value(), std::nothrow }; + } + + mset& operator &=(mset rhs) + { + return *this = { _value() & rhs._value(), std::nothrow }; + } + + mset& operator ^=(mset rhs) + { + return *this = { _value() ^ rhs._value(), std::nothrow }; + } + + friend constexpr mset operator +(mset lhs, mset rhs) + { + return{ lhs._value() | rhs._value(), std::nothrow }; + } + + friend constexpr mset operator -(mset lhs, mset rhs) + { + return{ lhs._value() & ~rhs._value(), std::nothrow }; + } + + friend constexpr mset operator &(mset lhs, mset rhs) + { + return{ lhs._value() & rhs._value(), std::nothrow }; + } + + friend constexpr mset operator ^(mset lhs, mset rhs) + { + return{ lhs._value() ^ rhs._value(), std::nothrow }; + } + + bool test(mset rhs) const + { + const under v = _value(); + const under s = rhs._value(); + return (v & s) != 0; + } + + bool test_and_set(mset rhs) + { + const under v = _value(); + const under s = rhs._value(); + *this = { v | s, std::nothrow }; + return (v & s) != 0; + } + + bool test_and_reset(mset rhs) + { + const under v = _value(); + const under s = rhs._value(); + *this = { v & ~s, std::nothrow }; + return (v & s) != 0; + } + + bool test_and_complement(mset rhs) + { + const under v = _value(); + const under s = rhs._value(); + *this = { v ^ s, std::nothrow }; + return (v & s) != 0; + } + +private: + [[noreturn]] static under xrange() + { + throw std::out_of_range("mset<>: bit out of range"); + } + + static constexpr under shift(const T& value) + { + return static_cast(value) < bitsize ? static_cast(1) << static_cast(value) : xrange(); + } + + T m_value; +}; + +template +constexpr RT to_mset() +{ + return RT{}; +} + +// Fold enum constants into mset<> +template::value, mset, T>> +constexpr RT to_mset(Arg&& _enum_const, Args&&... args) +{ + return RT{ std::forward(_enum_const) } + to_mset(std::forward(args)...); +} + +template +struct atomic_add, CT, std::enable_if_t::value>> +{ + using under = typename mset::under; + + static force_inline mset op1(mset& left, mset right) + { + return{ atomic_storage::fetch_or(reinterpret_cast(left), right._value()), std::nothrow }; + } + + static constexpr auto fetch_op = &op1; + + static force_inline mset op2(mset& left, mset right) + { + return{ atomic_storage::or_fetch(reinterpret_cast(left), right._value()), std::nothrow }; + } + + static constexpr auto op_fetch = &op2; + static constexpr auto atomic_op = &op2; +}; + +template +struct atomic_sub, CT, std::enable_if_t::value>> +{ + using under = typename mset::under; + + static force_inline mset op1(mset& left, mset right) + { + return{ atomic_storage::fetch_and(reinterpret_cast(left), ~right._value()), std::nothrow }; + } + + static constexpr auto fetch_op = &op1; + + static force_inline mset op2(mset& left, mset right) + { + return{ atomic_storage::and_fetch(reinterpret_cast(left), ~right._value()), std::nothrow }; + } + + static constexpr auto op_fetch = &op2; + static constexpr auto atomic_op = &op2; +}; + +template +struct atomic_and, CT, std::enable_if_t::value>> +{ + using under = typename mset::under; + + static force_inline mset op1(mset& left, mset right) + { + return{ atomic_storage::fetch_and(reinterpret_cast(left), right._value()), std::nothrow }; + } + + static constexpr auto fetch_op = &op1; + + static force_inline mset op2(mset& left, mset right) + { + return{ atomic_storage::and_fetch(reinterpret_cast(left), right._value()), std::nothrow }; + } + + static constexpr auto op_fetch = &op2; + static constexpr auto atomic_op = &op2; +}; + +template +struct atomic_xor, CT, std::enable_if_t::value>> +{ + using under = typename mset::under; + + static force_inline mset op1(mset& left, mset right) + { + return{ atomic_storage::fetch_xor(reinterpret_cast(left), right._value()), std::nothrow }; + } + + static constexpr auto fetch_op = &op1; + + static force_inline mset op2(mset& left, mset right) + { + return{ atomic_storage::xor_fetch(reinterpret_cast(left), right._value()), std::nothrow }; + } + + static constexpr auto op_fetch = &op2; + static constexpr auto atomic_op = &op2; +}; + +template +struct atomic_test_and_set, T, std::enable_if_t::value>> +{ + using under = typename mset::under; + + static force_inline bool _op(mset& left, const T& value) + { + return atomic_storage::bts(reinterpret_cast(left), static_cast(value)); + } + + static constexpr auto atomic_op = &_op; +}; + +template +struct atomic_test_and_reset, T, std::enable_if_t::value>> +{ + using under = typename mset::under; + + static force_inline bool _op(mset& left, const T& value) + { + return atomic_storage::btr(reinterpret_cast(left), static_cast(value)); + } + + static constexpr auto atomic_op = &_op; +}; + +template +struct atomic_test_and_complement, T, std::enable_if_t::value>> +{ + using under = typename mset::under; + + static force_inline bool _op(mset& left, const T& value) + { + return atomic_storage::btc(reinterpret_cast(left), static_cast(value)); + } + + static constexpr auto atomic_op = &_op; +}; + +template +T2 bijective_find(const T& left, const DT& def = {}) +{ + for (const auto& pair : bijective::map) + { + if (pair.first == left) + { + return pair.second; + } + } + + return def; +} + + template struct size2_base { @@ -1088,15 +1744,3 @@ using color2d = color2_base; using color1i = color1_base; using color1f = color1_base; using color1d = color1_base; - -namespace std -{ - template<> - struct hash<::position2i> - { - size_t operator()(const ::position2i& position) const - { - return (static_cast(position.x) << 32) | position.y; - } - }; -} diff --git a/Utilities/wxWidgets/setup.h b/Utilities/wxWidgets/setup.h index 9cfcf48126..ff342b42ac 100644 --- a/Utilities/wxWidgets/setup.h +++ b/Utilities/wxWidgets/setup.h @@ -300,7 +300,7 @@ // Recommended setting: 0 as the options below already provide a relatively // good level of interoperability and changing this option arguably isn't worth // diverging from the official builds of the library. -#define wxUSE_STL 0 +#define wxUSE_STL 1 // This is not a real option but is used as the default value for // wxUSE_STD_IOSTREAM, wxUSE_STD_STRING and wxUSE_STD_CONTAINERS_COMPATIBLY. diff --git a/Utilities/yaml-cpp.vcxproj b/Utilities/yaml-cpp.vcxproj new file mode 100644 index 0000000000..5eae51ce94 --- /dev/null +++ b/Utilities/yaml-cpp.vcxproj @@ -0,0 +1,99 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {FDC361C5-7734-493B-8CFB-037308B35122} + yamlcpp + 8.1 + + + + StaticLibrary + Unicode + v140 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Utilities/yaml-cpp.vcxproj.filters b/Utilities/yaml-cpp.vcxproj.filters new file mode 100644 index 0000000000..7b035827f8 --- /dev/null +++ b/Utilities/yaml-cpp.vcxproj.filters @@ -0,0 +1,92 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Utilities/yaml-cpp.vcxproj.user b/Utilities/yaml-cpp.vcxproj.user new file mode 100644 index 0000000000..abe8dd8961 --- /dev/null +++ b/Utilities/yaml-cpp.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file