fix(libc): preserve the guest ABI for array callbacks
This commit is contained in:
parent
325e0a8bdd
commit
ecd4e90628
|
|
@ -57,6 +57,16 @@ if(WIN32)
|
|||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_array_callbacks_tests ${LIBS_INCLUDE_DIR}/tests/GuestArrayCallbacks.cpp)
|
||||
target_include_directories(guest_array_callbacks_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_array_callbacks_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_array_callbacks_tests)
|
||||
add_test(NAME guest_array_callbacks COMMAND guest_array_callbacks_tests)
|
||||
set_tests_properties(guest_array_callbacks PROPERTIES TIMEOUT 10)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_array_callbacks PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_time_conversion_tests ${LIBS_INCLUDE_DIR}/tests/GuestTimeConversion.cpp)
|
||||
target_include_directories(guest_time_conversion_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_time_conversion_tests PRIVATE libc)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
#include <limits>
|
||||
|
||||
extern "C" {
|
||||
void APS5_VABI __cxa_vec_cleanup_nid_postfix(void* array, std::size_t count, std::size_t size, void (*destroy)(void*)) {
|
||||
void APS5_VABI __cxa_vec_cleanup_nid_postfix(void* array, std::size_t count, std::size_t size, void (APS5_VABI *destroy)(void*)) {
|
||||
if (!destroy) return;
|
||||
try {
|
||||
while (count) destroy(static_cast<unsigned char*>(array) + --count * size);
|
||||
} catch (...) { LibcException::Terminate(); }
|
||||
}
|
||||
void APS5_VABI __cxa_vec_ctor_nid_postfix(void* array, std::size_t count, std::size_t size, void (*construct)(void*), void (*destroy)(void*)) {
|
||||
void APS5_VABI __cxa_vec_ctor_nid_postfix(void* array, std::size_t count, std::size_t size, void (APS5_VABI *construct)(void*), void (APS5_VABI *destroy)(void*)) {
|
||||
if (!construct) return;
|
||||
std::size_t complete = 0;
|
||||
try {
|
||||
|
|
@ -18,7 +18,7 @@ void APS5_VABI __cxa_vec_ctor_nid_postfix(void* array, std::size_t count, std::s
|
|||
throw;
|
||||
}
|
||||
}
|
||||
void APS5_VABI __cxa_vec_cctor_nid_postfix(void* destination, void* source, std::size_t count, std::size_t size, void (*copy)(void*, void*), void (*destroy)(void*)) {
|
||||
void APS5_VABI __cxa_vec_cctor_nid_postfix(void* destination, void* source, std::size_t count, std::size_t size, void (APS5_VABI *copy)(void*, void*), void (APS5_VABI *destroy)(void*)) {
|
||||
if (!copy) return;
|
||||
std::size_t complete = 0;
|
||||
try {
|
||||
|
|
@ -29,7 +29,7 @@ void APS5_VABI __cxa_vec_cctor_nid_postfix(void* destination, void* source, std:
|
|||
throw;
|
||||
}
|
||||
}
|
||||
void APS5_VABI __cxa_vec_dtor_nid_postfix(void* array, std::size_t count, std::size_t size, void (*destroy)(void*)) {
|
||||
void APS5_VABI __cxa_vec_dtor_nid_postfix(void* array, std::size_t count, std::size_t size, void (APS5_VABI *destroy)(void*)) {
|
||||
if (!destroy) return;
|
||||
bool unwinding = LibcException::globals.uncaught != 0;
|
||||
try {
|
||||
|
|
@ -53,8 +53,8 @@ void* ArrayAllocate(std::size_t size) {
|
|||
if (!result) _ZSt11_Xbad_allocv_nid_postfix();
|
||||
return result;
|
||||
}
|
||||
template<class Deallocate>
|
||||
void* NewArray(std::size_t count, std::size_t size, std::size_t padding, void (*construct)(void*), void (*destroy)(void*), void* (*allocate)(std::size_t), Deallocate deallocate) {
|
||||
template<class TAllocate, class TDeallocate>
|
||||
void* NewArray(std::size_t count, std::size_t size, std::size_t padding, void (APS5_VABI *construct)(void*), void (APS5_VABI *destroy)(void*), TAllocate allocate, TDeallocate deallocate) {
|
||||
std::size_t total = ArraySize(count, size, padding);
|
||||
auto* storage = static_cast<unsigned char*>(allocate(total));
|
||||
if (!storage) return nullptr;
|
||||
|
|
@ -64,8 +64,8 @@ void* NewArray(std::size_t count, std::size_t size, std::size_t padding, void (*
|
|||
catch (...) { deallocate(storage, total); throw; }
|
||||
return array;
|
||||
}
|
||||
template<class Deallocate>
|
||||
void DeleteArray(void* array, std::size_t size, std::size_t padding, void (*destroy)(void*), Deallocate deallocate) {
|
||||
template<class TDeallocate>
|
||||
void DeleteArray(void* array, std::size_t size, std::size_t padding, void (APS5_VABI *destroy)(void*), TDeallocate deallocate) {
|
||||
if (!array) return;
|
||||
auto* storage = static_cast<unsigned char*>(array) - padding;
|
||||
std::size_t count = 0;
|
||||
|
|
@ -81,22 +81,22 @@ void DeleteArray(void* array, std::size_t size, std::size_t padding, void (*dest
|
|||
}
|
||||
|
||||
extern "C" {
|
||||
void* APS5_VABI __cxa_vec_new_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*)) {
|
||||
void* APS5_VABI __cxa_vec_new_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (APS5_VABI *ctor)(void*), void (APS5_VABI *dtor)(void*)) {
|
||||
return LibcException::NewArray(n, s, p, ctor, dtor, LibcException::ArrayAllocate, [](void* ptr, std::size_t) { std::free(ptr); });
|
||||
}
|
||||
void* APS5_VABI __cxa_vec_new2_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*), void* (*alloc)(std::size_t), void (*dealloc)(void*)) {
|
||||
void* APS5_VABI __cxa_vec_new2_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (APS5_VABI *ctor)(void*), void (APS5_VABI *dtor)(void*), void* (APS5_VABI *alloc)(std::size_t), void (APS5_VABI *dealloc)(void*)) {
|
||||
return LibcException::NewArray(n, s, p, ctor, dtor, alloc, [dealloc](void* ptr, std::size_t) { dealloc(ptr); });
|
||||
}
|
||||
void* APS5_VABI __cxa_vec_new3_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*), void* (*alloc)(std::size_t), void (*dealloc)(void*, std::size_t)) {
|
||||
void* APS5_VABI __cxa_vec_new3_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (APS5_VABI *ctor)(void*), void (APS5_VABI *dtor)(void*), void* (APS5_VABI *alloc)(std::size_t), void (APS5_VABI *dealloc)(void*, std::size_t)) {
|
||||
return LibcException::NewArray(n, s, p, ctor, dtor, alloc, dealloc);
|
||||
}
|
||||
void APS5_VABI __cxa_vec_delete_nid_postfix(void* array, std::size_t s, std::size_t p, void (*dtor)(void*)) {
|
||||
void APS5_VABI __cxa_vec_delete_nid_postfix(void* array, std::size_t s, std::size_t p, void (APS5_VABI *dtor)(void*)) {
|
||||
LibcException::DeleteArray(array, s, p, dtor, [](void* ptr, std::size_t) { std::free(ptr); });
|
||||
}
|
||||
void APS5_VABI __cxa_vec_delete2_nid_postfix(void* array, std::size_t s, std::size_t p, void (*dtor)(void*), void (*dealloc)(void*)) {
|
||||
void APS5_VABI __cxa_vec_delete2_nid_postfix(void* array, std::size_t s, std::size_t p, void (APS5_VABI *dtor)(void*), void (APS5_VABI *dealloc)(void*)) {
|
||||
LibcException::DeleteArray(array, s, p, dtor, [dealloc](void* ptr, std::size_t) { dealloc(ptr); });
|
||||
}
|
||||
void APS5_VABI __cxa_vec_delete3_nid_postfix(void* array, std::size_t s, std::size_t p, void (*dtor)(void*), void (*dealloc)(void*, std::size_t)) {
|
||||
void APS5_VABI __cxa_vec_delete3_nid_postfix(void* array, std::size_t s, std::size_t p, void (APS5_VABI *dtor)(void*), void (APS5_VABI *dealloc)(void*, std::size_t)) {
|
||||
LibcException::DeleteArray(array, s, p, dtor, dealloc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,201 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
using ElementCallback = void (APS5_VABI *)(void*);
|
||||
using CopyCallback = void (APS5_VABI *)(void*, void*);
|
||||
using AllocateCallback = void* (APS5_VABI *)(std::size_t);
|
||||
using FreeCallback = void (APS5_VABI *)(void*);
|
||||
using SizedFreeCallback = void (APS5_VABI *)(void*, std::size_t);
|
||||
|
||||
extern "C" {
|
||||
void APS5_VABI __cxa_vec_ctor_nid_postfix(void*, std::size_t, std::size_t, ElementCallback, ElementCallback);
|
||||
void APS5_VABI __cxa_vec_cctor_nid_postfix(void*, void*, std::size_t, std::size_t, CopyCallback, ElementCallback);
|
||||
void APS5_VABI __cxa_vec_dtor_nid_postfix(void*, std::size_t, std::size_t, ElementCallback);
|
||||
void APS5_VABI __cxa_vec_cleanup_nid_postfix(void*, std::size_t, std::size_t, ElementCallback);
|
||||
void* APS5_VABI __cxa_vec_new_nid_postfix(std::size_t, std::size_t, std::size_t, ElementCallback, ElementCallback);
|
||||
void* APS5_VABI __cxa_vec_new2_nid_postfix(std::size_t, std::size_t, std::size_t, ElementCallback, ElementCallback, AllocateCallback, FreeCallback);
|
||||
void* APS5_VABI __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, ElementCallback, ElementCallback, AllocateCallback, SizedFreeCallback);
|
||||
void APS5_VABI __cxa_vec_delete_nid_postfix(void*, std::size_t, std::size_t, ElementCallback);
|
||||
void APS5_VABI __cxa_vec_delete2_nid_postfix(void*, std::size_t, std::size_t, ElementCallback, FreeCallback);
|
||||
void APS5_VABI __cxa_vec_delete3_nid_postfix(void*, std::size_t, std::size_t, ElementCallback, SizedFreeCallback);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t Count = 4;
|
||||
constexpr std::size_t ElementBytes = 16;
|
||||
constexpr std::size_t Padding = 16;
|
||||
constexpr std::size_t TotalBytes = Count * ElementBytes + Padding;
|
||||
alignas(16) std::array<std::byte, TotalBytes> storage{};
|
||||
alignas(16) std::array<std::byte, Count * ElementBytes> source{};
|
||||
std::byte* destination = nullptr;
|
||||
std::size_t constructed = 0;
|
||||
std::size_t copied = 0;
|
||||
std::size_t destroyed = 0;
|
||||
std::size_t allocated = 0;
|
||||
std::size_t freed = 0;
|
||||
bool failAllocation = false;
|
||||
bool failConstruct = false;
|
||||
bool failCopy = false;
|
||||
bool failDestroy = false;
|
||||
|
||||
void require(bool condition, const char* message) {
|
||||
if (condition) return;
|
||||
std::fprintf(stderr, "%s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void reset(std::byte* first) {
|
||||
destination = first;
|
||||
constructed = copied = destroyed = allocated = freed = 0;
|
||||
failAllocation = false;
|
||||
failConstruct = failCopy = failDestroy = false;
|
||||
}
|
||||
|
||||
void APS5_VABI construct(void* pointer) {
|
||||
require(constructed < Count && pointer == destination + constructed * ElementBytes, "constructor received the wrong element");
|
||||
if (failConstruct && constructed == 2) throw 73;
|
||||
std::memset(pointer, static_cast<int>(++constructed), ElementBytes);
|
||||
}
|
||||
|
||||
void APS5_VABI copy(void* target, void* original) {
|
||||
require(copied < Count && target == destination + copied * ElementBytes, "copy constructor received the wrong destination");
|
||||
require(original == source.data() + copied * ElementBytes, "copy constructor received the wrong source");
|
||||
if (failCopy && copied == 2) throw 73;
|
||||
std::memcpy(target, original, ElementBytes);
|
||||
++copied;
|
||||
}
|
||||
|
||||
void APS5_VABI destroy(void* pointer) {
|
||||
const auto count = copied != 0 ? copied : constructed;
|
||||
require(destroyed < count && pointer == destination + (count - destroyed - 1) * ElementBytes, "destructor received the wrong element or order");
|
||||
require(*static_cast<std::byte*>(pointer) == std::byte(count - destroyed), "destructor received the wrong element contents");
|
||||
++destroyed;
|
||||
if (failDestroy && destroyed == 1) throw 73;
|
||||
}
|
||||
|
||||
void* APS5_VABI allocate(std::size_t bytes) {
|
||||
require(bytes == TotalBytes, "allocator received the wrong size");
|
||||
++allocated;
|
||||
return failAllocation ? nullptr : storage.data();
|
||||
}
|
||||
|
||||
void APS5_VABI release(void* pointer) {
|
||||
require(pointer == storage.data(), "deallocator received the wrong storage");
|
||||
require(destroyed == constructed, "deallocator ran before element destruction finished");
|
||||
++freed;
|
||||
}
|
||||
|
||||
void APS5_VABI releaseSized(void* pointer, std::size_t bytes) {
|
||||
require(bytes == TotalBytes, "sized deallocator received the wrong size");
|
||||
release(pointer);
|
||||
}
|
||||
|
||||
void checkCookie(const void* array) {
|
||||
std::size_t count = 0;
|
||||
std::memcpy(&count, static_cast<const std::byte*>(array) - sizeof(count), sizeof(count));
|
||||
require(count == Count, "array cookie has the wrong count");
|
||||
}
|
||||
|
||||
void checkElements() {
|
||||
reset(source.data());
|
||||
__cxa_vec_ctor_nid_postfix(source.data(), Count, ElementBytes, construct, destroy);
|
||||
require(constructed == Count, "constructor count is wrong");
|
||||
__cxa_vec_dtor_nid_postfix(source.data(), Count, ElementBytes, destroy);
|
||||
require(destroyed == Count, "destructor count is wrong");
|
||||
reset(storage.data());
|
||||
__cxa_vec_cctor_nid_postfix(storage.data(), source.data(), Count, ElementBytes, copy, destroy);
|
||||
require(copied == Count && std::memcmp(storage.data(), source.data(), source.size()) == 0, "copy construction failed");
|
||||
__cxa_vec_cleanup_nid_postfix(storage.data(), Count, ElementBytes, destroy);
|
||||
require(destroyed == Count, "cleanup count is wrong");
|
||||
__cxa_vec_ctor_nid_postfix(nullptr, Count, ElementBytes, nullptr, nullptr);
|
||||
__cxa_vec_cctor_nid_postfix(nullptr, nullptr, Count, ElementBytes, nullptr, nullptr);
|
||||
__cxa_vec_dtor_nid_postfix(nullptr, Count, ElementBytes, nullptr);
|
||||
__cxa_vec_cleanup_nid_postfix(nullptr, Count, ElementBytes, nullptr);
|
||||
}
|
||||
|
||||
void checkAllocation(bool sized) {
|
||||
reset(storage.data() + Padding);
|
||||
void* array = sized
|
||||
? __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized)
|
||||
: __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release);
|
||||
require(array == destination && constructed == Count && allocated == 1, "array allocation failed");
|
||||
checkCookie(array);
|
||||
if (sized) __cxa_vec_delete3_nid_postfix(array, ElementBytes, Padding, destroy, releaseSized);
|
||||
else __cxa_vec_delete2_nid_postfix(array, ElementBytes, Padding, destroy, release);
|
||||
require(destroyed == Count && freed == 1, "array deletion failed");
|
||||
reset(storage.data() + Padding);
|
||||
failAllocation = true;
|
||||
array = sized
|
||||
? __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized)
|
||||
: __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release);
|
||||
require(array == nullptr && allocated == 1 && constructed == 0 && freed == 0, "failed allocation invoked element callbacks");
|
||||
if (sized) __cxa_vec_delete3_nid_postfix(nullptr, ElementBytes, Padding, destroy, releaseSized);
|
||||
else __cxa_vec_delete2_nid_postfix(nullptr, ElementBytes, Padding, destroy, release);
|
||||
require(freed == 0, "null array invoked deallocation");
|
||||
}
|
||||
|
||||
void checkDefaultAllocation() {
|
||||
void* array = __cxa_vec_new_nid_postfix(Count, ElementBytes, Padding, nullptr, nullptr);
|
||||
require(array != nullptr, "default array allocation failed");
|
||||
checkCookie(array);
|
||||
reset(static_cast<std::byte*>(array));
|
||||
__cxa_vec_ctor_nid_postfix(array, Count, ElementBytes, construct, destroy);
|
||||
__cxa_vec_delete_nid_postfix(array, ElementBytes, Padding, destroy);
|
||||
require(constructed == Count && destroyed == Count, "default array deletion failed");
|
||||
__cxa_vec_delete_nid_postfix(nullptr, ElementBytes, Padding, destroy);
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
void expectException(TAction action) {
|
||||
bool caught = false;
|
||||
try { action(); }
|
||||
catch (int value) { require(value == 73, "callback exception changed"); caught = true; }
|
||||
require(caught, "callback exception was swallowed");
|
||||
}
|
||||
|
||||
void checkRollback(bool sized) {
|
||||
reset(storage.data() + Padding);
|
||||
failConstruct = true;
|
||||
expectException([&] {
|
||||
if (sized) __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized);
|
||||
else __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release);
|
||||
});
|
||||
require(constructed == 2 && destroyed == 2 && allocated == 1 && freed == 1, "constructor rollback failed");
|
||||
reset(storage.data() + Padding);
|
||||
void* array = sized
|
||||
? __cxa_vec_new3_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, releaseSized)
|
||||
: __cxa_vec_new2_nid_postfix(Count, ElementBytes, Padding, construct, destroy, allocate, release);
|
||||
failDestroy = true;
|
||||
expectException([&] {
|
||||
if (sized) __cxa_vec_delete3_nid_postfix(array, ElementBytes, Padding, destroy, releaseSized);
|
||||
else __cxa_vec_delete2_nid_postfix(array, ElementBytes, Padding, destroy, release);
|
||||
});
|
||||
require(constructed == Count && destroyed == Count && allocated == 1 && freed == 1, "destructor rollback failed");
|
||||
}
|
||||
|
||||
void checkCopyRollback() {
|
||||
reset(storage.data());
|
||||
failCopy = true;
|
||||
expectException([] {
|
||||
__cxa_vec_cctor_nid_postfix(storage.data(), source.data(), Count, ElementBytes, copy, destroy);
|
||||
});
|
||||
require(copied == 2 && destroyed == 2, "copy constructor rollback failed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
checkElements();
|
||||
checkAllocation(false);
|
||||
checkAllocation(true);
|
||||
checkDefaultAllocation();
|
||||
checkRollback(false);
|
||||
checkRollback(true);
|
||||
checkCopyRollback();
|
||||
std::puts("guest array callback arguments and lifecycle: PASS");
|
||||
}
|
||||
Loading…
Reference in New Issue