From 60a9de1246137334cfdf1385d6a131f05597ee20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 17:26:19 +0000 Subject: [PATCH 01/15] 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 --- .github/workflows/buildcommit.yml | 7 +- stackman/platforms/platform.h | 8 ++ stackman/platforms/switch_riscv64_gcc.S | 126 ++++++++++++++++++++++++ stackman/platforms/switch_riscv64_gcc.h | 19 ++++ 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 stackman/platforms/switch_riscv64_gcc.S create mode 100644 stackman/platforms/switch_riscv64_gcc.h diff --git a/.github/workflows/buildcommit.yml b/.github/workflows/buildcommit.yml index 6dc8fd8..31f63ae 100644 --- a/.github/workflows/buildcommit.yml +++ b/.github/workflows/buildcommit.yml @@ -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 @@ -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}} @@ -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/ @@ -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) diff --git a/stackman/platforms/platform.h b/stackman/platforms/platform.h index 007b852..daf3102 100644 --- a/stackman/platforms/platform.h +++ b/stackman/platforms/platform.h @@ -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__ */ @@ -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__ */ diff --git a/stackman/platforms/switch_riscv64_gcc.S b/stackman/platforms/switch_riscv64_gcc.S new file mode 100644 index 0000000..a4d40b2 --- /dev/null +++ b/stackman/platforms/switch_riscv64_gcc.S @@ -0,0 +1,126 @@ +/* + * 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 + */ + FUNCTION(stackman_switch) +LABEL(stackman_switch) +.LFB0: + CFI_STARTPROC + + /* TODO: Calculate frame size based on saved registers + * Need to save: + * - ra (return address) + * - s0-s11 (callee-saved registers) + * - fp/s0 for frame pointer + * + * Total: ~14 registers * 8 bytes = 112 bytes (round to 128 for alignment) + */ + + /* TODO: Allocate stack frame and save registers */ + /* addi sp, sp, -128 */ + /* sd ra, 120(sp) */ + /* sd s0, 112(sp) */ + /* sd s1-s11, ... */ + + /* TODO: Set up frame pointer */ + /* addi s0, sp, 128 */ + + /* TODO: Call callback with current sp */ + /* First call: callback(context, sp) */ + /* mv a1, sp # second arg = current sp */ + /* mv a2, a0 # save callback for later */ + /* mv a0, a1_original # first arg = context */ + /* jalr a2 # call callback */ + + /* TODO: Switch to new stack returned in a0 */ + /* mv sp, a0 */ + + /* TODO: Second callback invocation with new sp */ + /* Same pattern as first call */ + + /* TODO: Restore registers from new stack */ + /* ld s11, offset(sp) */ + /* ... */ + /* ld ra, offset(sp) */ + + /* TODO: Deallocate frame and return */ + /* addi sp, sp, 128 */ + /* ret */ + + /* PLACEHOLDER - just return for now */ + 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 + + /* TODO: Similar to stackman_switch but simpler + * - Save minimal state on current stack + * - Switch to new_stack (a2) + * - Call callback + * - Return + */ + + /* PLACEHOLDER */ + ret + + CFI_ENDPROC +.LFE1: + SIZE_FUNCTION(stackman_call) + +/* Mark stack as non-executable */ +GNU_STACK diff --git a/stackman/platforms/switch_riscv64_gcc.h b/stackman/platforms/switch_riscv64_gcc.h new file mode 100644 index 0000000..036c414 --- /dev/null +++ b/stackman/platforms/switch_riscv64_gcc.h @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#ifndef STACKMAN_SWITCH_IMPL +/* Just check if platform is supported */ +#define STACKMAN_SWITCH_INCLUDED +#else + +/* Stack pointer alignment for RISC-V - must be 16-byte aligned */ +#define STACKMAN_SP_ALIGN_BYTES 16 + +/* Use assembly source file */ +#define STACKMAN_ASSEMBLY_SRC "stackman/platforms/switch_riscv64_gcc.S" + +#endif /* STACKMAN_SWITCH_IMPL */ From f5577d322e78835d3ad672548c83c635e8add723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 17:51:04 +0000 Subject: [PATCH 02/15] 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 --- .github/workflows/riscv-test.yml | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/riscv-test.yml diff --git a/.github/workflows/riscv-test.yml b/.github/workflows/riscv-test.yml new file mode 100644 index 0000000..3981c66 --- /dev/null +++ b/.github/workflows/riscv-test.yml @@ -0,0 +1,52 @@ +name: RISC-V Test Build + +on: + push: + branches: [ riscv-support ] + pull_request: + branches: [ master ] + paths: + - 'stackman/platforms/switch_riscv64_gcc.*' + - 'stackman/platforms/platform.h' + - '.github/workflows/riscv-test.yml' + +jobs: + + build-riscv64: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install RISC-V toolchain and QEMU + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends -y \ + gcc-riscv64-linux-gnu \ + g++-riscv64-linux-gnu \ + qemu-user + + - name: Show toolchain info + run: | + riscv64-linux-gnu-gcc --version + qemu-riscv64 --version + + - name: Build library + env: + PLATFORM_PREFIX: riscv64-linux-gnu- + run: make all + + - name: Show ABI name + run: make abiname + + - name: Run tests + env: + PLATFORM_PREFIX: riscv64-linux-gnu- + EMULATOR: qemu-riscv64 + run: make test + + - name: Upload library artifact + uses: actions/upload-artifact@v4 + with: + name: riscv64-test-build + path: lib/riscv64/libstackman.a From b08b795ba02e940eec7ee80bbfc62a7352db9eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 17:53:27 +0000 Subject: [PATCH 03/15] 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-' --- .github/workflows/riscv-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/riscv-test.yml b/.github/workflows/riscv-test.yml index 3981c66..f8d7805 100644 --- a/.github/workflows/riscv-test.yml +++ b/.github/workflows/riscv-test.yml @@ -33,7 +33,7 @@ jobs: - name: Build library env: - PLATFORM_PREFIX: riscv64-linux-gnu- + PLATFORM_PREFIX: riscv64-linux-gnu run: make all - name: Show ABI name @@ -41,7 +41,7 @@ jobs: - name: Run tests env: - PLATFORM_PREFIX: riscv64-linux-gnu- + PLATFORM_PREFIX: riscv64-linux-gnu EMULATOR: qemu-riscv64 run: make test From 7e07e18ef9500ee69b783e704a9af04e9acb59c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 17:55:44 +0000 Subject: [PATCH 04/15] 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 --- stackman/platforms/switch_riscv64_gcc.S | 202 ++++++++++++++++++------ 1 file changed, 154 insertions(+), 48 deletions(-) diff --git a/stackman/platforms/switch_riscv64_gcc.S b/stackman/platforms/switch_riscv64_gcc.S index a4d40b2..7e7a770 100644 --- a/stackman/platforms/switch_riscv64_gcc.S +++ b/stackman/platforms/switch_riscv64_gcc.S @@ -39,53 +39,127 @@ * - 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 - /* TODO: Calculate frame size based on saved registers - * Need to save: - * - ra (return address) - * - s0-s11 (callee-saved registers) - * - fp/s0 for frame pointer - * - * Total: ~14 registers * 8 bytes = 112 bytes (round to 128 for alignment) + /* 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 */ - - /* TODO: Allocate stack frame and save registers */ - /* addi sp, sp, -128 */ - /* sd ra, 120(sp) */ - /* sd s0, 112(sp) */ - /* sd s1-s11, ... */ - - /* TODO: Set up frame pointer */ - /* addi s0, sp, 128 */ - - /* TODO: Call callback with current sp */ - /* First call: callback(context, sp) */ - /* mv a1, sp # second arg = current sp */ - /* mv a2, a0 # save callback for later */ - /* mv a0, a1_original # first arg = context */ - /* jalr a2 # call callback */ - - /* TODO: Switch to new stack returned in a0 */ - /* mv sp, a0 */ - - /* TODO: Second callback invocation with new sp */ - /* Same pattern as first call */ - - /* TODO: Restore registers from new stack */ - /* ld s11, offset(sp) */ - /* ... */ - /* ld ra, offset(sp) */ - - /* TODO: Deallocate frame and return */ - /* addi sp, sp, 128 */ - /* ret */ - - /* PLACEHOLDER - just return for now */ + 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, sp, 0) */ + mv a0, s3 /* a0 = context */ + mv a1, sp /* a1 = current sp */ + li a2, 0 /* a2 = 0 (first call) */ + 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, new_sp, 1) */ + mv a0, s3 /* a0 = context */ + mv a1, sp /* a1 = new sp */ + li a2, 1 /* a2 = 1 (second call) */ + 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 @@ -108,14 +182,46 @@ LABEL(stackman_call) .LFB1: CFI_STARTPROC - /* TODO: Similar to stackman_switch but simpler - * - Save minimal state on current stack - * - Switch to new_stack (a2) - * - Call callback - * - Return - */ - - /* PLACEHOLDER */ + /* Allocate small frame (32 bytes for ra + s0-s2) */ + addi sp, sp, -32 + CFI_DEF_CFA_OFFSET(32) + + /* Save return address and callee-saved registers we'll use */ + sd ra, 24(sp) + CFI_OFFSET(1, -8) + sd s0, 16(sp) + CFI_OFFSET(8, -16) + sd s1, 8(sp) + CFI_OFFSET(9, -24) + sd s2, 0(sp) + CFI_OFFSET(18, -32) + + /* Preserve callback and context */ + mv s0, a0 /* s0 = callback */ + mv s1, a1 /* s1 = context */ + mv s2, a2 /* s2 = new_stack */ + + /* Switch to new stack */ + mv sp, s2 + + /* Call callback(context, new_stack, 0) */ + mv a0, s1 /* a0 = context */ + mv a1, s2 /* a1 = new_stack */ + li a2, 0 /* a2 = 0 */ + jalr s0 /* call callback */ + + /* Restore registers and return */ + ld s2, 0(sp) + CFI_RESTORE(18) + ld s1, 8(sp) + CFI_RESTORE(9) + ld s0, 16(sp) + CFI_RESTORE(8) + ld ra, 24(sp) + CFI_RESTORE(1) + + addi sp, sp, 32 + CFI_DEF_CFA_OFFSET(0) ret CFI_ENDPROC From a5d8589c7916647b356db1d6d6519a0b634e40c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:05:29 +0000 Subject: [PATCH 05/15] 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. --- stackman/platforms/switch_riscv64_gcc.S | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/stackman/platforms/switch_riscv64_gcc.S b/stackman/platforms/switch_riscv64_gcc.S index 7e7a770..d7120c3 100644 --- a/stackman/platforms/switch_riscv64_gcc.S +++ b/stackman/platforms/switch_riscv64_gcc.S @@ -196,20 +196,23 @@ LABEL(stackman_call) sd s2, 0(sp) CFI_OFFSET(18, -32) - /* Preserve callback and context */ + /* Preserve callback, context, and OLD stack pointer */ mv s0, a0 /* s0 = callback */ mv s1, a1 /* s1 = context */ - mv s2, a2 /* s2 = new_stack */ + mv s2, sp /* s2 = old stack pointer (IMPORTANT!) */ /* Switch to new stack */ - mv sp, s2 + mv sp, a2 /* sp = new_stack */ /* Call callback(context, new_stack, 0) */ mv a0, s1 /* a0 = context */ - mv a1, s2 /* a1 = new_stack */ + mv a1, a2 /* a1 = new_stack */ li a2, 0 /* a2 = 0 */ jalr s0 /* call callback */ + /* Restore old stack pointer */ + mv sp, s2 + /* Restore registers and return */ ld s2, 0(sp) CFI_RESTORE(18) From 55388da605f449d5270eeb859c68fd7bea639db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:07:41 +0000 Subject: [PATCH 06/15] 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. --- stackman/platforms/switch_riscv64_gcc.S | 57 +++++++++++-------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/stackman/platforms/switch_riscv64_gcc.S b/stackman/platforms/switch_riscv64_gcc.S index d7120c3..c5968b2 100644 --- a/stackman/platforms/switch_riscv64_gcc.S +++ b/stackman/platforms/switch_riscv64_gcc.S @@ -182,53 +182,44 @@ LABEL(stackman_call) .LFB1: CFI_STARTPROC - /* Allocate small frame (32 bytes for ra + s0-s2) */ - addi sp, sp, -32 - CFI_DEF_CFA_OFFSET(32) + /* Allocate small frame (16 bytes for ra + old_sp) */ + addi sp, sp, -16 + CFI_DEF_CFA_OFFSET(16) - /* Save return address and callee-saved registers we'll use */ - sd ra, 24(sp) + /* Save return address and old stack pointer */ + sd ra, 8(sp) CFI_OFFSET(1, -8) - sd s0, 16(sp) - CFI_OFFSET(8, -16) - sd s1, 8(sp) - CFI_OFFSET(9, -24) - sd s2, 0(sp) - CFI_OFFSET(18, -32) + sd sp, 0(sp) /* Save old sp before we modify it */ + CFI_OFFSET(2, -16) - /* Preserve callback, context, and OLD stack pointer */ - mv s0, a0 /* s0 = callback */ - mv s1, a1 /* s1 = context */ - mv s2, sp /* s2 = old stack pointer (IMPORTANT!) */ - - /* Switch to new stack */ - mv sp, a2 /* sp = new_stack */ + /* Setup args and switch to new stack + * a0 already contains callback + * a1 already contains context + * a2 contains new_stack + */ + mv t0, a0 /* t0 = callback (temporary) */ + mv t1, a2 /* t1 = new_stack (for arg) */ + mv sp, a2 /* switch to new stack */ /* Call callback(context, new_stack, 0) */ - mv a0, s1 /* a0 = context */ - mv a1, a2 /* a1 = new_stack */ - li a2, 0 /* a2 = 0 */ - jalr s0 /* call callback */ + mv a0, a1 /* a0 = context (was in a1) */ + mv a1, t1 /* a1 = new_stack */ + li a2, 0 /* a2 = 0 */ + jalr t0 /* call callback */ /* Restore old stack pointer */ - mv sp, s2 + ld sp, 0(sp) /* Restore original sp */ - /* Restore registers and return */ - ld s2, 0(sp) - CFI_RESTORE(18) - ld s1, 8(sp) - CFI_RESTORE(9) - ld s0, 16(sp) - CFI_RESTORE(8) - ld ra, 24(sp) + /* Restore return address and return */ + ld ra, 8(sp) CFI_RESTORE(1) - addi sp, sp, 32 + addi sp, sp, 16 CFI_DEF_CFA_OFFSET(0) ret CFI_ENDPROC -.LFE1: +.LFB1: SIZE_FUNCTION(stackman_call) /* Mark stack as non-executable */ From 85671e878ce50efb5be1b6b4695b7eb9d7d5bc51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:10:02 +0000 Subject: [PATCH 07/15] 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. --- stackman/platforms/switch_riscv64_gcc.S | 29 +++++++++++++------------ 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/stackman/platforms/switch_riscv64_gcc.S b/stackman/platforms/switch_riscv64_gcc.S index c5968b2..3aab452 100644 --- a/stackman/platforms/switch_riscv64_gcc.S +++ b/stackman/platforms/switch_riscv64_gcc.S @@ -182,35 +182,36 @@ LABEL(stackman_call) .LFB1: CFI_STARTPROC - /* Allocate small frame (16 bytes for ra + old_sp) */ + /* Allocate frame for ra and s0 (callee-saved) */ addi sp, sp, -16 CFI_DEF_CFA_OFFSET(16) - /* Save return address and old stack pointer */ + /* Save return address and s0 (we'll use it to preserve old sp) */ sd ra, 8(sp) CFI_OFFSET(1, -8) - sd sp, 0(sp) /* Save old sp before we modify it */ - CFI_OFFSET(2, -16) + sd s0, 0(sp) + CFI_OFFSET(8, -16) - /* Setup args and switch to new stack - * a0 already contains callback - * a1 already contains context - * a2 contains new_stack - */ - mv t0, a0 /* t0 = callback (temporary) */ + /* 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, new_stack, 0) */ - mv a0, a1 /* a0 = context (was in a1) */ + mv a0, a1 /* a0 = context */ mv a1, t1 /* a1 = new_stack */ li a2, 0 /* a2 = 0 */ jalr t0 /* call callback */ - /* Restore old stack pointer */ - ld sp, 0(sp) /* Restore original sp */ + /* Restore old stack pointer from s0 */ + mv sp, s0 - /* Restore return address and return */ + /* Restore s0 and return address */ + ld s0, 0(sp) + CFI_RESTORE(8) ld ra, 8(sp) CFI_RESTORE(1) From 96c5b8a11025959221ec4138dc3714485b76be55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:18:27 +0000 Subject: [PATCH 08/15] 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. --- stackman/platforms/switch_riscv64_gcc.h | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/stackman/platforms/switch_riscv64_gcc.h b/stackman/platforms/switch_riscv64_gcc.h index 036c414..d5ccb67 100644 --- a/stackman/platforms/switch_riscv64_gcc.h +++ b/stackman/platforms/switch_riscv64_gcc.h @@ -13,7 +13,19 @@ /* Stack pointer alignment for RISC-V - must be 16-byte aligned */ #define STACKMAN_SP_ALIGN_BYTES 16 -/* Use assembly source file */ -#define STACKMAN_ASSEMBLY_SRC "stackman/platforms/switch_riscv64_gcc.S" +/* Always use assembly source file for RISC-V */ +#ifndef STACKMAN_ASSEMBLY_SRC +#define STACKMAN_ASSEMBLY_SRC "platforms/switch_riscv64_gcc.S" +#endif + +#ifndef STACKMAN_HAVE_CALL +#define STACKMAN_HAVE_CALL 1 +#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 */ From 128e40405d25dabeb3eaa78cd458059866049c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:23:37 +0000 Subject: [PATCH 09/15] Remove incorrect STACKMAN_SP_ALIGN_BYTES macro Should only use STACKMAN_STACK_ALIGN like all other platforms. --- stackman/platforms/switch_riscv64_gcc.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/stackman/platforms/switch_riscv64_gcc.h b/stackman/platforms/switch_riscv64_gcc.h index d5ccb67..f665d35 100644 --- a/stackman/platforms/switch_riscv64_gcc.h +++ b/stackman/platforms/switch_riscv64_gcc.h @@ -10,9 +10,6 @@ #define STACKMAN_SWITCH_INCLUDED #else -/* Stack pointer alignment for RISC-V - must be 16-byte aligned */ -#define STACKMAN_SP_ALIGN_BYTES 16 - /* Always use assembly source file for RISC-V */ #ifndef STACKMAN_ASSEMBLY_SRC #define STACKMAN_ASSEMBLY_SRC "platforms/switch_riscv64_gcc.S" From b65bd5f68324257319975280733e1224a045959b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:25:59 +0000 Subject: [PATCH 10/15] Fix duplicate .LFB1 label in stackman_call Should be .LFE1 (function end) not .LFB1 (function begin). --- stackman/platforms/switch_riscv64_gcc.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackman/platforms/switch_riscv64_gcc.S b/stackman/platforms/switch_riscv64_gcc.S index 3aab452..8f990c7 100644 --- a/stackman/platforms/switch_riscv64_gcc.S +++ b/stackman/platforms/switch_riscv64_gcc.S @@ -220,7 +220,7 @@ LABEL(stackman_call) ret CFI_ENDPROC -.LFB1: +.LFE1: SIZE_FUNCTION(stackman_call) /* Mark stack as non-executable */ From 2f23aae3999fc14edc11c0d672949b9aec014e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:30:38 +0000 Subject: [PATCH 11/15] 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 --- stackman/platforms/switch_riscv64_gcc.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/stackman/platforms/switch_riscv64_gcc.h b/stackman/platforms/switch_riscv64_gcc.h index f665d35..8e08597 100644 --- a/stackman/platforms/switch_riscv64_gcc.h +++ b/stackman/platforms/switch_riscv64_gcc.h @@ -5,18 +5,19 @@ * 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 -/* Always use assembly source file for RISC-V */ -#ifndef STACKMAN_ASSEMBLY_SRC -#define STACKMAN_ASSEMBLY_SRC "platforms/switch_riscv64_gcc.S" -#endif - #ifndef STACKMAN_HAVE_CALL #define STACKMAN_HAVE_CALL 1 +#undef STACKMAN_STACK_ALIGN #define STACKMAN_STACK_ALIGN 16 #endif From ac97814cf24e62c3436051a67b2cfb18b983d970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:34:57 +0000 Subject: [PATCH 12/15] 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. --- stackman/platforms/switch_riscv64_gcc.S | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/stackman/platforms/switch_riscv64_gcc.S b/stackman/platforms/switch_riscv64_gcc.S index 8f990c7..54d97c1 100644 --- a/stackman/platforms/switch_riscv64_gcc.S +++ b/stackman/platforms/switch_riscv64_gcc.S @@ -99,20 +99,20 @@ LABEL(stackman_switch) mv s2, a0 /* s2 = callback */ mv s3, a1 /* s3 = context */ - /* First callback: callback(context, sp, 0) */ + /* First callback: callback(context, opcode, sp) */ mv a0, s3 /* a0 = context */ - mv a1, sp /* a1 = current sp */ - li a2, 0 /* a2 = 0 (first call) */ + 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, new_sp, 1) */ + /* Second callback: callback(context, opcode, sp) */ mv a0, s3 /* a0 = context */ - mv a1, sp /* a1 = new sp */ - li a2, 1 /* a2 = 1 (second call) */ + li a1, 1 /* a1 = STACKMAN_OP_RESTORE */ + mv a2, sp /* a2 = new sp */ jalr s2 /* call callback */ /* Restore callee-saved floating point registers */ @@ -200,10 +200,10 @@ LABEL(stackman_call) mv t1, a2 /* t1 = new_stack (for arg) */ mv sp, a2 /* switch to new stack */ - /* Call callback(context, new_stack, 0) */ + /* Call callback(context, opcode, sp) */ mv a0, a1 /* a0 = context */ - mv a1, t1 /* a1 = new_stack */ - li a2, 0 /* a2 = 0 */ + li a1, 2 /* a1 = STACKMAN_OP_CALL */ + mv a2, t1 /* a2 = new_stack */ jalr t0 /* call callback */ /* Restore old stack pointer from s0 */ From 3ff65d555c407d2a678ed1593b6a634205f289d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:38:04 +0000 Subject: [PATCH 13/15] 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 --- CHANGELOG.md | 16 ++++++++++++++++ README.md | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb1f4a..495227c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index eab378a..abee91c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # stackman -**Version 1.1.0** +**Version 1.2.0** Simple low-level stack manipulation API and implementation for common platforms @@ -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) From df2b54f232b8e305b891103a1e8d4c185f0ce605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:38:54 +0000 Subject: [PATCH 14/15] Remove temporary RISC-V test workflow RISC-V is now part of the main buildcommit.yml workflow. --- .github/workflows/riscv-test.yml | 52 -------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 .github/workflows/riscv-test.yml diff --git a/.github/workflows/riscv-test.yml b/.github/workflows/riscv-test.yml deleted file mode 100644 index f8d7805..0000000 --- a/.github/workflows/riscv-test.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: RISC-V Test Build - -on: - push: - branches: [ riscv-support ] - pull_request: - branches: [ master ] - paths: - - 'stackman/platforms/switch_riscv64_gcc.*' - - 'stackman/platforms/platform.h' - - '.github/workflows/riscv-test.yml' - -jobs: - - build-riscv64: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install RISC-V toolchain and QEMU - run: | - sudo apt-get update - sudo apt-get install --no-install-recommends -y \ - gcc-riscv64-linux-gnu \ - g++-riscv64-linux-gnu \ - qemu-user - - - name: Show toolchain info - run: | - riscv64-linux-gnu-gcc --version - qemu-riscv64 --version - - - name: Build library - env: - PLATFORM_PREFIX: riscv64-linux-gnu - run: make all - - - name: Show ABI name - run: make abiname - - - name: Run tests - env: - PLATFORM_PREFIX: riscv64-linux-gnu - EMULATOR: qemu-riscv64 - run: make test - - - name: Upload library artifact - uses: actions/upload-artifact@v4 - with: - name: riscv64-test-build - path: lib/riscv64/libstackman.a From d225ab7ed551385eb614093a20ac9ddfc0854fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= Date: Sun, 16 Nov 2025 18:39:35 +0000 Subject: [PATCH 15/15] Bump version to 1.2.0 in header file --- stackman/stackman.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stackman/stackman.h b/stackman/stackman.h index 483c1e9..257ec54 100644 --- a/stackman/stackman.h +++ b/stackman/stackman.h @@ -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) + \