From 58640a598754e3318338574ca480785bb27e27b6 Mon Sep 17 00:00:00 2001 From: Hiroshi Hatake Date: Fri, 7 Aug 2026 15:28:56 +0900 Subject: [PATCH] riscv64: Add RISC-V64 Linux support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The implementation details as follows. The backend works because it saves exactly the state that the RISC-V ELF ABI says must survive an ordinary C function call. From each coroutine’s perspective, `co_switch()` behaves like a function that pauses and later returns normally. | State | Why it is preserved | |---|---| | `ra` | Becomes the resume address for the suspended coroutine | | `sp` | Restores that coroutine’s independent stack | | `s0`–`s11` | Integer registers classified as callee-saved | | `fs0`–`fs11` | Floating-point callee-saved registers under LP64F/LP64D | | `fcsr` | Preserves floating-point rounding mode and exception flags | The register classification comes from the [RISC-V ELF psABI calling convention](https://riscv-non-isa.github.io/riscv-elf-psabi-doc/). The C wrapper passes two context pointers to the assembly routine: ```c previous_handle = co_active(); co_active_handle = handle; co_switch_riscv64(handle, previous_handle); ``` By the normal RISC-V calling convention: ```text a0 = target context a1 = current context ``` The assembly first saves the current coroutine: ```asm sd ra, 0(a1) sd sp, 8(a1) sd s0, 16(a1) ... sd s11, 104(a1) ``` It then loads the target coroutine: ```asm ld ra, 0(a0) ld sp, 8(a0) ld s0, 16(a0) ... ld s11, 104(a0) ret ``` The final `ret` jumps to the `ra` loaded from the target context. For a suspended coroutine, that `ra` points immediately after its earlier call to `co_switch_riscv64()`. Execution therefore continues as though `co_switch()` had just returned. For a new coroutine, [co_create()](C:/Users/cosmo/Documents/GitHub/fluent-bit/lib/flb_libco/riscv64.c:173) initializes: ```c context->ra = (uintptr_t) co_entry_trampoline; context->sp = (uintptr_t) context + total_size; context->entrypoint = entrypoint; ``` Its first `ret` enters the trampoline on the newly allocated stack. The trampoline invokes the actual coroutine entry function and aborts if that function unexpectedly returns. The backend does not need to save `a0`–`a7` or `t0`–`t6`. They are caller-saved registers. When the compiler generates a call to `co_switch()`, it already spills any live caller-saved values that will be needed afterward. Likewise: - `gp` is fixed by the ABI and must not be modified. - `tp` identifies the current OS thread. Fluent Bit coroutines remain on the same pthread, so it must remain unchanged. - Standard vector registers are caller-saved. RVV code must spill live vector values before calling `co_switch()`. This backend therefore assumes that a coroutine is never migrated to another pthread, matching Fluent Bit’s current coroutine model. The compiler exposes the selected ABI through predefined macros: - LP64: neither floating-point macro is defined. - LP64F: `__riscv_float_abi_single` - LP64D: `__riscv_float_abi_double` LP64D uses 64-bit operations: ```asm fsd fs0, 112(a1) ... fld fs0, 112(a0) ``` LP64F uses 32-bit operations: ```asm fsw fs0, 112(a1) ... flw fs0, 112(a0) ``` Soft-float LP64 does not preserve floating-point registers because that ABI does not classify them as callee-saved program state. On a typical DC ROMA II LP64D environment, GCC should report: ```bash gcc -dM -E - --- libco.c | 4 + riscv64.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 riscv64.c diff --git a/libco.c b/libco.c index a66b5b3..76a7e22 100644 --- a/libco.c +++ b/libco.c @@ -16,6 +16,10 @@ #include "arm.c" #elif defined(__aarch64__) #include "aarch64.c" + #elif defined(__riscv) && defined(__riscv_xlen) && \ + __riscv_xlen == 64 && !defined(__riscv_abi_rve) && \ + !defined(__riscv_float_abi_quad) + #include "riscv64.c" #elif defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2 #include "ppc64le.c" #elif defined(_ARCH_PPC) && !defined(__LITTLE_ENDIAN__) diff --git a/riscv64.c b/riscv64.c new file mode 100644 index 0000000..026c25e --- /dev/null +++ b/riscv64.c @@ -0,0 +1,223 @@ +/* + libco.riscv64 + license: public domain +*/ + +#define LIBCO_C +#include "libco.h" +#include "settings.h" + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__riscv_float_abi_double) || defined(__riscv_float_abi_single) + #define LIBCO_RISCV64_HARD_FLOAT +#endif + +typedef struct { + uintptr_t ra; + uintptr_t sp; + uintptr_t s[12]; +#ifdef LIBCO_RISCV64_HARD_FLOAT + uint64_t fs[12]; + uintptr_t fcsr; +#endif + void (*entrypoint)(void); +} co_context; + +typedef char co_context_ra_offset[(offsetof(co_context, ra) == 0) ? 1 : -1]; +typedef char co_context_sp_offset[(offsetof(co_context, sp) == 8) ? 1 : -1]; +typedef char co_context_s_offset[(offsetof(co_context, s) == 16) ? 1 : -1]; +#ifdef LIBCO_RISCV64_HARD_FLOAT +typedef char co_context_fs_offset[(offsetof(co_context, fs) == 112) ? 1 : -1]; +typedef char co_context_fcsr_offset[(offsetof(co_context, fcsr) == 208) ? 1 : -1]; +#endif + +static thread_local co_context co_active_context; +static thread_local cothread_t co_active_handle; + +#if defined(__riscv_float_abi_double) + #define LIBCO_RISCV64_SAVE_FLOAT \ + " fsd fs0, 112(a1)\n" \ + " fsd fs1, 120(a1)\n" \ + " fsd fs2, 128(a1)\n" \ + " fsd fs3, 136(a1)\n" \ + " fsd fs4, 144(a1)\n" \ + " fsd fs5, 152(a1)\n" \ + " fsd fs6, 160(a1)\n" \ + " fsd fs7, 168(a1)\n" \ + " fsd fs8, 176(a1)\n" \ + " fsd fs9, 184(a1)\n" \ + " fsd fs10, 192(a1)\n" \ + " fsd fs11, 200(a1)\n" \ + " csrr t0, fcsr\n" \ + " sd t0, 208(a1)\n" + #define LIBCO_RISCV64_RESTORE_FLOAT \ + " fld fs0, 112(a0)\n" \ + " fld fs1, 120(a0)\n" \ + " fld fs2, 128(a0)\n" \ + " fld fs3, 136(a0)\n" \ + " fld fs4, 144(a0)\n" \ + " fld fs5, 152(a0)\n" \ + " fld fs6, 160(a0)\n" \ + " fld fs7, 168(a0)\n" \ + " fld fs8, 176(a0)\n" \ + " fld fs9, 184(a0)\n" \ + " fld fs10, 192(a0)\n" \ + " fld fs11, 200(a0)\n" \ + " ld t0, 208(a0)\n" \ + " csrw fcsr, t0\n" +#elif defined(__riscv_float_abi_single) + #define LIBCO_RISCV64_SAVE_FLOAT \ + " fsw fs0, 112(a1)\n" \ + " fsw fs1, 120(a1)\n" \ + " fsw fs2, 128(a1)\n" \ + " fsw fs3, 136(a1)\n" \ + " fsw fs4, 144(a1)\n" \ + " fsw fs5, 152(a1)\n" \ + " fsw fs6, 160(a1)\n" \ + " fsw fs7, 168(a1)\n" \ + " fsw fs8, 176(a1)\n" \ + " fsw fs9, 184(a1)\n" \ + " fsw fs10, 192(a1)\n" \ + " fsw fs11, 200(a1)\n" \ + " csrr t0, fcsr\n" \ + " sd t0, 208(a1)\n" + #define LIBCO_RISCV64_RESTORE_FLOAT \ + " flw fs0, 112(a0)\n" \ + " flw fs1, 120(a0)\n" \ + " flw fs2, 128(a0)\n" \ + " flw fs3, 136(a0)\n" \ + " flw fs4, 144(a0)\n" \ + " flw fs5, 152(a0)\n" \ + " flw fs6, 160(a0)\n" \ + " flw fs7, 168(a0)\n" \ + " flw fs8, 176(a0)\n" \ + " flw fs9, 184(a0)\n" \ + " flw fs10, 192(a0)\n" \ + " flw fs11, 200(a0)\n" \ + " ld t0, 208(a0)\n" \ + " csrw fcsr, t0\n" +#else + #define LIBCO_RISCV64_SAVE_FLOAT + #define LIBCO_RISCV64_RESTORE_FLOAT +#endif + +asm( + ".text\n" + ".p2align 2\n" + ".globl co_switch_riscv64\n" + ".type co_switch_riscv64, @function\n" + "co_switch_riscv64:\n" + " sd ra, 0(a1)\n" + " sd sp, 8(a1)\n" + " sd s0, 16(a1)\n" + " sd s1, 24(a1)\n" + " sd s2, 32(a1)\n" + " sd s3, 40(a1)\n" + " sd s4, 48(a1)\n" + " sd s5, 56(a1)\n" + " sd s6, 64(a1)\n" + " sd s7, 72(a1)\n" + " sd s8, 80(a1)\n" + " sd s9, 88(a1)\n" + " sd s10, 96(a1)\n" + " sd s11, 104(a1)\n" + LIBCO_RISCV64_SAVE_FLOAT + " ld ra, 0(a0)\n" + " ld sp, 8(a0)\n" + " ld s0, 16(a0)\n" + " ld s1, 24(a0)\n" + " ld s2, 32(a0)\n" + " ld s3, 40(a0)\n" + " ld s4, 48(a0)\n" + " ld s5, 56(a0)\n" + " ld s6, 64(a0)\n" + " ld s7, 72(a0)\n" + " ld s8, 80(a0)\n" + " ld s9, 88(a0)\n" + " ld s10, 96(a0)\n" + " ld s11, 104(a0)\n" + LIBCO_RISCV64_RESTORE_FLOAT + " ret\n" + ".size co_switch_riscv64, .-co_switch_riscv64\n" + ".previous\n" +); + +void co_switch_riscv64(cothread_t handle, cothread_t current); + +static void co_entry_trampoline(void) +{ + co_context *context; + + context = (co_context *)co_active_handle; + context->entrypoint(); + abort(); +} + +cothread_t co_active(void) +{ + if (!co_active_handle) { + co_active_handle = &co_active_context; + } + + return co_active_handle; +} + +cothread_t co_create(unsigned int size, void (*entrypoint)(void), + size_t *out_size) +{ + size_t context_size; + size_t stack_size; + size_t total_size; + co_context *context; + + context_size = (sizeof(co_context) + 15) & ~(size_t)15; + + if ((size_t)size > SIZE_MAX - 15) { + return 0; + } + + stack_size = ((size_t)size + 15) & ~(size_t)15; + if (stack_size > SIZE_MAX - context_size) { + return 0; + } + + total_size = context_size + stack_size; + context = (co_context *)malloc(total_size); + if (!context) { + return 0; + } + + memset(context, 0, context_size); + context->ra = (uintptr_t)co_entry_trampoline; + context->sp = (uintptr_t)context + total_size; + context->entrypoint = entrypoint; + + *out_size = total_size; + return (cothread_t)context; +} + +void co_delete(cothread_t handle) +{ + free(handle); +} + +void co_switch(cothread_t handle) +{ + cothread_t previous_handle; + + previous_handle = co_active(); + co_active_handle = handle; + co_switch_riscv64(handle, previous_handle); +} + +#ifdef __cplusplus +} +#endif