summaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/CMakeLists.txt1
-rw-r--r--src/common/bounded_threadsafe_queue.h316
-rw-r--r--src/common/logging/backend.cpp16
-rw-r--r--src/common/string_util.cpp14
-rw-r--r--src/common/string_util.h8
-rw-r--r--src/common/typed_address.h320
6 files changed, 543 insertions, 132 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 61ab68864..90805babe 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -132,6 +132,7 @@ add_library(common STATIC
time_zone.h
tiny_mt.h
tree.h
+ typed_address.h
uint128.h
unique_function.h
uuid.cpp
diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h
index 14e887c70..bd87aa09b 100644
--- a/src/common/bounded_threadsafe_queue.h
+++ b/src/common/bounded_threadsafe_queue.h
@@ -1,159 +1,249 @@
-// SPDX-FileCopyrightText: Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se>
-// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
-#include <bit>
#include <condition_variable>
-#include <memory>
+#include <cstddef>
#include <mutex>
#include <new>
-#include <type_traits>
-#include <utility>
#include "common/polyfill_thread.h"
namespace Common {
-#if defined(__cpp_lib_hardware_interference_size)
-constexpr size_t hardware_interference_size = std::hardware_destructive_interference_size;
-#else
-constexpr size_t hardware_interference_size = 64;
-#endif
+namespace detail {
+constexpr size_t DefaultCapacity = 0x1000;
+} // namespace detail
+
+template <typename T, size_t Capacity = detail::DefaultCapacity>
+class SPSCQueue {
+ static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two.");
-template <typename T, size_t capacity = 0x400>
-class MPSCQueue {
public:
- explicit MPSCQueue() : allocator{std::allocator<Slot<T>>()} {
- // Allocate one extra slot to prevent false sharing on the last slot
- slots = allocator.allocate(capacity + 1);
- // Allocators are not required to honor alignment for over-aligned types
- // (see http://eel.is/c++draft/allocator.requirements#10) so we verify
- // alignment here
- if (reinterpret_cast<uintptr_t>(slots) % alignof(Slot<T>) != 0) {
- allocator.deallocate(slots, capacity + 1);
- throw std::bad_alloc();
- }
- for (size_t i = 0; i < capacity; ++i) {
- std::construct_at(&slots[i]);
- }
- static_assert(std::has_single_bit(capacity), "capacity must be an integer power of 2");
- static_assert(alignof(Slot<T>) == hardware_interference_size,
- "Slot must be aligned to cache line boundary to prevent false sharing");
- static_assert(sizeof(Slot<T>) % hardware_interference_size == 0,
- "Slot size must be a multiple of cache line size to prevent "
- "false sharing between adjacent slots");
- static_assert(sizeof(MPSCQueue) % hardware_interference_size == 0,
- "Queue size must be a multiple of cache line size to "
- "prevent false sharing between adjacent queues");
- }
-
- ~MPSCQueue() noexcept {
- for (size_t i = 0; i < capacity; ++i) {
- std::destroy_at(&slots[i]);
- }
- allocator.deallocate(slots, capacity + 1);
+ template <typename... Args>
+ bool TryEmplace(Args&&... args) {
+ return Emplace<PushMode::Try>(std::forward<Args>(args)...);
}
- // The queue must be both non-copyable and non-movable
- MPSCQueue(const MPSCQueue&) = delete;
- MPSCQueue& operator=(const MPSCQueue&) = delete;
+ template <typename... Args>
+ void EmplaceWait(Args&&... args) {
+ Emplace<PushMode::Wait>(std::forward<Args>(args)...);
+ }
- MPSCQueue(MPSCQueue&&) = delete;
- MPSCQueue& operator=(MPSCQueue&&) = delete;
+ bool TryPop(T& t) {
+ return Pop<PopMode::Try>(t);
+ }
- void Push(const T& v) noexcept {
- static_assert(std::is_nothrow_copy_constructible_v<T>,
- "T must be nothrow copy constructible");
- emplace(v);
+ void PopWait(T& t) {
+ Pop<PopMode::Wait>(t);
}
- template <typename P, typename = std::enable_if_t<std::is_nothrow_constructible_v<T, P&&>>>
- void Push(P&& v) noexcept {
- emplace(std::forward<P>(v));
+ void PopWait(T& t, std::stop_token stop_token) {
+ Pop<PopMode::WaitWithStopToken>(t, stop_token);
}
- void Pop(T& v, std::stop_token stop) noexcept {
- auto const tail = tail_.fetch_add(1);
- auto& slot = slots[idx(tail)];
- if (!slot.turn.test()) {
- std::unique_lock lock{cv_mutex};
- Common::CondvarWait(cv, lock, stop, [&slot] { return slot.turn.test(); });
- }
- v = slot.move();
- slot.destroy();
- slot.turn.clear();
- slot.turn.notify_one();
+ T PopWait() {
+ T t;
+ Pop<PopMode::Wait>(t);
+ return t;
+ }
+
+ T PopWait(std::stop_token stop_token) {
+ T t;
+ Pop<PopMode::WaitWithStopToken>(t, stop_token);
+ return t;
}
private:
- template <typename U = T>
- struct Slot {
- ~Slot() noexcept {
- if (turn.test()) {
- destroy();
+ enum class PushMode {
+ Try,
+ Wait,
+ Count,
+ };
+
+ enum class PopMode {
+ Try,
+ Wait,
+ WaitWithStopToken,
+ Count,
+ };
+
+ template <PushMode Mode, typename... Args>
+ bool Emplace(Args&&... args) {
+ const size_t write_index = m_write_index.load(std::memory_order::relaxed);
+
+ if constexpr (Mode == PushMode::Try) {
+ // Check if we have free slots to write to.
+ if ((write_index - m_read_index.load(std::memory_order::acquire)) == Capacity) {
+ return false;
}
+ } else if constexpr (Mode == PushMode::Wait) {
+ // Wait until we have free slots to write to.
+ std::unique_lock lock{producer_cv_mutex};
+ producer_cv.wait(lock, [this, write_index] {
+ return (write_index - m_read_index.load(std::memory_order::acquire)) < Capacity;
+ });
+ } else {
+ static_assert(Mode < PushMode::Count, "Invalid PushMode.");
}
- template <typename... Args>
- void construct(Args&&... args) noexcept {
- static_assert(std::is_nothrow_constructible_v<U, Args&&...>,
- "T must be nothrow constructible with Args&&...");
- std::construct_at(reinterpret_cast<U*>(&storage), std::forward<Args>(args)...);
- }
+ // Determine the position to write to.
+ const size_t pos = write_index % Capacity;
- void destroy() noexcept {
- static_assert(std::is_nothrow_destructible_v<U>, "T must be nothrow destructible");
- std::destroy_at(reinterpret_cast<U*>(&storage));
- }
+ // Emplace into the queue.
+ std::construct_at(std::addressof(m_data[pos]), std::forward<Args>(args)...);
+
+ // Increment the write index.
+ ++m_write_index;
+
+ // Notify the consumer that we have pushed into the queue.
+ std::scoped_lock lock{consumer_cv_mutex};
+ consumer_cv.notify_one();
- U&& move() noexcept {
- return reinterpret_cast<U&&>(storage);
+ return true;
+ }
+
+ template <PopMode Mode>
+ bool Pop(T& t, [[maybe_unused]] std::stop_token stop_token = {}) {
+ const size_t read_index = m_read_index.load(std::memory_order::relaxed);
+
+ if constexpr (Mode == PopMode::Try) {
+ // Check if the queue is empty.
+ if (read_index == m_write_index.load(std::memory_order::acquire)) {
+ return false;
+ }
+ } else if constexpr (Mode == PopMode::Wait) {
+ // Wait until the queue is not empty.
+ std::unique_lock lock{consumer_cv_mutex};
+ consumer_cv.wait(lock, [this, read_index] {
+ return read_index != m_write_index.load(std::memory_order::acquire);
+ });
+ } else if constexpr (Mode == PopMode::WaitWithStopToken) {
+ // Wait until the queue is not empty.
+ std::unique_lock lock{consumer_cv_mutex};
+ Common::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] {
+ return read_index != m_write_index.load(std::memory_order::acquire);
+ });
+ if (stop_token.stop_requested()) {
+ return false;
+ }
+ } else {
+ static_assert(Mode < PopMode::Count, "Invalid PopMode.");
}
- // Align to avoid false sharing between adjacent slots
- alignas(hardware_interference_size) std::atomic_flag turn{};
- struct aligned_store {
- struct type {
- alignas(U) unsigned char data[sizeof(U)];
- };
- };
- typename aligned_store::type storage;
- };
+ // Determine the position to read from.
+ const size_t pos = read_index % Capacity;
+
+ // Pop the data off the queue, moving it.
+ t = std::move(m_data[pos]);
+
+ // Increment the read index.
+ ++m_read_index;
+
+ // Notify the producer that we have popped off the queue.
+ std::scoped_lock lock{producer_cv_mutex};
+ producer_cv.notify_one();
+
+ return true;
+ }
+
+ alignas(128) std::atomic_size_t m_read_index{0};
+ alignas(128) std::atomic_size_t m_write_index{0};
+ std::array<T, Capacity> m_data;
+
+ std::condition_variable_any producer_cv;
+ std::mutex producer_cv_mutex;
+ std::condition_variable_any consumer_cv;
+ std::mutex consumer_cv_mutex;
+};
+
+template <typename T, size_t Capacity = detail::DefaultCapacity>
+class MPSCQueue {
+public:
+ template <typename... Args>
+ bool TryEmplace(Args&&... args) {
+ std::scoped_lock lock{write_mutex};
+ return spsc_queue.TryEmplace(std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ void EmplaceWait(Args&&... args) {
+ std::scoped_lock lock{write_mutex};
+ spsc_queue.EmplaceWait(std::forward<Args>(args)...);
+ }
+
+ bool TryPop(T& t) {
+ return spsc_queue.TryPop(t);
+ }
+
+ void PopWait(T& t) {
+ spsc_queue.PopWait(t);
+ }
+
+ void PopWait(T& t, std::stop_token stop_token) {
+ spsc_queue.PopWait(t, stop_token);
+ }
+
+ T PopWait() {
+ return spsc_queue.PopWait();
+ }
+
+ T PopWait(std::stop_token stop_token) {
+ return spsc_queue.PopWait(stop_token);
+ }
+
+private:
+ SPSCQueue<T, Capacity> spsc_queue;
+ std::mutex write_mutex;
+};
+
+template <typename T, size_t Capacity = detail::DefaultCapacity>
+class MPMCQueue {
+public:
template <typename... Args>
- void emplace(Args&&... args) noexcept {
- static_assert(std::is_nothrow_constructible_v<T, Args&&...>,
- "T must be nothrow constructible with Args&&...");
- auto const head = head_.fetch_add(1);
- auto& slot = slots[idx(head)];
- slot.turn.wait(true);
- slot.construct(std::forward<Args>(args)...);
- slot.turn.test_and_set();
- cv.notify_one();
+ bool TryEmplace(Args&&... args) {
+ std::scoped_lock lock{write_mutex};
+ return spsc_queue.TryEmplace(std::forward<Args>(args)...);
}
- constexpr size_t idx(size_t i) const noexcept {
- return i & mask;
+ template <typename... Args>
+ void EmplaceWait(Args&&... args) {
+ std::scoped_lock lock{write_mutex};
+ spsc_queue.EmplaceWait(std::forward<Args>(args)...);
}
- static constexpr size_t mask = capacity - 1;
+ bool TryPop(T& t) {
+ std::scoped_lock lock{read_mutex};
+ return spsc_queue.TryPop(t);
+ }
- // Align to avoid false sharing between head_ and tail_
- alignas(hardware_interference_size) std::atomic<size_t> head_{0};
- alignas(hardware_interference_size) std::atomic<size_t> tail_{0};
+ void PopWait(T& t) {
+ std::scoped_lock lock{read_mutex};
+ spsc_queue.PopWait(t);
+ }
- std::mutex cv_mutex;
- std::condition_variable_any cv;
+ void PopWait(T& t, std::stop_token stop_token) {
+ std::scoped_lock lock{read_mutex};
+ spsc_queue.PopWait(t, stop_token);
+ }
- Slot<T>* slots;
- [[no_unique_address]] std::allocator<Slot<T>> allocator;
+ T PopWait() {
+ std::scoped_lock lock{read_mutex};
+ return spsc_queue.PopWait();
+ }
- static_assert(std::is_nothrow_copy_assignable_v<T> || std::is_nothrow_move_assignable_v<T>,
- "T must be nothrow copy or move assignable");
+ T PopWait(std::stop_token stop_token) {
+ std::scoped_lock lock{read_mutex};
+ return spsc_queue.PopWait(stop_token);
+ }
- static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible");
+private:
+ SPSCQueue<T, Capacity> spsc_queue;
+ std::mutex write_mutex;
+ std::mutex read_mutex;
};
} // namespace Common
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 2a3bded40..f96c7c222 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -28,7 +28,7 @@
#ifdef _WIN32
#include "common/string_util.h"
#endif
-#include "common/threadsafe_queue.h"
+#include "common/bounded_threadsafe_queue.h"
namespace Common::Log {
@@ -204,11 +204,11 @@ public:
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, std::string&& message) {
- if (!filter.CheckMessage(log_class, log_level))
+ if (!filter.CheckMessage(log_class, log_level)) {
return;
- const Entry& entry =
- CreateEntry(log_class, log_level, filename, line_num, function, std::move(message));
- message_queue.Push(entry);
+ }
+ message_queue.EmplaceWait(
+ CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
}
private:
@@ -225,7 +225,7 @@ private:
ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
};
while (!stop_token.stop_requested()) {
- entry = message_queue.PopWait(stop_token);
+ message_queue.PopWait(entry, stop_token);
if (entry.filename != nullptr) {
write_logs();
}
@@ -233,7 +233,7 @@ private:
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
// case where a system is repeatedly spamming logs even on close.
int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
- while (max_logs_to_write-- && message_queue.Pop(entry)) {
+ while (max_logs_to_write-- && message_queue.TryPop(entry)) {
write_logs();
}
});
@@ -273,7 +273,7 @@ private:
ColorConsoleBackend color_console_backend{};
FileBackend file_backend;
- MPSCQueue<Entry, true> message_queue{};
+ MPSCQueue<Entry> message_queue{};
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
std::jthread backend_thread;
};
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index e0b6180c5..feab1653d 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -125,18 +125,18 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
return result;
}
-std::string UTF16ToUTF8(const std::u16string& input) {
+std::string UTF16ToUTF8(std::u16string_view input) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
- return convert.to_bytes(input);
+ return convert.to_bytes(input.data(), input.data() + input.size());
}
-std::u16string UTF8ToUTF16(const std::string& input) {
+std::u16string UTF8ToUTF16(std::string_view input) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
- return convert.from_bytes(input);
+ return convert.from_bytes(input.data(), input.data() + input.size());
}
#ifdef _WIN32
-static std::wstring CPToUTF16(u32 code_page, const std::string& input) {
+static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
const auto size =
MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
@@ -154,7 +154,7 @@ static std::wstring CPToUTF16(u32 code_page, const std::string& input) {
return output;
}
-std::string UTF16ToUTF8(const std::wstring& input) {
+std::string UTF16ToUTF8(std::wstring_view input) {
const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()),
nullptr, 0, nullptr, nullptr);
if (size == 0) {
@@ -172,7 +172,7 @@ std::string UTF16ToUTF8(const std::wstring& input) {
return output;
}
-std::wstring UTF8ToUTF16W(const std::string& input) {
+std::wstring UTF8ToUTF16W(std::string_view input) {
return CPToUTF16(CP_UTF8, input);
}
diff --git a/src/common/string_util.h b/src/common/string_util.h
index f8aecc875..c351f1a0c 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -36,12 +36,12 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src,
const std::string& dest);
-[[nodiscard]] std::string UTF16ToUTF8(const std::u16string& input);
-[[nodiscard]] std::u16string UTF8ToUTF16(const std::string& input);
+[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
+[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
#ifdef _WIN32
-[[nodiscard]] std::string UTF16ToUTF8(const std::wstring& input);
-[[nodiscard]] std::wstring UTF8ToUTF16W(const std::string& str);
+[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
+[[nodiscard]] std::wstring UTF8ToUTF16W(std::string_view str);
#endif
diff --git a/src/common/typed_address.h b/src/common/typed_address.h
new file mode 100644
index 000000000..cf7bbeae1
--- /dev/null
+++ b/src/common/typed_address.h
@@ -0,0 +1,320 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <compare>
+#include <type_traits>
+#include <fmt/format.h>
+
+#include "common/common_types.h"
+
+namespace Common {
+
+template <bool Virtual, typename T>
+class TypedAddress {
+public:
+ // Constructors.
+ constexpr inline TypedAddress() : m_address(0) {}
+ constexpr inline TypedAddress(uint64_t a) : m_address(a) {}
+
+ template <typename U>
+ constexpr inline explicit TypedAddress(const U* ptr)
+ : m_address(reinterpret_cast<uint64_t>(ptr)) {}
+
+ // Copy constructor.
+ constexpr inline TypedAddress(const TypedAddress& rhs) = default;
+
+ // Assignment operator.
+ constexpr inline TypedAddress& operator=(const TypedAddress& rhs) = default;
+
+ // Arithmetic operators.
+ template <typename I>
+ constexpr inline TypedAddress operator+(I rhs) const {
+ static_assert(std::is_integral_v<I>);
+ return m_address + rhs;
+ }
+
+ constexpr inline TypedAddress operator+(TypedAddress rhs) const {
+ return m_address + rhs.m_address;
+ }
+
+ constexpr inline TypedAddress operator++() {
+ return ++m_address;
+ }
+
+ constexpr inline TypedAddress operator++(int) {
+ return m_address++;
+ }
+
+ template <typename I>
+ constexpr inline TypedAddress operator-(I rhs) const {
+ static_assert(std::is_integral_v<I>);
+ return m_address - rhs;
+ }
+
+ constexpr inline ptrdiff_t operator-(TypedAddress rhs) const {
+ return m_address - rhs.m_address;
+ }
+
+ constexpr inline TypedAddress operator--() {
+ return --m_address;
+ }
+
+ constexpr inline TypedAddress operator--(int) {
+ return m_address--;
+ }
+
+ template <typename I>
+ constexpr inline TypedAddress operator+=(I rhs) {
+ static_assert(std::is_integral_v<I>);
+ m_address += rhs;
+ return *this;
+ }
+
+ template <typename I>
+ constexpr inline TypedAddress operator-=(I rhs) {
+ static_assert(std::is_integral_v<I>);
+ m_address -= rhs;
+ return *this;
+ }
+
+ // Logical operators.
+ constexpr inline uint64_t operator&(uint64_t mask) const {
+ return m_address & mask;
+ }
+
+ constexpr inline uint64_t operator|(uint64_t mask) const {
+ return m_address | mask;
+ }
+
+ template <typename I>
+ constexpr inline TypedAddress operator|=(I rhs) {
+ static_assert(std::is_integral_v<I>);
+ m_address |= rhs;
+ return *this;
+ }
+
+ constexpr inline uint64_t operator<<(int shift) const {
+ return m_address << shift;
+ }
+
+ constexpr inline uint64_t operator>>(int shift) const {
+ return m_address >> shift;
+ }
+
+ template <typename U>
+ constexpr inline size_t operator/(U size) const {
+ return m_address / size;
+ }
+
+ constexpr explicit operator bool() const {
+ return m_address != 0;
+ }
+
+ // constexpr inline uint64_t operator%(U align) const { return m_address % align; }
+
+ // Comparison operators.
+ constexpr bool operator==(const TypedAddress&) const = default;
+ constexpr bool operator!=(const TypedAddress&) const = default;
+ constexpr auto operator<=>(const TypedAddress&) const = default;
+
+ // For convenience, also define comparison operators versus uint64_t.
+ constexpr inline bool operator==(uint64_t rhs) const {
+ return m_address == rhs;
+ }
+
+ constexpr inline bool operator!=(uint64_t rhs) const {
+ return m_address != rhs;
+ }
+
+ // Allow getting the address explicitly, for use in accessors.
+ constexpr inline uint64_t GetValue() const {
+ return m_address;
+ }
+
+private:
+ uint64_t m_address{};
+};
+
+struct PhysicalAddressTag {};
+struct VirtualAddressTag {};
+struct ProcessAddressTag {};
+
+using PhysicalAddress = TypedAddress<false, PhysicalAddressTag>;
+using VirtualAddress = TypedAddress<true, VirtualAddressTag>;
+using ProcessAddress = TypedAddress<true, ProcessAddressTag>;
+
+// Define accessors.
+template <typename T>
+concept IsTypedAddress = std::same_as<T, PhysicalAddress> || std::same_as<T, VirtualAddress> ||
+ std::same_as<T, ProcessAddress>;
+
+template <typename T>
+constexpr inline T Null = [] {
+ if constexpr (std::is_same<T, uint64_t>::value) {
+ return 0;
+ } else {
+ static_assert(std::is_same<T, PhysicalAddress>::value ||
+ std::is_same<T, VirtualAddress>::value ||
+ std::is_same<T, ProcessAddress>::value);
+ return T(0);
+ }
+}();
+
+// Basic type validations.
+static_assert(sizeof(PhysicalAddress) == sizeof(uint64_t));
+static_assert(sizeof(VirtualAddress) == sizeof(uint64_t));
+static_assert(sizeof(ProcessAddress) == sizeof(uint64_t));
+
+static_assert(std::is_trivially_copyable_v<PhysicalAddress>);
+static_assert(std::is_trivially_copyable_v<VirtualAddress>);
+static_assert(std::is_trivially_copyable_v<ProcessAddress>);
+
+static_assert(std::is_trivially_copy_constructible_v<PhysicalAddress>);
+static_assert(std::is_trivially_copy_constructible_v<VirtualAddress>);
+static_assert(std::is_trivially_copy_constructible_v<ProcessAddress>);
+
+static_assert(std::is_trivially_move_constructible_v<PhysicalAddress>);
+static_assert(std::is_trivially_move_constructible_v<VirtualAddress>);
+static_assert(std::is_trivially_move_constructible_v<ProcessAddress>);
+
+static_assert(std::is_trivially_copy_assignable_v<PhysicalAddress>);
+static_assert(std::is_trivially_copy_assignable_v<VirtualAddress>);
+static_assert(std::is_trivially_copy_assignable_v<ProcessAddress>);
+
+static_assert(std::is_trivially_move_assignable_v<PhysicalAddress>);
+static_assert(std::is_trivially_move_assignable_v<VirtualAddress>);
+static_assert(std::is_trivially_move_assignable_v<ProcessAddress>);
+
+static_assert(std::is_trivially_destructible_v<PhysicalAddress>);
+static_assert(std::is_trivially_destructible_v<VirtualAddress>);
+static_assert(std::is_trivially_destructible_v<ProcessAddress>);
+
+static_assert(Null<uint64_t> == 0);
+static_assert(Null<PhysicalAddress> == Null<uint64_t>);
+static_assert(Null<VirtualAddress> == Null<uint64_t>);
+static_assert(Null<ProcessAddress> == Null<uint64_t>);
+
+// Constructor/assignment validations.
+static_assert([] {
+ const PhysicalAddress a(5);
+ PhysicalAddress b(a);
+ return b;
+}() == PhysicalAddress(5));
+static_assert([] {
+ const PhysicalAddress a(5);
+ PhysicalAddress b(10);
+ b = a;
+ return b;
+}() == PhysicalAddress(5));
+
+// Arithmetic validations.
+static_assert(PhysicalAddress(10) + 5 == PhysicalAddress(15));
+static_assert(PhysicalAddress(10) - 5 == PhysicalAddress(5));
+static_assert([] {
+ PhysicalAddress v(10);
+ v += 5;
+ return v;
+}() == PhysicalAddress(15));
+static_assert([] {
+ PhysicalAddress v(10);
+ v -= 5;
+ return v;
+}() == PhysicalAddress(5));
+static_assert(PhysicalAddress(10)++ == PhysicalAddress(10));
+static_assert(++PhysicalAddress(10) == PhysicalAddress(11));
+static_assert(PhysicalAddress(10)-- == PhysicalAddress(10));
+static_assert(--PhysicalAddress(10) == PhysicalAddress(9));
+
+// Logical validations.
+static_assert((PhysicalAddress(0b11111111) >> 1) == 0b01111111);
+static_assert((PhysicalAddress(0b10101010) >> 1) == 0b01010101);
+static_assert((PhysicalAddress(0b11111111) << 1) == 0b111111110);
+static_assert((PhysicalAddress(0b01010101) << 1) == 0b10101010);
+static_assert((PhysicalAddress(0b11111111) & 0b01010101) == 0b01010101);
+static_assert((PhysicalAddress(0b11111111) & 0b10101010) == 0b10101010);
+static_assert((PhysicalAddress(0b01010101) & 0b10101010) == 0b00000000);
+static_assert((PhysicalAddress(0b00000000) | 0b01010101) == 0b01010101);
+static_assert((PhysicalAddress(0b11111111) | 0b01010101) == 0b11111111);
+static_assert((PhysicalAddress(0b10101010) | 0b01010101) == 0b11111111);
+
+// Comparisons.
+static_assert(PhysicalAddress(0) == PhysicalAddress(0));
+static_assert(PhysicalAddress(0) != PhysicalAddress(1));
+static_assert(PhysicalAddress(0) < PhysicalAddress(1));
+static_assert(PhysicalAddress(0) <= PhysicalAddress(1));
+static_assert(PhysicalAddress(1) > PhysicalAddress(0));
+static_assert(PhysicalAddress(1) >= PhysicalAddress(0));
+
+static_assert(!(PhysicalAddress(0) == PhysicalAddress(1)));
+static_assert(!(PhysicalAddress(0) != PhysicalAddress(0)));
+static_assert(!(PhysicalAddress(1) < PhysicalAddress(0)));
+static_assert(!(PhysicalAddress(1) <= PhysicalAddress(0)));
+static_assert(!(PhysicalAddress(0) > PhysicalAddress(1)));
+static_assert(!(PhysicalAddress(0) >= PhysicalAddress(1)));
+
+} // namespace Common
+
+template <bool Virtual, typename T>
+constexpr inline uint64_t GetInteger(Common::TypedAddress<Virtual, T> address) {
+ return address.GetValue();
+}
+
+template <>
+struct fmt::formatter<Common::PhysicalAddress> {
+ constexpr auto parse(fmt::format_parse_context& ctx) {
+ return ctx.begin();
+ }
+ template <typename FormatContext>
+ auto format(const Common::PhysicalAddress& addr, FormatContext& ctx) {
+ return fmt::format_to(ctx.out(), "{:#x}", static_cast<u64>(addr.GetValue()));
+ }
+};
+
+template <>
+struct fmt::formatter<Common::ProcessAddress> {
+ constexpr auto parse(fmt::format_parse_context& ctx) {
+ return ctx.begin();
+ }
+ template <typename FormatContext>
+ auto format(const Common::ProcessAddress& addr, FormatContext& ctx) {
+ return fmt::format_to(ctx.out(), "{:#x}", static_cast<u64>(addr.GetValue()));
+ }
+};
+
+template <>
+struct fmt::formatter<Common::VirtualAddress> {
+ constexpr auto parse(fmt::format_parse_context& ctx) {
+ return ctx.begin();
+ }
+ template <typename FormatContext>
+ auto format(const Common::VirtualAddress& addr, FormatContext& ctx) {
+ return fmt::format_to(ctx.out(), "{:#x}", static_cast<u64>(addr.GetValue()));
+ }
+};
+
+namespace std {
+
+template <>
+struct hash<Common::PhysicalAddress> {
+ size_t operator()(const Common::PhysicalAddress& k) const noexcept {
+ return k.GetValue();
+ }
+};
+
+template <>
+struct hash<Common::ProcessAddress> {
+ size_t operator()(const Common::ProcessAddress& k) const noexcept {
+ return k.GetValue();
+ }
+};
+
+template <>
+struct hash<Common::VirtualAddress> {
+ size_t operator()(const Common::VirtualAddress& k) const noexcept {
+ return k.GetValue();
+ }
+};
+
+} // namespace std