Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/buildcommit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
name: [AMD64, i386, arm, aarch64]
name: [AMD64, i386, arm, aarch64, riscv64]
include:
- name: i386
platformflags: -m32
Expand All @@ -26,6 +26,9 @@ jobs:
- name: aarch64
platformtools: aarch64-linux-gnu
emulator: qemu-aarch64
- name: riscv64
platformtools: riscv64-linux-gnu
emulator: qemu-riscv64
# name: build-linux-gnu (${{matrix.name}})
env:
PLATFORMFLAGS: ${{matrix.platformflags}}
Expand Down Expand Up @@ -156,6 +159,7 @@ jobs:
cp -r artifacts/sysv_i386 release/lib/
cp -r artifacts/arm32 release/lib/
cp -r artifacts/aarch64 release/lib/
cp -r artifacts/riscv64 release/lib/
cp -r artifacts/darwin_x86_64 release/lib/
cp -r artifacts/darwin_arm64 release/lib/
cp -r artifacts/win_x86 release/lib/
Expand Down Expand Up @@ -197,6 +201,7 @@ jobs:
sysv_i386/ - Linux x86 (32-bit)
arm32/ - Linux ARM (32-bit, AAPCS)
aarch64/ - Linux ARM64 (AAPCS64)
riscv64/ - Linux RISC-V 64-bit (LP64D)
darwin_x86_64/ - macOS x86_64 (Intel)
darwin_arm64/ - macOS ARM64 (Apple Silicon)
win_x86/ - Windows x86 (32-bit)
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.0] - 2025-11-16

### Added
- RISC-V platform support
- `riscv64` - RISC-V 64-bit (LP64D ABI with hardware double-precision floating point)
- Platform detection for RISC-V in `platforms/platform.h` using `__riscv` and `__riscv_xlen` macros
- Assembly implementation in `platforms/switch_riscv64_gcc.S`
- Full register preservation (s0-s11, fs0-fs11, ra)
- 16-byte stack alignment per RISC-V ABI
- Both `stackman_switch` and `stackman_call` functions
- RISC-V build job in CI workflow with qemu-riscv64 emulation
- RISC-V library included in release archives

### Changed
- Release archives now contain 10 platform libraries (was 9)

## [1.1.0] - 2025-11-16

### Added
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# stackman

**Version 1.1.0**
**Version 1.2.0**

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

Expand Down Expand Up @@ -80,6 +80,7 @@ calling convention, plus archive format:
- sysv_amd64 (64-bit x86_64)
- arm32 (32-bit ARM, AAPCS)
- aarch64 (64-bit ARM, AAPCS64)
- riscv64 (64-bit RISC-V, LP64D ABI)
- **macOS (Darwin)**
- darwin_x86_64 (Intel)
- darwin_arm64 (Apple Silicon)
Expand Down
8 changes: 8 additions & 0 deletions stackman/platforms/platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@
#else
#define _STACKMAN_ABI aarch64
#endif
#elif defined(__riscv) && (__riscv_xlen == 64)
#include "switch_riscv64_gcc.h" /* gcc using riscv64 */
#define _STACKMAN_PLATFORM riscv64_clang
#define _STACKMAN_ABI riscv64
#endif
#endif /* __clang__ */

Expand Down Expand Up @@ -103,6 +107,10 @@
#else
#define _STACKMAN_ABI aarch64
#endif
#elif defined(__riscv) && (__riscv_xlen == 64)
#include "switch_riscv64_gcc.h" /* gcc using riscv64 */
#define _STACKMAN_PLATFORM riscv64_gcc
#define _STACKMAN_ABI riscv64
#endif
#endif /* __GNUC__ */

Expand Down
227 changes: 227 additions & 0 deletions stackman/platforms/switch_riscv64_gcc.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
* RISC-V RV64 (LP64D ABI) stack switching implementation
*
* This file provides stack manipulation for RISC-V 64-bit (RV64) using the
* LP64D ABI (64-bit pointers, hardware double-precision floating point).
*
* RISC-V RV64 LP64D Calling Convention:
* - Arguments: a0-a7 (x10-x17)
* - Return: a0-a1 (x10-x11)
* - Callee-saved: s0-s11 (x8-x9, x18-x27), sp (x2)
* - Caller-saved: a0-a7, t0-t6, ra (x1)
* - Stack pointer: sp (x2) - must be 16-byte aligned
* - Frame pointer: s0/fp (x8) - optional
*/

