Skip to content

Commit 0c18816

Browse files
authored
Merge pull request #1934 from nicolasnoble/ucontext
Adding ucontext.
2 parents 7730d21 + 9afc64a commit 0c18816

File tree

7 files changed

+488
-0
lines changed

7 files changed

+488
-0
lines changed

src/mips/common/psxlibc/ucontext.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
3+
MIT License
4+
5+
Copyright (c) 2025 PCSX-Redux authors
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
25+
*/
26+
27+
#pragma once
28+
29+
// This file provides an ersatz for the ucontext.h API, which is otherwise not available on PSX.
30+
// It defines the necessary structures and function prototypes to allow for context switching,
31+
// similar to what ucontext.h provides in POSIX systems. It's not strictly compliant with the POSIX standard,
32+
// but it provides a minimal implementation suitable for coroutine management in a PSX environment.
33+
34+
#include <stdint.h>
35+
36+
struct stack_t {
37+
void *ss_sp;
38+
unsigned ss_size;
39+
};
40+
41+
struct mcontext_t {
42+
uint32_t reserved[16];
43+
};
44+
45+
struct ucontext_t {
46+
struct mcontext_t uc_mcontext;
47+
struct ucontext_t *uc_link;
48+
struct stack_t uc_stack;
49+
};
50+
51+
#ifdef __cplusplus
52+
extern "C" {
53+
#endif
54+
55+
int getcontext(struct ucontext_t *ucp);
56+
int setcontext(const struct ucontext_t *ucp);
57+
int makecontext(struct ucontext_t *ucp, void (*func)(void *), void *arg);
58+
int swapcontext(struct ucontext_t *oucp, const struct ucontext_t *ucp);
59+
60+
#ifdef __cplusplus
61+
}
62+
#endif

