Merge branch 'master' into macos-dj-hero-turntable

This commit is contained in:
silvatyrant 2026-06-30 17:54:45 +02:00 committed by GitHub
commit e98bd8aa7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 617 additions and 45 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -289,8 +289,8 @@ error_code cellGifDecReadHeader(vm::ptr<GifDecoder> mainHandle, vm::ptr<GifStrea
default: break; // TODO
}
if (read_from_ptr<be_t<u32>>(buffer + 0) != 0x47494638u ||
(read_from_ptr<le_t<u16>>(buffer + 4) != 0x6139u && read_from_ptr<le_t<u16>>(buffer + 4) != 0x6137u)) // Error: The first 6 bytes are not a valid GIF signature
if (read_from_ptr<be_t<u32>>(buffer, 0) != 0x47494638u ||
(read_from_ptr<le_t<u16>>(buffer, 4) != 0x6139u && read_from_ptr<le_t<u16>>(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
}

View File

@ -125,26 +125,26 @@ error_code cellJpgDecReadHeader(u32 mainHandle, u32 subHandle, vm::ptr<CellJpgDe
CellJpgDecInfo& current_info = subHandle_data->info;
// Write the header to buffer
std::unique_ptr<u8[]> buffer(new u8[fileSize]);
std::vector<u8> 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<lv2_fs_object, lv2_file>(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<le_t<u32>>(buffer.get() + 0) != 0xE0FFD8FF || // Error: Not a valid SOI header
read_from_ptr<u32>(buffer.get() + 6) != "JFIF"_u32) // Error: Not a valid JFIF string
if (read_from_ptr<le_t<u32>>(buffer, 0) != 0xE0FFD8FF || // Error: Not a valid SOI header
read_from_ptr<u32>(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<CellJpgDe
if (i >= 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<CellJpgDe
return CELL_JPGDEC_ERROR_HEADER;
}
if (buffer[i + 1] == 0xC0)
const u8 next = ::at32(buffer, i + 1);
if (next == 0xC0)
break; // 0xFFC0 is the "Start of frame" marker which contains the file size
i += 2; // Skip the block marker
block_length = buffer[i] * 0xFF + buffer[i + 1]; // Go to the next block
block_length = ::at32(buffer, i) * 0xFF + next; // Go to the next block
}
current_info.imageWidth = buffer[i + 7] * 0x100 + buffer[i + 8];
current_info.imageHeight = buffer[i + 5] * 0x100 + buffer[i + 6];
current_info.imageWidth = ::at32(buffer, i + 7) * 0x100 + ::at32(buffer, i + 8);
current_info.imageHeight = ::at32(buffer, i + 5) * 0x100 + ::at32(buffer, i + 6);
current_info.numComponents = 3; // Unimplemented
current_info.colorSpace = CELL_JPG_RGB;

View File

@ -589,7 +589,7 @@ u32 ppu_read_mmio_aware_u32(u8* vm_base, u32 eal)
}
// Value is assumed to be swapped
return read_from_ptr<u32>(vm_base + eal);
return read_from_ptr<u32>(vm_base, eal);
}
void ppu_write_mmio_aware_u32(u8* vm_base, u32 eal, u32 value)

View File

@ -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<const char*>(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<u32>(SPU_LS_SIZE - vaddr, size);
if (!g_cfg.core.llvm_precompilation)
{
return;
@ -2364,11 +2378,22 @@ std::vector<u32> spu_thread::discover_functions(u32 base_addr, std::span<const u
// TODO: Does not detect jumptables or fixed-addr indirect calls
const v128 brasl_mask = is_known_addr ? v128::from32p(0x62u << 23) : v128::from32p(umax);
for (u32 i = utils::align<u32>(base_addr, 0x10); i < std::min<u32>(base_addr + ::size32(ls), 0x3FFF0); i += 0x10)
for (u32 i = base_addr, end_ls = std::min<u32>(base_addr + ::size32(ls), SPU_LS_SIZE); i < end_ls; i = utils::align<u32>(i + 1, 0x10))
{
// Search for BRSL LR and BRASL LR or BR
// TODO: BISL
const v128 inst = read_from_ptr<be_t<v128>>(ls.data(), i - base_addr);
be_t<v128> 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<be_t<v128>>(ls, i - base_addr);
}
const v128 inst = inst_be;
const v128 cleared_i16 = gv_and32(inst, v128::from32p(std::rotl<u32>(~0xffff, 7)));
const v128 eq_brsl = gv_eq32(cleared_i16, v128::from32p(0x66u << 23));
const v128 eq_brasl = gv_eq32(cleared_i16, brasl_mask);

View File

@ -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<fs::stat_t>(&ar.data[patch_stats_pos], stats);
write_to_ptr<fs::stat_t>(ar.data, patch_stats_pos, stats);
}
}

View File

@ -214,7 +214,7 @@ u32 dimensions_toypad::scramble(const std::array<u8, 7>& uid, u8 count)
}
::at32(to_scramble, count * 4 - 1) = 0xaa;
return read_from_ptr<be_t<u32>>(dimensions_randomize(to_scramble, count).data());
return read_from_ptr<be_t<u32>>(dimensions_randomize(to_scramble, count));
}
std::array<u8, 4> dimensions_toypad::dimensions_randomize(const std::vector<u8>& key, u8 count)
@ -522,7 +522,7 @@ bool dimensions_toypad::create_blank_character(std::array<u8, 0x2D * 0x04>& buf,
else
{
// Page 38 is used as verification for blank tags
write_to_ptr<be_t<u16>>(buf.data(), 38 * 4, 1);
write_to_ptr<be_t<u16>>(buf, 38 * 4, 1);
}
return true;

View File

@ -359,7 +359,7 @@ namespace np
return;
}
u32 given_size = read_from_ptr<be_t<u32>>(data() + 4);
u32 given_size = read_from_ptr<be_t<u32>>(data(), 4);
if ((given_size + 8) != size())
{
ticket_log.error("Size mismatch (gs: %d vs s: %d)", given_size, size());

View File

@ -503,8 +503,8 @@ namespace rpcn
{
if (msg.size() == 6)
{
const u32 new_addr_sig = read_from_ptr<le_t<u32>>(&msg[0]);
const u16 new_port_sig = read_from_ptr<be_t<u16>>(&msg[4]);
const u32 new_addr_sig = read_from_ptr<le_t<u32>>(msg, 0);
const u16 new_port_sig = read_from_ptr<be_t<u16>>(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<u8, 16> new_ipv6_addr;
// std::memcpy(new_ipv6_addr.data(), &msg[3], 16);
// const u32 new_ipv6_port = read_from_ptr<be_t<u16>>(&msg[16]);
// const u32 new_ipv6_port = read_from_ptr<be_t<u16>>(msg, 16);
last_pong_time_ipv6 = now;
}
@ -626,9 +626,9 @@ namespace rpcn
}
const u8 packet_type = header[0];
const auto command = static_cast<rpcn::CommandType>(static_cast<u16>(read_from_ptr<le_t<u16>>(&header[1])));
const u32 packet_size = read_from_ptr<le_t<u32>>(&header[3]);
const u64 packet_id = read_from_ptr<le_t<u64>>(&header[7]);
const auto command = static_cast<rpcn::CommandType>(static_cast<u16>(read_from_ptr<le_t<u16>>(header, 1)));
const u32 packet_size = read_from_ptr<le_t<u32>>(header, 3);
const u64 packet_id = read_from_ptr<le_t<u64>>(header, 7);
if (packet_size < RPCN_HEADER_SIZE)
return error_and_disconnect("Invalid packet size");

View File

@ -67,7 +67,7 @@ public:
error = true;
return static_cast<T>(0);
}
T res = read_from_ptr<le_t<T>>(&vec[i]);
T res = read_from_ptr<le_t<T>>(vec, i);
i += sizeof(T);
return res;
}

View File

@ -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<be_t<u64>>(fifo_span.data() + first_index_off);
const u64 src_op2_2 = read_from_ptr<be_t<u64>>(fifo_span.data() + second_index_off);
const u64 src_op1_2 = read_from_ptr<be_t<u64>>(fifo_span, first_index_off);
const u64 src_op2_2 = read_from_ptr<be_t<u64>>(fifo_span, second_index_off);
// Fast comparison
if (src_op1_2 != read_from_ptr<u64>(out_ptr + first_index_off) || src_op2_2 != read_from_ptr<u64>(out_ptr + second_index_off))
if (src_op1_2 != read_from_ptr<u64>(out_ptr, first_index_off) || src_op2_2 != read_from_ptr<u64>(out_ptr, second_index_off))
{
to_set_dirty = rsx::pipeline_state::vertex_program_ucode_dirty;
}

View File

@ -56,7 +56,7 @@ private:
#endif
#endif
#include <vk_mem_alloc.h>
#ifdef _MSC_VER
#if defined(_MSC_VER) && !defined(__clang__)
#pragma warning(pop)
#else
#pragma GCC diagnostic pop

View File

@ -126,7 +126,7 @@ struct cfg_root : cfg::node
cfg::_enum<frame_limit_type> 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<msaa_level> antialiasing_level{ this, "MSAA", msaa_level::_auto };
cfg::_enum<shader_mode> shadermode{ this, "Shader Mode", shader_mode::async_recompiler };
cfg::_enum<shader_mode> shadermode{ this, "Shader Mode", shader_mode::async_with_interpreter };
cfg::_enum<gpu_preset_level> shader_precision{ this, "Shader Precision", gpu_preset_level::high };
cfg::_enum<vsync_mode> 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 };

