summaryrefslogtreecommitdiff
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
132 time_zone.h 132 time_zone.h
133 tiny_mt.h 133 tiny_mt.h
134 tree.h 134 tree.h
135 typed_address.h
135 uint128.h 136 uint128.h
136 unique_function.h 137 unique_function.h
137 uuid.cpp 138 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 @@
1// SPDX-FileCopyrightText: Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se> 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: MIT 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#pragma once 4#pragma once
5 5
6#include <atomic> 6#include <atomic>
7#include <bit>
8#include <condition_variable> 7#include <condition_variable>
9#include <memory> 8#include <cstddef>
10#include <mutex> 9#include <mutex>
11#include <new> 10#include <new>
12#include <type_traits>
13#include <utility>
14 11
15#include "common/polyfill_thread.h" 12#include "common/polyfill_thread.h"
16 13
17namespace Common { 14namespace Common {
18 15
19#if defined(__cpp_lib_hardware_interference_size) 16namespace detail {
20constexpr size_t hardware_interference_size = std::hardware_destructive_interference_size; 17constexpr size_t DefaultCapacity = 0x1000;
21#else 18} // namespace detail
22constexpr size_t hardware_interference_size = 64; 19
23#endif 20template <typename T, size_t Capacity = detail::DefaultCapacity>
21class SPSCQueue {
22 static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two.");
24 23
25template <typename T, size_t capacity = 0x400>
26class MPSCQueue {
27public: 24public:
28 explicit MPSCQueue() : allocator{std::allocator<Slot<T>>()} { 25 template <typename... Args>
29 // Allocate one extra slot to prevent false sharing on the last slot 26 bool TryEmplace(Args&&... args) {
30 slots = allocator.allocate(capacity + 1); 27 return Emplace<PushMode::Try>(std::forward<Args>(args)...);
31 // Allocators are not required to honor alignment for over-aligned types
32 // (see http://eel.is/c++draft/allocator.requirements#10) so we verify
33 // alignment here
34 if (reinterpret_cast<uintptr_t>(slots) % alignof(Slot<T>) != 0) {
35 allocator.deallocate(slots, capacity + 1);
36 throw std::bad_alloc();
37 }
38 for (size_t i = 0; i < capacity; ++i) {
39 std::construct_at(&slots[i]);
40 }
41 static_assert(std::has_single_bit(capacity), "capacity must be an integer power of 2");
42 static_assert(alignof(Slot<T>) == hardware_interference_size,
43 "Slot must be aligned to cache line boundary to prevent false sharing");
44 static_assert(sizeof(Slot<T>) % hardware_interference_size == 0,
45 "Slot size must be a multiple of cache line size to prevent "
46 "false sharing between adjacent slots");
47 static_assert(sizeof(MPSCQueue) % hardware_interference_size == 0,
48 "Queue size must be a multiple of cache line size to "
49 "prevent false sharing between adjacent queues");
50 }
51
52 ~MPSCQueue() noexcept {
53 for (size_t i = 0; i < capacity; ++i) {
54 std::destroy_at(&slots[i]);
55 }
56 allocator.deallocate(slots, capacity + 1);
57 } 28 }
58 29
59 // The queue must be both non-copyable and non-movable 30 template <typename... Args>
60 MPSCQueue(const MPSCQueue&) = delete; 31 void EmplaceWait(Args&&... args) {
61 MPSCQueue& operator=(const MPSCQueue&) = delete; 32 Emplace<PushMode::Wait>(std::forward<Args>(args)...);
33 }
62 34
63 MPSCQueue(MPSCQueue&&) = delete; 35 bool TryPop(T& t) {
64 MPSCQueue& operator=(MPSCQueue&&) = delete; 36 return Pop<PopMode::Try>(t);
37 }
65 38
66 void Push(const T& v) noexcept { 39 void PopWait(T& t) {
67 static_assert(std::is_nothrow_copy_constructible_v<T>, 40 Pop<PopMode::Wait>(t);
68 "T must be nothrow copy constructible");
69 emplace(v);
70 } 41 }
71 42
72 template <typename P, typename = std::enable_if_t<std::is_nothrow_constructible_v<T, P&&>>> 43 void PopWait(T& t, std::stop_token stop_token) {
73 void Push(P&& v) noexcept { 44 Pop<PopMode::WaitWithStopToken>(t, stop_token);
74 emplace(std::forward<P>(v));
75 } 45 }
76 46
77 void Pop(T& v, std::stop_token stop) noexcept { 47 T PopWait() {
78 auto const tail = tail_.fetch_add(1); 48 T t;
79 auto& slot = slots[idx(tail)]; 49 Pop<PopMode::Wait>(t);
80 if (!slot.turn.test()) { 50 return t;
81 std::unique_lock lock{cv_mutex}; 51 }
82 Common::CondvarWait(cv, lock, stop, [&slot] { return slot.turn.test(); }); 52
83 } 53 T PopWait(std::stop_token stop_token) {
84 v = slot.move(); 54 T t;
85 slot.destroy(); 55 Pop<PopMode::WaitWithStopToken>(t, stop_token);
86 slot.turn.clear(); 56 return t;
87 slot.turn.notify_one();
88 } 57 }
89 58
90private: 59private:
91 template <typename U = T> 60 enum class PushMode {
92 struct Slot { 61 Try,
93 ~Slot() noexcept { 62 Wait,
94 if (turn.test()) { 63 Count,
95 destroy(); 64 };
65
66 enum class PopMode {
67 Try,
68 Wait,
69 WaitWithStopToken,
70 Count,
71 };
72
73 template <PushMode Mode, typename... Args>
74 bool Emplace(Args&&... args) {
75 const size_t write_index = m_write_index.load(std::memory_order::relaxed);
76
77 if constexpr (Mode == PushMode::Try) {
78 // Check if we have free slots to write to.
79 if ((write_index - m_read_index.load(std::memory_order::acquire)) == Capacity) {
80 return false;
96 } 81 }
82 } else if constexpr (Mode == PushMode::Wait) {
83 // Wait until we have free slots to write to.
84 std::unique_lock lock{producer_cv_mutex};
85 producer_cv.wait(lock, [this, write_index] {
86 return (write_index - m_read_index.load(std::memory_order::acquire)) < Capacity;
87 });
88 } else {
89 static_assert(Mode < PushMode::Count, "Invalid PushMode.");
97 } 90 }
98 91
99 template <typename... Args> 92 // Determine the position to write to.
100 void construct(Args&&... args) noexcept { 93 const size_t pos = write_index % Capacity;
101 static_assert(std::is_nothrow_constructible_v<U, Args&&...>,
102 "T must be nothrow constructible with Args&&...");
103 std::construct_at(reinterpret_cast<U*>(&storage), std::forward<Args>(args)...);
104 }
105 94
106 void destroy() noexcept { 95 // Emplace into the queue.
107 static_assert(std::is_nothrow_destructible_v<U>, "T must be nothrow destructible"); 96 std::construct_at(std::addressof(m_data[pos]), std::forward<Args>(args)...);
108 std::destroy_at(reinterpret_cast<U*>(&storage)); 97
109 } 98 // Increment the write index.
99 ++m_write_index;
100
101 // Notify the consumer that we have pushed into the queue.
102 std::scoped_lock lock{consumer_cv_mutex};
103 consumer_cv.notify_one();
110 104
111 U&& move() noexcept { 105 return true;
112 return reinterpret_cast<U&&>(storage); 106 }
107
108 template <PopMode Mode>
109 bool Pop(T& t, [[maybe_unused]] std::stop_token stop_token = {}) {
110 const size_t read_index = m_read_index.load(std::memory_order::relaxed);
111
112 if constexpr (Mode == PopMode::Try) {
113 // Check if the queue is empty.
114 if (read_index == m_write_index.load(std::memory_order::acquire)) {
115 return false;
116 }
117 } else if constexpr (Mode == PopMode::Wait) {
118 // Wait until the queue is not empty.
119 std::unique_lock lock{consumer_cv_mutex};
120 consumer_cv.wait(lock, [this, read_index] {
121 return read_index != m_write_index.load(std::memory_order::acquire);
122 });
123 } else if constexpr (Mode == PopMode::WaitWithStopToken) {
124 // Wait until the queue is not empty.
125 std::unique_lock lock{consumer_cv_mutex};
126 Common::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] {
127 return read_index != m_write_index.load(std::memory_order::acquire);
128 });
129 if (stop_token.stop_requested()) {
130 return false;
131 }
132 } else {
133 static_assert(Mode < PopMode::Count, "Invalid PopMode.");
113 } 134 }
114 135
115 // Align to avoid false sharing between adjacent slots 136 // Determine the position to read from.
116 alignas(hardware_interference_size) std::atomic_flag turn{}; 137 const size_t pos = read_index % Capacity;
117 struct aligned_store { 138
118 struct type { 139 // Pop the data off the queue, moving it.
119 alignas(U) unsigned char data[sizeof(U)]; 140 t = std::move(m_data[pos]);
120 }; 141
121 }; 142 // Increment the read index.
122 typename aligned_store::type storage; 143 ++m_read_index;
123 }; 144
145 // Notify the producer that we have popped off the queue.
146 std::scoped_lock lock{producer_cv_mutex};
147 producer_cv.notify_one();
148
149 return true;
150 }
151
152 alignas(128) std::atomic_size_t m_read_index{0};
153 alignas(128) std::atomic_size_t m_write_index{0};
124 154
155 std::array<T, Capacity> m_data;
156
157 std::condition_variable_any producer_cv;
158 std::mutex producer_cv_mutex;
159 std::condition_variable_any consumer_cv;
160 std::mutex consumer_cv_mutex;
161};
162
163template <typename T, size_t Capacity = detail::DefaultCapacity>
164class MPSCQueue {
165public:
166 template <typename... Args>
167 bool TryEmplace(Args&&... args) {
168 std::scoped_lock lock{write_mutex};
169 return spsc_queue.TryEmplace(std::forward<Args>(args)...);
170 }
171
172 template <typename... Args>
173 void EmplaceWait(Args&&... args) {
174 std::scoped_lock lock{write_mutex};
175 spsc_queue.EmplaceWait(std::forward<Args>(args)...);
176 }
177
178 bool TryPop(T& t) {
179 return spsc_queue.TryPop(t);
180 }
181
182 void PopWait(T& t) {
183 spsc_queue.PopWait(t);
184 }
185
186 void PopWait(T& t, std::stop_token stop_token) {
187 spsc_queue.PopWait(t, stop_token);
188 }
189
190 T PopWait() {
191 return spsc_queue.PopWait();
192 }
193
194 T PopWait(std::stop_token stop_token) {
195 return spsc_queue.PopWait(stop_token);
196 }
197
198private:
199 SPSCQueue<T, Capacity> spsc_queue;
200 std::mutex write_mutex;
201};
202
203template <typename T, size_t Capacity = detail::DefaultCapacity>
204class MPMCQueue {
205public:
125 template <typename... Args> 206 template <typename... Args>
126 void emplace(Args&&... args) noexcept { 207 bool TryEmplace(Args&&... args) {
127 static_assert(std::is_nothrow_constructible_v<T, Args&&...>, 208 std::scoped_lock lock{write_mutex};
128 "T must be nothrow constructible with Args&&..."); 209 return spsc_queue.TryEmplace(std::forward<Args>(args)...);
129 auto const head = head_.fetch_add(1);
130 auto& slot = slots[idx(head)];
131 slot.turn.wait(true);
132 slot.construct(std::forward<Args>(args)...);
133 slot.turn.test_and_set();
134 cv.notify_one();
135 } 210 }
136 211
137 constexpr size_t idx(size_t i) const noexcept { 212 template <typename... Args>
138 return i & mask; 213 void EmplaceWait(Args&&... args) {
214 std::scoped_lock lock{write_mutex};
215 spsc_queue.EmplaceWait(std::forward<Args>(args)...);
139 } 216 }
140 217
141 static constexpr size_t mask = capacity - 1; 218 bool TryPop(T& t) {
219 std::scoped_lock lock{read_mutex};
220 return spsc_queue.TryPop(t);
221 }
142 222
143 // Align to avoid false sharing between head_ and tail_ 223 void PopWait(T& t) {
144 alignas(hardware_interference_size) std::atomic<size_t> head_{0}; 224 std::scoped_lock lock{read_mutex};
145 alignas(hardware_interference_size) std::atomic<size_t> tail_{0}; 225 spsc_queue.PopWait(t);
226 }
146 227
147 std::mutex cv_mutex; 228 void PopWait(T& t, std::stop_token stop_token) {
148 std::condition_variable_any cv; 229 std::scoped_lock lock{read_mutex};
230 spsc_queue.PopWait(t, stop_token);
231 }
149 232
150 Slot<T>* slots; 233 T PopWait() {
151 [[no_unique_address]] std::allocator<Slot<T>> allocator; 234 std::scoped_lock lock{read_mutex};
235 return spsc_queue.PopWait();
236 }
152 237
153 static_assert(std::is_nothrow_copy_assignable_v<T> || std::is_nothrow_move_assignable_v<T>, 238 T PopWait(std::stop_token stop_token) {
154 "T must be nothrow copy or move assignable"); 239 std::scoped_lock lock{read_mutex};
240 return spsc_queue.PopWait(stop_token);
241 }
155 242
156 static_assert(std::is_nothrow_destructible_v<T>, "T must be nothrow destructible"); 243private:
244 SPSCQueue<T, Capacity> spsc_queue;
245 std::mutex write_mutex;
246 std::mutex read_mutex;
157}; 247};
158 248
159} // namespace Common 249} // 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 @@
28#ifdef _WIN32 28#ifdef _WIN32
29#include "common/string_util.h" 29#include "common/string_util.h"
30#endif 30#endif
31#include "common/threadsafe_queue.h" 31#include "common/bounded_threadsafe_queue.h"
32 32
33namespace Common::Log { 33namespace Common::Log {
34 34
@@ -204,11 +204,11 @@ public:
204 204
205 void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, 205 void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
206 const char* function, std::string&& message) { 206 const char* function, std::string&& message) {
207 if (!filter.CheckMessage(log_class, log_level)) 207 if (!filter.CheckMessage(log_class, log_level)) {
208 return; 208 return;
209 const Entry& entry = 209 }
210 CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)); 210 message_queue.EmplaceWait(
211 message_queue.Push(entry); 211 CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
212 } 212 }
213 213
214private: 214private:
@@ -225,7 +225,7 @@ private:
225 ForEachBackend([&entry](Backend& backend) { backend.Write(entry); }); 225 ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
226 }; 226 };
227 while (!stop_token.stop_requested()) { 227 while (!stop_token.stop_requested()) {
228 entry = message_queue.PopWait(stop_token); 228 message_queue.PopWait(entry, stop_token);
229 if (entry.filename != nullptr) { 229 if (entry.filename != nullptr) {
230 write_logs(); 230 write_logs();
231 } 231 }
@@ -233,7 +233,7 @@ private:
233 // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a 233 // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
234 // case where a system is repeatedly spamming logs even on close. 234 // case where a system is repeatedly spamming logs even on close.
235 int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100; 235 int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
236 while (max_logs_to_write-- && message_queue.Pop(entry)) { 236 while (max_logs_to_write-- && message_queue.TryPop(entry)) {
237 write_logs(); 237 write_logs();
238 } 238 }
239 }); 239 });
@@ -273,7 +273,7 @@ private:
273 ColorConsoleBackend color_console_backend{}; 273 ColorConsoleBackend color_console_backend{};
274 FileBackend file_backend; 274 FileBackend file_backend;
275 275
276 MPSCQueue<Entry, true> message_queue{}; 276 MPSCQueue<Entry> message_queue{};
277 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; 277 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
278 std::jthread backend_thread; 278 std::jthread backend_thread;
279}; 279};
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
125 return result; 125 return result;
126} 126}
127 127
128std::string UTF16ToUTF8(const std::u16string& input) { 128std::string UTF16ToUTF8(std::u16string_view input) {
129 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; 129 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
130 return convert.to_bytes(input); 130 return convert.to_bytes(input.data(), input.data() + input.size());
131} 131}
132 132
133std::u16string UTF8ToUTF16(const std::string& input) { 133std::u16string UTF8ToUTF16(std::string_view input) {
134 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; 134 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
135 return convert.from_bytes(input); 135 return convert.from_bytes(input.data(), input.data() + input.size());
136} 136}
137 137
138#ifdef _WIN32 138#ifdef _WIN32
139static std::wstring CPToUTF16(u32 code_page, const std::string& input) { 139static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
140 const auto size = 140 const auto size =
141 MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0); 141 MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
142 142
@@ -154,7 +154,7 @@ static std::wstring CPToUTF16(u32 code_page, const std::string& input) {
154 return output; 154 return output;
155} 155}
156 156
157std::string UTF16ToUTF8(const std::wstring& input) { 157std::string UTF16ToUTF8(std::wstring_view input) {
158 const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), 158 const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(input.size()),
159 nullptr, 0, nullptr, nullptr); 159 nullptr, 0, nullptr, nullptr);
160 if (size == 0) { 160 if (size == 0) {
@@ -172,7 +172,7 @@ std::string UTF16ToUTF8(const std::wstring& input) {
172 return output; 172 return output;
173} 173}
174 174
175std::wstring UTF8ToUTF16W(const std::string& input) { 175std::wstring UTF8ToUTF16W(std::string_view input) {
176 return CPToUTF16(CP_UTF8, input); 176 return CPToUTF16(CP_UTF8, input);
177} 177}
178 178
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* _
36[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src, 36[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src,
37 const std::string& dest); 37 const std::string& dest);
38 38
39[[nodiscard]] std::string UTF16ToUTF8(const std::u16string& input); 39[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
40[[nodiscard]] std::u16string UTF8ToUTF16(const std::string& input); 40[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
41 41
42#ifdef _WIN32 42#ifdef _WIN32
43[[nodiscard]] std::string UTF16ToUTF8(const std::wstring& input); 43[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
44[[nodiscard]] std::wstring UTF8ToUTF16W(const std::string& str); 44[[nodiscard]] std::wstring UTF8ToUTF16W(std::string_view str);
45 45
46#endif 46#endif
47 47
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 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <compare>
7#include <type_traits>
8#include <fmt/format.h>
9
10#include "common/common_types.h"
11
12namespace Common {
13
14template <bool Virtual, typename T>
15class TypedAddress {
16public:
17 // Constructors.
18 constexpr inline TypedAddress() : m_address(0) {}
19 constexpr inline TypedAddress(uint64_t a) : m_address(a) {}
20
21 template <typename U>
22 constexpr inline explicit TypedAddress(const U* ptr)
23 : m_address(reinterpret_cast<uint64_t>(ptr)) {}
24
25 // Copy constructor.
26 constexpr inline TypedAddress(const TypedAddress& rhs) = default;
27
28 // Assignment operator.
29 constexpr inline TypedAddress& operator=(const TypedAddress& rhs) = default;
30
31 // Arithmetic operators.
32 template <typename I>
33 constexpr inline TypedAddress operator+(I rhs) const {
34 static_assert(std::is_integral_v<I>);
35 return m_address + rhs;
36 }
37
38 constexpr inline TypedAddress operator+(TypedAddress rhs) const {
39 return m_address + rhs.m_address;
40 }
41
42 constexpr inline TypedAddress operator++() {
43 return ++m_address;
44 }
45
46 constexpr inline TypedAddress operator++(int) {
47 return m_address++;
48 }
49
50 template <typename I>
51 constexpr inline TypedAddress operator-(I rhs) const {
52 static_assert(std::is_integral_v<I>);
53 return m_address - rhs;
54 }
55
56 constexpr inline ptrdiff_t operator-(TypedAddress rhs) const {
57 return m_address - rhs.m_address;
58 }
59
60 constexpr inline TypedAddress operator--() {
61 return --m_address;
62 }
63
64 constexpr inline TypedAddress operator--(int) {
65 return m_address--;
66 }
67
68 template <typename I>
69 constexpr inline TypedAddress operator+=(I rhs) {
70 static_assert(std::is_integral_v<I>);
71 m_address += rhs;
72 return *this;
73 }
74
75 template <typename I>
76 constexpr inline TypedAddress operator-=(I rhs) {
77 static_assert(std::is_integral_v<I>);
78 m_address -= rhs;
79 return *this;
80 }
81
82 // Logical operators.
83 constexpr inline uint64_t operator&(uint64_t mask) const {
84 return m_address & mask;
85 }
86
87 constexpr inline uint64_t operator|(uint64_t mask) const {
88 return m_address | mask;
89 }
90
91 template <typename I>
92 constexpr inline TypedAddress operator|=(I rhs) {
93 static_assert(std::is_integral_v<I>);
94 m_address |= rhs;
95 return *this;
96 }
97
98 constexpr inline uint64_t operator<<(int shift) const {
99 return m_address << shift;
100 }
101
102 constexpr inline uint64_t operator>>(int shift) const {
103 return m_address >> shift;
104 }
105
106 template <typename U>
107 constexpr inline size_t operator/(U size) const {
108 return m_address / size;
109 }
110
111 constexpr explicit operator bool() const {
112 return m_address != 0;
113 }
114
115 // constexpr inline uint64_t operator%(U align) const { return m_address % align; }
116
117 // Comparison operators.
118 constexpr bool operator==(const TypedAddress&) const = default;
119 constexpr bool operator!=(const TypedAddress&) const = default;
120 constexpr auto operator<=>(const TypedAddress&) const = default;
121
122 // For convenience, also define comparison operators versus uint64_t.
123 constexpr inline bool operator==(uint64_t rhs) const {
124 return m_address == rhs;
125 }
126
127 constexpr inline bool operator!=(uint64_t rhs) const {
128 return m_address != rhs;
129 }
130
131 // Allow getting the address explicitly, for use in accessors.
132 constexpr inline uint64_t GetValue() const {
133 return m_address;
134 }
135
136private:
137 uint64_t m_address{};
138};
139
140struct PhysicalAddressTag {};
141struct VirtualAddressTag {};
142struct ProcessAddressTag {};
143
144using PhysicalAddress = TypedAddress<false, PhysicalAddressTag>;
145using VirtualAddress = TypedAddress<true, VirtualAddressTag>;
146using ProcessAddress = TypedAddress<true, ProcessAddressTag>;
147
148// Define accessors.
149template <typename T>
150concept IsTypedAddress = std::same_as<T, PhysicalAddress> || std::same_as<T, VirtualAddress> ||
151 std::same_as<T, ProcessAddress>;
152
153template <typename T>
154constexpr inline T Null = [] {
155 if constexpr (std::is_same<T, uint64_t>::value) {
156 return 0;
157 } else {
158 static_assert(std::is_same<T, PhysicalAddress>::value ||
159 std::is_same<T, VirtualAddress>::value ||
160 std::is_same<T, ProcessAddress>::value);
161 return T(0);
162 }
163}();
164
165// Basic type validations.
166static_assert(sizeof(PhysicalAddress) == sizeof(uint64_t));
167static_assert(sizeof(VirtualAddress) == sizeof(uint64_t));
168static_assert(sizeof(ProcessAddress) == sizeof(uint64_t));
169
170static_assert(std::is_trivially_copyable_v<PhysicalAddress>);
171static_assert(std::is_trivially_copyable_v<VirtualAddress>);
172static_assert(std::is_trivially_copyable_v<ProcessAddress>);
173
174static_assert(std::is_trivially_copy_constructible_v<PhysicalAddress>);
175static_assert(std::is_trivially_copy_constructible_v<VirtualAddress>);
176static_assert(std::is_trivially_copy_constructible_v<ProcessAddress>);
177
178static_assert(std::is_trivially_move_constructible_v<PhysicalAddress>);
179static_assert(std::is_trivially_move_constructible_v<VirtualAddress>);
180static_assert(std::is_trivially_move_constructible_v<ProcessAddress>);
181
182static_assert(std::is_trivially_copy_assignable_v<PhysicalAddress>);
183static_assert(std::is_trivially_copy_assignable_v<VirtualAddress>);
184static_assert(std::is_trivially_copy_assignable_v<ProcessAddress>);
185
186static_assert(std::is_trivially_move_assignable_v<PhysicalAddress>);
187static_assert(std::is_trivially_move_assignable_v<VirtualAddress>);
188static_assert(std::is_trivially_move_assignable_v<ProcessAddress>);
189
190static_assert(std::is_trivially_destructible_v<PhysicalAddress>);
191static_assert(std::is_trivially_destructible_v<VirtualAddress>);
192static_assert(std::is_trivially_destructible_v<ProcessAddress>);
193
194static_assert(Null<uint64_t> == 0);
195static_assert(Null<PhysicalAddress> == Null<uint64_t>);
196static_assert(Null<VirtualAddress> == Null<uint64_t>);
197static_assert(Null<ProcessAddress> == Null<uint64_t>);
198
199// Constructor/assignment validations.
200static_assert([] {
201 const PhysicalAddress a(5);
202 PhysicalAddress b(a);
203 return b;
204}() == PhysicalAddress(5));
205static_assert([] {
206 const PhysicalAddress a(5);
207 PhysicalAddress b(10);
208 b = a;
209 return b;
210}() == PhysicalAddress(5));
211
212// Arithmetic validations.
213static_assert(PhysicalAddress(10) + 5 == PhysicalAddress(15));
214static_assert(PhysicalAddress(10) - 5 == PhysicalAddress(5));
215static_assert([] {
216 PhysicalAddress v(10);
217 v += 5;
218 return v;
219}() == PhysicalAddress(15));
220static_assert([] {
221 PhysicalAddress v(10);
222 v -= 5;
223 return v;
224}() == PhysicalAddress(5));
225static_assert(PhysicalAddress(10)++ == PhysicalAddress(10));
226static_assert(++PhysicalAddress(10) == PhysicalAddress(11));
227static_assert(PhysicalAddress(10)-- == PhysicalAddress(10));
228static_assert(--PhysicalAddress(10) == PhysicalAddress(9));
229
230// Logical validations.
231static_assert((PhysicalAddress(0b11111111) >> 1) == 0b01111111);
232static_assert((PhysicalAddress(0b10101010) >> 1) == 0b01010101);
233static_assert((PhysicalAddress(0b11111111) << 1) == 0b111111110);
234static_assert((PhysicalAddress(0b01010101) << 1) == 0b10101010);
235static_assert((PhysicalAddress(0b11111111) & 0b01010101) == 0b01010101);
236static_assert((PhysicalAddress(0b11111111) & 0b10101010) == 0b10101010);
237static_assert((PhysicalAddress(0b01010101) & 0b10101010) == 0b00000000);
238static_assert((PhysicalAddress(0b00000000) | 0b01010101) == 0b01010101);
239static_assert((PhysicalAddress(0b11111111) | 0b01010101) == 0b11111111);
240static_assert((PhysicalAddress(0b10101010) | 0b01010101) == 0b11111111);
241
242// Comparisons.
243static_assert(PhysicalAddress(0) == PhysicalAddress(0));
244static_assert(PhysicalAddress(0) != PhysicalAddress(1));
245static_assert(PhysicalAddress(0) < PhysicalAddress(1));
246static_assert(PhysicalAddress(0) <= PhysicalAddress(1));
247static_assert(PhysicalAddress(1) > PhysicalAddress(0));
248static_assert(PhysicalAddress(1) >= PhysicalAddress(0));
249
250static_assert(!(PhysicalAddress(0) == PhysicalAddress(1)));
251static_assert(!(PhysicalAddress(0) != PhysicalAddress(0)));
252static_assert(!(PhysicalAddress(1) < PhysicalAddress(0)));
253static_assert(!(PhysicalAddress(1) <= PhysicalAddress(0)));
254static_assert(!(PhysicalAddress(0) > PhysicalAddress(1)));
255static_assert(!(PhysicalAddress(0) >= PhysicalAddress(1)));
256
257} // namespace Common
258
259template <bool Virtual, typename T>
260constexpr inline uint64_t GetInteger(Common::TypedAddress<Virtual, T> address) {
261 return address.GetValue();
262}
263
264template <>
265struct fmt::formatter<Common::PhysicalAddress> {
266 constexpr auto parse(fmt::format_parse_context& ctx) {
267 return ctx.begin();
268 }
269 template <typename FormatContext>
270 auto format(const Common::PhysicalAddress& addr, FormatContext& ctx) {
271 return fmt::format_to(ctx.out(), "{:#x}", static_cast<u64>(addr.GetValue()));
272 }
273};
274
275template <>
276struct fmt::formatter<Common::ProcessAddress> {
277 constexpr auto parse(fmt::format_parse_context& ctx) {
278 return ctx.begin();
279 }
280 template <typename FormatContext>
281 auto format(const Common::ProcessAddress& addr, FormatContext& ctx) {
282 return fmt::format_to(ctx.out(), "{:#x}", static_cast<u64>(addr.GetValue()));
283 }
284};
285
286template <>
287struct fmt::formatter<Common::VirtualAddress> {
288 constexpr auto parse(fmt::format_parse_context& ctx) {
289 return ctx.begin();
290 }
291 template <typename FormatContext>
292 auto format(const Common::VirtualAddress& addr, FormatContext& ctx) {
293 return fmt::format_to(ctx.out(), "{:#x}", static_cast<u64>(addr.GetValue()));
294 }
295};
296
297namespace std {
298
299template <>
300struct hash<Common::PhysicalAddress> {
301 size_t operator()(const Common::PhysicalAddress& k) const noexcept {
302 return k.GetValue();
303 }
304};
305
306template <>
307struct hash<Common::ProcessAddress> {
308 size_t operator()(const Common::ProcessAddress& k) const noexcept {
309 return k.GetValue();
310 }
311};
312
313template <>
314struct hash<Common::VirtualAddress> {
315 size_t operator()(const Common::VirtualAddress& k) const noexcept {
316 return k.GetValue();
317 }
318};
319
320} // namespace std