/* ELF metadata and CFI directives */
#define FUNCTION(name) .globl name ; .type name, @function
#define LABEL(name) name:
#define TYPE_FUNCTION(name) .type name, @function
#define SIZE_FUNCTION(name) .size name, .-name
#define GNU_STACK .section .note.GNU-stack,"",@progbits
#define CFI_STARTPROC .cfi_startproc
#define CFI_ENDPROC .cfi_endproc
#define CFI_DEF_CFA_OFFSET(x) .cfi_def_cfa_offset x
#define CFI_OFFSET(r,o) .cfi_offset r, o
#define CFI_RESTORE(r) .cfi_restore r

.file "switch_riscv64_gcc.S"
.text
.align 1

/*
* void *stackman_switch(stackman_cb_t callback, void *context)
*
* Saves current stack state, calls callback twice with stack pointer,
* and switches to new stack returned by first callback invocation.
*
* Register usage:
* - a0: callback function pointer (first arg)
* - a1: context pointer (second arg)
* - Callee-saved regs: s0-s11 must be preserved
* - FP regs: fs0-fs11 (callee-saved floating point)
*/
FUNCTION(stackman_switch)
LABEL(stackman_switch)
.LFB0:
CFI_STARTPROC

/* Allocate stack frame (160 bytes)
* Layout: ra(8) + s0-s11(12*8=96) + fs0-fs11(12*8=96) = 200 bytes
* Round to 208 for 16-byte alignment
*/
addi sp, sp, -208
CFI_DEF_CFA_OFFSET(208)

/* Save return address and callee-saved registers */
sd ra, 200(sp)
CFI_OFFSET(1, -8) /* ra is x1 */
sd s0, 192(sp)
CFI_OFFSET(8, -16)
sd s1, 184(sp)
CFI_OFFSET(9, -24)
sd s2, 176(sp)
CFI_OFFSET(18, -32)
sd s3, 168(sp)
CFI_OFFSET(19, -40)
sd s4, 160(sp)
CFI_OFFSET(20, -48)
sd s5, 152(sp)
CFI_OFFSET(21, -56)
sd s6, 144(sp)
CFI_OFFSET(22, -64)
sd s7, 136(sp)
CFI_OFFSET(23, -72)
sd s8, 128(sp)
CFI_OFFSET(24, -80)
sd s9, 120(sp)
CFI_OFFSET(25, -88)
sd s10, 112(sp)
CFI_OFFSET(26, -96)
sd s11, 104(sp)
CFI_OFFSET(27, -104)

/* Save callee-saved floating point registers */
fsd fs0, 96(sp)
fsd fs1, 88(sp)
fsd fs2, 80(sp)
fsd fs3, 72(sp)
fsd fs4, 64(sp)
fsd fs5, 56(sp)
fsd fs6, 48(sp)
fsd fs7, 40(sp)
fsd fs8, 32(sp)
fsd fs9, 24(sp)
fsd fs10, 16(sp)
fsd fs11, 8(sp)

/* Preserve callback and context in callee-saved registers */
mv s2, a0 /* s2 = callback */
mv s3, a1 /* s3 = context */

/* First callback: callback(context, opcode, sp) */
mv a0, s3 /* a0 = context */
li a1, 0 /* a1 = STACKMAN_OP_SAVE */
mv a2, sp /* a2 = current sp */
jalr s2 /* call callback */

/* Save returned pointer and switch stack */
mv s4, a0 /* s4 = returned value */
mv sp, a0 /* switch to new stack */

/* Second callback: callback(context, opcode, sp) */
mv a0, s3 /* a0 = context */
li a1, 1 /* a1 = STACKMAN_OP_RESTORE */
mv a2, sp /* a2 = new sp */
jalr s2 /* call callback */

/* Restore callee-saved floating point registers */
fld fs11, 8(sp)
fld fs10, 16(sp)
fld fs9, 24(sp)
fld fs8, 32(sp)
fld fs7, 40(sp)
fld fs6, 48(sp)
fld fs5, 56(sp)
fld fs4, 64(sp)
fld fs3, 72(sp)
fld fs2, 80(sp)
fld fs1, 88(sp)
fld fs0, 96(sp)

/* Restore callee-saved integer registers */
ld s11, 104(sp)
CFI_RESTORE(27)
ld s10, 112(sp)
CFI_RESTORE(26)
ld s9, 120(sp)
CFI_RESTORE(25)
ld s8, 128(sp)
CFI_RESTORE(24)
ld s7, 136(sp)
CFI_RESTORE(23)
ld s6, 144(sp)
CFI_RESTORE(22)
ld s5, 152(sp)
CFI_RESTORE(21)
ld s4, 160(sp)
CFI_RESTORE(20)
ld s3, 168(sp)
CFI_RESTORE(19)
ld s2, 176(sp)
CFI_RESTORE(18)
ld s1, 184(sp)
CFI_RESTORE(9)
ld s0, 192(sp)
CFI_RESTORE(8)
ld ra, 200(sp)
CFI_RESTORE(1)

/* Deallocate frame and return */
addi sp, sp, 208
CFI_DEF_CFA_OFFSET(0)
ret

CFI_ENDPROC
.LFE0:
SIZE_FUNCTION(stackman_switch)

/*
* void *stackman_call(stackman_cb_t callback, void *context, void *new_stack)
*
* Similar to stackman_switch but uses explicitly provided stack pointer.
*
* Register usage:
* - a0: callback function pointer
* - a1: context pointer
* - a2: new stack pointer
*/
.align 1
FUNCTION(stackman_call)
LABEL(stackman_call)
.LFB1:
CFI_STARTPROC

/* Allocate frame for ra and s0 (callee-saved) */
addi sp, sp, -16
CFI_DEF_CFA_OFFSET(16)

/* Save return address and s0 (we'll use it to preserve old sp) */
sd ra, 8(sp)
CFI_OFFSET(1, -8)
sd s0, 0(sp)
CFI_OFFSET(8, -16)

/* Save old stack pointer in s0 (callee-saved register) */
mv s0, sp

/* Setup args for callback and switch to new stack */
mv t0, a0 /* t0 = callback */
mv t1, a2 /* t1 = new_stack (for arg) */
mv sp, a2 /* switch to new stack */

/* Call callback(context, opcode, sp) */
mv a0, a1 /* a0 = context */
li a1, 2 /* a1 = STACKMAN_OP_CALL */
mv a2, t1 /* a2 = new_stack */
jalr t0 /* call callback */

/* Restore old stack pointer from s0 */
mv sp, s0

/* Restore s0 and return address */
ld s0, 0(sp)
CFI_RESTORE(8)
ld ra, 8(sp)
CFI_RESTORE(1)

addi sp, sp, 16
CFI_DEF_CFA_OFFSET(0)
ret

CFI_ENDPROC
.LFE1:
SIZE_FUNCTION(stackman_call)

/* Mark stack as non-executable */
GNU_STACK
29 changes: 29 additions & 0 deletions stackman/platforms/switch_riscv64_gcc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* RISC-V RV64 (LP64D ABI) stack switching - GCC implementation
*
* This implementation uses separate assembly files (.S) for RISC-V 64-bit.
* The LP64D ABI is used: 64-bit pointers with hardware floating point.
*/

/* Always use assembly source file for RISC-V */
#if !defined(STACKMAN_ASSEMBLY_SRC)
#define STACKMAN_ASSEMBLY_SRC "platforms/switch_riscv64_gcc.S"
#endif

#ifndef STACKMAN_SWITCH_IMPL
/* Just check if platform is supported */
#define STACKMAN_SWITCH_INCLUDED
#else

#ifndef STACKMAN_HAVE_CALL
#define STACKMAN_HAVE_CALL 1
#undef STACKMAN_STACK_ALIGN
#define STACKMAN_STACK_ALIGN 16
#endif

#if __ASSEMBLER__ && defined(STACKMAN_ASSEMBLY_SRC)
/* Include pre-generated assembly code */
#include STACKMAN_ASSEMBLY_SRC
#endif

#endif /* STACKMAN_SWITCH_IMPL */
4 changes: 2 additions & 2 deletions stackman/stackman.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

/* Version information */
#define STACKMAN_VERSION_MAJOR 1
#define STACKMAN_VERSION_MINOR 1
#define STACKMAN_VERSION_MINOR 2
#define STACKMAN_VERSION_PATCH 0

/* Version as a string */
#define STACKMAN_VERSION "1.1.0"
#define STACKMAN_VERSION "1.2.0"

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