View File

@ -29,7 +29,7 @@ Most of this information can be found on the PlayStation 3 Developer Wiki.</p>
<url type="bugtracker">https://github.com/RPCS3/rpcs3/issues</url>
<url type="faq">https://rpcs3.net/faq</url>
<url type="help">https://rpcs3.net/quickstart</url>
<url type="donation">https://www.patreon.com/Nekotekina</url>
<url type="donation">https://rpcs3.net/patreon</url>
<url type="vcs-browser">https://github.com/RPCS3/rpcs3</url>
<url type="contribute">https://github.com/RPCS3/rpcs3#contributing</url>
<categories>

View File

@ -234,7 +234,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent)
std::array<u8, 16> figure_data = {static_cast<u8>(dist(mt)), 0x03, 0x00, 0x00, 0x01, 0x0e, 0x0a, 0x0a,
static_cast<u8>(fig_id & 0xff), static_cast<u8>((fig_id >> 8) & 0xff),
static_cast<u8>((fig_id >> 16) & 0xff), static_cast<u8>((fig_id >> 24) & 0xff)};
write_to_ptr<le_t<u32>>(figure_data.data(), 0xC, kamen_rider_crc32(figure_data));
write_to_ptr<le_t<u32>>(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();

View File

@ -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());

View File

