Skip to content

[Canary] Build and test for RISC-V64 libco backend - #12243

Draft
cosmo0920 wants to merge 3 commits into
masterfrom
cosmo0920-canary-testing-riscv64-libco
Draft

[Canary] Build and test for RISC-V64 libco backend#12243
cosmo0920 wants to merge 3 commits into
masterfrom
cosmo0920-canary-testing-riscv64-libco

Conversation

@cosmo0920

@cosmo0920 cosmo0920 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

THIS IS CANARY PR. DO NOT MERGE.

Currently, we didn't have riscv64 backend on flb_libco library and bundled library as in monkey HTTP server.
This PR fills the gap of riscv64 coroutine implementation for RISC-V64 Linux environment.
Without this patch, runtime tests of filter_wasm were failed.

After applying this patch, we get on the actual RISC-V64 Linux environment:

% bin/flb-rt-filter_wasm                                                           (git)[tmp-fix-cfl-undefined-fix][OK]
Test hello_world...                             [2026/08/06 19:11:24.580] [ info] [fluent bit] version=5.1.0, commit=69fe0cea8b, pid=74259
[2026/08/06 19:11:24.581] [ info] [storage] ver=1.5.4, type=memory, sync=normal, checksum=off, max_chunks_up=128
[2026/08/06 19:11:24.581] [ info] [simd    ] RVV
[2026/08/06 19:11:24.581] [ info] [cmetrics] version=2.2.1
[2026/08/06 19:11:24.582] [ info] [ctraces ] version=0.7.1
[2026/08/06 19:11:24.583] [ info] [input:dummy:dummy.0] initializing
[2026/08/06 19:11:24.583] [ info] [input:dummy:dummy.0] storage_strategy='memory' (memory only)
[2026/08/06 19:11:24.760] [ info] [sp] stream processor started
[2026/08/06 19:11:24.760] [ info] [output:stdout:stdout.0] worker #0 started
[2026/08/06 19:11:24.760] [ info] [engine] Shutdown Grace Period=1, Shutdown Input Grace Period=0
[2026/08/06 19:11:24.760] [ warn] [engine] service will shutdown in max 1 seconds
[2026/08/06 19:11:24.761] [ info] [engine] pausing all inputs..
[2026/08/06 19:11:24.761] [ info] [input] pausing dummy.0
[2026/08/06 19:11:25.706] [ info] [engine] service has stopped (0 pending tasks)
[2026/08/06 19:11:25.706] [ info] [input] pausing dummy.0
[2026/08/06 19:11:25.708] [ info] [output:stdout:stdout.0] thread worker #0 stopping...
[2026/08/06 19:11:25.708] [ info] [output:stdout:stdout.0] thread worker #0 stopped
[ OK ]
Test append_tag...                              [2026/08/06 19:11:25.743] [ info] [fluent bit] version=5.1.0, commit=69fe0cea8b, pid=74263
[2026/08/06 19:11:25.743] [ info] [storage] ver=1.5.4, type=memory, sync=normal, checksum=off, max_chunks_up=128
[2026/08/06 19:11:25.744] [ info] [simd    ] RVV
[2026/08/06 19:11:25.744] [ info] [cmetrics] version=2.2.1
[2026/08/06 19:11:25.744] [ info] [ctraces ] version=0.7.1
[2026/08/06 19:11:25.745] [ info] [input:lib:lib.0] initializing
[2026/08/06 19:11:25.745] [ info] [input:lib:lib.0] storage_strategy='memory' (memory only)
[2026/08/06 19:11:25.984] [ info] [sp] stream processor started
[2026/08/06 19:11:25.984] [ info] [engine] Shutdown Grace Period=1, Shutdown Input Grace Period=0
[2026/08/06 19:11:26.785] [ warn] [engine] service will shutdown in max 1 seconds
[2026/08/06 19:11:26.785] [ info] [engine] pausing all inputs..
[2026/08/06 19:11:27.706] [ info] [engine] service has stopped (0 pending tasks)
[ OK ]
Test numeric_records...                         [2026/08/06 19:11:27.743] [ info] [fluent bit] version=5.1.0, commit=69fe0cea8b, pid=74266
[2026/08/06 19:11:27.744] [ info] [storage] ver=1.5.4, type=memory, sync=normal, checksum=off, max_chunks_up=128
[2026/08/06 19:11:27.744] [ info] [simd    ] RVV
[2026/08/06 19:11:27.744] [ info] [cmetrics] version=2.2.1
[2026/08/06 19:11:27.744] [ info] [ctraces ] version=0.7.1
[2026/08/06 19:11:27.745] [ info] [input:lib:lib.0] initializing
[2026/08/06 19:11:27.745] [ info] [input:lib:lib.0] storage_strategy='memory' (memory only)
[2026/08/06 19:11:27.980] [ info] [sp] stream processor started
[2026/08/06 19:11:27.981] [ info] [engine] Shutdown Grace Period=1, Shutdown Input Grace Period=0
[2026/08/06 19:11:28.782] [ warn] [engine] service will shutdown in max 1 seconds
[2026/08/06 19:11:28.782] [ info] [engine] pausing all inputs..
[2026/08/06 19:11:29.706] [ info] [engine] service has stopped (0 pending tasks)
[ OK ]
Test array_contains_null...                     [2026/08/06 19:11:29.743] [ info] [fluent bit] version=5.1.0, commit=69fe0cea8b, pid=74269
[2026/08/06 19:11:29.744] [ info] [storage] ver=1.5.4, type=memory, sync=normal, checksum=off, max_chunks_up=128
[2026/08/06 19:11:29.744] [ info] [simd    ] RVV
[2026/08/06 19:11:29.744] [ info] [cmetrics] version=2.2.1
[2026/08/06 19:11:29.744] [ info] [ctraces ] version=0.7.1
[2026/08/06 19:11:29.745] [ info] [input:lib:lib.0] initializing
[2026/08/06 19:11:29.745] [ info] [input:lib:lib.0] storage_strategy='memory' (memory only)
[2026/08/06 19:11:29.982] [ info] [sp] stream processor started
[2026/08/06 19:11:29.982] [ info] [engine] Shutdown Grace Period=1, Shutdown Input Grace Period=0
[2026/08/06 19:11:30.784] [ warn] [engine] service will shutdown in max 1 seconds
[2026/08/06 19:11:30.784] [ info] [engine] pausing all inputs..
[2026/08/06 19:11:31.706] [ info] [engine] service has stopped (0 pending tasks)
[ OK ]
Test drop_all_records...                        [2026/08/06 19:11:31.743] [ info] [fluent bit] version=5.1.0, commit=69fe0cea8b, pid=74272
[2026/08/06 19:11:31.744] [ info] [storage] ver=1.5.4, type=memory, sync=normal, checksum=off, max_chunks_up=128
[2026/08/06 19:11:31.744] [ info] [simd    ] RVV
[2026/08/06 19:11:31.744] [ info] [cmetrics] version=2.2.1
[2026/08/06 19:11:31.744] [ info] [ctraces ] version=0.7.1
[2026/08/06 19:11:31.745] [ info] [input:lib:lib.0] initializing
[2026/08/06 19:11:31.745] [ info] [input:lib:lib.0] storage_strategy='memory' (memory only)
[2026/08/06 19:11:31.787] [ info] [sp] stream processor started
[2026/08/06 19:11:31.788] [ info] [engine] Shutdown Grace Period=1, Shutdown Input Grace Period=0
[2026/08/06 19:11:33.790] [ warn] [timeout] elapsed_time: 2002
[2026/08/06 19:11:33.790] [ warn] [engine] service will shutdown in max 1 seconds
[2026/08/06 19:11:33.790] [ info] [engine] pausing all inputs..
[2026/08/06 19:11:34.706] [ info] [engine] service has stopped (0 pending tasks)
[ OK ]
Test append_kv_on_msgpack_format...             [2026/08/06 19:11:34.741] [ info] [fluent bit] version=5.1.0, commit=69fe0cea8b, pid=74275
[2026/08/06 19:11:34.742] [ info] [storage] ver=1.5.4, type=memory, sync=normal, checksum=off, max_chunks_up=128
[2026/08/06 19:11:34.742] [ info] [simd    ] RVV
[2026/08/06 19:11:34.742] [ info] [cmetrics] version=2.2.1
[2026/08/06 19:11:34.742] [ info] [ctraces ] version=0.7.1
[2026/08/06 19:11:34.743] [ info] [input:lib:lib.0] initializing
[2026/08/06 19:11:34.743] [ info] [input:lib:lib.0] storage_strategy='memory' (memory only)
[2026/08/06 19:11:34.855] [ info] [sp] stream processor started
[2026/08/06 19:11:34.856] [ info] [engine] Shutdown Grace Period=1, Shutdown Input Grace Period=0
[2026/08/06 19:11:35.757] [ warn] [engine] service will shutdown in max 1 seconds
[2026/08/06 19:11:35.757] [ info] [engine] pausing all inputs..
[2026/08/06 19:11:36.706] [ info] [engine] service has stopped (0 pending tasks)
[ OK ]
Test wasm_preserve_otlp_group_metadata...       [2026/08/06 19:11:36.743] [ info] [fluent bit] version=5.1.0, commit=69fe0cea8b, pid=74278
[2026/08/06 19:11:36.743] [ info] [storage] ver=1.5.4, type=memory, sync=normal, checksum=off, max_chunks_up=128
[2026/08/06 19:11:36.743] [ info] [simd    ] RVV
[2026/08/06 19:11:36.743] [ info] [cmetrics] version=2.2.1
[2026/08/06 19:11:36.744] [ info] [ctraces ] version=0.7.1
[2026/08/06 19:11:36.745] [ info] [input:opentelemetry:opentelemetry.0] initializing
[2026/08/06 19:11:36.745] [ info] [input:opentelemetry:opentelemetry.0] storage_strategy='memory' (memory only)
[2026/08/06 19:11:36.746] [ info] [input:opentelemetry:opentelemetry.0] listening on 0.0.0.0:41269 with 1 worker
[2026/08/06 19:11:36.923] [ info] [http_server] listen iface=127.0.0.1 tcp_port=2020
[2026/08/06 19:11:36.923] [ info] [sp] stream processor started
[2026/08/06 19:11:36.924] [ info] [engine] Shutdown Grace Period=1, Shutdown Input Grace Period=0
Hello from WASM!
[2026/08/06 19:11:38.453] [ warn] [engine] service will shutdown in max 1 seconds
[2026/08/06 19:11:38.453] [ info] [engine] pausing all inputs..
[2026/08/06 19:11:38.453] [ info] [input] pausing opentelemetry.0
[2026/08/06 19:11:38.710] [ info] [engine] service has stopped (0 pending tasks)
[2026/08/06 19:11:38.710] [ info] [input] pausing opentelemetry.0
[ OK ]
SUCCESS: All unit tests have passed.

