Skip to content

Commit 12e2b45

Browse files
committed
RISC-V Fiber context switching native support
1 parent 1cceff3 commit 12e2b45

File tree

3 files changed

+231
-1
lines changed

3 files changed

+231
-1
lines changed

runtime/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,11 @@ endif()
234234
# druntime ASM parts
235235
set(DRUNTIME_ASM)
236236
if("${TARGET_SYSTEM}" MATCHES "UNIX")
237-
list(APPEND DRUNTIME_ASM ${RUNTIME_DIR}/src/core/threadasm.S ${RUNTIME_DIR}/src/ldc/eh_asm.S)
237+
list(APPEND DRUNTIME_ASM
238+
${RUNTIME_DIR}/src/core/threadasm.S
239+
${RUNTIME_DIR}/src/core/thread/fiber/switch_context_riscv.S
240+
${RUNTIME_DIR}/src/ldc/eh_asm.S
241+
)
238242
endif()
239243

240244
if(PHOBOS2_DIR)

runtime/druntime/src/core/thread/fiber.d

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ private
160160
version = AsmExternal;
161161
}
162162
}
163+
else version (RISCV32)
164+
{
165+
version = RISCV_Any;
166+
version = AsmExternal;
167+
}
168+
else version (RISCV64)
169+
{
170+
version = RISCV_Any;
171+
version = AsmExternal;
172+
}
163173
else version (SPARC)
164174
{
165175
// NOTE: The SPARC ABI specifies only doubleword alignment.
@@ -247,6 +257,13 @@ private
247257
extern (C) void fiber_trampoline() nothrow;
248258
version (LoongArch64)
249259
extern (C) void fiber_trampoline() nothrow;
260+
version (RISCV_Any)
261+
{
262+
// External asm stack initialization is used to support different register
263+
// storage sizes that the D compiler does not know about
264+
extern (C) void* fiber_initStack(void* stack, void* entry) nothrow @nogc;
265+
extern (C) void fiber_trampoline() nothrow;
266+
}
250267
}
251268
else version (LDC_Windows)
252269
{
@@ -2055,6 +2072,13 @@ private:
20552072
*/
20562073
pstack += int.sizeof * 1;
20572074
}
2075+
else version (RISCV_Any)
2076+
{
2077+
version (StackGrowsDown) {}
2078+
else static assert(false, "RISC-V only supports decrementing stacks");
2079+
2080+
pstack = fiber_initStack(pstack, &fiber_trampoline);
2081+
}
20582082
else static if ( __traits( compiles, ucontext_t ) )
20592083
{
20602084
getcontext( &m_utxt );
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/**
2+
* Support code for RISC-V fibers.
3+
*
4+
* Copyright: Copyright Denis Feklushkin 2025.
5+
* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
6+
* Authors: Denis Feklushkin
7+
*/
8+
9+
#if defined(__riscv)
10+
11+
// For save/load a register in memory, regardless of the size of machine register bit size
12+
#if(__riscv_xlen == 32)
13+
#define save sw
14+
#define load lw
15+
#elif(__riscv_xlen == 64)
16+
#define save sd
17+
#define load ld
18+
#else
19+
#error Unsupported integer register bit size
20+
#endif
21+
22+
// Integer register size, bytes
23+
reg_s = __riscv_xlen / 8
24+
25+
#if defined(__riscv_flen)
26+
27+
#if(__riscv_flen == 32)
28+
#define fsave fsw
29+
#define fload flw
30+
#elif(__riscv_flen == 64)
31+
#define fsave fsd
32+
#define fload fld
33+
#elif(__riscv_flen == 128)
34+
#define fsave fsq
35+
#define fload flq
36+
#else
37+
#error Unsupported float register bit size
38+
#endif
39+
40+
// Floating register size, bytes
41+
freg_s = __riscv_flen / 8
42+
#else
43+
freg_s = 0 // hard float is not supported
44+
#endif
45+
46+
ints_storage_size = reg_s * 12 // all callee-saved integer registers (embedded ABI isn't supported for now)
47+
floats_storage_size = freg_s * 12 // all callee-saved float registers
48+
49+
/**
50+
* Parameters:
51+
* a0 - void* - pointer to a new stack
52+
* a1 - void* - pointer to the entry point
53+
*
54+
* Returns:
55+
* a0 - void* - modified new stack pointer
56+
*/
57+
.text
58+
.globl fiber_initStack
59+
.type fiber_initStack, @function
60+
fiber_initStack:
61+
// At this point assumed that memory for the stack is already allocated (but not zeroed)
62+
63+
// adjust stack pointer
64+
addi a0, a0, -ints_storage_size
65+
66+
// store entry point start address as saved ra register below stack pointer
67+
save a1, -reg_s(a0)
68+
69+
ret
70+
71+
.text
72+
.globl fiber_trampoline
73+
.type fiber_trampoline, @function
74+
fiber_trampoline:
75+
.cfi_startproc // necessary for .eh_frame
76+
// discard ra value - fiber_entryPoint never returns
77+
.cfi_undefined ra
78+
79+
// non-returnable jump (i.e., a non-unwinding tail-call) to fiber_entryPoint
80+
tail fiber_entryPoint
81+
.cfi_endproc
82+
83+
/**
84+
* Parameters:
85+
* a0 - void** - ptr to old stack pointer
86+
* a1 - void* - new stack pointer
87+
*
88+
* RISCV ABI registers:
89+
* x0 zero : hardwired to zero
90+
* x1 ra : return address
91+
* x2 sp : stack pointer
92+
* x3 gp : global pointer (variables are ‘relaxed’ and accessed via a relative imm offset from the gp)
93+
* x4 tp : thread pointer
94+
* x5-x7 t0-t2 : temporary/scratch registers
95+
* x8 s0/fp : callee-saved register 0 AKA frame pointer
96+
* x9 s1 : callee-saved register 1
97+
* x10-x17 a0-a7 : function arguments
98+
* x18-x27 s2-s11 : callee-saved registers
99+
* x28-x31 t3-t6 : temporary/scratch registers
100+
*
101+
* (floating registers omitted)
102+
*/
103+
.text
104+
.globl fiber_switchContext
105+
.type fiber_switchContext, @function
106+
fiber_switchContext:
107+
108+
// Reserve space on the stack to store registers
109+
// Moving stack pointer so hardware stack size checker can make sure
110+
// that stack boundary are not violated
111+
addi sp, sp, -(ints_storage_size + floats_storage_size + reg_s /*additional space for ra register*/)
112+
113+
// Move stack pointer back a little and store ra and floats above of
114+
// the stack border to avoid GC scan them in the stack frame
115+
addi sp, sp, reg_s /*excluded ra*/ + floats_storage_size
116+
117+
// ra stored above of the current stack
118+
save ra, -(1 * reg_s)(sp)
119+
120+
#if defined(__riscv_flen)
121+
// Floats also stored above of the current stack.
122+
//
123+
// For the convenience of manual verification counting is shifted so
124+
// that in most cases register names match the offsets (except the last one).
125+
//
126+
// Offset by one (ra) register size is added in addition to multiplication due
127+
// to the fact that the sizes of integer and float registers can differ.
128+
fsave fs1, -(1 * freg_s + reg_s)(sp)
129+
fsave fs2, -(2 * freg_s + reg_s)(sp)
130+
fsave fs3, -(3 * freg_s + reg_s)(sp)
131+
fsave fs4, -(4 * freg_s + reg_s)(sp)
132+
fsave fs5, -(5 * freg_s + reg_s)(sp)
133+
fsave fs6, -(6 * freg_s + reg_s)(sp)
134+
fsave fs7, -(7 * freg_s + reg_s)(sp)
135+
fsave fs8, -(8 * freg_s + reg_s)(sp)
136+
fsave fs9, -(9 * freg_s + reg_s)(sp)
137+
fsave fs10, -(10 * freg_s + reg_s)(sp)
138+
fsave fs11, -(11 * freg_s + reg_s)(sp)
139+
fsave fs0, -(12 * freg_s + reg_s)(sp)
140+
#endif
141+
142+
// Integer register data stored on the stack in the usual way
143+
save s0, (0 * reg_s)(sp)
144+
save s1, (1 * reg_s)(sp)
145+
save s2, (2 * reg_s)(sp)
146+
save s3, (3 * reg_s)(sp)
147+
save s4, (4 * reg_s)(sp)
148+
save s5, (5 * reg_s)(sp)
149+
save s6, (6 * reg_s)(sp)
150+
save s7, (7 * reg_s)(sp)
151+
save s8, (8 * reg_s)(sp)
152+
save s9, (9 * reg_s)(sp)
153+
save s10, (10 * reg_s)(sp)
154+
save s11, (11 * reg_s)(sp)
155+
156+
// Save current sp to oldp
157+
save sp, (a0)
158+
159+
// Load sp from newp
160+
addi sp, a1, 0
161+
162+
// Load ra from above of the stack border
163+
load ra, -(1 * reg_s)(sp)
164+
165+
#if defined(__riscv_flen)
166+
// Loading floats
167+
fload fs1, -(1 * freg_s + reg_s)(sp)
168+
fload fs2, -(2 * freg_s + reg_s)(sp)
169+
fload fs3, -(3 * freg_s + reg_s)(sp)
170+
fload fs4, -(4 * freg_s + reg_s)(sp)
171+
fload fs5, -(5 * freg_s + reg_s)(sp)
172+
fload fs6, -(6 * freg_s + reg_s)(sp)
173+
fload fs7, -(7 * freg_s + reg_s)(sp)
174+
fload fs8, -(8 * freg_s + reg_s)(sp)
175+
fload fs9, -(9 * freg_s + reg_s)(sp)
176+
fload fs10, -(10 * freg_s + reg_s)(sp)
177+
fload fs11, -(11 * freg_s + reg_s)(sp)
178+
fload fs0, -(12 * freg_s + reg_s)(sp)
179+
#endif
180+
181+
// Load registers from obtained stack
182+
load s0, (0 * reg_s)(sp)
183+
load s1, (1 * reg_s)(sp)
184+
load s2, (2 * reg_s)(sp)
185+
load s3, (3 * reg_s)(sp)
186+
load s4, (4 * reg_s)(sp)
187+
load s5, (5 * reg_s)(sp)
188+
load s6, (6 * reg_s)(sp)
189+
load s7, (7 * reg_s)(sp)
190+
load s8, (8 * reg_s)(sp)
191+
load s9, (9 * reg_s)(sp)
192+
load s10, (10 * reg_s)(sp)
193+
load s11, (11 * reg_s)(sp)
194+
195+
// Freeing stack
196+
// (Floats storage was "freed" before floats was actually stored)
197+
addi sp, sp, ints_storage_size
198+
199+
// Return
200+
jr ra
201+
202+
#endif

0 commit comments

Comments
 (0)