@ -105,6 +105,7 @@
<ClCompile Include="test_sys_fs.cpp" />
<ClCompile Include="test_tuple.cpp" />
<ClCompile Include="test_pair.cpp" />
<ClCompile Include="test_types_util.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" Condition="'$(GTestInstalled)' == 'true'">

View File

@ -0,0 +1,487 @@
#include "stdafx.h"
#include <gtest/gtest.h>
extern atomic_t<bool> 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<u8, sizeof(u64)> arr{};
write_to_ptr(arr, u8(umax));
EXPECT_EQ(arr.at(0), u8(umax));
write_to_ptr(arr, u16(umax));
EXPECT_EQ(*utils::bless<u16>(arr.data()), u16(umax));
write_to_ptr(arr, u32(umax));
EXPECT_EQ(*utils::bless<u32>(arr.data()), u32(umax));
write_to_ptr(arr, u64(umax));
EXPECT_EQ(*utils::bless<u64>(arr.data()), u64(umax));
// write_to_ptr and read_from_ptr with pos
std::array<u8, sizeof(u128)> 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<u8>(arr2, i), i);
}
arr2 = {};
for (u16 i = 0; i < arr2.size(); i += sizeof(u16))
{
write_to_ptr(arr2, i, i);
EXPECT_EQ(*utils::bless<u16>(arr2.data() + i), i);
EXPECT_EQ(read_from_ptr<u16>(arr2, i), i);
}
arr2 = {};
for (u32 i = 0; i < arr2.size(); i += sizeof(u32))
{
write_to_ptr(arr2, i, i);
EXPECT_EQ(*utils::bless<u32>(arr2.data() + i), i);
EXPECT_EQ(read_from_ptr<u32>(arr2, i), i);
}
arr2 = {};
for (u64 i = 0; i < arr2.size(); i += sizeof(u64))
{
write_to_ptr(arr2, i, i);
EXPECT_EQ(*utils::bless<u64>(arr2.data() + i), i);
EXPECT_EQ(read_from_ptr<u64>(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<u128>(arr2, i);
EXPECT_EQ(std::memcmp(&val_u128, &exp_u128, sizeof(u128)), 0);
}
// write_to_ptr without pos
std::array<u32, sizeof(u64) / sizeof(u32)> arr32{};
write_to_ptr(arr32, u32(umax));
EXPECT_EQ(*utils::bless<u32>(arr32.data()), u32(umax));
write_to_ptr(arr32, u64(umax));
EXPECT_EQ(*utils::bless<u64>(arr32.data()), u64(umax));
// write_to_ptr and read_from_ptr with pos
std::array<u32, sizeof(u128) / sizeof(u32)> 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<u32>(arr32_2.data() + index), i);
EXPECT_EQ(read_from_ptr<u32>(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<u64>(arr32_2.data() + index), i);
EXPECT_EQ(read_from_ptr<u64>(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<u128>(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<u8>(arr2, arr2.size()), ".*");
EXPECT_DEATH(read_from_ptr<u16>(arr2, arr2.size()), ".*");
EXPECT_DEATH(read_from_ptr<u32>(arr2, arr2.size()), ".*");
EXPECT_DEATH(read_from_ptr<u64>(arr2, arr2.size()), ".*");
EXPECT_DEATH(read_from_ptr<u128>(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<u32>(arr32_2, arr32_2.size()), ".*");
EXPECT_DEATH(read_from_ptr<u64>(arr32_2, arr32_2.size()), ".*");
EXPECT_DEATH(read_from_ptr<u128>(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<u16>(arr + 0), u16(umax));
write_to_ptr(arr, u32(umax));
EXPECT_EQ(*utils::bless<u32>(arr + 0), u32(umax));
write_to_ptr(arr, u64(umax));
EXPECT_EQ(*utils::bless<u64>(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<u8>(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<u16>(arr2 + i), i);
EXPECT_EQ(read_from_ptr<u16>(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<u32>(arr2 + i), i);
EXPECT_EQ(read_from_ptr<u32>(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<u64>(arr2 + i), i);
EXPECT_EQ(read_from_ptr<u64>(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<u128>(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<u32>(arr32 + 0), u32(umax));
write_to_ptr(arr32, u64(umax));
EXPECT_EQ(*utils::bless<u64>(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<u32>(arr32_2 + index), i);
EXPECT_EQ(read_from_ptr<u32>(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<u64>(arr32_2 + index), i);
EXPECT_EQ(read_from_ptr<u64>(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<u128>(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<u8>(arr2, std::size(arr2)), ".*");
EXPECT_DEATH(read_from_ptr<u16>(arr2, std::size(arr2)), ".*");
EXPECT_DEATH(read_from_ptr<u32>(arr2, std::size(arr2)), ".*");
EXPECT_DEATH(read_from_ptr<u64>(arr2, std::size(arr2)), ".*");
EXPECT_DEATH(read_from_ptr<u128>(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<u32>(arr32_2, std::size(arr32_2)), ".*");
EXPECT_DEATH(read_from_ptr<u64>(arr32_2, std::size(arr32_2)), ".*");
EXPECT_DEATH(read_from_ptr<u128>(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<u16>(arr), u16(umax));
write_to_ptr(arr, u32(umax));
EXPECT_EQ(*utils::bless<u32>(arr), u32(umax));
write_to_ptr(arr, u64(umax));
EXPECT_EQ(*utils::bless<u64>(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<u8>(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<u16>(arr2 + i), i);
EXPECT_EQ(read_from_ptr<u16>(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<u32>(arr2 + i), i);
EXPECT_EQ(read_from_ptr<u32>(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<u64>(arr2 + i), i);
EXPECT_EQ(read_from_ptr<u64>(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<u128>(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<u32>(arr32), u32(umax));
write_to_ptr(arr32, u64(umax));
EXPECT_EQ(*utils::bless<u64>(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<u32>(arr32_2 + index), i);
EXPECT_EQ(read_from_ptr<u32>(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<u64>(arr32_2 + index), i);
EXPECT_EQ(read_from_ptr<u64>(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<u128>(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<u8>(arr2, arr2_size), ".*");
EXPECT_DEATH(read_from_ptr<u16>(arr2, arr2_size), ".*");
EXPECT_DEATH(read_from_ptr<u32>(arr2, arr2_size), ".*");
EXPECT_DEATH(read_from_ptr<u64>(arr2, arr2_size), ".*");
EXPECT_DEATH(read_from_ptr<u128>(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<u32>(arr32_2, arr32_count), ".*");
EXPECT_DEATH(read_from_ptr<u64>(arr32_2, arr32_count), ".*");
EXPECT_DEATH(read_from_ptr<u128>(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<u8, sizeof(u64)> arr { 1, 2, 3, 4, 5, 6, 7, 8 };
static_assert(read_from_ptr<u8>(arr, 0) == u8(1));
static_assert(read_from_ptr<u8>(arr, 1) == u8(2));
static_assert(read_from_ptr<u8>(arr, 2) == u8(3));
static_assert(read_from_ptr<u8>(arr, 3) == u8(4));
static_assert(read_from_ptr<u8>(arr, 4) == u8(5));
static_assert(read_from_ptr<u8>(arr, 5) == u8(6));
static_assert(read_from_ptr<u8>(arr, 6) == u8(7));
static_assert(read_from_ptr<u8>(arr, 7) == u8(8));
static_assert(read_from_ptr<le_t<u16>>(arr, 0) == u16(0x0201));
static_assert(read_from_ptr<le_t<u16>>(arr, 2) == u16(0x0403));
static_assert(read_from_ptr<le_t<u16>>(arr, 4) == u16(0x0605));
static_assert(read_from_ptr<le_t<u16>>(arr, 6) == u16(0x0807));
static_assert(read_from_ptr<le_t<u32>>(arr, 0) == u32(0x04030201));
static_assert(read_from_ptr<le_t<u32>>(arr, 4) == u32(0x08070605));
static_assert(read_from_ptr<le_t<u64>>(arr, 0) == u64(0x0807060504030201));
constexpr std::array<u32, sizeof(u64) / sizeof(u32)> arr32 { 0x00000000, 0xFFFFFFFF };
static_assert(read_from_ptr<u32>(arr32, 0) == u32(0x00000000));
static_assert(read_from_ptr<u32>(arr32, 1) == u32(0xFFFFFFFF));
static_assert(read_from_ptr<le_t<u64>>(arr32, 0) == u64(0xFFFFFFFF00000000));
// This should not compile
#if CHECK_COMPILATION_ERRORS
constexpr auto v8 = read_from_ptr<u8>(arr, arr.size());
constexpr auto v16 = read_from_ptr<u16>(arr, arr.size() + 1 - sizeof(u16));
constexpr auto v32 = read_from_ptr<u32>(arr, arr.size() + 1 - sizeof(u32));
constexpr auto v64 = read_from_ptr<u64>(arr, arr.size() + 1 - sizeof(u64));
constexpr auto v128 = read_from_ptr<u128>(arr, 0);
constexpr auto v32_32 = read_from_ptr<u32>(arr32, arr32.size());
constexpr auto v64_32 = read_from_ptr<u64>(arr32, arr32.size() + 1 - sizeof(u64) / sizeof(u32));
constexpr auto v128_32 = read_from_ptr<u128>(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<u8>(arr, 0) == u8(1));
static_assert(read_from_ptr<u8>(arr, 1) == u8(2));
static_assert(read_from_ptr<u8>(arr, 2) == u8(3));
static_assert(read_from_ptr<u8>(arr, 3) == u8(4));
static_assert(read_from_ptr<u8>(arr, 4) == u8(5));
static_assert(read_from_ptr<u8>(arr, 5) == u8(6));
static_assert(read_from_ptr<u8>(arr, 6) == u8(7));
static_assert(read_from_ptr<u8>(arr, 7) == u8(8));
static_assert(read_from_ptr<le_t<u16>>(arr, 0) == u16(0x0201));
static_assert(read_from_ptr<le_t<u16>>(arr, 2) == u16(0x0403));
static_assert(read_from_ptr<le_t<u16>>(arr, 4) == u16(0x0605));
static_assert(read_from_ptr<le_t<u16>>(arr, 6) == u16(0x0807));
static_assert(read_from_ptr<le_t<u32>>(arr, 0) == u32(0x04030201));
static_assert(read_from_ptr<le_t<u32>>(arr, 4) == u32(0x08070605));
static_assert(read_from_ptr<le_t<u64>>(arr, 0) == u64(0x0807060504030201));
constexpr u32 arr32[sizeof(u64) / sizeof(u32)] { 0x00000000, 0xFFFFFFFF };
static_assert(read_from_ptr<u32>(arr32, 0) == u32(0x00000000));
static_assert(read_from_ptr<u32>(arr32, 1) == u32(0xFFFFFFFF));
static_assert(read_from_ptr<le_t<u64>>(arr32, 0) == u64(0xFFFFFFFF00000000));
// This should not compile
#if CHECK_COMPILATION_ERRORS
constexpr auto v8 = read_from_ptr<u8>(arr, std::size(arr));
constexpr auto v16 = read_from_ptr<u16>(arr, std::size(arr) + 1 - sizeof(u16));
constexpr auto v32 = read_from_ptr<u32>(arr, std::size(arr) + 1 - sizeof(u32));
constexpr auto v64 = read_from_ptr<u64>(arr, std::size(arr) + 1 - sizeof(u64));
constexpr auto v128 = read_from_ptr<u128>(arr, 0);
constexpr auto v32_32 = read_from_ptr<u32>(arr32, std::size(arr32));
constexpr auto v64_32 = read_from_ptr<u64>(arr32, std::size(arr32) + 1 - sizeof(u64) / sizeof(u32));
constexpr auto v128_32 = read_from_ptr<u128>(arr32, 0);
#endif
}
}

View File

@ -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 <typename T>
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<T>(arg)) [[likely]]
{
return std::forward<T>(arg);
}
fmt::raw_verify_error(src_loc, propagated_loc, msg, 0);
}
template <typename T, typename F> requires (std::is_invocable_v<F, T&&>)
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 <typename T, typename U>
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<decltype(array[0])> buf[sizeof(T) / sizeof(array[0])];
constexpr usz elements_per_value = sizeof(T) / sizeof(array[0]);
std::decay_t<decltype(array[0])> 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<T>(buf);
}
template <typename T, typename U>
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<void*>(&array[pos]), &value, sizeof(value));
}
else
{
ensure(!"Unimplemented");
}
}
template <typename T, typename U>
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<void*>(&array[0]), &value, sizeof(value));
}
else
{
ensure(!"Unimplemented");
}
}
constexpr struct aref_tag_t{} aref_tag{};