Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/libs/prx/libc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
175 changes: 175 additions & 0 deletions core/libs/prx/libc/src/DinkumwareAtomics.cpp
Original file line number Diff line number Diff line change
@@ -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);
}

}
120 changes: 120 additions & 0 deletions core/libs/tests/GuestDinkumwareAtomics.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
Loading
Loading