libco: riscv64: Add RISC-V64 Linux support - #13
Open
cosmo0920 wants to merge 1 commit into
Open
Conversation
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 - </dev/null | grep -E '__riscv($|_)|__riscv_float_abi' ``` Expected relevant definitions include: ```text ``` These macros select [riscv64.c](C:/Users/cosmo/Documents/GitHub/fluent-bit/lib/flb_libco/libco.c:18) and enable its double-precision register-save path. RISC-V requires the stack pointer to remain 16-byte aligned. Both the requested stack size and context header are rounded to multiples of 16. Linux RV64 `malloc()` supplies suitably aligned memory, so the calculated top of the allocation remains 16-byte aligned. The stack grows downward from that address, while the saved context resides at the bottom of the allocation: ```text low address ┌──────────────────────────┐ │ Saved context │ │ ra, sp, s0-s11, FP state │ ├──────────────────────────┤ │ Coroutine stack │ │ │ │ ↓ growth │ └──────────────────────────┘ ← initial sp, 16-byte aligned high address ``` The test commit does more than verify that switching does not crash: 1. The primary coroutine loads known values into `s0`–`s11`, `fs0`–`fs11`, and `fcsr`. 2. It switches to a worker coroutine. 3. The worker deliberately overwrites those registers with zero. 4. The worker switches back. 5. The primary coroutine compares every restored register with its original pattern. Correct return flow also implicitly verifies `ra` and `sp`: an incorrect value for either normally returns to the wrong instruction or accesses the wrong stack and crashes. RV64E and LP64Q are excluded because their register sets or floating-point widths require different layouts. They continue to the generic backend instead of silently using an incompatible context representation. Signed-off-by: Hiroshi Hatake <hiroshi@chronosphere.io>
cosmo0920
force-pushed
the
cosmo0920-riscv64-linux-support
branch
from
August 7, 2026 06:35
56b952f to
58640a5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.raThe register classification comes from the RISC-V ELF psABI calling convention.
The C wrapper passes two context pointers to the assembly routine:
By the normal RISC-V calling convention:
The assembly first saves the current coroutine:
It then loads the target coroutine:
The final
retjumps to theraloaded from the target context.For a suspended coroutine, that
rapoints immediately after its earlier call toco_switch_riscv64(). Execution therefore continues as thoughco_switch()had just returned.For a new coroutine, co_create() initializes:
Its first
retenters 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–a7ort0–t6. They are caller-saved registers. When the compiler generates a call toco_switch(), it already spills any live caller-saved values that will be needed afterward.Likewise:
gpis fixed by the ABI and must not be modified.tpidentifies the current OS thread. Fluent Bit coroutines remain on the same pthread, so it must remain unchanged.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:
__riscv_float_abi_single__riscv_float_abi_doubleLP64D uses 64-bit operations:
LP64F uses 32-bit operations:
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:
Expected relevant definitions include:
These macros select riscv64.c and enable its double-precision register-save path.
RISC-V requires the stack pointer to remain 16-byte aligned. Both the requested stack size and context header are rounded to multiples of 16. Linux RV64
malloc()supplies suitably aligned memory, so the calculated top of the allocation remains 16-byte aligned.The stack grows downward from that address, while the saved context resides at the bottom of the allocation:
The test commit does more than verify that switching does not crash:
s0–s11,fs0–fs11, andfcsr.Correct return flow also implicitly verifies
raandsp: an incorrect value for either normally returns to the wrong instruction or accesses the wrong stack and crashes.RV64E and LP64Q are excluded because their register sets or floating-point widths require different layouts. They continue to the generic backend instead of silently using an incompatible context representation.
Additional Contexts
The corresponding canary PR of Fluent Bit is here:
fluent/fluent-bit#12243.
That PR succeeded to work with the actual RISC-V64 Linux laptop which is called as DC-ROMA II that has 8-cores of RISC-V 64bit processors.