Skip to content

Commit e05c3cb

Browse files
Add RISC-V RV64 platform support (v1.2.0) (#13)
* Add RISC-V RV64 platform support (skeleton) - Added platform detection for RISC-V 64-bit (__riscv && __riscv_xlen == 64) - Created skeleton assembly implementation (switch_riscv64_gcc.S) - Added RISC-V header file (switch_riscv64_gcc.h) - Updated CI workflow to build and test riscv64 with QEMU - Added riscv64 to release archives - Uses LP64D ABI (64-bit pointers, hardware floating point) TODO: Implement actual stack switching logic in assembly file * Add dedicated RISC-V test workflow - Triggers on push to riscv-support branch - Installs RISC-V toolchain and QEMU - Builds and tests riscv64 platform only - Fast feedback for RISC-V development * Fix PLATFORM_PREFIX in RISC-V workflow - remove trailing dash The Makefile adds the dash automatically, so PLATFORM_PREFIX should be 'riscv64-linux-gnu' not 'riscv64-linux-gnu-' * Implement RISC-V stack switching functions - Implement stackman_switch with full register save/restore - Implement stackman_call with minimal frame - Save/restore callee-saved registers: s0-s11, fs0-fs11, ra - Follow RISC-V LP64D calling convention - Based on ARM64 implementation pattern * Fix stackman_call to restore original stack pointer Critical bug fix: Save old stack pointer in s2 before switching stacks, then restore it after callback returns. Previously tried to restore registers from the new stack where they were never saved. * Simplify stackman_call - only preserve old stack pointer No need to preserve callback and context in callee-saved registers since they're used immediately. Only need to save old sp and ra. Reduced frame from 32 to 16 bytes. * Fix stackman_call to use callee-saved register for old sp CRITICAL FIX: Must save old stack pointer in s0 (callee-saved register), not on the stack. After switching to new stack, can't access old stack memory. The callback won't clobber s0, so it's safe across the call. * Fix RISC-V header to properly include assembly source Added __ASSEMBLER__ check and proper assembly source inclusion, following the pattern used in ARM64. This ensures the assembly file is actually compiled into the object file. * Remove incorrect STACKMAN_SP_ALIGN_BYTES macro Should only use STACKMAN_STACK_ALIGN like all other platforms. * Fix duplicate .LFB1 label in stackman_call Should be .LFE1 (function end) not .LFB1 (function begin). * Fix RISC-V header for test_static compilation - Move STACKMAN_ASSEMBLY_SRC definition outside STACKMAN_SWITCH_IMPL so it's always defined (prevents inline asm fallback) - Add #undef before redefining STACKMAN_STACK_ALIGN to avoid warning * Fix callback argument order in RISC-V implementation Callback signature is: callback(context, opcode, sp) Was incorrectly calling: callback(context, sp, opcode) Fixed for both stackman_switch (two calls) and stackman_call. * Update documentation for RISC-V support - Bump version to 1.2.0 - Add riscv64 to supported platforms list in README - Add v1.2.0 changelog entry with RISC-V details * Remove temporary RISC-V test workflow RISC-V is now part of the main buildcommit.yml workflow. * Bump version to 1.2.0 in header file
1 parent fc4cf42 commit e05c3cb

File tree

7 files changed

+290
-4
lines changed

7 files changed

+290
-4
lines changed

.github/workflows/buildcommit.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
fail-fast: false
1818
matrix:
19-
name: [AMD64, i386, arm, aarch64]
19+
name: [AMD64, i386, arm, aarch64, riscv64]
2020
include:
2121
- name: i386
2222
platformflags: -m32
@@ -26,6 +26,9 @@ jobs:
2626
- name: aarch64
2727
platformtools: aarch64-linux-gnu
2828
emulator: qemu-aarch64
29+
- name: riscv64
30+
platformtools: riscv64-linux-gnu
31+
emulator: qemu-riscv64
2932
# name: build-linux-gnu (${{matrix.name}})
3033
env:
3134
PLATFORMFLAGS: ${{matrix.platformflags}}
@@ -156,6 +159,7 @@ jobs:
156159
cp -r artifacts/sysv_i386 release/lib/
157160
cp -r artifacts/arm32 release/lib/
158161
cp -r artifacts/aarch64 release/lib/
162+
cp -r artifacts/riscv64 release/lib/
159163
cp -r artifacts/darwin_x86_64 release/lib/
160164
cp -r artifacts/darwin_arm64 release/lib/
161165
cp -r artifacts/win_x86 release/lib/
@@ -197,6 +201,7 @@ jobs:
197201
sysv_i386/ - Linux x86 (32-bit)
198202
arm32/ - Linux ARM (32-bit, AAPCS)
199203
aarch64/ - Linux ARM64 (AAPCS64)
204+
riscv64/ - Linux RISC-V 64-bit (LP64D)
200205
darwin_x86_64/ - macOS x86_64 (Intel)
201206
darwin_arm64/ - macOS ARM64 (Apple Silicon)
202207
win_x86/ - Windows x86 (32-bit)

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.2.0] - 2025-11-16
9+
10+
### Added
11+
- RISC-V platform support
12+
- `riscv64` - RISC-V 64-bit (LP64D ABI with hardware double-precision floating point)
13+
- Platform detection for RISC-V in `platforms/platform.h` using `__riscv` and `__riscv_xlen` macros
14+
- Assembly implementation in `platforms/switch_riscv64_gcc.S`
15+
- Full register preservation (s0-s11, fs0-fs11, ra)
16+
- 16-byte stack alignment per RISC-V ABI
17+
- Both `stackman_switch` and `stackman_call` functions
18+
- RISC-V build job in CI workflow with qemu-riscv64 emulation
19+
- RISC-V library included in release archives
20+
21+
### Changed
22+
- Release archives now contain 10 platform libraries (was 9)
23+
824
## [1.1.0] - 2025-11-16
925

1026
### Added

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# stackman
44

5-
**Version 1.1.0**
5+
**Version 1.2.0**
66

77
Simple low-level stack manipulation API and implementation for common platforms
88

@@ -80,6 +80,7 @@ calling convention, plus archive format:
8080
- sysv_amd64 (64-bit x86_64)
8181
- arm32 (32-bit ARM, AAPCS)
8282
- aarch64 (64-bit ARM, AAPCS64)
83+
- riscv64 (64-bit RISC-V, LP64D ABI)
8384
- **macOS (Darwin)**
8485
- darwin_x86_64 (Intel)
8586
- darwin_arm64 (Apple Silicon)

stackman/platforms/platform.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@
7474
#else
7575
#define _STACKMAN_ABI aarch64
7676
#endif
77+
#elif defined(__riscv) && (__riscv_xlen == 64)
78+
#include "switch_riscv64_gcc.h" /* gcc using riscv64 */
79+
#define _STACKMAN_PLATFORM riscv64_clang
80+
#define _STACKMAN_ABI riscv64
7781
#endif
7882
#endif /* __clang__ */
7983

@@ -103,6 +107,10 @@
103107
#else
104108
#define _STACKMAN_ABI aarch64
105109
#endif
110+
#elif defined(__riscv) && (__riscv_xlen == 64)
111+
#include "switch_riscv64_gcc.h" /* gcc using riscv64 */
112+
#define _STACKMAN_PLATFORM riscv64_gcc
113+
#define _STACKMAN_ABI riscv64
106114
#endif
107115
#endif /* __GNUC__ */
108116

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
* RISC-V RV64 (LP64D ABI) stack switching implementation
3+
*
4+
* This file provides stack manipulation for RISC-V 64-bit (RV64) using the
5+
* LP64D ABI (64-bit pointers, hardware double-precision floating point).
6+
*
7+
* RISC-V RV64 LP64D Calling Convention:
8+
* - Arguments: a0-a7 (x10-x17)
9+
* - Return: a0-a1 (x10-x11)
10+
* - Callee-saved: s0-s11 (x8-x9, x18-x27), sp (x2)
11+
* - Caller-saved: a0-a7, t0-t6, ra (x1)
12+
* - Stack pointer: sp (x2) - must be 16-byte aligned
13+
* - Frame pointer: s0/fp (x8) - optional
14+
*/
15+
16+
/* ELF metadata and CFI directives */
17+
#define FUNCTION(name) .globl name ; .type name, @function
18+
#define LABEL(name) name:
19+
#define TYPE_FUNCTION(name) .type name, @function
20+
#define SIZE_FUNCTION(name) .size name, .-name
21+
#define GNU_STACK .section .note.GNU-stack,"",@progbits
22+
#define CFI_STARTPROC .cfi_startproc
23+
#define CFI_ENDPROC .cfi_endproc
24+
#define CFI_DEF_CFA_OFFSET(x) .cfi_def_cfa_offset x
25+
#define CFI_OFFSET(r,o) .cfi_offset r, o
26+
#define CFI_RESTORE(r) .cfi_restore r
27+
28+
.file "switch_riscv64_gcc.S"
29+
.text
30+
.align 1
31+
32+
/*
33+
* void *stackman_switch(stackman_cb_t callback, void *context)
34+
*
35+
* Saves current stack state, calls callback twice with stack pointer,
36+
* and switches to new stack returned by first callback invocation.
37+
*
38+
* Register usage:
39+
* - a0: callback function pointer (first arg)
40+
* - a1: context pointer (second arg)
41+
* - Callee-saved regs: s0-s11 must be preserved
42+
* - FP regs: fs0-fs11 (callee-saved floating point)
43+
*/
44+
FUNCTION(stackman_switch)
45+
LABEL(stackman_switch)
46+
.LFB0:
47+
CFI_STARTPROC
48+
49+
/* Allocate stack frame (160 bytes)
50+
* Layout: ra(8) + s0-s11(12*8=96) + fs0-fs11(12*8=96) = 200 bytes
51+
* Round to 208 for 16-byte alignment
52+
*/
53+
addi sp, sp, -208
54+
CFI_DEF_CFA_OFFSET(208)
55+
56+
/* Save return address and callee-saved registers */
57+
sd ra, 200(sp)
58+
CFI_OFFSET(1, -8) /* ra is x1 */
59+
sd s0, 192(sp)
60+
CFI_OFFSET(8, -16)
61+
sd s1, 184(sp)
62+
CFI_OFFSET(9, -24)
63+
sd s2, 176(sp)
64+
CFI_OFFSET(18, -32)
65+
sd s3, 168(sp)
66+
CFI_OFFSET(19, -40)
67+
sd s4, 160(sp)
68+
CFI_OFFSET(20, -48)
69+
sd s5, 152(sp)
70+
CFI_OFFSET(21, -56)
71+
sd s6, 144(sp)
72+
CFI_OFFSET(22, -64)
73+
sd s7, 136(sp)
74+
CFI_OFFSET(23, -72)
75+
sd s8, 128(sp)
76+
CFI_OFFSET(24, -80)
77+
sd s9, 120(sp)
78+
CFI_OFFSET(25, -88)
79+
sd s10, 112(sp)
80+
CFI_OFFSET(26, -96)
81+
sd s11, 104(sp)
82+
CFI_OFFSET(27, -104)
83+
84+
/* Save callee-saved floating point registers */
85+
fsd fs0, 96(sp)
86+
fsd fs1, 88(sp)
87+
fsd fs2, 80(sp)
88+
fsd fs3, 72(sp)
89+
fsd fs4, 64(sp)
90+
fsd fs5, 56(sp)
91+
fsd fs6, 48(sp)
92+
fsd fs7, 40(sp)
93+
fsd fs8, 32(sp)
94+
fsd fs9, 24(sp)
95+
fsd fs10, 16(sp)
96+
fsd fs11, 8(sp)
97+
98+
/* Preserve callback and context in callee-saved registers */
99+
mv s2, a0 /* s2 = callback */
100+
mv s3, a1 /* s3 = context */
101+
102+
/* First callback: callback(context, opcode, sp) */
103+
mv a0, s3 /* a0 = context */
104+
li a1, 0 /* a1 = STACKMAN_OP_SAVE */
105+
mv a2, sp /* a2 = current sp */
106+
jalr s2 /* call callback */
107+
108+
/* Save returned pointer and switch stack */
109+
mv s4, a0 /* s4 = returned value */
110+
mv sp, a0 /* switch to new stack */
111+
112+
/* Second callback: callback(context, opcode, sp) */
113+
mv a0, s3 /* a0 = context */
114+
li a1, 1 /* a1 = STACKMAN_OP_RESTORE */
115+
mv a2, sp /* a2 = new sp */
116+
jalr s2 /* call callback */
117+
118+
/* Restore callee-saved floating point registers */
119+
fld fs11, 8(sp)
120+
fld fs10, 16(sp)
121+
fld fs9, 24(sp)
122+
fld fs8, 32(sp)
123+
fld fs7, 40(sp)
124+
fld fs6, 48(sp)
125+
fld fs5, 56(sp)
126+
fld fs4, 64(sp)
127+
fld fs3, 72(sp)
128+
fld fs2, 80(sp)
129+
fld fs1, 88(sp)
130+
fld fs0, 96(sp)
131+
132+
/* Restore callee-saved integer registers */
133+
ld s11, 104(sp)
134+
CFI_RESTORE(27)
135+
ld s10, 112(sp)
136+
CFI_RESTORE(26)
137+
ld s9, 120(sp)
138+
CFI_RESTORE(25)
139+
ld s8, 128(sp)
140+
CFI_RESTORE(24)
141+
ld s7, 136(sp)
142+
CFI_RESTORE(23)
143+
ld s6, 144(sp)
144+
CFI_RESTORE(22)
145+
ld s5, 152(sp)
146+
CFI_RESTORE(21)
147+
ld s4, 160(sp)
148+
CFI_RESTORE(20)
149+
ld s3, 168(sp)
150+
CFI_RESTORE(19)
151+
ld s2, 176(sp)
152+
CFI_RESTORE(18)
153+
ld s1, 184(sp)
154+
CFI_RESTORE(9)
155+
ld s0, 192(sp)
156+
CFI_RESTORE(8)
157+
ld ra, 200(sp)
158+
CFI_RESTORE(1)
159+
160+
/* Deallocate frame and return */
161+
addi sp, sp, 208
162+
CFI_DEF_CFA_OFFSET(0)
163+
ret
164+
165+
CFI_ENDPROC
166+
.LFE0:
167+
SIZE_FUNCTION(stackman_switch)
168+
169+
/*
170+
* void *stackman_call(stackman_cb_t callback, void *context, void *new_stack)
171+
*
172+
* Similar to stackman_switch but uses explicitly provided stack pointer.
173+
*
174+
* Register usage:
175+
* - a0: callback function pointer
176+
* - a1: context pointer
177+
* - a2: new stack pointer
178+
*/
179+
.align 1
180+
FUNCTION(stackman_call)
181+
LABEL(stackman_call)
182+
.LFB1:
183+
CFI_STARTPROC
184+
185+
/* Allocate frame for ra and s0 (callee-saved) */
186+
addi sp, sp, -16
187+
CFI_DEF_CFA_OFFSET(16)
188+
189+
/* Save return address and s0 (we'll use it to preserve old sp) */
190+
sd ra, 8(sp)
191+
CFI_OFFSET(1, -8)
192+
sd s0, 0(sp)
193+
CFI_OFFSET(8, -16)
194+
195+
/* Save old stack pointer in s0 (callee-saved register) */
196+
mv s0, sp
197+
198+
/* Setup args for callback and switch to new stack */
199+
mv t0, a0 /* t0 = callback */
200+
mv t1, a2 /* t1 = new_stack (for arg) */
201+
mv sp, a2 /* switch to new stack */
202+
203+
/* Call callback(context, opcode, sp) */
204+
mv a0, a1 /* a0 = context */
205+
li a1, 2 /* a1 = STACKMAN_OP_CALL */
206+
mv a2, t1 /* a2 = new_stack */
207+
jalr t0 /* call callback */
208+
209+
/* Restore old stack pointer from s0 */
210+
mv sp, s0
211+
212+
/* Restore s0 and return address */
213+
ld s0, 0(sp)
214+
CFI_RESTORE(8)
215+
ld ra, 8(sp)
216+
CFI_RESTORE(1)
217+
218+
addi sp, sp, 16
219+
CFI_DEF_CFA_OFFSET(0)
220+
ret
221+
222+
CFI_ENDPROC
223+
.LFE1:
224+
SIZE_FUNCTION(stackman_call)
225+
226+
/* Mark stack as non-executable */
227+
GNU_STACK
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* RISC-V RV64 (LP64D ABI) stack switching - GCC implementation
3+
*
4+
* This implementation uses separate assembly files (.S) for RISC-V 64-bit.
5+
* The LP64D ABI is used: 64-bit pointers with hardware floating point.
6+
*/
7+
8+
/* Always use assembly source file for RISC-V */
9+
#if !defined(STACKMAN_ASSEMBLY_SRC)
10+
#define STACKMAN_ASSEMBLY_SRC "platforms/switch_riscv64_gcc.S"
11+
#endif
12+
13+
#ifndef STACKMAN_SWITCH_IMPL
14+
/* Just check if platform is supported */
15+
#define STACKMAN_SWITCH_INCLUDED
16+
#else
17+
18+
#ifndef STACKMAN_HAVE_CALL
19+
#define STACKMAN_HAVE_CALL 1
20+
#undef STACKMAN_STACK_ALIGN
21+
#define STACKMAN_STACK_ALIGN 16
22+
#endif
23+
24+
#if __ASSEMBLER__ && defined(STACKMAN_ASSEMBLY_SRC)
25+
/* Include pre-generated assembly code */
26+
#include STACKMAN_ASSEMBLY_SRC
27+
#endif
28+
29+
#endif /* STACKMAN_SWITCH_IMPL */

stackman/stackman.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
/* Version information */
66
#define STACKMAN_VERSION_MAJOR 1
7-
#define STACKMAN_VERSION_MINOR 1
7+
#define STACKMAN_VERSION_MINOR 2
88
#define STACKMAN_VERSION_PATCH 0
99

1010
/* Version as a string */
11-
#define STACKMAN_VERSION "1.1.0"
11+
#define STACKMAN_VERSION "1.2.0"
1212

1313
/* Version as a single number for comparisons (MMmmpp: Major, minor, patch) */
1414
#define STACKMAN_VERSION_NUMBER ((STACKMAN_VERSION_MAJOR * 10000) + \

0 commit comments

Comments
 (0)