src/mips/common/psxlibc/ucontext.s

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
3+
MIT License
4+
5+
Copyright (c) 2025 PCSX-Redux authors
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
25+
*/
26+
27+
.set RAPTR, 0
28+
.set SPPTR, 4
29+
.set S8PTR, 8
30+
.set S0PTR, 12
31+
.set S1PTR, 16
32+
.set S2PTR, 20
33+
.set S3PTR, 24
34+
.set S4PTR, 28
35+
.set S5PTR, 32
36+
.set S6PTR, 36
37+
.set S7PTR, 40
38+
.set GPPTR, 44
39+
.set FUNCPTR, 48
40+
.set ARGPTR, 52
41+
.set UNUSED1, 56
42+
.set UNUSED2, 60
43+
.set LINKPTR, 64
44+
.set STACKPTR, 68
45+
.set STACKSIZE, 72
46+
47+
.section .text.getcontext, "ax", @progbits
48+
.align 2
49+
.global getcontext
50+
.type getcontext, @function
51+
52+
getcontext:
53+
sw $ra, RAPTR($a0)
54+
sw $sp, SPPTR($a0)
55+
sw $s8, S8PTR($a0)
56+
sw $s0, S0PTR($a0)
57+
sw $s1, S1PTR($a0)
58+
sw $s2, S2PTR($a0)
59+
sw $s3, S3PTR($a0)
60+
sw $s4, S4PTR($a0)
61+
sw $s5, S5PTR($a0)
62+
sw $s6, S6PTR($a0)
63+
sw $s7, S7PTR($a0)
64+
sw $gp, GPPTR($a0)
65+
move $v0, $0
66+
jr $ra
67+
68+
.section .text.setcontext, "ax", @progbits
69+
.align 2
70+
.global setcontext
71+
.type setcontext, @function
72+
73+
setcontext:
74+
lw $ra, RAPTR($a0)
75+
lw $sp, SPPTR($a0)
76+
lw $s8, S8PTR($a0)
77+
lw $s0, S0PTR($a0)
78+
lw $s1, S1PTR($a0)
79+
lw $s2, S2PTR($a0)
80+
lw $s3, S3PTR($a0)
81+
lw $s4, S4PTR($a0)
82+
lw $s5, S5PTR($a0)
83+
lw $s6, S6PTR($a0)
84+
lw $s7, S7PTR($a0)
85+
lw $gp, GPPTR($a0)
86+
move $v0, $0
87+
jr $ra
88+
89+
.section .text.makecontext, "ax", @progbits
90+
.align 2
91+
.global makecontext
92+
.type makecontext, @function
93+
.type contexttrampoline, @function
94+
95+
contexttrampoline:
96+
lw $a0, ARGPTR($s0)
97+
lw $a1, FUNCPTR($s0)
98+
lw $sp, SPPTR($s0)
99+
lw $s8, S8PTR($s0)
100+
lw $gp, GPPTR($s0)
101+
jalr $a1
102+
lw $a0, LINKPTR($s0)
103+
j setcontext
104+
105+
makecontext:
106+
lw $v1, STACKPTR($a0)
107+
lw $t0, STACKSIZE($a0)
108+
la $v0, contexttrampoline
109+
sw $v0, RAPTR($a0)
110+
sw $a1, FUNCPTR($a0)
111+
sw $a2, ARGPTR($a0)
112+
sw $a0, S0PTR($a0)
113+
addu $v1, $t0
114+
sw $v1, SPPTR($a0)
115+
jr $ra
116+
117+
.section .text.swapcontext, "ax", @progbits
118+
.align 2
119+
.global swapcontext
120+
.type swapcontext, @function
121+
122+
swapcontext:
123+
sw $ra, RAPTR($a0)
124+
sw $sp, SPPTR($a0)
125+
sw $s8, S8PTR($a0)
126+
sw $s0, S0PTR($a0)
127+
sw $s1, S1PTR($a0)
128+
sw $s2, S2PTR($a0)
129+
sw $s3, S3PTR($a0)
130+
sw $s4, S4PTR($a0)
131+
sw $s5, S5PTR($a0)
132+
sw $s6, S6PTR($a0)
133+
sw $s7, S7PTR($a0)
134+
sw $gp, GPPTR($a0)
135+
lw $ra, RAPTR($a1)
136+
lw $sp, SPPTR($a1)
137+
lw $s8, S8PTR($a1)
138+
lw $s0, S0PTR($a1)
139+
lw $s1, S1PTR($a1)
140+
lw $s2, S2PTR($a1)
141+
lw $s3, S3PTR($a1)
142+
lw $s4, S4PTR($a1)
143+
lw $s5, S5PTR($a1)
144+
lw $s6, S6PTR($a1)
145+
lw $s7, S7PTR($a1)
146+
lw $gp, GPPTR($a1)
147+
move $v0, $0
148+
jr $ra