Plus, the implemented coroutine function can be used as:

% nm -g lib/monkey/deps/flb_libco/CMakeFiles/co.dir/libco.c.o
                 U __tls_get_addr
                 U abort
0000000000000104 T co_active
000000000000016a T co_create
0000000000000224 T co_delete
0000000000000246 T co_switch
0000000000000002 T co_switch_riscv64
                 U free
                 U malloc
                 U memset

Background of RISC-V64 implementation

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.

How a context switch happens

The C wrapper passes two context pointers to the assembly routine:

previous_handle = co_active();
co_active_handle = handle;
co_switch_riscv64(handle, previous_handle);

By the normal RISC-V calling convention:

a0 = target context
a1 = current context

The assembly first saves the current coroutine:

sd ra, 0(a1)
sd sp, 8(a1)
sd s0, 16(a1)
...
sd s11, 104(a1)

It then loads the target coroutine:

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() (line 173)](C:/Users/cosmo/Documents/GitHub/fluent-bit/lib/flb_libco/riscv64.c:173) initializes:

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.

Why caller-saved registers are omitted

The backend does not need to save a0a7 or t0t6. 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.

Floating-point ABI handling

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:

fsd fs0, 112(a1)
...
fld fs0, 112(a0)

LP64F uses 32-bit operations:

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:

gcc -dM -E - </dev/null |
grep -E '__riscv($|_)|__riscv_float_abi'

Expected relevant definitions include:

#define __riscv 1
#define __riscv_xlen 64
#define __riscv_float_abi_double 1

These macros select riscv64.c and enable its double-precision register-save path.

Stack correctness

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:

low address
┌──────────────────────────┐
│ Saved context            │
│ ra, sp, s0-s11, FP state │
├──────────────────────────┤
│ Coroutine stack          │
│                          │
│                 ↓ growth │
└──────────────────────────┘ ← initial sp, 16-byte aligned
high address

How the focused test proves preservation

The test commit does more than verify that switching does not crash:

  1. The primary coroutine loads known values into s0s11, fs0fs11, 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.

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
s0s11 Integer registers classified as callee-saved
fs0fs11 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/).

How a context switch happens

The C wrapper passes two context pointers to the assembly routine:

previous_handle = co_active();
co_active_handle = handle;
co_switch_riscv64(handle, previous_handle);

By the normal RISC-V calling convention:

a0 = target context
a1 = current context

The assembly first saves the current coroutine:

sd ra, 0(a1)
sd sp, 8(a1)
sd s0, 16(a1)
...
sd s11, 104(a1)

It then loads the target coroutine:

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() initializes:

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.

Why caller-saved registers are omitted

The backend does not need to save a0a7 or t0t6. 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.

Floating-point ABI handling

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:

fsd fs0, 112(a1)
...
fld fs0, 112(a0)

LP64F uses 32-bit operations:

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:

gcc -dM -E - </dev/null |
grep -E '__riscv($|_)|__riscv_float_abi'

Expected relevant definitions include:

#define __riscv 1
#define __riscv_xlen 64
#define __riscv_float_abi_double 1

These macros select riscv64.c and enable its double-precision register-save path.

Stack correctness

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:

low address
┌──────────────────────────┐
│ Saved context            │
│ ra, sp, s0-s11, FP state │
├──────────────────────────┤
│ Coroutine stack          │
│                          │
│                 ↓ growth │
└──────────────────────────┘ ← initial sp, 16-byte aligned
high address

How the focused test proves preservation

The test commit does more than verify that switching does not crash:

  1. The primary coroutine loads known values into s0s11, fs0fs11, 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.


Enter [N/A] in the box, if an item is not applicable to your change.

Testing
Before we can approve your change; please submit the following in a comment:

  • Example configuration file for the change
  • Debug log output from testing the change
  • Attached Valgrind output that shows no leaks or memory corruption was found

If this is a change to packaging of containers or native binaries then please confirm it works for all targets.

  • Run local packaging test showing all targets (including any new ones) build.
  • Set ok-package-test label to test for all targets (requires maintainer to do).

Documentation

  • Documentation required for this feature

Backporting

  • Backport to latest stable release.

Fluent Bit is licensed under Apache 2.0, by submitting this pull request I understand that this code will be released under the terms of that license.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d00b860-59cd-4502-a84c-fc384e0f2adf

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant