From 0eb7012b5750939d8e44617dfcb3f2d3f0cbd8cd Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sat, 7 Feb 2026 21:30:27 +0000 Subject: [PATCH] AK: Add `TypedTransfer::relocate()` This moves objects from source to destination destructively, ensuring the destructors are called if necessary. --- AK/TypedTransfer.h | 18 +++++++ Tests/AK/TestTypedTransfer.cpp | 89 ++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/AK/TypedTransfer.h b/AK/TypedTransfer.h index 4b11b071928..a286913a81d 100644 --- a/AK/TypedTransfer.h +++ b/AK/TypedTransfer.h @@ -70,6 +70,24 @@ public: return true; } + static void relocate(T* destination, T* source, size_t count) + { + VERIFY(source + count <= destination || destination + count <= source); + + if (count == 0) + return; + + if constexpr (IsTriviallyRelocatable) { + __builtin_memcpy(destination, source, count * sizeof(T)); + return; + } + + for (size_t i = 0; i < count; ++i) { + new (&destination[i]) T(AK::move(source[i])); + source[i].~T(); + } + } + static void delete_(T* ptr, size_t count) { if (count == 0) diff --git a/Tests/AK/TestTypedTransfer.cpp b/Tests/AK/TestTypedTransfer.cpp index 392d6a6600b..911701741f6 100644 --- a/Tests/AK/TestTypedTransfer.cpp +++ b/Tests/AK/TestTypedTransfer.cpp @@ -7,6 +7,7 @@ #include #include +#include #include struct NonPrimitiveIntWrapper { @@ -18,6 +19,41 @@ struct NonPrimitiveIntWrapper { int m_value; }; +static_assert(IsTriviallyRelocatable); + +struct NonTriviallyRelocatable { + NonTriviallyRelocatable(int value) + : m_value(value) + { + ++s_construct_count; + } + + NonTriviallyRelocatable(NonTriviallyRelocatable&& other) + : m_value(other.m_value) + { + other.m_value = -1; + ++s_construct_count; + } + + ~NonTriviallyRelocatable() + { + ++s_destruct_count; + } + + NonTriviallyRelocatable(NonTriviallyRelocatable const&) = delete; + NonTriviallyRelocatable& operator=(NonTriviallyRelocatable const&) = delete; + + int m_value; + + static int s_construct_count; + static int s_destruct_count; +}; + +int NonTriviallyRelocatable::s_construct_count = 0; +int NonTriviallyRelocatable::s_destruct_count = 0; + +static_assert(!IsTriviallyRelocatable); + TEST_CASE(overlapping_source_and_destination_1) { Array const expected { 3, 4, 5, 6, 5, 6 }; @@ -39,3 +75,56 @@ TEST_CASE(overlapping_source_and_destination_2) for (size_t i = 0; i < 6; ++i) EXPECT_EQ(actual[i].m_value, expected[i].m_value); } + +TEST_CASE(relocate_trivially_relocatable) +{ + Array source { 10, 20, 30, 40 }; + alignas(int) u8 destination_storage[4 * sizeof(int)]; + auto* destination = reinterpret_cast(destination_storage); + + AK::TypedTransfer::relocate(destination, source.data(), 4); + + EXPECT_EQ(destination[0], 10); + EXPECT_EQ(destination[1], 20); + EXPECT_EQ(destination[2], 30); + EXPECT_EQ(destination[3], 40); +} + +TEST_CASE(relocate_non_trivially_relocatable) +{ + alignas(NonTriviallyRelocatable) u8 source_storage[3 * sizeof(NonTriviallyRelocatable)]; + alignas(NonTriviallyRelocatable) u8 destination_storage[3 * sizeof(NonTriviallyRelocatable)]; + auto* source = reinterpret_cast(source_storage); + auto* destination = reinterpret_cast(destination_storage); + + new (&source[0]) NonTriviallyRelocatable(100); + new (&source[1]) NonTriviallyRelocatable(200); + new (&source[2]) NonTriviallyRelocatable(300); + + ScopeGuard cleanup([&] { + AK::TypedTransfer::delete_(destination, 3); + }); + + NonTriviallyRelocatable::s_construct_count = 0; + NonTriviallyRelocatable::s_destruct_count = 0; + + AK::TypedTransfer::relocate(destination, source, 3); + + EXPECT_EQ(destination[0].m_value, 100); + EXPECT_EQ(destination[1].m_value, 200); + EXPECT_EQ(destination[2].m_value, 300); + + EXPECT_EQ(NonTriviallyRelocatable::s_construct_count, 3); + EXPECT_EQ(NonTriviallyRelocatable::s_destruct_count, 3); +} + +TEST_CASE(relocate_zero_count) +{ + NonTriviallyRelocatable::s_construct_count = 0; + NonTriviallyRelocatable::s_destruct_count = 0; + + AK::TypedTransfer::relocate(nullptr, nullptr, 0); + + EXPECT_EQ(NonTriviallyRelocatable::s_construct_count, 0); + EXPECT_EQ(NonTriviallyRelocatable::s_destruct_count, 0); +}