Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
coroutine.hh
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2022 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27#pragma once
28
29#include <EASTL/functional.h>
30#include <EASTL/utility.h>
31
32#include <coroutine>
33#include <type_traits>
34
37#include "psyqo/kernel.hh"
38#include "psyqo/shadow-stack.hh"
39
40namespace psyqo {
41
54template <typename T = void>
55struct Coroutine {
56 struct Empty {};
57 typedef typename std::conditional<std::is_void<T>::value, Empty, T>::type SafeT;
58
59 Coroutine() = default;
60
62 if (m_handle) m_handle.destroy();
63 m_handle = nullptr;
64 m_handle = other.m_handle;
65 m_value = eastl::move(other.m_value);
66 m_suspended = other.m_suspended;
67 m_earlyResume = other.m_earlyResume;
68
69 other.m_handle = nullptr;
70 other.m_value = SafeT{};
71 other.m_suspended = true;
72 other.m_earlyResume = false;
73 }
74
76 if (this != &other) {
77 if (m_handle) m_handle.destroy();
78 m_handle = nullptr;
79 m_handle = other.m_handle;
80 m_value = eastl::move(other.m_value);
81 m_suspended = other.m_suspended;
82 m_earlyResume = other.m_earlyResume;
83
84 other.m_handle = nullptr;
85 other.m_value = SafeT{};
86 other.m_suspended = true;
87 other.m_earlyResume = false;
88 }
89 return *this;
90 }
91
92 Coroutine(Coroutine const &) = delete;
93 Coroutine &operator=(Coroutine const &) = delete;
95 if (m_handle) m_handle.destroy();
96 m_handle = nullptr;
97 }
98
107 struct Awaiter {
108 Awaiter(Awaiter &&other) = default;
109 Awaiter &operator=(Awaiter &&other) = default;
110 Awaiter(Awaiter const &) = default;
111 Awaiter &operator=(Awaiter const &) = default;
112 constexpr bool await_ready() const noexcept {
113 bool ret = m_coroutine->m_earlyResume;
114 m_coroutine->m_earlyResume = false;
115 return ret;
116 }
117 constexpr void await_suspend(std::coroutine_handle<> h) { m_coroutine->m_suspended = true; }
118 constexpr void await_resume() const noexcept {}
119
120 private:
121 Awaiter(Coroutine *coroutine) : m_coroutine(coroutine) {}
122 Coroutine *m_coroutine;
123 friend struct Coroutine;
124 };
125
132 Awaiter awaiter() & { return Awaiter(this); }
133
143 void resume() {
144 if (!m_handle) return;
145 if (!m_suspended) {
146 m_earlyResume = true;
147 return;
148 }
149 m_suspended = false;
150 m_handle.resume();
151 }
152
162 bool done() {
163 if (!m_handle) return true;
164 bool isDone = m_handle.done();
165 if (isDone) {
166 if constexpr (!std::is_void<T>::value) {
167 m_value = eastl::move(m_handle.promise().m_value);
168 }
169 m_handle.destroy();
170 m_handle = nullptr;
171 }
172 return isDone;
173 }
174
186 const SafeT &value() const { return m_value; }
187
188 private:
189 struct PromiseVoid {
190 Coroutine<> get_return_object() {
191 return Coroutine<>{eastl::move(std::coroutine_handle<Promise>::from_promise(*this))};
192 }
193 std::suspend_always initial_suspend() { return {}; }
194 std::suspend_always final_suspend() noexcept { return {}; }
195 void unhandled_exception() {}
196 void return_void() {
197 if (m_awaitingCoroutine) {
198 Kernel::queueCallback([h = m_awaitingCoroutine]() { h.resume(); });
199 m_awaitingCoroutine = nullptr;
200 }
201 }
202 [[no_unique_address]] Empty m_value;
203 std::coroutine_handle<> m_awaitingCoroutine;
204 };
205
206 struct PromiseValue {
207 Coroutine<T> get_return_object() {
208 return Coroutine{eastl::move(std::coroutine_handle<Promise>::from_promise(*this))};
209 }
210 std::suspend_always initial_suspend() { return {}; }
211 std::suspend_always final_suspend() noexcept { return {}; }
212 void unhandled_exception() {}
213 void return_value(T &&value) {
214 m_value = eastl::move(value);
215 if (m_awaitingCoroutine) {
216 Kernel::queueCallback([h = m_awaitingCoroutine]() { h.resume(); });
217 m_awaitingCoroutine = nullptr;
218 }
219 }
220 T m_value;
221 std::coroutine_handle<> m_awaitingCoroutine;
222 };
223
224 typedef typename std::conditional<std::is_void<T>::value, PromiseVoid, PromiseValue>::type Promise;
225
226 Coroutine(std::coroutine_handle<Promise> &&handle) : m_handle(eastl::move(handle)) {}
227
228 std::coroutine_handle<Promise> m_handle;
229 [[no_unique_address]] SafeT m_value;
230 bool m_suspended = true;
231 bool m_earlyResume = false;
232
233 public:
234 using promise_type = Promise;
235
245 std::coroutine_handle<Promise> handle;
246
247 explicit ChainAwaiter(std::coroutine_handle<Promise> h) : handle(h) {}
249 if (handle) handle.destroy();
250 }
251
252 ChainAwaiter(ChainAwaiter &&other) : handle(other.handle) { other.handle = nullptr; }
254 ChainAwaiter(const ChainAwaiter &) = delete;
256
257 constexpr bool await_ready() { return handle.done(); }
258
259 void await_suspend(std::coroutine_handle<> h) {
260 handle.promise().m_awaitingCoroutine = h;
261 if (!handle.done()) handle.resume();
262 }
263
264 constexpr T await_resume() {
265 if constexpr (std::is_void<T>::value) {
266 handle.destroy();
267 handle = nullptr;
268 return;
269 } else {
270 auto val = eastl::move(handle.promise().m_value);
271 handle.destroy();
272 handle = nullptr;
273 return val;
274 }
275 }
276 };
277
278 ChainAwaiter operator co_await() && {
279 auto h = m_handle;
280 m_handle = nullptr;
281 return ChainAwaiter{h};
282 }
283};
284
286 protected:
287 void initializeInternal(eastl::function<void()> &&func, void *ss_sp, unsigned ss_size);
288 void resume();
289 void yield();
290 [[nodiscard]] bool isAlive() const { return m_isAlive; }
291
292 StackfulBase() = default;
293 StackfulBase(const StackfulBase &) = delete;
295
296 private:
297 static void trampoline(void *arg) {
298 StackfulBase *self = static_cast<StackfulBase *>(arg);
299 self->trampoline();
300 }
301 void trampoline();
302 ucontext_t m_coroutine;
303 ucontext_t m_return;
304 eastl::function<void()> m_func;
305 bool m_isAlive = false;
306#ifdef PSYQO_SHADOW_STACK
307 ShadowStack::Context m_shadowStack;
308 ShadowStack::Context *m_shadowReturn = nullptr;
309#endif
310};
311
325template <unsigned StackSize = 0x10000>
326class Stackful : public StackfulBase {
327 public:
328 static constexpr unsigned c_stackSize = (StackSize + 7) & ~7;
329
330 Stackful() = default;
331 Stackful(const Stackful &) = delete;
332 Stackful &operator=(const Stackful &) = delete;
333
340 void initialize(eastl::function<void()> &&func) {
341 initializeInternal(eastl::move(func), m_stack.data, c_stackSize);
342 }
343
354
364
373 [[nodiscard]] bool isAlive() const { return StackfulBase::isAlive(); }
374
375 private:
376 struct alignas(8) Stack {
377 uint8_t data[c_stackSize];
378 };
379 Stack m_stack;
380};
381
382// End of the linked .scratchpad section, from the link script.
383extern "C" char __scratchpad_end[];
384
416template <unsigned StackSize = 1024>
418 static_assert(StackSize <= 1024, "The scratchpad is 1024 bytes");
419 static_assert(StackSize > 16, "makecontext reserves 16 bytes at the top of the stack");
420 static_assert((StackSize & 7) == 0, "The stack size needs to be a multiple of 8");
421
422 public:
423 static constexpr uintptr_t c_stackBase = 0x1f800400 - StackSize;
424 static constexpr unsigned c_stackSize = StackSize;
425 static constexpr unsigned c_freeBytes = 1024 - StackSize;
429
435 void initialize(eastl::function<void()> &&func) {
436 Kernel::assert(reinterpret_cast<uintptr_t>(__scratchpad_end) <= c_stackBase,
437 "ScratchpadStackful: the .scratchpad section overlaps the stack");
438 initializeInternal(eastl::move(func), reinterpret_cast<void *>(c_stackBase), c_stackSize);
439 }
440
445
450
454 [[nodiscard]] bool isAlive() const { return StackfulBase::isAlive(); }
455};
456
457} // namespace psyqo
Stackful coroutine whose stack is the scratchpad.
Definition coroutine.hh:417
ScratchpadStackful(const ScratchpadStackful &)=delete
static constexpr uintptr_t c_stackBase
Definition coroutine.hh:423
void initialize(eastl::function< void()> &&func)
Initialize the coroutine with a function.
Definition coroutine.hh:435
ScratchpadStackful & operator=(const ScratchpadStackful &)=delete
static constexpr unsigned c_freeBytes
Definition coroutine.hh:425
void yield()
Yield the coroutine. See Stackful::yield.
Definition coroutine.hh:449
bool isAlive() const
Check if the coroutine is currently alive. See Stackful::isAlive.
Definition coroutine.hh:454
void resume()
Resume the coroutine. See Stackful::resume.
Definition coroutine.hh:444
static constexpr unsigned c_stackSize
Definition coroutine.hh:424
Definition coroutine.hh:285
void yield()
Definition coroutine.cpp:60
bool isAlive() const
Definition coroutine.hh:290
StackfulBase & operator=(const StackfulBase &)=delete
void resume()
Definition coroutine.cpp:47
StackfulBase()=default
StackfulBase(const StackfulBase &)=delete
void initializeInternal(eastl::function< void()> &&func, void *ss_sp, unsigned ss_size)
Definition coroutine.cpp:29
Stackful coroutine class.
Definition coroutine.hh:326
Stackful(const Stackful &)=delete
bool isAlive() const
Check if the coroutine is currently alive.
Definition coroutine.hh:373
void yield()
Yield the coroutine.
Definition coroutine.hh:363
Stackful()=default
void resume()
Resume the coroutine.
Definition coroutine.hh:353
void initialize(eastl::function< void()> &&func)
Initialize the coroutine with a function and an argument.
Definition coroutine.hh:340
static constexpr unsigned c_stackSize
Definition coroutine.hh:328
Stackful & operator=(const Stackful &)=delete
constexpr uint32_t move(Reg tgt, Reg src)
Definition encoder.hh:243
void assert(bool condition, const char *message, std::source_location location=std::source_location::current())
A simple assert macro.
Definition kernel.hh:366
void queueCallback(eastl::function< void()> &&lambda)
Queues a callback to be called from the main thead.
Definition kernel.cpp:410
Definition lua.hh:40
char __scratchpad_end[]
Definition coroutine.hh:383
The awaiter type.
Definition coroutine.hh:107
Awaiter(Awaiter &&other)=default
Awaiter(Awaiter const &)=default
constexpr void await_suspend(std::coroutine_handle<> h)
Definition coroutine.hh:117
Awaiter & operator=(Awaiter const &)=default
constexpr void await_resume() const noexcept
Definition coroutine.hh:118
constexpr bool await_ready() const noexcept
Definition coroutine.hh:112
Awaiter & operator=(Awaiter &&other)=default
The awaiter type for coroutine-to-coroutine chaining via co_await.
Definition coroutine.hh:244
constexpr T await_resume()
Definition coroutine.hh:264
constexpr bool await_ready()
Definition coroutine.hh:257
void await_suspend(std::coroutine_handle<> h)
Definition coroutine.hh:259
std::coroutine_handle< Promise > handle
Definition coroutine.hh:245
ChainAwaiter & operator=(const ChainAwaiter &)=delete
ChainAwaiter & operator=(ChainAwaiter &&)=delete
~ChainAwaiter()
Definition coroutine.hh:248
ChainAwaiter(ChainAwaiter &&other)
Definition coroutine.hh:252
ChainAwaiter(const ChainAwaiter &)=delete
ChainAwaiter(std::coroutine_handle< Promise > h)
Definition coroutine.hh:247
Definition coroutine.hh:56
A suitable type to hold and return a C++20 coroutine.
Definition coroutine.hh:55
Coroutine(Coroutine const &)=delete
~Coroutine()
Definition coroutine.hh:94
Coroutine & operator=(Coroutine const &)=delete
Promise promise_type
Definition coroutine.hh:234
const SafeT & value() const
Returns the value returned by the coroutine.
Definition coroutine.hh:186
Coroutine(Coroutine &&other)
Definition coroutine.hh:61
Coroutine()=default
std::conditional< std::is_void< T >::value, Empty, T >::type SafeT
Definition coroutine.hh:57
Coroutine & operator=(Coroutine &&other)
Definition coroutine.hh:75
void resume()
Resumes the coroutine.
Definition coroutine.hh:143
Awaiter awaiter() &
Creates an Awaiter object.
Definition coroutine.hh:132
bool done()
Returns the status of the coroutine.
Definition coroutine.hh:162
One shadow stack, for one machine stack.
Definition shadow-stack.hh:76
Definition ucontext.h:45
static int ret
Definition syscalls.h:125
uint16_t val
Definition timers.c:319