diff --git a/README.md b/README.md index 8ccf4f12ce..351688be64 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The world's first free and open-source PlayStation 3 emulator/debugger, written You can find some basic information on our [**website**](https://rpcs3.net/). Game info is being populated on the [**Wiki**](https://wiki.rpcs3.net/). For discussion about this emulator, PS3 emulation, and game compatibility reports, please visit our [**forums**](https://forums.rpcs3.net) and our [**Discord server**](https://discord.gg/RPCS3). -[**Support Lead Developers Nekotekina and kd-11 on Patreon**](https://www.patreon.com/Nekotekina) +[**Support the Lead Developers on Patreon**](https://rpcs3.net/patreon) ## Contributing diff --git a/Utilities/StrFmt.cpp b/Utilities/StrFmt.cpp index cd0fe2b5dc..39a2cb9233 100644 --- a/Utilities/StrFmt.cpp +++ b/Utilities/StrFmt.cpp @@ -630,6 +630,13 @@ namespace fmt thread_ctrl::emergency_exit(out); } + [[noreturn]] void raw_verify_error(std::source_location loc, std::source_location propagated_loc, const char8_t* msg, usz object) + { + std::string out; + fmt::append(out, "%s (object: 0x%x)%s%s", msg ? msg : u8"Verification failed", object, loc, propagated_loc); + thread_ctrl::emergency_exit(out); + } + [[noreturn]] void raw_range_error(std::source_location loc, std::string_view index, usz container_size) { std::string out; diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index 2fa497467c..fee8fa4327 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -193,6 +193,7 @@ if(BUILD_RPCS3_TESTS) tests/test_rsx_fp_asm.cpp tests/test_dmux_pamf.cpp tests/test_spu_analyser.cpp + tests/test_types_util.cpp ) target_link_libraries(rpcs3_test diff --git a/rpcs3/Emu/Cell/Modules/cellGifDec.cpp b/rpcs3/Emu/Cell/Modules/cellGifDec.cpp index faf8defa6d..03566183e6 100644 --- a/rpcs3/Emu/Cell/Modules/cellGifDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGifDec.cpp @@ -289,8 +289,8 @@ error_code cellGifDecReadHeader(vm::ptr mainHandle, vm::ptr>(buffer + 0) != 0x47494638u || - (read_from_ptr>(buffer + 4) != 0x6139u && read_from_ptr>(buffer + 4) != 0x6137u)) // Error: The first 6 bytes are not a valid GIF signature + if (read_from_ptr>(buffer, 0) != 0x47494638u || + (read_from_ptr>(buffer, 4) != 0x6139u && read_from_ptr>(buffer, 4) != 0x6137u)) // Error: The first 6 bytes are not a valid GIF signature { return CELL_GIFDEC_ERROR_STREAM_FORMAT; // Surprisingly there is no error code related with headerss } diff --git a/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp b/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp index 5123ec68cf..a6a656e698 100644 --- a/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp @@ -125,26 +125,26 @@ error_code cellJpgDecReadHeader(u32 mainHandle, u32 subHandle, vm::ptrinfo; // Write the header to buffer - std::unique_ptr buffer(new u8[fileSize]); + std::vector buffer(fileSize); switch (subHandle_data->src.srcSelect) { case CELL_JPGDEC_BUFFER: - std::memcpy(buffer.get(), vm::base(subHandle_data->src.streamPtr), fileSize); + std::memcpy(buffer.data(), vm::base(subHandle_data->src.streamPtr), fileSize); break; case CELL_JPGDEC_FILE: { auto file = idm::get_unlocked(fd); file->file.seek(0); - file->file.read(buffer.get(), fileSize); + file->file.read(buffer.data(), fileSize); break; } default: break; // TODO } - if (read_from_ptr>(buffer.get() + 0) != 0xE0FFD8FF || // Error: Not a valid SOI header - read_from_ptr(buffer.get() + 6) != "JFIF"_u32) // Error: Not a valid JFIF string + if (read_from_ptr>(buffer, 0) != 0xE0FFD8FF || // Error: Not a valid SOI header + read_from_ptr(buffer, 6) != "JFIF"_u32) // Error: Not a valid JFIF string { return CELL_JPGDEC_ERROR_HEADER; } @@ -154,7 +154,7 @@ error_code cellJpgDecReadHeader(u32 mainHandle, u32 subHandle, vm::ptr= fileSize) return CELL_JPGDEC_ERROR_HEADER; - u16 block_length = buffer[i] * 0xFF + buffer[i + 1]; + u16 block_length = ::at32(buffer, i) * 0xFF + ::at32(buffer, i + 1); while (true) { @@ -165,15 +165,16 @@ error_code cellJpgDecReadHeader(u32 mainHandle, u32 subHandle, vm::ptr(vm_base + eal); + return read_from_ptr(vm_base, eal); } void ppu_write_mmio_aware_u32(u8* vm_base, u32 eal, u32 value) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index ab92524d27..dbb7e048f8 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -646,18 +646,32 @@ spu_cache::~spu_cache() extern void utilize_spu_data_segment(u32 vaddr, const void* ls_data_vaddr, u32 size) { - if (vaddr % 4) + if (u32 rem = vaddr % 4) { - return; + // The remainder that it needs to be aligned up to the next DWORD + rem = 4 - rem; + + if (size < rem) + { + return; + } + + vaddr += rem; + ls_data_vaddr = reinterpret_cast(ls_data_vaddr) + rem; + size -= rem; } size &= -4; - if (!size || vaddr + size > SPU_LS_SIZE) + if (!size || vaddr >= SPU_LS_SIZE) { return; } + // Let SPU block search mistakes pass through + // SPU code mining is too important + size = std::min(SPU_LS_SIZE - vaddr, size); + if (!g_cfg.core.llvm_precompilation) { return; @@ -2364,11 +2378,22 @@ std::vector spu_thread::discover_functions(u32 base_addr, std::span(base_addr, 0x10); i < std::min(base_addr + ::size32(ls), 0x3FFF0); i += 0x10) + for (u32 i = base_addr, end_ls = std::min(base_addr + ::size32(ls), SPU_LS_SIZE); i < end_ls; i = utils::align(i + 1, 0x10)) { // Search for BRSL LR and BRASL LR or BR // TODO: BISL - const v128 inst = read_from_ptr>(ls.data(), i - base_addr); + be_t inst_be{}; + + if (end_ls - i < 16) + { + std::memcpy(&inst_be, ls.data() + (i - base_addr), end_ls - i); + } + else + { + inst_be = read_from_ptr>(ls, i - base_addr); + } + + const v128 inst = inst_be; const v128 cleared_i16 = gv_and32(inst, v128::from32p(std::rotl(~0xffff, 7))); const v128 eq_brsl = gv_eq32(cleared_i16, v128::from32p(0x66u << 23)); const v128 eq_brasl = gv_eq32(cleared_i16, brasl_mask); diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 79bd85206d..61f8ab45f1 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -658,7 +658,7 @@ void lv2_file::save(utils::serial& ar) sys_fs.error("Read less than expected! (new-size=0x%x)", read_size); stats.size = read_size; ar.data.resize(old_end + stats.size); - write_to_ptr(&ar.data[patch_stats_pos], stats); + write_to_ptr(ar.data, patch_stats_pos, stats); } } diff --git a/rpcs3/Emu/Io/Dimensions.cpp b/rpcs3/Emu/Io/Dimensions.cpp index 84a604a86e..fb3327779d 100644 --- a/rpcs3/Emu/Io/Dimensions.cpp +++ b/rpcs3/Emu/Io/Dimensions.cpp @@ -214,7 +214,7 @@ u32 dimensions_toypad::scramble(const std::array& uid, u8 count) } ::at32(to_scramble, count * 4 - 1) = 0xaa; - return read_from_ptr>(dimensions_randomize(to_scramble, count).data()); + return read_from_ptr>(dimensions_randomize(to_scramble, count)); } std::array dimensions_toypad::dimensions_randomize(const std::vector& key, u8 count) @@ -522,7 +522,7 @@ bool dimensions_toypad::create_blank_character(std::array& buf, else { // Page 38 is used as verification for blank tags - write_to_ptr>(buf.data(), 38 * 4, 1); + write_to_ptr>(buf, 38 * 4, 1); } return true; diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 6b4ada8e84..df67c47990 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -359,7 +359,7 @@ namespace np return; } - u32 given_size = read_from_ptr>(data() + 4); + u32 given_size = read_from_ptr>(data(), 4); if ((given_size + 8) != size()) { ticket_log.error("Size mismatch (gs: %d vs s: %d)", given_size, size()); diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index da590278f5..410713209b 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -503,8 +503,8 @@ namespace rpcn { if (msg.size() == 6) { - const u32 new_addr_sig = read_from_ptr>(&msg[0]); - const u16 new_port_sig = read_from_ptr>(&msg[4]); + const u32 new_addr_sig = read_from_ptr>(msg, 0); + const u16 new_port_sig = read_from_ptr>(msg, 4); const u32 old_addr_sig = addr_sig; const u32 old_port_sig = port_sig; @@ -533,7 +533,7 @@ namespace rpcn // We don't really need ipv6 info stored so we just update the pong data // std::array new_ipv6_addr; // std::memcpy(new_ipv6_addr.data(), &msg[3], 16); - // const u32 new_ipv6_port = read_from_ptr>(&msg[16]); + // const u32 new_ipv6_port = read_from_ptr>(msg, 16); last_pong_time_ipv6 = now; } @@ -626,9 +626,9 @@ namespace rpcn } const u8 packet_type = header[0]; - const auto command = static_cast(static_cast(read_from_ptr>(&header[1]))); - const u32 packet_size = read_from_ptr>(&header[3]); - const u64 packet_id = read_from_ptr>(&header[7]); + const auto command = static_cast(static_cast(read_from_ptr>(header, 1))); + const u32 packet_size = read_from_ptr>(header, 3); + const u64 packet_id = read_from_ptr>(header, 7); if (packet_size < RPCN_HEADER_SIZE) return error_and_disconnect("Invalid packet size"); diff --git a/rpcs3/Emu/NP/rpcn_client.h b/rpcs3/Emu/NP/rpcn_client.h index e37b1bb2e0..f95fb282b4 100644 --- a/rpcs3/Emu/NP/rpcn_client.h +++ b/rpcs3/Emu/NP/rpcn_client.h @@ -67,7 +67,7 @@ public: error = true; return static_cast(0); } - T res = read_from_ptr>(&vec[i]); + T res = read_from_ptr>(vec, i); i += sizeof(T); return res; } diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp index 3cc40efed1..fb64f6870f 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp @@ -183,11 +183,11 @@ namespace rsx const usz first_index_off = 0; const usz second_index_off = (((rcount / 4) - 1) / 2) * 4; - const u64 src_op1_2 = read_from_ptr>(fifo_span.data() + first_index_off); - const u64 src_op2_2 = read_from_ptr>(fifo_span.data() + second_index_off); + const u64 src_op1_2 = read_from_ptr>(fifo_span, first_index_off); + const u64 src_op2_2 = read_from_ptr>(fifo_span, second_index_off); // Fast comparison - if (src_op1_2 != read_from_ptr(out_ptr + first_index_off) || src_op2_2 != read_from_ptr(out_ptr + second_index_off)) + if (src_op1_2 != read_from_ptr(out_ptr, first_index_off) || src_op2_2 != read_from_ptr(out_ptr, second_index_off)) { to_set_dirty = rsx::pipeline_state::vertex_program_ucode_dirty; } diff --git a/rpcs3/Emu/RSX/VK/VKMemAlloc.cpp b/rpcs3/Emu/RSX/VK/VKMemAlloc.cpp index 4689e910d2..09d6ae13fb 100644 --- a/rpcs3/Emu/RSX/VK/VKMemAlloc.cpp +++ b/rpcs3/Emu/RSX/VK/VKMemAlloc.cpp @@ -56,7 +56,7 @@ private: #endif #endif #include -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) #else #pragma GCC diagnostic pop diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index ea7de326f8..c431ee2f5a 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -126,7 +126,7 @@ struct cfg_root : cfg::node cfg::_enum frame_limit{ this, "Frame limit", frame_limit_type::_auto, true }; cfg::_float<0, 1000> second_frame_limit{ this, "Second Frame Limit", 0, true }; // 0 disables its effect cfg::_enum antialiasing_level{ this, "MSAA", msaa_level::_auto }; - cfg::_enum shadermode{ this, "Shader Mode", shader_mode::async_recompiler }; + cfg::_enum shadermode{ this, "Shader Mode", shader_mode::async_with_interpreter }; cfg::_enum shader_precision{ this, "Shader Precision", gpu_preset_level::high }; cfg::_enum vsync{ this, "VSync Mode", vsync_mode::off, true }; @@ -366,7 +366,7 @@ struct cfg_root : cfg::node cfg::_bool prevent_display_sleep{ this, "Prevent display sleep while running games", true, true }; cfg::_bool show_trophy_popups{ this, "Show trophy popups", true, true }; cfg::_bool show_rpcn_popups{ this, "Show RPCN popups", true, true }; - cfg::_bool show_shader_compilation_hint{ this, "Show shader compilation hint", true, true }; + cfg::_bool show_shader_compilation_hint{ this, "Show shader compilation hint", false, true }; cfg::_bool show_ppu_compilation_hint{ this, "Show PPU compilation hint", true, true }; cfg::_bool show_autosave_autoload_hint{ this, "Show autosave/autoload hint", false, true }; cfg::_bool show_pressure_intensity_toggle_hint{ this, "Show pressure intensity toggle hint", true, true }; diff --git a/rpcs3/rpcs3.metainfo.xml b/rpcs3/rpcs3.metainfo.xml index 327c90c0f2..3449f39c7f 100644 --- a/rpcs3/rpcs3.metainfo.xml +++ b/rpcs3/rpcs3.metainfo.xml @@ -29,7 +29,7 @@ Most of this information can be found on the PlayStation 3 Developer Wiki.