src/mips/psyqo/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ $(wildcard ../../../third_party/EASTL/source/*.cpp) \
1010
../common/crt0/memory-c.c \
1111
../common/crt0/memory-s.s \
1212
../common/hardware/flushcache.s \
13+
../common/psxlibc/ucontext.s \
1314
../common/syscalls/printf.s \
1415
$(wildcard src/hardware/*.cpp) \
1516
$(wildcard src/*.cpp) \

src/mips/psyqo/coroutine.hh

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@ SOFTWARE.
2626

2727
#pragma once
2828

29+
#include <EASTL/functional.h>
30+
#include <EASTL/utility.h>
31+
2932
#include <coroutine>
3033
#include <type_traits>
3134

35+
#include "common/psxlibc/ucontext.h"
3236
#include "common/syscalls/syscalls.h"
3337

3438
namespace psyqo {
@@ -208,4 +212,97 @@ struct Coroutine {
208212
}
209213
};
210214

215+
class StackfulBase {
216+
protected:
217+
void initializeInternal(eastl::function<void()>&& func, void* ss_sp, unsigned ss_size);
218+
void resume();
219+
void yield();
220+
[[nodiscard]] bool isAlive() const { return m_isAlive; }
221+
222+
StackfulBase() = default;
223+
StackfulBase(const StackfulBase&) = delete;
224+
StackfulBase& operator=(const StackfulBase&) = delete;
225+
226+
private:
227+
static void trampoline(void* arg) {
228+
StackfulBase* self = static_cast<StackfulBase*>(arg);
229+
self->trampoline();
230+
}
231+
void trampoline();
232+
ucontext_t m_coroutine;
233+
ucontext_t m_return;
234+
eastl::function<void()> m_func;
235+
bool m_isAlive = false;
236+
};
237+
238+
/**
239+
* @brief Stackful coroutine class.
240+
*
241+
* @details This class provides a simple stackful coroutine implementation.
242+
* It allows you to create coroutines that can yield and resume execution.
243+
* While the Coroutine class above is a C++20 coroutine, it requires
244+
* that all of the code being run are coroutines or awaitables all the way down.
245+
* This class is a more traditional coroutine implementation that uses
246+
* a separate stack for each coroutine, allowing it to yield and resume
247+
* execution without requiring the entire call stack to be coroutine-aware.
248+
* It is suitable for use in scenarios where you need to yield execution
249+
* from legacy code without converting it to C++20 coroutines.
250+
*/
251+
template <unsigned StackSize = 0x10000>
252+
class Stackful : public StackfulBase {
253+
public:
254+
static constexpr unsigned c_stackSize = (StackSize + 7) & ~7;
255+
256+
Stackful() = default;
257+
Stackful(const Stackful&) = delete;
258+
Stackful& operator=(const Stackful&) = delete;
259+
260+
/**
261+
* @brief Initialize the coroutine with a function and an argument.
262+
*
263+
* @param func Function to be executed by the coroutine.
264+
* @param arg Argument to be passed to the function.
265+
*/
266+
void initialize(eastl::function<void()>&& func) {
267+
initializeInternal(eastl::move(func), m_stack.data, c_stackSize);
268+
}
269+
270+
/**
271+
* @brief Resume the coroutine.
272+
*
273+
* @details This will switch to the coroutine's context and execute it.
274+
* If the coroutine is not alive, this function does nothing. This
275+
* function should be called after the coroutine has been initialized,
276+
* and it will return to the point where the coroutine was last yielded.
277+
* It can only be called from the "main thread".
278+
*/
279+
void resume() { StackfulBase::resume(); }
280+
281+
/**
282+
* @brief Yield the coroutine.
283+
*
284+
* @details This will switch back to the main thread and save the
285+
* coroutine's context. The coroutine can be resumed later using
286+
* `resume()`. It can only be called from within the coroutine
287+
* to yield execution.
288+
*/
289+
void yield() { StackfulBase::yield(); }
290+
291+
/**
292+
* @brief Check if the coroutine is currently alive.
293+
* @details A coroutine is considered alive if it has been initialized
294+
* and has not yet completed its execution. It becomes not alive
295+
* when it returns from its function.
296+
*
297+
* @return true if the coroutine is alive, false otherwise.
298+
*/
299+
[[nodiscard]] bool isAlive() const { return StackfulBase::isAlive(); }
300+
301+
private:
302+
struct alignas(8) Stack {
303+
uint8_t data[c_stackSize];
304+
};
305+
Stack m_stack;
306+
};
307+
211308
} // namespace psyqo
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
TARGET = coroutine-demo-3
2+
TYPE = ps-exe
3+
4+
SRCS = \
5+
coroutine-demo-3.cpp \
6+
7+
ifeq ($(TEST),true)
8+
CPPFLAGS = -Werror
9+
endif
10+
CXXFLAGS = -std=c++20 -fcoroutines
11+
12+
include ../../psyqo.mk

0 commit comments

Comments
 (0)