Skip to content

Commit d6e7b1e

Browse files
authored
Merge pull request #1999 from nicolasnoble/fix-coroutine-lifetime
Fix coroutine lifetime and realloc fragmentation
2 parents c2ad3b2 + 451b6df commit d6e7b1e

4 files changed

Lines changed: 188 additions & 52 deletions

File tree

src/mips/openbios/kernel/alloc.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ static __attribute__((section(".ramtext"))) void *multi_realloc(void *ptr_, size
238238
if (block < head) {
239239
if (size < old_size) {
240240
empty_block *new_block = (empty_block *)((char *)block + size);
241-
if (head == (empty_block *)((char *)block + size)) {
241+
if (head == (empty_block *)((char *)block + old_size)) {
242242
new_block->next = head->next;
243243
new_block->size = head->size + (old_size - size);
244244
} else {
@@ -292,7 +292,7 @@ static __attribute__((section(".ramtext"))) void *multi_realloc(void *ptr_, size
292292

293293
if (size < old_size) {
294294
empty_block *new_block = (empty_block *)((char *)block + size);
295-
if ((next != &marker) && (((char *)block + size) == (char *)next)) {
295+
if ((next != &marker) && (((char *)block + old_size) == (char *)next)) {
296296
new_block->next = next->next;
297297
new_block->size = old_size - size + next->size;
298298
} else {

src/mips/psyqo/coroutine.hh

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ struct Coroutine {
128128
* @details This method is used to create an instance of the `Awaiter` object.
129129
* It's used to suspend the coroutine after scheduling an asynchronous operation.
130130
*/
131-
Awaiter awaiter() { return Awaiter(this); }
131+
Awaiter awaiter() & { return Awaiter(this); }
132132

133133
/**
134134
* @brief Resumes the coroutine.
@@ -232,18 +232,52 @@ struct Coroutine {
232232
public:
233233
using promise_type = Promise;
234234

235-
constexpr bool await_ready() { return m_handle.done(); }
236-
template <typename U>
237-
constexpr void await_suspend(std::coroutine_handle<U> h) {
238-
m_handle.promise().m_awaitingCoroutine = h;
239-
resume();
240-
}
241-
constexpr T await_resume() {
242-
if constexpr (std::is_void<T>::value) {
243-
return;
244-
} else {
245-
return eastl::move(m_handle.promise().m_value);
235+
/**
236+
* @brief The awaiter type for coroutine-to-coroutine chaining via co_await.
237+
*
238+
* @details GCC has known bugs across versions 10-15+ where the awaitable temporary
239+
* in a `co_await` expression is not correctly promoted to the coroutine frame.
240+
* By using `operator co_await()`, we transfer the coroutine handle to this
241+
* awaiter object that GCC's coroutine pass reliably stores in the frame.
242+
*/
243+
struct ChainAwaiter {
244+
std::coroutine_handle<Promise> handle;
245+
246+
explicit ChainAwaiter(std::coroutine_handle<Promise> h) : handle(h) {}
247+
~ChainAwaiter() {
248+
if (handle) handle.destroy();
249+
}
250+
251+
ChainAwaiter(ChainAwaiter &&other) : handle(other.handle) { other.handle = nullptr; }
252+
ChainAwaiter &operator=(ChainAwaiter &&) = delete;
253+
ChainAwaiter(const ChainAwaiter &) = delete;
254+
ChainAwaiter &operator=(const ChainAwaiter &) = delete;
255+
256+
constexpr bool await_ready() { return handle.done(); }
257+
258+
void await_suspend(std::coroutine_handle<> h) {
259+
handle.promise().m_awaitingCoroutine = h;
260+
if (!handle.done()) handle.resume();
261+
}
262+
263+
constexpr T await_resume() {
264+
if constexpr (std::is_void<T>::value) {
265+
handle.destroy();
266+
handle = nullptr;
267+
return;
268+
} else {
269+
auto val = eastl::move(handle.promise().m_value);
270+
handle.destroy();
271+
handle = nullptr;
272+
return val;
273+
}
246274
}
275+
};
276+
277+
ChainAwaiter operator co_await() && {
278+
auto h = m_handle;
279+
m_handle = nullptr;
280+
return ChainAwaiter{h};
247281
}
248282
};
249283

src/mips/psyqo/src/alloc.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,8 +584,8 @@ void *psyqo_realloc(void *ptr_, size_t size_) {
584584
if (size < old_size) {
585585
// We're going to create a new block at the end of what we are re-allocating.
586586
empty_block *new_block = (empty_block *)((char *)block + size);
587-
// Is the next block adjacent to the block we're re-allocating?
588-
if ((next != &marker) && (((char *)block + size) == (char *)next)) {
587+
// Is the next block adjacent to the end of our original allocation?
588+
if ((next != &marker) && (((char *)block + old_size) == (char *)next)) {
589589
// Yes, we can merge them.
590590
new_block->next = next->next;
591591
new_block->size = old_size - size + next->size;

src/support/coroutine.h

Lines changed: 138 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,52 +26,86 @@ SOFTWARE.
2626

2727
#pragma once
2828

29-
#if defined(__APPLE__) && (__clang_major__ < 15)
30-
// Why has Apple become the Microsoft of Software Engineering?
31-
#include <experimental/coroutine>
32-
#else
3329
#include <coroutine>
34-
#endif
3530
#include <type_traits>
31+
#include <utility>
3632

3733
namespace PCSX {
3834

3935
template <typename T = void>
4036
struct Coroutine {
41-
#if defined(__APPLE__) && (__clang_major__ < 15)
42-
template <typename U>
43-
using CoroutineHandle = std::experimental::coroutine_handle<U>;
44-
using CoroutineHandleVoid = std::experimental::coroutine_handle<void>;
45-
#else
46-
template <typename U>
47-
using CoroutineHandle = std::coroutine_handle<U>;
48-
using CoroutineHandleVoid = std::coroutine_handle<void>;
49-
#endif
5037
struct Empty {};
5138
typedef typename std::conditional<std::is_void<T>::value, Empty, T>::type SafeT;
5239

5340
Coroutine() = default;
54-
Coroutine(Coroutine &&other) = default;
55-
Coroutine &operator=(Coroutine &&other) = default;
41+
42+
Coroutine(Coroutine &&other)
43+
: m_handle(std::exchange(other.m_handle, nullptr)),
44+
m_value(std::move(other.m_value)),
45+
m_suspended(other.m_suspended),
46+
m_earlyResume(other.m_earlyResume) {
47+
other.m_suspended = true;
48+
other.m_earlyResume = false;
49+
}
50+
51+
Coroutine &operator=(Coroutine &&other) {
52+
if (this != &other) {
53+
if (m_handle) m_handle.destroy();
54+
m_handle = std::exchange(other.m_handle, nullptr);
55+
m_value = std::move(other.m_value);
56+
m_suspended = other.m_suspended;
57+
m_earlyResume = other.m_earlyResume;
58+
other.m_suspended = true;
59+
other.m_earlyResume = false;
60+
}
61+
return *this;
62+
}
63+
5664
Coroutine(Coroutine const &) = delete;
5765
Coroutine &operator=(Coroutine const &) = delete;
66+
~Coroutine() {
67+
if (m_handle) m_handle.destroy();
68+
m_handle = nullptr;
69+
}
5870

5971
struct Awaiter {
60-
constexpr bool await_ready() const noexcept { return false; }
61-
constexpr void await_suspend(CoroutineHandleVoid) const noexcept {}
72+
Awaiter(Awaiter &&other) = default;
73+
Awaiter &operator=(Awaiter &&other) = default;
74+
Awaiter(Awaiter const &) = default;
75+
Awaiter &operator=(Awaiter const &) = default;
76+
constexpr bool await_ready() const noexcept {
77+
bool ret = m_coroutine->m_earlyResume;
78+
m_coroutine->m_earlyResume = false;
79+
return ret;
80+
}
81+
constexpr void await_suspend(std::coroutine_handle<> h) { m_coroutine->m_suspended = true; }
6282
constexpr void await_resume() const noexcept {}
83+
84+
private:
85+
Awaiter(Coroutine *coroutine) : m_coroutine(coroutine) {}
86+
Coroutine *m_coroutine;
87+
friend struct Coroutine;
6388
};
6489

65-
Awaiter awaiter() { return {}; }
90+
Awaiter awaiter() & { return Awaiter(this); }
91+
6692
void resume() {
6793
if (!m_handle) return;
94+
if (!m_suspended) {
95+
m_earlyResume = true;
96+
return;
97+
}
98+
m_suspended = false;
6899
m_handle.resume();
69100
}
70101

71102
bool done() {
72103
if (!m_handle) return true;
73104
bool isDone = m_handle.done();
74105
if (isDone) {
106+
if constexpr (!std::is_void<T>::value) {
107+
m_value = std::move(m_handle.promise().m_value);
108+
}
75109
m_handle.destroy();
76110
m_handle = nullptr;
77111
}
@@ -82,38 +116,106 @@ struct Coroutine {
82116

83117
private:
84118
struct PromiseVoid {
85-
Coroutine<T> get_return_object() { return Coroutine{std::move(CoroutineHandle<Promise>::from_promise(*this))}; }
86-
Awaiter initial_suspend() { return {}; }
87-
Awaiter final_suspend() noexcept { return {}; }
119+
struct FinalAwaiter {
120+
bool await_ready() const noexcept { return false; }
121+
std::coroutine_handle<> await_suspend(std::coroutine_handle<PromiseVoid> h) const noexcept {
122+
auto next = h.promise().m_awaitingCoroutine;
123+
return next ? next : std::noop_coroutine();
124+
}
125+
void await_resume() const noexcept {}
126+
};
127+
Coroutine<> get_return_object() {
128+
return Coroutine<>{std::move(std::coroutine_handle<Promise>::from_promise(*this))};
129+
}
130+
std::suspend_always initial_suspend() { return {}; }
131+
FinalAwaiter final_suspend() noexcept { return {}; }
88132
void unhandled_exception() {}
89133
template <typename From>
90-
Awaiter yield_value(From &&from) {
91-
return {};
134+
From yield_value(From &&from) {
135+
return std::forward<From>(from);
92136
}
93137
void return_void() {}
138+
[[no_unique_address]] Empty m_value;
139+
std::coroutine_handle<> m_awaitingCoroutine;
94140
};
141+
95142
struct PromiseValue {
96-
PromiseValue(Coroutine<T> *c) : coroutine(c) {}
97-
Coroutine<T> get_return_object() { return Coroutine{std::move(CoroutineHandle<Promise>::from_promise(*this))}; }
98-
Awaiter initial_suspend() { return {}; }
99-
Awaiter final_suspend() noexcept { return {}; }
143+
struct FinalAwaiter {
144+
bool await_ready() const noexcept { return false; }
145+
std::coroutine_handle<> await_suspend(std::coroutine_handle<PromiseValue> h) const noexcept {
146+
auto next = h.promise().m_awaitingCoroutine;
147+
return next ? next : std::noop_coroutine();
148+
}
149+
void await_resume() const noexcept {}
150+
};
151+
Coroutine<T> get_return_object() {
152+
return Coroutine{std::move(std::coroutine_handle<Promise>::from_promise(*this))};
153+
}
154+
std::suspend_always initial_suspend() { return {}; }
155+
FinalAwaiter final_suspend() noexcept { return {}; }
100156
void unhandled_exception() {}
101-
// This should be an std::convertible_to<T>, but Apple still doesn't have a fully C++-20 conformant library.
102157
template <typename From>
103-
Awaiter yield_value(From &&from) {
104-
coroutine->m_value = std::forward<From>(from);
105-
return {};
158+
From yield_value(From &&from) {
159+
return std::forward<From>(from);
160+
}
161+
void return_value(T &&value) {
162+
m_value = std::move(value);
106163
}
107-
void return_value(T &&value) { coroutine->m_value = std::forward(value); }
108-
Coroutine<T> *coroutine = nullptr;
164+
T m_value;
165+
std::coroutine_handle<> m_awaitingCoroutine;
109166
};
167+
110168
typedef typename std::conditional<std::is_void<T>::value, PromiseVoid, PromiseValue>::type Promise;
111-
Coroutine(CoroutineHandle<Promise> &&handle) : m_handle(std::move(handle)) {}
112-
CoroutineHandle<Promise> m_handle;
169+
170+
Coroutine(std::coroutine_handle<Promise> &&handle) : m_handle(std::move(handle)) {}
171+
172+
std::coroutine_handle<Promise> m_handle;
113173
[[no_unique_address]] SafeT m_value;
174+
bool m_suspended = true;
175+
bool m_earlyResume = false;
114176

115177
public:
116178
using promise_type = Promise;
179+
180+
struct ChainAwaiter {
181+
std::coroutine_handle<Promise> handle;
182+
183+
explicit ChainAwaiter(std::coroutine_handle<Promise> h) : handle(h) {}
184+
~ChainAwaiter() {
185+
if (handle) handle.destroy();
186+
}
187+
188+
ChainAwaiter(ChainAwaiter &&other) : handle(other.handle) { other.handle = nullptr; }
189+
ChainAwaiter &operator=(ChainAwaiter &&) = delete;
190+
ChainAwaiter(const ChainAwaiter &) = delete;
191+
ChainAwaiter &operator=(const ChainAwaiter &) = delete;
192+
193+
constexpr bool await_ready() { return handle.done(); }
194+
195+
void await_suspend(std::coroutine_handle<> h) {
196+
handle.promise().m_awaitingCoroutine = h;
197+
if (!handle.done()) handle.resume();
198+
}
199+
200+
constexpr T await_resume() {
201+
if constexpr (std::is_void<T>::value) {
202+
handle.destroy();
203+
handle = nullptr;
204+
return;
205+
} else {
206+
auto val = std::move(handle.promise().m_value);
207+
handle.destroy();
208+
handle = nullptr;
209+
return val;
210+
}
211+
}
212+
};
213+
214+
ChainAwaiter operator co_await() && {
215+
auto h = m_handle;
216+
m_handle = nullptr;
217+
return ChainAwaiter{h};
218+
}
117219
};
118220

119221
} // namespace PCSX

0 commit comments

Comments
 (0)