https://github.com/RPCS3/rpcs3/issues https://rpcs3.net/faq https://rpcs3.net/quickstart - https://www.patreon.com/Nekotekina + https://rpcs3.net/patreon https://github.com/RPCS3/rpcs3 https://github.com/RPCS3/rpcs3#contributing diff --git a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp index 20202a68f2..e4884d6e64 100644 --- a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp +++ b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp @@ -234,7 +234,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) std::array figure_data = {static_cast(dist(mt)), 0x03, 0x00, 0x00, 0x01, 0x0e, 0x0a, 0x0a, static_cast(fig_id & 0xff), static_cast((fig_id >> 8) & 0xff), static_cast((fig_id >> 16) & 0xff), static_cast((fig_id >> 24) & 0xff)}; - write_to_ptr>(figure_data.data(), 0xC, kamen_rider_crc32(figure_data)); + write_to_ptr>(figure_data, 0xC, kamen_rider_crc32(figure_data)); memcpy(&buf[16], figure_data.data(), figure_data.size()); fig_file.write(buf.data(), buf.size()); fig_file.close(); diff --git a/rpcs3/rpcs3qt/log_frame.cpp b/rpcs3/rpcs3qt/log_frame.cpp index 61ca66a909..2d07bcdb26 100644 --- a/rpcs3/rpcs3qt/log_frame.cpp +++ b/rpcs3/rpcs3qt/log_frame.cpp @@ -638,7 +638,7 @@ void log_frame::UpdateUI() // If ANSI TTY is enabled, remove all control characters except for ESC (0x1B) for ANSI sequences if (m_ansi_tty) { - buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](s8 c) + buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](u8 c) // Use u8. Otherwise we drop valid UTF-8 characters. { return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F && c != 0x1B); }), buf_line.end()); @@ -646,7 +646,7 @@ void log_frame::UpdateUI() // Otherwise, remove all control characters to keep the output clean else { - buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](s8 c) + buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](u8 c) // Use u8. Otherwise we drop valid UTF-8 characters. { return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F); }), buf_line.end()); diff --git a/rpcs3/tests/rpcs3_test.vcxproj b/rpcs3/tests/rpcs3_test.vcxproj index fb96921efc..274d9d028d 100644 --- a/rpcs3/tests/rpcs3_test.vcxproj +++ b/rpcs3/tests/rpcs3_test.vcxproj @@ -105,6 +105,7 @@ + diff --git a/rpcs3/tests/test_types_util.cpp b/rpcs3/tests/test_types_util.cpp new file mode 100644 index 0000000000..8f9251e0a5 --- /dev/null +++ b/rpcs3/tests/test_types_util.cpp @@ -0,0 +1,487 @@ +#include "stdafx.h" +#include + +extern atomic_t g_headless; + +#define CHECK_COMPILATION_ERRORS 0 +#define CHECK_DEATH 0 + +namespace utils +{ + TEST(Utils, read_write_to_ptr_array) + { + g_headless = true; // Disable exception popups + + // write_to_ptr without pos + std::array arr{}; + write_to_ptr(arr, u8(umax)); + EXPECT_EQ(arr.at(0), u8(umax)); + write_to_ptr(arr, u16(umax)); + EXPECT_EQ(*utils::bless(arr.data()), u16(umax)); + write_to_ptr(arr, u32(umax)); + EXPECT_EQ(*utils::bless(arr.data()), u32(umax)); + write_to_ptr(arr, u64(umax)); + EXPECT_EQ(*utils::bless(arr.data()), u64(umax)); + + // write_to_ptr and read_from_ptr with pos + std::array arr2{}; + + for (u8 i = 0; i < arr2.size(); i++) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(arr2.at(i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + arr2 = {}; + for (u16 i = 0; i < arr2.size(); i += sizeof(u16)) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(*utils::bless(arr2.data() + i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + arr2 = {}; + for (u32 i = 0; i < arr2.size(); i += sizeof(u32)) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(*utils::bless(arr2.data() + i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + arr2 = {}; + for (u64 i = 0; i < arr2.size(); i += sizeof(u64)) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(*utils::bless(arr2.data() + i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + arr2 = {}; + for (usz i = 0; i < arr2.size(); i += sizeof(u128)) + { + write_to_ptr(arr2, i, u128(i)); + const u128 exp_u128 = i; + EXPECT_EQ(std::memcmp(arr2.data(), &exp_u128, sizeof(u128)), 0); + const u128 val_u128 = read_from_ptr(arr2, i); + EXPECT_EQ(std::memcmp(&val_u128, &exp_u128, sizeof(u128)), 0); + } + + // write_to_ptr without pos + std::array arr32{}; + write_to_ptr(arr32, u32(umax)); + EXPECT_EQ(*utils::bless(arr32.data()), u32(umax)); + write_to_ptr(arr32, u64(umax)); + EXPECT_EQ(*utils::bless(arr32.data()), u64(umax)); + + // write_to_ptr and read_from_ptr with pos + std::array arr32_2{}; + + std::memset(arr32_2.data(), 0, arr32_2.size() * sizeof(u32)); + for (u32 i = 0; i < arr32_2.size() * sizeof(u32); i += sizeof(u32)) + { + const usz index = i / sizeof(u32); + write_to_ptr(arr32_2, index, i); + EXPECT_EQ(*utils::bless(arr32_2.data() + index), i); + EXPECT_EQ(read_from_ptr(arr32_2, index), i); + } + + std::memset(arr32_2.data(), 0, arr32_2.size() * sizeof(u32)); + for (u64 i = 0; i < arr32_2.size() * sizeof(u32); i += sizeof(u64)) + { + const usz index = i / sizeof(u64); + write_to_ptr(arr32_2, index, i); + EXPECT_EQ(*utils::bless(arr32_2.data() + index), i); + EXPECT_EQ(read_from_ptr(arr32_2, index), i); + } + + std::memset(arr32_2.data(), 0, arr32_2.size() * sizeof(u32)); + for (usz i = 0; i < arr32_2.size() * sizeof(u32); i += sizeof(u128)) + { + const usz index = i / sizeof(u128); + write_to_ptr(arr32_2, index, u128(i)); + const u128 exp_u128 = i; + EXPECT_EQ(std::memcmp(arr32_2.data(), &exp_u128, sizeof(u128)), 0); + const u128 val_u128 = read_from_ptr(arr32_2, index); + EXPECT_EQ(std::memcmp(&val_u128, &exp_u128, sizeof(u128)), 0); + } + + // These tests can take a long time and produce warnings, so let's only run them in local builds +#if CHECK_DEATH + EXPECT_DEATH(write_to_ptr(arr, u128()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2.size(), u8()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2.size(), u16()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2.size(), u32()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2.size(), u64()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2.size(), u128()), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2.size()), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2.size()), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2.size()), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2.size()), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2.size()), ".*"); + + EXPECT_DEATH(write_to_ptr(arr32, u128()), ".*"); + EXPECT_DEATH(write_to_ptr(arr32_2, arr32_2.size(), u32()), ".*"); + EXPECT_DEATH(write_to_ptr(arr32_2, arr32_2.size(), u64()), ".*"); + EXPECT_DEATH(write_to_ptr(arr32_2, arr32_2.size(), u128()), ".*"); + EXPECT_DEATH(read_from_ptr(arr32_2, arr32_2.size()), ".*"); + EXPECT_DEATH(read_from_ptr(arr32_2, arr32_2.size()), ".*"); + EXPECT_DEATH(read_from_ptr(arr32_2, arr32_2.size()), ".*"); +#endif + } + + TEST(Utils, read_write_to_ptr_c_array) + { + g_headless = true; // Disable exception popups + + // write_to_ptr without pos + u8 arr[sizeof(u64)]{}; + write_to_ptr(arr, u8(umax)); + EXPECT_EQ(*arr, u8(umax)); + write_to_ptr(arr, u16(umax)); + EXPECT_EQ(*utils::bless(arr + 0), u16(umax)); + write_to_ptr(arr, u32(umax)); + EXPECT_EQ(*utils::bless(arr + 0), u32(umax)); + write_to_ptr(arr, u64(umax)); + EXPECT_EQ(*utils::bless(arr + 0), u64(umax)); + + // write_to_ptr and read_from_ptr with pos + u8 arr2[sizeof(u128)]{}; + + for (u8 i = 0; i < sizeof(arr2); i++) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(arr2[i], i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + std::memset(arr2, 0, sizeof(arr2)); + for (u16 i = 0; i < sizeof(arr2); i += sizeof(u16)) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(*utils::bless(arr2 + i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + std::memset(arr2, 0, sizeof(arr2)); + for (u32 i = 0; i < sizeof(arr2); i += sizeof(u32)) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(*utils::bless(arr2 + i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + std::memset(arr2, 0, sizeof(arr2)); + for (u64 i = 0; i < sizeof(arr2); i += sizeof(u64)) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(*utils::bless(arr2 + i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + std::memset(arr2, 0, sizeof(arr2)); + for (usz i = 0; i < sizeof(arr2); i += sizeof(u128)) + { + write_to_ptr(arr2, i, u128(i)); + const u128 exp_u128 = i; + EXPECT_EQ(std::memcmp(arr2, &exp_u128, sizeof(u128)), 0); + const u128 val_u128 = read_from_ptr(arr2, i); + EXPECT_EQ(std::memcmp(&val_u128, &exp_u128, sizeof(u128)), 0); + } + + // write_to_ptr without pos + u32 arr32[sizeof(u64) / sizeof(u32)]{}; + write_to_ptr(arr32, u32(umax)); + EXPECT_EQ(*utils::bless(arr32 + 0), u32(umax)); + write_to_ptr(arr32, u64(umax)); + EXPECT_EQ(*utils::bless(arr32 + 0), u64(umax)); + + // write_to_ptr and read_from_ptr with pos + u32 arr32_2[sizeof(u128) / sizeof(u32)]{}; + + std::memset(arr32_2, 0, sizeof(arr32_2)); + for (u32 i = 0; i < sizeof(arr32_2); i += sizeof(u32)) + { + const usz index = i / sizeof(u32); + write_to_ptr(arr32_2, index, i); + EXPECT_EQ(*utils::bless(arr32_2 + index), i); + EXPECT_EQ(read_from_ptr(arr32_2, index), i); + } + + std::memset(arr32_2, 0, sizeof(arr32_2)); + for (u64 i = 0; i < sizeof(arr32_2); i += sizeof(u64)) + { + const usz index = i / sizeof(u64); + write_to_ptr(arr32_2, index, i); + EXPECT_EQ(*utils::bless(arr32_2 + index), i); + EXPECT_EQ(read_from_ptr(arr32_2, index), i); + } + + std::memset(arr32_2, 0, sizeof(arr32_2)); + for (usz i = 0; i < sizeof(arr32_2); i += sizeof(u128)) + { + const usz index = i / sizeof(u128); + write_to_ptr(arr32_2, index, u128(i)); + const u128 exp_u128 = i; + EXPECT_EQ(std::memcmp(arr32_2, &exp_u128, sizeof(u128)), 0); + const u128 val_u128 = read_from_ptr(arr32_2, index); + EXPECT_EQ(std::memcmp(&val_u128, &exp_u128, sizeof(u128)), 0); + } + + // These tests can take a long time and produce warnings, so let's only run them in local builds +#if CHECK_DEATH + EXPECT_DEATH(write_to_ptr(arr, u128()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, std::size(arr2), u8()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, std::size(arr2), u16()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, std::size(arr2), u32()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, std::size(arr2), u64()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, std::size(arr2), u128()), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, std::size(arr2)), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, std::size(arr2)), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, std::size(arr2)), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, std::size(arr2)), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, std::size(arr2)), ".*"); + + EXPECT_DEATH(write_to_ptr(arr32, u128()), ".*"); + EXPECT_DEATH(write_to_ptr(arr32_2, std::size(arr32_2), u32()), ".*"); + EXPECT_DEATH(write_to_ptr(arr32_2, std::size(arr32_2), u64()), ".*"); + EXPECT_DEATH(write_to_ptr(arr32_2, std::size(arr32_2), u128()), ".*"); + EXPECT_DEATH(read_from_ptr(arr32_2, std::size(arr32_2)), ".*"); + EXPECT_DEATH(read_from_ptr(arr32_2, std::size(arr32_2)), ".*"); + EXPECT_DEATH(read_from_ptr(arr32_2, std::size(arr32_2)), ".*"); +#endif + } + + TEST(Utils, read_write_to_ptr_raw_array) + { + g_headless = true; // Disable exception popups + + // write_to_ptr without pos + constexpr usz arr_size = sizeof(u64); + u8* arr = new u8[arr_size]; + std::memset(arr, 0, arr_size); + + write_to_ptr(arr, u8(umax)); + EXPECT_EQ(*arr, u8(umax)); + write_to_ptr(arr, u16(umax)); + EXPECT_EQ(*utils::bless(arr), u16(umax)); + write_to_ptr(arr, u32(umax)); + EXPECT_EQ(*utils::bless(arr), u32(umax)); + write_to_ptr(arr, u64(umax)); + EXPECT_EQ(*utils::bless(arr), u64(umax)); + + // write_to_ptr and read_from_ptr with pos + constexpr usz arr2_size = sizeof(u128); + u8* arr2 = new u8[arr2_size]; + + std::memset(arr2, 0, arr2_size); + for (u8 i = 0; i < arr2_size; i++) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(arr2[i], i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + std::memset(arr2, 0, arr2_size); + for (u16 i = 0; i < arr2_size; i += sizeof(u16)) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(*utils::bless(arr2 + i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + std::memset(arr2, 0, arr2_size); + for (u32 i = 0; i < arr2_size; i += sizeof(u32)) + { + write_to_ptr(arr2, i, i); + EXPECT_EQ(*utils::bless(arr2 + i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + std::memset(arr2, 0, arr2_size); + for (usz i = 0; i < arr2_size; i += sizeof(u64)) + { + write_to_ptr(arr2, i, u64(i)); + EXPECT_EQ(*utils::bless(arr2 + i), i); + EXPECT_EQ(read_from_ptr(arr2, i), i); + } + + std::memset(arr2, 0, arr2_size); + for (usz i = 0; i < arr2_size; i += sizeof(u128)) + { + write_to_ptr(arr2, i, u128(i)); + const u128 exp_u128 = i; + EXPECT_EQ(std::memcmp(arr2, &exp_u128, sizeof(u128)), 0); + const u128 val_u128 = read_from_ptr(arr2, i); + EXPECT_EQ(std::memcmp(&val_u128, &exp_u128, sizeof(u128)), 0); + } + + // write_to_ptr without pos + constexpr usz arr32_count = sizeof(u64) / sizeof(u32); + constexpr usz arr32_size = arr32_count * sizeof(u32); + u32* arr32 = new u32[arr32_count]; + std::memset(arr32, 0, arr32_size); + + write_to_ptr(arr32, u32(umax)); + EXPECT_EQ(*utils::bless(arr32), u32(umax)); + write_to_ptr(arr32, u64(umax)); + EXPECT_EQ(*utils::bless(arr32), u64(umax)); + + // write_to_ptr and read_from_ptr with pos + constexpr usz arr32_2_count = sizeof(u128) / sizeof(u32); + constexpr usz arr32_2_size = arr32_2_count * sizeof(u32); + u32* arr32_2 = new u32[arr32_2_count]; + + std::memset(arr32_2, 0, arr32_2_size); + for (u32 i = 0; i < arr32_2_size; i += sizeof(u32)) + { + const usz index = i / sizeof(u32); + write_to_ptr(arr32_2, index, i); + EXPECT_EQ(*utils::bless(arr32_2 + index), i); + EXPECT_EQ(read_from_ptr(arr32_2, index), i); + } + + std::memset(arr32_2, 0, arr32_2_size); + for (u64 i = 0; i < arr32_2_size; i += sizeof(u64)) + { + const usz index = i / sizeof(u64); + write_to_ptr(arr32_2, index, i); + EXPECT_EQ(*utils::bless(arr32_2 + index), i); + EXPECT_EQ(read_from_ptr(arr32_2, index), i); + } + + std::memset(arr32_2, 0, arr32_2_size); + for (usz i = 0; i < arr32_2_size; i += sizeof(u128)) + { + const usz index = i / sizeof(u128); + write_to_ptr(arr32_2, index, u128(i)); + const u128 exp_u128 = i; + EXPECT_EQ(std::memcmp(arr32_2, &exp_u128, sizeof(u128)), 0); + const u128 val_u128 = read_from_ptr(arr32_2, index); + EXPECT_EQ(std::memcmp(&val_u128, &exp_u128, sizeof(u128)), 0); + } + + // There are no sanity checks for raw pointers in these functions, so these will not fail. +#if 0 + // These tests can take a long time and produce warnings, so let's only run them in local builds +#if CHECK_DEATH + EXPECT_DEATH(write_to_ptr(arr, u128()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2_size, u8()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2_size, u16()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2_size, u32()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2_size, u64()), ".*"); + EXPECT_DEATH(write_to_ptr(arr2, arr2_size, u128()), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2_size), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2_size), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2_size), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2_size), ".*"); + EXPECT_DEATH(read_from_ptr(arr2, arr2_size), ".*"); + + EXPECT_DEATH(write_to_ptr(arr32, u128()), ".*"); + EXPECT_DEATH(write_to_ptr(arr32_2, arr32_count, u32()), ".*"); + EXPECT_DEATH(write_to_ptr(arr32_2, arr32_count, u64()), ".*"); + EXPECT_DEATH(write_to_ptr(arr32_2, arr32_count, u128()), ".*"); + EXPECT_DEATH(read_from_ptr(arr32_2, arr32_count), ".*"); + EXPECT_DEATH(read_from_ptr(arr32_2, arr32_count), ".*"); + EXPECT_DEATH(read_from_ptr(arr32_2, arr32_count), ".*"); +#endif +#endif + + delete[] arr; + delete[] arr2; + delete[] arr32; + delete[] arr32_2; + } + + TEST(Utils, read_from_ptr_const_eval_array) + { + g_headless = true; // Disable exception popups + + constexpr std::array arr { 1, 2, 3, 4, 5, 6, 7, 8 }; + + static_assert(read_from_ptr(arr, 0) == u8(1)); + static_assert(read_from_ptr(arr, 1) == u8(2)); + static_assert(read_from_ptr(arr, 2) == u8(3)); + static_assert(read_from_ptr(arr, 3) == u8(4)); + static_assert(read_from_ptr(arr, 4) == u8(5)); + static_assert(read_from_ptr(arr, 5) == u8(6)); + static_assert(read_from_ptr(arr, 6) == u8(7)); + static_assert(read_from_ptr(arr, 7) == u8(8)); + + static_assert(read_from_ptr>(arr, 0) == u16(0x0201)); + static_assert(read_from_ptr>(arr, 2) == u16(0x0403)); + static_assert(read_from_ptr>(arr, 4) == u16(0x0605)); + static_assert(read_from_ptr>(arr, 6) == u16(0x0807)); + + static_assert(read_from_ptr>(arr, 0) == u32(0x04030201)); + static_assert(read_from_ptr>(arr, 4) == u32(0x08070605)); + + static_assert(read_from_ptr>(arr, 0) == u64(0x0807060504030201)); + + constexpr std::array arr32 { 0x00000000, 0xFFFFFFFF }; + + static_assert(read_from_ptr(arr32, 0) == u32(0x00000000)); + static_assert(read_from_ptr(arr32, 1) == u32(0xFFFFFFFF)); + + static_assert(read_from_ptr>(arr32, 0) == u64(0xFFFFFFFF00000000)); + + // This should not compile +#if CHECK_COMPILATION_ERRORS + constexpr auto v8 = read_from_ptr(arr, arr.size()); + constexpr auto v16 = read_from_ptr(arr, arr.size() + 1 - sizeof(u16)); + constexpr auto v32 = read_from_ptr(arr, arr.size() + 1 - sizeof(u32)); + constexpr auto v64 = read_from_ptr(arr, arr.size() + 1 - sizeof(u64)); + constexpr auto v128 = read_from_ptr(arr, 0); + + constexpr auto v32_32 = read_from_ptr(arr32, arr32.size()); + constexpr auto v64_32 = read_from_ptr(arr32, arr32.size() + 1 - sizeof(u64) / sizeof(u32)); + constexpr auto v128_32 = read_from_ptr(arr32, 0); +#endif + } + + TEST(Utils, read_from_ptr_const_eval_c_array) + { + g_headless = true; // Disable exception popups + + constexpr u8 arr[sizeof(u64)] { 1, 2, 3, 4, 5, 6, 7, 8 }; + + static_assert(read_from_ptr(arr, 0) == u8(1)); + static_assert(read_from_ptr(arr, 1) == u8(2)); + static_assert(read_from_ptr(arr, 2) == u8(3)); + static_assert(read_from_ptr(arr, 3) == u8(4)); + static_assert(read_from_ptr(arr, 4) == u8(5)); + static_assert(read_from_ptr(arr, 5) == u8(6)); + static_assert(read_from_ptr(arr, 6) == u8(7)); + static_assert(read_from_ptr(arr, 7) == u8(8)); + + static_assert(read_from_ptr>(arr, 0) == u16(0x0201)); + static_assert(read_from_ptr>(arr, 2) == u16(0x0403)); + static_assert(read_from_ptr>(arr, 4) == u16(0x0605)); + static_assert(read_from_ptr>(arr, 6) == u16(0x0807)); + + static_assert(read_from_ptr>(arr, 0) == u32(0x04030201)); + static_assert(read_from_ptr>(arr, 4) == u32(0x08070605)); + + static_assert(read_from_ptr>(arr, 0) == u64(0x0807060504030201)); + + constexpr u32 arr32[sizeof(u64) / sizeof(u32)] { 0x00000000, 0xFFFFFFFF }; + + static_assert(read_from_ptr(arr32, 0) == u32(0x00000000)); + static_assert(read_from_ptr(arr32, 1) == u32(0xFFFFFFFF)); + + static_assert(read_from_ptr>(arr32, 0) == u64(0xFFFFFFFF00000000)); + + // This should not compile +#if CHECK_COMPILATION_ERRORS + constexpr auto v8 = read_from_ptr(arr, std::size(arr)); + constexpr auto v16 = read_from_ptr(arr, std::size(arr) + 1 - sizeof(u16)); + constexpr auto v32 = read_from_ptr(arr, std::size(arr) + 1 - sizeof(u32)); + constexpr auto v64 = read_from_ptr(arr, std::size(arr) + 1 - sizeof(u64)); + constexpr auto v128 = read_from_ptr(arr, 0); + + constexpr auto v32_32 = read_from_ptr(arr32, std::size(arr32)); + constexpr auto v64_32 = read_from_ptr(arr32, std::size(arr32) + 1 - sizeof(u64) / sizeof(u32)); + constexpr auto v128_32 = read_from_ptr(arr32, 0); +#endif + } +} diff --git a/rpcs3/util/types.hpp b/rpcs3/util/types.hpp index a1a07bac69..c33724a054 100644 --- a/rpcs3/util/types.hpp +++ b/rpcs3/util/types.hpp @@ -941,6 +941,7 @@ using const_str = const_str_t<>; namespace fmt { [[noreturn]] void raw_verify_error(std::source_location loc, const char8_t* msg, usz object); + [[noreturn]] void raw_verify_error(std::source_location loc, std::source_location propagated_loc, const char8_t* msg, usz object); [[noreturn]] void raw_range_error(std::source_location loc, std::string_view index, usz container_size); [[noreturn]] void raw_range_error(std::source_location loc, usz index, usz container_size); } @@ -976,6 +977,17 @@ constexpr decltype(auto) ensure(T&& arg, const_str msg = const_str(), std::sourc fmt::raw_verify_error(src_loc, msg, 0); } +template +constexpr decltype(auto) ensure(T&& arg, std::source_location propagated_loc, const_str msg = const_str(), std::source_location src_loc = std::source_location::current()) noexcept +{ + if (std::forward(arg)) [[likely]] + { + return std::forward(arg); + } + + fmt::raw_verify_error(src_loc, propagated_loc, msg, 0); +} + template requires (std::is_invocable_v) constexpr decltype(auto) ensure(T&& arg, F&& pred, const_str msg = const_str(), std::source_location src_loc = std::source_location::current()) noexcept { @@ -1185,36 +1197,74 @@ namespace stx // Read object of type T from raw pointer, array, string, vector, or any contiguous container template -constexpr T read_from_ptr(U&& array, usz pos = 0) +constexpr T read_from_ptr(U&& array, usz pos = 0, std::source_location src_loc = std::source_location::current()) { // TODO: ensure array element types are trivial static_assert(sizeof(T) % sizeof(array[0]) == 0); - std::decay_t buf[sizeof(T) / sizeof(array[0])]; + constexpr usz elements_per_value = sizeof(T) / sizeof(array[0]); + + std::decay_t buf[elements_per_value]; + if (!std::is_constant_evaluated()) + { + if constexpr (requires { std::size(array); }) + { + ensure((pos + elements_per_value) <= std::size(array), src_loc); + } + std::memcpy(+buf, &array[pos], sizeof(buf)); + } else - for (usz i = 0; i < pos; buf[i] = array[pos + i], i++); + { + // We could add an ensure or static_assert for OOB, but lucky for us, the [] operator will not compile with OOB. + for (usz i = 0; i < elements_per_value; i++) + { + buf[i] = array[pos + i]; + } + } return std::bit_cast(buf); } template -constexpr void write_to_ptr(U&& array, usz pos, const T& value) +constexpr void write_to_ptr(U&& array, usz pos, const T& value, std::source_location src_loc = std::source_location::current()) { static_assert(sizeof(T) % sizeof(array[0]) == 0); + constexpr usz elements_per_value = sizeof(T) / sizeof(array[0]); + + if constexpr (requires { std::size(array); }) + { + ensure((pos + elements_per_value) <= std::size(array), src_loc); + } + if (!std::is_constant_evaluated()) + { std::memcpy(static_cast(&array[pos]), &value, sizeof(value)); + } else + { ensure(!"Unimplemented"); + } } template -constexpr void write_to_ptr(U&& array, const T& value) +constexpr void write_to_ptr(U&& array, const T& value, std::source_location src_loc = std::source_location::current()) { static_assert(sizeof(T) % sizeof(array[0]) == 0); + constexpr usz elements_per_value = sizeof(T) / sizeof(array[0]); + + if constexpr (requires { std::size(array); }) + { + ensure(elements_per_value <= std::size(array), src_loc); + } + if (!std::is_constant_evaluated()) + { std::memcpy(static_cast(&array[0]), &value, sizeof(value)); + } else + { ensure(!"Unimplemented"); + } } constexpr struct aref_tag_t{} aref_tag{};