tools/mpy_ld: Support picolibc on RISC-V (.srodata, absolute LO12).#19378
Open
o-murphy wants to merge 1 commit into
Open
tools/mpy_ld: Support picolibc on RISC-V (.srodata, absolute LO12).#19378o-murphy wants to merge 1 commit into
o-murphy wants to merge 1 commit into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #19378 +/- ##
=======================================
Coverage 98.51% 98.51%
=======================================
Files 177 177
Lines 22927 22927
=======================================
Hits 22586 22586
Misses 341 341 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
47fb00c to
bfe512b
Compare
|
Code size report: |
Native modules built with LINK_RUNTIME=1 for rv32imc/rv64imc could not link against picolibc's libc.a/libm.a as soon as a floating-point library function (sinf, expf, powf, ...) was called with a runtime (non-constant) argument. Two issues in mpy_ld.py were responsible: 1. load_object_file() only recognised section names starting with ".rodata", ".text", ".bss" etc. picolibc stores RISC-V float/double constants in the small-data read-only sections ".srodata.cst4" / ".srodata.cst8", which were silently skipped, leaving symbols that point into them without a .section attribute. 2. process_riscv32_relocation() handled R_RISCV_LO12_I/S with the same parent-lookup logic as the PCREL variants. For PCREL the relocation symbol is a local label pointing at the matching HI20 instruction in .text, so the parent lookup is required. For the absolute variant used by picolibc, the symbol points directly at the data itself, so there is no parent to find and the lookup fell through to assert 0. Add ".srodata" to the recognised section prefixes, and give the absolute LO12 case its own code path that computes the address directly instead of going through a HI20 parent. Tested on rv32imc with gcc-riscv64-unknown-elf 13.2.0 and picolibc-riscv64-unknown-elf 1.8.6 (Ubuntu 24.04): a natmod calling sinf/cosf/atan2f/sqrtf/expf/powf with runtime arguments now builds and links against picolibc successfully, where it previously failed with two separate exceptions (see PR description). Module size also drops by ~1.5 KB compared to vendoring fdlibm, since picolibc isn't duplicated into the natmod. Closes: micropython#19364 Signed-off-by: o-murphy thehelixpg@gmail.com
bfe512b to
dba01bd
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.
Native modules built with LINK_RUNTIME=1 for rv32imc/rv64imc could not link against picolibc's libc.a/libm.a as soon as a floating-point library function (sinf, expf, powf, ...) was called with a runtime (non-constant) argument. Two issues in mpy_ld.py were responsible:
load_object_file() only recognised section names starting with ".rodata", ".text", ".bss" etc. picolibc stores RISC-V float/double constants in the small-data read-only sections ".srodata.cst4" / ".srodata.cst8", which were silently skipped, leaving symbols that point into them without a .section attribute.
process_riscv32_relocation() handled R_RISCV_LO12_I/S with the same parent-lookup logic as the PCREL variants. For PCREL the relocation symbol is a local label pointing at the matching HI20 instruction in .text, so the parent lookup is required. For the absolute variant used by picolibc, the symbol points directly at the data itself, so there is no parent to find and the lookup fell through to assert 0.
Add ".srodata" to the recognised section prefixes, and give the absolute LO12 case its own code path that computes the address directly instead of going through a HI20 parent.
Tested on rv32imc with gcc-riscv64-unknown-elf 13.2.0 and picolibc-riscv64-unknown-elf 1.8.6 (Ubuntu 24.04): a natmod calling sinf/cosf/atan2f/sqrtf/expf/powf with runtime arguments now builds and links against picolibc successfully, where it previously failed with two separate exceptions (see PR description). Module size also drops by ~1.5 KB compared to vendoring fdlibm, since picolibc isn't duplicated into the natmod.
Closes: #19364
Signed-off-by: o-murphy thehelixpg@gmail.com
Summary
tools/mpy_ld.pycannot link native modules against picolibc'sprebuilt
libc.a/libm.aon RISC-V (rv32imc/rv64imc,LINK_RUNTIME=1) once a libm function is called with a runtime value.Two independent bugs are responsible, both in RISC-V-specific code
paths, so this never showed up for ARM/Xtensa.
Reproducer — a natmod with a runtime (non-constant) float argument
into libm:
A compile-time-constant argument (
sinf(1.0f)) is folded by GCC at-Osand never reaches the linker as an external call, so a naive"hello world" natmod can look fine — the bug only appears once a real
runtime value flows into the library call, which is what any practical
use of
<math.h>on a natmod does.Bug 1.
load_object_file():picolibc keeps RISC-V float/double constants in
.srodata.cst4/.srodata.cst8(the RISC-V "small data" area — doesn't exist on ARM,which is why
LINK_RUNTIME=1is unaffected there). Not being on thelist, those sections are skipped, and symbols pointing into them are
left without a
.sectionattribute:Bug 2. Fixing bug 1 alone (just adding
.srodatato the prefixlist) uncovers a second bug in
process_riscv32_relocation():R_RISCV_LO12_I/R_RISCV_LO12_Sgo through the same parent-lookuploop as the
PCRELvariants, which is wrong for the absolute casepicolibc emits (the symbol already points at the target data, e.g.
.LC1in.srodata.cst4— there's noHI20parent to find):(
r_info_type: 27=R_RISCV_LO12_I.)The fix adds
.srodatato the recognised section prefixes, andgives the absolute-LO12 case (no
HI20parent found) its own branchthat computes the address directly instead of asserting:
Testing
Verified all three states on the reproducer above,
ARCH=rv32imc,MICROPY_FLOAT_IMPL=float,gcc-riscv64-unknown-elf13.2.0 +picolibc-riscv64-unknown-elf1.8.6 (Ubuntu 24.04), against a clean,unmodified
v1.28.0checkout for each step:LINK_RUNTIME=1AttributeError(bug 1, above).srodatafix onlyAssertionError(bug 2, above)Also rebuilt a larger, real-world natmod (~20 libm call sites covering
sin/cos/atan2/sqrt/pow/exp) with and without the patch tocheck for regressions and measure size impact:
LINK_RUNTIME=1.mpytext sizepowf/expfavailablelib/libmNot tested on real RISC-V hardware, only QEMU/build-level — same
verification depth as the existing
rv32imc/rv64imcnatmod CI jobs,which currently only build (no runtime test exists for these archs).
Trade-offs and Alternatives
None — this only relaxes section-prefix matching and removes an overly
strict assert; behaviour for ARM/Xtensa/x86/x64 (which don't generate
.srodataor absoluteLO12relocations) is unchanged. An alternativewould be to keep vendoring fdlibm instead of fixing the linker, but
that duplicates code already in the target's libc, and MicroPython's
bundled single-precision
lib/libmdoesn't providepow/expat all,so it isn't a full substitute.
Generative AI
I used generative AI tools when creating this PR, but a human has
checked the code and is responsible for the code and the description
above.