mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
feat(libc): implement the remaining Dinkumware _Atomic helpers
The 1-, 2- and 8-byte loads, stores, exchanges, compare-exchanges and fetch operations, _Atomic_is_lock_free_N, the flag and fence functions and the lock-based generic _Atomic_copy, _Atomic_exchange and _Atomic_compare_exchange_*, matching the libc.prx bundled with PPSA08427 including its handling of invalid memory orders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q8tinbP1MUpUaDsbNWnGnu
This commit is contained in:
1 parent
9121a9e455
commit
ec3fe412db
4 files changed
+306
No files matched your search
@@ -43,6 +43,7 @@ target_sources(libc PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Random.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Backtrace.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareAtomics.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/FileAndHeap.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/HostErrno.cpp
|
||||
@@ -371,6 +372,15 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_environment PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_dinkumware_atomics_tests ${LIBS_INCLUDE_DIR}/tests/GuestDinkumwareAtomics.cpp)
|
||||
target_include_directories(guest_dinkumware_atomics_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_dinkumware_atomics_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_dinkumware_atomics_tests)
|
||||
add_test(NAME guest_dinkumware_atomics COMMAND guest_dinkumware_atomics_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_dinkumware_atomics PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_hostname_tests ${LIBS_INCLUDE_DIR}/tests/GuestHostname.cpp)
|
||||
target_include_directories(guest_hostname_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_hostname_tests PRIVATE libc)
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int MemoryOrderSeqCst = 5;
|
||||
|
||||
template<typename TValue>
|
||||
void Store(volatile TValue* target, TValue value) {
|
||||
__atomic_store_n(target, value, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
template<typename TValue>
|
||||
TValue Load(volatile TValue* target) {
|
||||
return __atomic_load_n(target, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
bool IsValidOrder(int order) {
|
||||
return order >= 0 && order <= MemoryOrderSeqCst;
|
||||
}
|
||||
|
||||
template<typename TValue>
|
||||
TValue Exchange(volatile TValue* target, TValue value) {
|
||||
return __atomic_exchange_n(target, value, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
template<typename TValue>
|
||||
int CompareExchange(volatile TValue* target, TValue* expected, TValue desired) {
|
||||
return __atomic_compare_exchange_n(target, expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ? 1 : 0;
|
||||
}
|
||||
|
||||
void LockFlag(volatile unsigned char* flag) {
|
||||
while (__atomic_exchange_n(flag, static_cast<unsigned char>(1), __ATOMIC_ACQUIRE) != 0) {
|
||||
}
|
||||
}
|
||||
|
||||
void UnlockFlag(volatile unsigned char* flag) {
|
||||
__atomic_store_n(flag, static_cast<unsigned char>(0), __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
void CopyBytes(volatile void* destination, const volatile void* source, std::size_t size) {
|
||||
std::memcpy(const_cast<void*>(destination), const_cast<const void*>(source), size);
|
||||
}
|
||||
|
||||
int LockedCompareExchange(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired) {
|
||||
LockFlag(flag);
|
||||
void* object = const_cast<void*>(target);
|
||||
const bool equal = std::memcmp(object, expected, size) == 0;
|
||||
if (equal) {
|
||||
std::memcpy(object, desired, size);
|
||||
} else {
|
||||
std::memcpy(expected, object, size);
|
||||
}
|
||||
UnlockFlag(flag);
|
||||
return equal ? 1 : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { Store(target, value); }
|
||||
void APS5_VABI _Atomic_store_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { Store(target, value); }
|
||||
void APS5_VABI _Atomic_store_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { Store(target, value); }
|
||||
void APS5_VABI _Atomic_store_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int) { Store(target, value); }
|
||||
|
||||
std::uint8_t APS5_VABI _Atomic_load_1_nid_postfix(volatile std::uint8_t* target, int) { return Load(target); }
|
||||
std::uint16_t APS5_VABI _Atomic_load_2_nid_postfix(volatile std::uint16_t* target, int) { return Load(target); }
|
||||
std::uint64_t APS5_VABI _Atomic_load_8_nid_postfix(volatile std::uint64_t* target, int order) {
|
||||
if (order == 3 || order == 4 || !IsValidOrder(order)) return 0;
|
||||
return Load(target);
|
||||
}
|
||||
|
||||
std::uint8_t APS5_VABI _Atomic_exchange_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return Exchange(target, value); }
|
||||
std::uint16_t APS5_VABI _Atomic_exchange_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return Exchange(target, value); }
|
||||
std::uint32_t APS5_VABI _Atomic_exchange_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return Exchange(target, value); }
|
||||
std::uint64_t APS5_VABI _Atomic_exchange_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? Exchange(target, value) : 0; }
|
||||
|
||||
int APS5_VABI _Atomic_compare_exchange_strong_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int) {
|
||||
return CompareExchange(target, expected, desired);
|
||||
}
|
||||
|
||||
int APS5_VABI _Atomic_compare_exchange_strong_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int) {
|
||||
return CompareExchange(target, expected, desired);
|
||||
}
|
||||
|
||||
int APS5_VABI _Atomic_compare_exchange_strong_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t* expected, std::uint32_t desired, int, int) {
|
||||
return CompareExchange(target, expected, desired);
|
||||
}
|
||||
|
||||
int APS5_VABI _Atomic_compare_exchange_strong_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int) {
|
||||
return CompareExchange(target, expected, desired);
|
||||
}
|
||||
|
||||
int APS5_VABI _Atomic_compare_exchange_weak_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t* expected, std::uint8_t desired, int, int) {
|
||||
return CompareExchange(target, expected, desired);
|
||||
}
|
||||
|
||||
int APS5_VABI _Atomic_compare_exchange_weak_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t* expected, std::uint16_t desired, int, int) {
|
||||
return CompareExchange(target, expected, desired);
|
||||
}
|
||||
|
||||
int APS5_VABI _Atomic_compare_exchange_weak_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t* expected, std::uint64_t desired, int, int) {
|
||||
return CompareExchange(target, expected, desired);
|
||||
}
|
||||
|
||||
std::uint8_t APS5_VABI _Atomic_fetch_add_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint16_t APS5_VABI _Atomic_fetch_add_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint64_t APS5_VABI _Atomic_fetch_add_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_add(target, value, __ATOMIC_SEQ_CST) : 0; }
|
||||
std::uint8_t APS5_VABI _Atomic_fetch_sub_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint16_t APS5_VABI _Atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint64_t APS5_VABI _Atomic_fetch_sub_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_sub(target, value, __ATOMIC_SEQ_CST) : 0; }
|
||||
std::uint8_t APS5_VABI _Atomic_fetch_and_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint16_t APS5_VABI _Atomic_fetch_and_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint32_t APS5_VABI _Atomic_fetch_and_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint64_t APS5_VABI _Atomic_fetch_and_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST) : 0; }
|
||||
std::uint8_t APS5_VABI _Atomic_fetch_or_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint16_t APS5_VABI _Atomic_fetch_or_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint32_t APS5_VABI _Atomic_fetch_or_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint64_t APS5_VABI _Atomic_fetch_or_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST) : 0; }
|
||||
std::uint8_t APS5_VABI _Atomic_fetch_xor_1_nid_postfix(volatile std::uint8_t* target, std::uint8_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint16_t APS5_VABI _Atomic_fetch_xor_2_nid_postfix(volatile std::uint16_t* target, std::uint16_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint32_t APS5_VABI _Atomic_fetch_xor_4_nid_postfix(volatile std::uint32_t* target, std::uint32_t value, int) { return __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST); }
|
||||
std::uint64_t APS5_VABI _Atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t* target, std::uint64_t value, int order) { return IsValidOrder(order) ? __atomic_fetch_xor(target, value, __ATOMIC_SEQ_CST) : 0; }
|
||||
|
||||
int APS5_VABI _Atomic_is_lock_free_1_nid_postfix() { return 1; }
|
||||
int APS5_VABI _Atomic_is_lock_free_2_nid_postfix() { return 1; }
|
||||
int APS5_VABI _Atomic_is_lock_free_4_nid_postfix() { return 1; }
|
||||
int APS5_VABI _Atomic_is_lock_free_8_nid_postfix() { return 1; }
|
||||
|
||||
int APS5_VABI _Atomic_flag_test_and_set_nid_postfix(volatile unsigned char* flag, int) {
|
||||
return __atomic_exchange_n(flag, static_cast<unsigned char>(1), __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
void APS5_VABI _Atomic_flag_clear_nid_postfix(volatile unsigned char* flag, int) {
|
||||
__atomic_store_n(flag, static_cast<unsigned char>(0), __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
void APS5_VABI _Atomic_thread_fence_nid_postfix(int order) {
|
||||
if (order == MemoryOrderSeqCst) __atomic_thread_fence(__ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
void APS5_VABI _Atomic_signal_fence_nid_postfix(int) {
|
||||
}
|
||||
|
||||
void APS5_VABI _Atomic_copy_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, const volatile void* source, int) {
|
||||
LockFlag(flag);
|
||||
CopyBytes(target, source, size);
|
||||
UnlockFlag(flag);
|
||||
}
|
||||
|
||||
void APS5_VABI _Atomic_exchange_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, volatile void* value, int) {
|
||||
LockFlag(flag);
|
||||
auto* object = static_cast<unsigned char*>(const_cast<void*>(target));
|
||||
auto* other = static_cast<unsigned char*>(const_cast<void*>(value));
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
const unsigned char byte = object[i];
|
||||
object[i] = other[i];
|
||||
other[i] = byte;
|
||||
}
|
||||
UnlockFlag(flag);
|
||||
}
|
||||
|
||||
int APS5_VABI _Atomic_compare_exchange_strong_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired, int, int) {
|
||||
return LockedCompareExchange(flag, size, target, expected, desired);
|
||||
}
|
||||
|
||||
int APS5_VABI _Atomic_compare_exchange_weak_nid_postfix(volatile unsigned char* flag, std::size_t size, volatile void* target, void* expected, const void* desired, int, int) {
|
||||
return LockedCompareExchange(flag, size, target, expected, desired);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
void APS5_VABI _Atomic_store_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int);
|
||||
void APS5_VABI _Atomic_store_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int);
|
||||
std::uint16_t APS5_VABI _Atomic_load_2_nid_postfix(volatile std::uint16_t*, int);
|
||||
std::uint64_t APS5_VABI _Atomic_load_8_nid_postfix(volatile std::uint64_t*, int);
|
||||
std::uint32_t APS5_VABI _Atomic_exchange_4_nid_postfix(volatile std::uint32_t*, std::uint32_t, int);
|
||||
std::uint64_t APS5_VABI _Atomic_exchange_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int);
|
||||
int APS5_VABI _Atomic_compare_exchange_strong_2_nid_postfix(volatile std::uint16_t*, std::uint16_t*, std::uint16_t, int, int);
|
||||
int APS5_VABI _Atomic_compare_exchange_weak_8_nid_postfix(volatile std::uint64_t*, std::uint64_t*, std::uint64_t, int, int);
|
||||
std::uint8_t APS5_VABI _Atomic_fetch_add_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int);
|
||||
std::uint64_t APS5_VABI _Atomic_fetch_add_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int);
|
||||
std::uint16_t APS5_VABI _Atomic_fetch_sub_2_nid_postfix(volatile std::uint16_t*, std::uint16_t, int);
|
||||
std::uint32_t APS5_VABI _Atomic_fetch_and_4_nid_postfix(volatile std::uint32_t*, std::uint32_t, int);
|
||||
std::uint8_t APS5_VABI _Atomic_fetch_or_1_nid_postfix(volatile std::uint8_t*, std::uint8_t, int);
|
||||
std::uint64_t APS5_VABI _Atomic_fetch_xor_8_nid_postfix(volatile std::uint64_t*, std::uint64_t, int);
|
||||
int APS5_VABI _Atomic_is_lock_free_4_nid_postfix();
|
||||
int APS5_VABI _Atomic_flag_test_and_set_nid_postfix(volatile unsigned char*, int);
|
||||
void APS5_VABI _Atomic_flag_clear_nid_postfix(volatile unsigned char*, int);
|
||||
void APS5_VABI _Atomic_thread_fence_nid_postfix(int);
|
||||
void APS5_VABI _Atomic_signal_fence_nid_postfix(int);
|
||||
void APS5_VABI _Atomic_copy_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, const volatile void*, int);
|
||||
void APS5_VABI _Atomic_exchange_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, volatile void*, int);
|
||||
int APS5_VABI _Atomic_compare_exchange_strong_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, void*, const void*, int, int);
|
||||
int APS5_VABI _Atomic_compare_exchange_weak_nid_postfix(volatile unsigned char*, std::size_t, volatile void*, void*, const void*, int, int);
|
||||
}
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
constexpr int Relaxed = 0, Acquire = 2, Release = 3, AcqRel = 4, SeqCst = 5;
|
||||
|
||||
static void CheckTyped() {
|
||||
std::uint8_t byte = 0;
|
||||
_Atomic_store_1_nid_postfix(&byte, 7, SeqCst);
|
||||
Require(byte == 7 && _Atomic_fetch_add_1_nid_postfix(&byte, 250, Relaxed) == 7 && byte == 1);
|
||||
Require(_Atomic_fetch_or_1_nid_postfix(&byte, 0x80, AcqRel) == 1 && byte == 0x81);
|
||||
std::uint16_t half = 5;
|
||||
Require(_Atomic_load_2_nid_postfix(&half, Release) == 5 && _Atomic_fetch_sub_2_nid_postfix(&half, 6, SeqCst) == 5 && half == 0xffff);
|
||||
std::uint16_t expectedHalf = 1;
|
||||
Require(_Atomic_compare_exchange_strong_2_nid_postfix(&half, &expectedHalf, 2, SeqCst, Relaxed) == 0 && expectedHalf == 0xffff);
|
||||
Require(_Atomic_compare_exchange_strong_2_nid_postfix(&half, &expectedHalf, 2, SeqCst, Relaxed) == 1 && half == 2);
|
||||
std::uint32_t word = 0xff0f;
|
||||
Require(_Atomic_fetch_and_4_nid_postfix(&word, 0xf0ff, Acquire) == 0xff0f && word == 0xf00f);
|
||||
Require(_Atomic_exchange_4_nid_postfix(&word, 9, 6) == 0xf00f && word == 9);
|
||||
std::uint64_t wide = 0;
|
||||
_Atomic_store_8_nid_postfix(&wide, 0x123456789ull, Release);
|
||||
Require(_Atomic_load_8_nid_postfix(&wide, Acquire) == 0x123456789ull);
|
||||
Require(_Atomic_load_8_nid_postfix(&wide, Release) == 0 && _Atomic_load_8_nid_postfix(&wide, AcqRel) == 0);
|
||||
Require(_Atomic_fetch_add_8_nid_postfix(&wide, 1, SeqCst) == 0x123456789ull && wide == 0x12345678aull);
|
||||
Require(_Atomic_fetch_add_8_nid_postfix(&wide, 1, 6) == 0 && wide == 0x12345678aull);
|
||||
Require(_Atomic_exchange_8_nid_postfix(&wide, 1, 7) == 0 && wide == 0x12345678aull);
|
||||
Require(_Atomic_fetch_xor_8_nid_postfix(&wide, 0xf, Relaxed) == 0x12345678aull && wide == 0x123456785ull);
|
||||
std::uint64_t expectedWide = 0x123456785ull;
|
||||
Require(_Atomic_compare_exchange_weak_8_nid_postfix(&wide, &expectedWide, 3, SeqCst, SeqCst) == 1 && wide == 3);
|
||||
Require(_Atomic_is_lock_free_4_nid_postfix() == 1);
|
||||
}
|
||||
|
||||
static void CheckFlagsAndFences() {
|
||||
unsigned char flag = 0;
|
||||
Require(_Atomic_flag_test_and_set_nid_postfix(&flag, SeqCst) == 0 && flag == 1);
|
||||
Require(_Atomic_flag_test_and_set_nid_postfix(&flag, Relaxed) == 1);
|
||||
_Atomic_flag_clear_nid_postfix(&flag, Release);
|
||||
Require(flag == 0);
|
||||
_Atomic_thread_fence_nid_postfix(SeqCst);
|
||||
_Atomic_signal_fence_nid_postfix(SeqCst);
|
||||
}
|
||||
|
||||
struct Wide {
|
||||
std::uint64_t parts[3];
|
||||
};
|
||||
|
||||
static void CheckGeneric() {
|
||||
unsigned char lock = 0;
|
||||
Wide target{{1, 2, 3}}, source{{4, 5, 6}}, expected{{1, 2, 3}};
|
||||
_Atomic_copy_nid_postfix(&lock, sizeof(Wide), &target, &source, SeqCst);
|
||||
Require(std::memcmp(&target, &source, sizeof(Wide)) == 0 && lock == 0);
|
||||
Wide other{{7, 8, 9}};
|
||||
_Atomic_exchange_nid_postfix(&lock, sizeof(Wide), &target, &other, SeqCst);
|
||||
Require(target.parts[0] == 7 && other.parts[0] == 4 && lock == 0);
|
||||
const Wide desired{{10, 11, 12}};
|
||||
Require(_Atomic_compare_exchange_strong_nid_postfix(&lock, sizeof(Wide), &target, &expected, &desired, SeqCst, SeqCst) == 0);
|
||||
Require(std::memcmp(&expected, &target, sizeof(Wide)) == 0 && lock == 0);
|
||||
Require(_Atomic_compare_exchange_weak_nid_postfix(&lock, sizeof(Wide), &target, &expected, &desired, SeqCst, SeqCst) == 1);
|
||||
Require(std::memcmp(&target, &desired, sizeof(Wide)) == 0 && lock == 0);
|
||||
}
|
||||
|
||||
static void CheckConcurrentGeneric() {
|
||||
unsigned char lock = 0;
|
||||
Wide counter{{0, 0, 0}};
|
||||
constexpr int Iterations = 20000;
|
||||
auto worker = [&] {
|
||||
for (int i = 0; i < Iterations; ++i) {
|
||||
Wide expected;
|
||||
_Atomic_copy_nid_postfix(&lock, sizeof(Wide), &expected, &counter, SeqCst);
|
||||
Wide next;
|
||||
do {
|
||||
next = Wide{{expected.parts[0] + 1, expected.parts[1] + 2, expected.parts[2] + 3}};
|
||||
} while (!_Atomic_compare_exchange_strong_nid_postfix(&lock, sizeof(Wide), &counter, &expected, &next, SeqCst, SeqCst));
|
||||
}
|
||||
};
|
||||
std::thread first(worker);
|
||||
std::thread second(worker);
|
||||
first.join();
|
||||
second.join();
|
||||
Require(counter.parts[0] == 2 * Iterations && counter.parts[1] == 4 * Iterations && counter.parts[2] == 6 * Iterations);
|
||||
}
|
||||
|
||||
int main() {
|
||||
CheckTyped();
|
||||
CheckFlagsAndFences();
|
||||
CheckGeneric();
|
||||
CheckConcurrentGeneric();
|
||||
return 0;
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
- [sceLibcMspaceReallocalign](../../../core/libs/prx/libc/src/Mspace.cpp) (libSceLibcInternal, implemented in libc) takes `(void* heap, void* block, size_t alignment, size_t size)`, the order of the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x13880, whose internal routine at 0x12390 treats the third argument as the alignment and, for a null block, passes it to the routine `sceLibcMspaceMemalign` uses). Measured on a PS5 (firmware 13.60) from a homebrew payload: a null block with alignment 4096 and size 100 returns a 4096-aligned block; an alignment of 0 or 3 succeeds and 100 gives a 128-aligned block, as the routine raises it to at least 32 and to the next power of two; an alignment of 2^63 or more returns null, keeps the block and leaves `errno` alone; and a size of 0 returns a block (the same one when it is already suitable) instead of freeing it. A block that is aligned and large enough is kept where it is, without giving back the bytes past the new size as the console's in-place resize does
|
||||
- [acosl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, like the other libc math functions here, not by the console's (FreeBSD msun ld80 code in Sony's `libc.prx`): results can differ from the console's in the last bit, and on Linux glibc sets `errno` to `EDOM` for `acosl` outside [-1, 1] where msun only raises the floating-point exception. `long double` is the x87 80-bit format, as Sony's `frexpl` uses it (`fldt 0x8(%rsp)`, result in `st(0)`)
|
||||
- [atanl, sinl, cosl, tanl, sinhl, coshl, tanhl, expl, exp2l, logl, log10l and powl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, as `acosl` is, not by the console's FreeBSD msun ld80 code: results can differ from the console's in the last bit (glibc's `log10l(1000)` is not exactly 3), and on Linux glibc sets `errno` to `EDOM` or `ERANGE` where msun only raises the floating-point exception. `ldexpl` and `truncl` are exact operations and give the console's result; `logb` and `remainder` give the console's value too, but on Linux glibc sets `errno` (`ERANGE` for `logb(0)`, `EDOM` for `remainder` by zero or of an infinity) where msun only raises the floating-point exception
|
||||
- [Dinkumware atomics](../../../core/libs/prx/libc/src/DinkumwareAtomics.cpp) (libc) - every operation is sequentially consistent whatever memory order it is given, which is never weaker than the console. Matching the `libc.prx` bundled with PPSA08427 (that binary run side by side on every order from 0 to 7), `_Atomic_load_8` returns 0 for a release, acq_rel or invalid order and the 8-byte exchange and fetch operations return 0 without touching memory for an order above `memory_order_seq_cst`; the generic `_Atomic_copy`, `_Atomic_exchange` and `_Atomic_compare_exchange_*` lock the byte flag passed first, as the console does
|
||||
- [std_execute_once](../../../core/libs/prx/libc/Export.cpp) (libc) - assumed to be the plain-named twin of `std::_Execute_once` (`_ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1_`, the callee of Dinkumware `call_once`): it delegates there and gets the [thread.once.callonce] semantics already verified for that symbol (one invocation per flag, concurrent callers block until it completes, and a failing or throwing invocation leaves the flag unready so the next caller runs it). The name equivalence is not confirmed on a title, and the callback convention (called with (null, arg, null), non-zero return meaning success) is the twin's, unverified for this symbol
|
||||
- [Dinkumware mutexes and conditions](../../../core/libs/prx/libc/src/Threads.cpp) (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in [#3079](https://github.com/boykopovar/AnyPS5/pull/3079). The title's `_Mtx_lock` maps `0` to `_Thrd_success` (0), `0x8002000b` to `_Thrd_busy` (3), and other failures to `_Thrd_error` (4); its internal mutex init recognizes recursive flag `0x100`, while every other type uses default attributes. The observed relock failure for a plain mutex suggests those defaults are error-checking, but that has not been confirmed on hardware. The title's `_Mtx_unlock` and `_Cnd_wait` return success regardless of their internal result. The host implementation treats invalid ownership preconditions as no-ops, and throws for invalid handles, host wait errors and destroying a locked mutex as debug guards; those cases are not confirmed against the console.
|
||||
- [random, srandom, initstate, setstate](../../../core/libs/prx/libc/src/Random.cpp) (libc) follow FreeBSD 11 [random.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdlib/random.c): the additive feedback generator on a default 128-byte `TYPE_3` table, `good_rand` seeding, state sizes rounded down to 8, 32, 64, 128 or 256 bytes, and `setstate` returning the previous state or null for a rear position past the degree. `initstate` below 8 bytes returns null without setting errno, as that file and its manual do (glibc sets `EINVAL`). FreeBSD does not lock the generator; here every call is serialised by one lock, so concurrent callers draw the serial sequence between them. A null state throws, where FreeBSD faults. That the console's `libSceLibcInternal` uses this FreeBSD code and not another generator is unverified
|
||||
|
||||
Reference in new issue
Block a user