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{};