Thanks to visit codestin.com
Credit goes to github.com

Skip to content

natmod: Allow linking with static libraries #15838

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

vshymanskyy
Copy link
Contributor

@vshymanskyy vshymanskyy commented Sep 12, 2024

Summary

When building non-trivial native modules, the compiler runtime needs to be linked manually, which is a tedious process. This PR allows mpy_ld to automatically resolve dependencies and link with libgcc.a and libm.a (or other user-specified static libraries). It also improves reporting format of multiple definitions and unresolved symbol errors.

Also, the automatic process ensures that only the required object files from archives are being linked into the mpy file. When doing it manually, one can include unneeded obj files which will just inflate the binary.

Loading large .a files takes quite some time, so the implementation caches the parsing results to minimize the impact on the developer experience (and the planet 🌱).

MICROPY_ARCH_CFLAGS was added so it can be conveniently used in the Makefile (i.e. it can be used to cross-build third-party libs)

Testing

This was developed as part of wasm2mpy initiative.
As a result, I was able to produce builds without including the runtime object files in wasm2mpy repository:
https://github.com/vshymanskyy/wasm2mpy/actions/runs/10834266784/job/30063030978

┌────────────────┬───────┬───────┬──────────┬──────────┬─────────────┬─────────────┬──────────┬─────────────┐
│                │ x86   │ x64   │ armv6m   │ armv7m   │ armv7emsp   │ armv7emdp   │ xtensa   │ xtensawin   │
├────────────────┼───────┼───────┼──────────┼──────────┼─────────────┼─────────────┼──────────┼─────────────┤
│ assemblyscript │ 🟢    │ 🟢    │ 🟢       │ 🟢       │ 🟢          │ 🟢          │ 🟢       │ 🟢          │
│ cpp            │ 🟢    │ 🟢    │ 🟢       │ 🟢       │ 🟢          │ 🟢          │ 🟢       │ 🟢          │
│ rust           │ 🟢    │ 🟢    │ 🟢       │ 🟢       │ 🟢          │ 🟢          │ 🟢       │ 🟢          │
│ tinygo         │ 🟢    │ 🟢    │ 🟢       │ 🟢       │ 🟢          │ 🟢          │ 🟢       │ 🟢          │
│ zig            │ 🟢    │ 🟢    │ 🟢       │ 🟢       │ 🟢          │ 🟢          │ 🟢       │ 🟢          │
│ virgil         │ 🟢    │ 🟢    │ 🟢       │ 🟢       │ 🟢          │ 🟢          │ 🟢       │ 🟢          │
│ wat            │ 🟢    │ 🟢    │ 🟢       │ 🟢       │ 🟢          │ 🟢          │ 🟢       │ 🟢          │
│ coremark       │ 🟢    │ 🟢    │ 🟥       │ 🟢       │ 🟢          │ 🟢          │ 🟢       │ 🟢          │
└────────────────┴───────┴───────┴──────────┴──────────┴─────────────┴─────────────┴──────────┴─────────────┘

Note: armv6m/coremark build fails due to unrelated reason1, reason2

Also, examples/natmod/features2 was updated to use this functionality so it is included in the regular MicroPython CI tests.

Also, this was successfully used by @agatti and @jonnor . See comments below.

Motivation

Trade-offs and Alternatives

  • Alternatively, one can do it manually, i.e. find and unpack relevant .a, resolve dependencies, include required object files in the build process
  • mpy_ld gets a new (optional) dependency on ar package.
    ar is not required unless you either set LINK_RUNTIME=1, or pass -l option to the mpy_ld.
  • I've added some basic support for weak symbols, but it should be addressed via a separate PR.

Copy link

codecov bot commented Sep 12, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.58%. Comparing base (495ce91) to head (a22b653).
Report is 212 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #15838      +/-   ##
==========================================
- Coverage   98.59%   98.58%   -0.01%     
==========================================
  Files         167      167              
  Lines       21617    21596      -21     
==========================================
- Hits        21313    21291      -22     
- Misses        304      305       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

github-actions bot commented Sep 12, 2024

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +0 +0.000% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

@vshymanskyy vshymanskyy changed the title natmod: Allow linking with .a static libraries natmod: Allow linking with static libraries Sep 12, 2024
@vshymanskyy vshymanskyy force-pushed the mpy-ld-link-archives branch 2 times, most recently from faa9d08 to ceabe58 Compare September 12, 2024 13:56
@agatti
Copy link
Contributor

agatti commented Sep 12, 2024

@vshymanskyy nice! On RV32 it's able to pull softfp symbols from libgcc when building examples/natmod/random, and the resulting mpy file seems to work (I didn't test all the exposed functions yet).

A couple of things worth mentioning... Is a new pip dependency really needed? The archive format isn't that complex to write a reader for, as you already know, and the dependency in question isn't so much code to require a separate installation step.

Regarding collecting all duplicate symbol messages and print them all at once, (warning: personal preference) it might be useful whilst developing the feature but I'm not sure how much use a final user would get out of it.

@vshymanskyy
Copy link
Contributor Author

vshymanskyy commented Sep 12, 2024

@agatti Thanks for testing it, glad it worked out of the box.

No, I really don't want to write yet another AR parser. The format is not actually simple, it's tricky to get right as seen from vidstige/ar#17 and similar pull requests on that repo (and alternatives, which don't work due to the same reasons).

Collecting unresolved symbol messages and print them all at once provides much better insight into the scope of work and leads to much better decisions, which is why all "big" linkers do this (llvm, gcc, even tinycc afaik). And.. it will definitely help with issue reports on github.

@agatti
Copy link
Contributor

agatti commented Sep 12, 2024

@vshymanskyy for the symbol messages, I meant showing them all to the user, my bad. Collecting is one thing, but displaying the whole lot may be a bit too much for the final user, especially if there are a lot of them - it's still an exception being raised so there's possibly quite a bit of text to read (traceback plus entries). Again, personal preference here.

I'm still a bit torn about having an extra dependency. I mean, it does work, don't get me wrong! It's just that it looks like a small dependency compared to pyelftools, that's it. But it's not up to me to decide :)

Still, this just unlocked one more natmod working on RV32, thanks!

@vshymanskyy
Copy link
Contributor Author

vshymanskyy commented Sep 12, 2024

It turns out, that some .a and .so files are actually LD scripts...
Here's how my /usr/lib/x86_64-linux-gnu/libm.a looks like:

/* GNU ld script
*/
OUTPUT_FORMAT(elf64-x86-64)
GROUP ( /usr/lib/x86_64-linux-gnu/libm-2.38.a /usr/lib/x86_64-linux-gnu/libmvec.a )

I've also updated this PR to handle this.

@jonnor
Copy link
Contributor

jonnor commented Sep 13, 2024

@vshymanskyy This looks like a super contribution to the natmod functionality. In addition to the mentioned issue, I think this would also close #5629

I am not a reviewer for the MicroPython project, but I took a quick look at the code, and the integration into mpy_ld looks very clean. I also like that it is enabled by default without needing special configuration, so that it will just-work for native module writers.

It might be useful to now use floating point in one of the existing modules (or a new one) - such that this functionality gets tested automatically by CI - and prevent breakage in the future.
A couple of lines to the natmod documentation could also be useful. For example, to explain that on targets without an FPU, floating point library code will be copied into the native module. And that such code will not be shared between multiple modules. I have found the code size to be very unproblematic in practice, but it is good to inform people - so they for example can compare/contrast with user C modules (where only a single copy of these symbols will exist in an entire firmware image).

@vshymanskyy
Copy link
Contributor Author

vshymanskyy commented Sep 13, 2024

Thanks for comments, I think these are all very relevant.

Regarding size increase, it wont happen automatically, the developer will have to explicitly use this feature. But the docs need to be added for sure.

@jonnor
Copy link
Contributor

jonnor commented Sep 13, 2024

I noticed now that it was opt-in. That is probably a smart choice, since there is an added Python dependency (ar). And that actually depends on Python 3.11, which I believe is more recent than the minimum version required by the MicroPython tooling per now.

I tested this briefly in emlfft module in https://github.com/emlearn/emlearn-micropython (where before I had a lot of manual .a files). It built fine on all platforms, and I could delete all the dirty hacks! Hope to test more soon :)

@vshymanskyy
Copy link
Contributor Author

I don't think ar depends on Python 3.11, as it's verified for versions back to 3.7.
However, I didn't test the code of my PR for such compatibility. I'll start by adding some tests to the CI

@vshymanskyy vshymanskyy force-pushed the mpy-ld-link-archives branch 6 times, most recently from 546005f to 6e66a21 Compare September 14, 2024 10:09
@jonnor
Copy link
Contributor

jonnor commented Sep 14, 2024

I do not know the details of ar. But on the default Python version on ubuntu-latest Docker image I got the following exception. The function file_digest was added to hashlib in Python 3.11, and updating to Python 3.11 resolved the issue.

  File "/home/runner/work/emlearn-micropython/emlearn-micropython/micropython/tools/mpy_ld.py", line 1060, in do_link
    archives.extend(ar_util.load_archive(item))
  File "/home/runner/work/emlearn-micropython/emlearn-micropython/micropython/tools/ar_util.py", line 221, in load_archive
    return [CachedArFile(fn)]
  File "/home/runner/work/emlearn-micropython/emlearn-micropython/micropython/tools/ar_util.py", line 92, in __init__
    info = self.load_symbols()
  File "/home/runner/work/emlearn-micropython/emlearn-micropython/micropython/tools/ar_util.py", line 61, in wrapper
    cache_key = key(*args, **kwargs)
  File "/home/runner/work/emlearn-micropython/emlearn-micropython/micropython/tools/ar_util.py", line 101, in _cache_key
    digest = hashlib.file_digest(f, "sha3_256")
AttributeError: module 'hashlib' has no attribute 'file_digest'

@vshymanskyy
Copy link
Contributor Author

vshymanskyy commented Sep 14, 2024

@jonnor yes, that was used in my code. But it's already fixed in the latest version of this PR.
I'm now fixing other CI tests.

@jonnor
Copy link
Contributor

jonnor commented Sep 14, 2024

@vshymanskyy thanks, can confirm that the lastest works with Python 3.10. It also builds all the modules in emlearn-micropython (5 different DSP/ML algorithms) on armv7m/armv7emsp/xtensawin. However one of the modules fails to find one symbol (out of many) on armv6m. See build log attached below

(venv) [jon@jon-thinkpad emlearn-micropython]$ make tinymaix_cnn.results ARCH=armv6m
make -C src/tinymaix_cnn/ ARCH=armv6m MPY_DIR=/home/jon/projects/micropython  V=1 clean dist
make[1]: Entering directory '/home/jon/projects/emlearn-micropython/src/tinymaix_cnn'
/bin/rm -rf build 
/bin/mkdir -p build/
GEN build/tinymaix_cnn.config.h
python3 /home/jon/projects/micropython/tools/mpy_ld.py '-vvv' --arch armv6m --preprocess -o build/tinymaix_cnn.config.h mod_cnn.c
CC mod_cnn.c
arm-none-eabi-gcc -I. -I/home/jon/projects/micropython -std=c99 -Os -Wall -Werror -DNDEBUG -DNO_QSTR -DMICROPY_ENABLE_DYNRUNTIME -DMP_CONFIGFILE='<build/tinymaix_cnn.config.h>' -fpic -fno-common -U _FORTIFY_SOURCE  -mthumb -mcpu=cortex-m0 -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_NONE  -I../../dependencies/TinyMaix/include -I../../dependencies/TinyMaix/src -Wno-error=unused-variable -Wno-error=multichar -o build/mod_cnn.o -c mod_cnn.c
In file included from mod_cnn.c:9:
../../dependencies/TinyMaix/src/tm_layers.c: In function 'tml_conv2d_dwconv2d':
../../dependencies/TinyMaix/src/tm_layers.c:159:26: warning: unused variable 's_step' [-Wunused-variable]
  159 |                 uint32_t s_step = (_ky1-_ky0)*(_kx1-_kx0);
      |                          ^~~~~~
In file included from mod_cnn.c:10:
../../dependencies/TinyMaix/src/tm_model.c: In function 'tm_load':
../../dependencies/TinyMaix/src/tm_model.c:20:26: warning: multi-character character constant [-Wmultichar]
   20 |     if(mdl_bin->magic != TM_MDL_MAGIC)   return TM_ERR_MAGIC;   //FIXME: big-endian not compatible
      |                          ^~~~~~~~~~~~
../../dependencies/TinyMaix/src/tm_model.c: In function 'tm_run':
../../dependencies/TinyMaix/src/tm_model.c:112:24: warning: unused variable 'l' [-Wunused-variable]
  112 |             tml_gap_t* l = (tml_gap_t*)(mdl->layer_body);
      |                        ^
../../dependencies/TinyMaix/src/tm_model.c:121:28: warning: unused variable 'l' [-Wunused-variable]
  121 |             tml_softmax_t* l = (tml_softmax_t*)(mdl->layer_body);
      |                            ^
../../dependencies/TinyMaix/src/tm_model.c:125:28: warning: unused variable 'l' [-Wunused-variable]
  125 |             tml_reshape_t* l = (tml_reshape_t*)(mdl->layer_body);
      |                            ^
LINK build/mod_cnn.o
python3 /home/jon/projects/micropython/tools/mpy_ld.py '-vvv' --arch armv6m --qstrs build/tinymaix_cnn.config.h -l/usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a -l/usr/arm-none-eabi/lib/thumb/v6-m/nofp/libm.a -o build/tinymaix_cnn.native.mpy build/mod_cnn.o
qstr vals: __del__, new, run, tinymaixcnn
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:mulsf3.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:truncdfsf2.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:extendsfdf2.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:floatsisf.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_fixunssfsi.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:subdf3.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:fixsfsi.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:addsf3.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_thumb1_case_uqi.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_divsi3.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_arm_cmpsf2.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:divsf3.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:subsf3.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_clzsi2.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_dvmd_tls.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:eqsf2.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:lesf2.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:gesf2.o
LinkError: /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_divsi3.o: undefined symbol: __aeabi_idiv0
make[1]: *** [/home/jon/projects/micropython/py/dynruntime.mk:155: build/tinymaix_cnn.native.mpy] Error 1
make[1]: Leaving directory '/home/jon/projects/emlearn-micropython/src/tinymaix_cnn'
make: *** [Makefile:26: dist/armv6m_6.3/tinymaix_cnn.mpy] Error 2

@vshymanskyy
Copy link
Contributor Author

@jonnor please try the latest version. These symbols are weak and mpy_ld never really handled them.
I added some basic support for weak symbols, but it should be addressed via a separate PR.

@vshymanskyy
Copy link
Contributor Author

The documentation and CI tests are added.

One test fails: esp32 port / build_idf (esp32_build_cmod_spiram_s2)
This is due to Cached ESP-IDF install. Once cache is dropped, all should work as expected.

@jonnor
Copy link
Contributor

jonnor commented Sep 14, 2024

@vshymanskyy I tested your latest commit now (git SHA 02d5060). The module above that failed to build now builds. However, at least one of the other modules now fails with a duplicate symbol:

(venv) [jon@jon-thinkpad emlearn-micropython]$ make emltrees.results ARCH=armv6m
make -C src/emltrees/ ARCH=armv6m MPY_DIR=/home/jon/projects/micropython  V=1 clean dist
make[1]: Entering directory '/home/jon/projects/emlearn-micropython/src/emltrees'
/bin/rm -rf build 
/bin/mkdir -p build/
GEN build/emltrees.config.h
python3 /home/jon/projects/micropython/tools/mpy_ld.py '-vvv' --arch armv6m --preprocess -o build/emltrees.config.h trees.c trees.py
CC trees.c
arm-none-eabi-gcc -I. -I/home/jon/projects/micropython -std=c99 -Os -Wall -Werror -DNDEBUG -DNO_QSTR -DMICROPY_ENABLE_DYNRUNTIME -DMP_CONFIGFILE='<build/emltrees.config.h>' -fpic -fno-common -U _FORTIFY_SOURCE  -mthumb -mcpu=cortex-m0 -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_NONE  -I/home/jon/projects/emlearn-micropython/venv/lib/python3.12/site-packages/emlearn -o build/trees.o -c trees.c
LINK build/trees.o
python3 /home/jon/projects/micropython/tools/mpy_ld.py '-vvv' --arch armv6m --qstrs build/emltrees.config.h -l/usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a -l/usr/arm-none-eabi/lib/thumb/v6-m/nofp/libm.a -o build/emltrees.native.mpy build/trees.o
qstr vals: __del__, addleaf, addnode, addroot, emltrees, new, predict, setdata
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_udivsi3.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_arm_cmpsf2.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:divsf3.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:addsf3.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:floatsisf.o
using /usr/lib/gcc/arm-none-eabi/14.1.0/thumb/v6-m/nofp/libgcc.a:_dvmd_tls.o
LinkError: duplicate symbol: __aeabi_idiv0
make[1]: *** [/home/jon/projects/micropython/py/dynruntime.mk:155: build/emltrees.native.mpy] Error 1
make[1]: Leaving directory '/home/jon/projects/emlearn-micropython/src/emltrees'
make: *** [Makefile:14: dist/armv6m_6.3/emltrees.mpy] Error 2

@vshymanskyy vshymanskyy force-pushed the mpy-ld-link-archives branch 2 times, most recently from 8677961 to b1b1294 Compare January 9, 2025 10:23
@vshymanskyy vshymanskyy force-pushed the mpy-ld-link-archives branch 2 times, most recently from e08c7a4 to b9293ca Compare January 9, 2025 11:10
@vshymanskyy vshymanskyy requested a review from dpgeorge January 10, 2025 11:55
Signed-off-by: Volodymyr Shymanskyy <[email protected]>
Copy link
Contributor

@projectgus projectgus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really impressive and useful, @vshymanskyy!

I have some minor inline comments, but as far as I'm concerned none of them are blockers for merging this as-is.

git clone --depth 1 --branch $IDF_VER https://github.com/espressif/esp-idf.git
# doing a treeless clone isn't quite as good as --shallow-submodules, but it
# is smaller than full clones and works when the submodule commit isn't a head.
git -C esp-idf submodule update --init --recursive --filter=tree:0
./esp-idf/install.sh
# Install additional packages into the IDF env
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Install additional packages into the IDF env
# Install additional packages for mpy_ld into the IDF env

@agatti
Copy link
Contributor

agatti commented Feb 7, 2025

You may also want to delete this fragment of tools/ci.sh as part of the PR since soft-float is supposed to be working out of the box for that specific native module.

micropython/tools/ci.sh

Lines 496 to 499 in e44a2c6

# features2 requires soft-float on armv7m and rv32imc.
if [ $arch != rv32imc ] && [ $arch != armv7m ]; then
make -C examples/natmod/features2 ARCH=$arch
fi

@jonnor
Copy link
Contributor

jonnor commented Mar 3, 2025

@projectgus @dpgeorge I believe @vshymanskyy said on Discord that he is rather preoccupied at the moment (the situation in Ukraine remains difficult). So I am sure he would appreciate it if anyone can help out with the small things remaining to get this unblocked and merged :)

@agatti
Copy link
Contributor

agatti commented Mar 3, 2025

If nobody else has time for this I can put the finishing touch to this PR to let it go through, the hard part is already done anyway.

@@ -1465,6 +1490,7 @@ def main():
cmd_parser.add_argument("--arch", default="x64", help="architecture")
cmd_parser.add_argument("--preprocess", action="store_true", help="preprocess source files")
cmd_parser.add_argument("--qstrs", default=None, help="file defining additional qstrs")
cmd_parser.add_argument("-l", dest="libs", action="append", help="Static .a libraries to link")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding a long form name --libs or --lib for this (as well as keeping -l).

And lowercase "static .a ..." to match the help of other commands.

@dpgeorge
Copy link
Member

dpgeorge commented Mar 4, 2025

@agatti yes please, if you could pick this up that would be great!

I guess you'll need to make a new PR because you won't have write access to this branch.

@agatti
Copy link
Contributor

agatti commented Mar 4, 2025

Right, I have a version that incorporates most of the changes requested in this PR - however the code changed a bit since the first time I tried and whilst this fully works on ARMv7m and normal soft-float stuff works on RV32 (adding MICROPY_FLOAT_IMPL=float to the command line), linking in functions from libm does not work on Picolibc (features2 was changed to reference roundf after I first tried it).

On Picolibc there's no real libm file to speak of, as the floating point code is in libc.a which is somewhere else. Usually this is not a problem, as the specs file adds the necessary includes to the GCC command line, but path resolution with --print-file-name doesn't seem to take the specs's extra paths into account. So, for example, $(CROSS)gcc -specs=<picolibc.specs path here> --print-file-name=libc.a won't find anything worthwhile even though normal compilation and linking works otherwise.

So, I can see two options for this:

  • either I keep working on this to make it work on Picolibc, delaying this even further
  • or I submit this with the new changes, making this official only for ARMv7m, while Picolibc support is added at a later date.

I'd go for the former option, so I can also look at #16042 if there aren't any takers for that one as well, (edit: looks like that is taken care of :)) but I need some input on this to better fit within 1.25.0's release schedule.

@dpgeorge
Copy link
Member

dpgeorge commented Mar 4, 2025

or I submit this with the new changes, making this official only for ARMv7m, while Picolibc support is added at a later date.

I'm pretty sure more than just ARMv7m is supported by this PR. According to the top post, there are many architectures supported. But that's with newlib, of course.

I'd go for the former option [keep working on this to make it work on Picolibc]

I suggest getting this PR across the line first, then adding Picolibc support as a follow up. That could all still fit within the 1.25.0 release. (I don't think the amount of work would be that different if you added Picolibc support together with this PR, or as a follow up, right?)

The release is at least one week away, so you have that much time, at least.

Note that this PR is not critical for the release and can be left until after the release if needed.

@agatti
Copy link
Contributor

agatti commented Mar 4, 2025

I'm pretty sure more than just ARMv7m is supported by this PR. According to the top post, there are many architectures supported. But that's with newlib, of course.

Indeed they're all newlib-based. The only other platform that requires soft-float is xtensa as far as I can see, but I'm looking at CI targets right now and natmods aren't built for it.

I suggest getting this PR across the line first, then adding Picolibc support as a follow up. That could all still fit within the 1.25.0 release. (I don't think the amount of work would be that different if you added Picolibc support together with this PR, or as a follow up, right?)

The amount of work would be the same, but I do have a chunk of contiguous free time available now so I'd rather finish this off in one go if I can. For other things and follow up PRs, I may or may not have the time for them in the next few weeks or so (although w.r.t. inline LX6/LX7 assembler I might have a business case for it so it's different). If I happen to not have the time I don't want to keep you folks waiting :)

The release is at least one week away, so you have that much time, at least.

Note that this PR is not critical for the release and can be left until after the release if needed.

Fair enough, I'll see what I can do then. I'll clean up newlib support and submit a PR so there's at least something that can be merged in the meantime.

@vshymanskyy
Copy link
Contributor Author

@agatti I'm pretty sure that -specs DOES have impact on --print-file-name.

@agatti
Copy link
Contributor

agatti commented Mar 4, 2025

@agatti I'm pretty sure that -specs DOES have impact on --print-file-name.

Glad you're here :) Anyway, I'm quite sure this doesn't seem to happen on the RV32 compiler version for Ubuntu 22.04, the same running in CI jobs - unless I'm missing something that is. Here's what's going on my environment:

[/micropython/examples/natmod/features2] $ riscv64-unknown-elf-gcc --version
riscv64-unknown-elf-gcc () 10.2.0

[/micropython/examples/natmod/features2] $ riscv64-unknown-elf-gcc -march=rv32imac -mabi=ilp32 --print-file-name=picolibc.specs
/usr/lib/gcc/riscv64-unknown-elf/10.2.0/picolibc.specs

[/micropython/examples/natmod/features2] $ riscv64-unknown-elf-gcc -specs=/usr/lib/gcc/riscv64-unknown-elf/10.2.0/picolibc.specs -march=rv32imac -mabi=ilp32 --print-file-name=libgcc.a
/usr/lib/gcc/riscv64-unknown-elf/10.2.0/rv32imac/ilp32/libgcc.a

[/micropython/examples/natmod/features2] $ riscv64-unknown-elf-gcc -specs=/usr/lib/gcc/riscv64-unknown-elf/10.2.0/picolibc.specs -march=rv32imac -mabi=ilp32 --print-file-name=libm.a  
libm.a

[/micropython/examples/natmod/features2] $ riscv64-unknown-elf-gcc -specs=/usr/lib/gcc/riscv64-unknown-elf/10.2.0/picolibc.specs -march=rv32imac -mabi=ilp32 --print-file-name=libc.a  
libc.a

[/micropython/examples/natmod/features2] $ cat /usr/lib/gcc/riscv64-unknown-elf/10.2.0/picolibc.specs
[...]
*link:
[...] -L%{-picolibc-prefix=*:%*/lib/picolibc/riscv64-unknown-elf/lib/%M;:/usr/lib/picolibc/riscv64-unknown-elf/lib/%M} [...]

[/micropython/examples/natmod/features2] $ ll /usr/lib/picolibc/riscv64-unknown-elf/lib/rv32imac/ilp32 
[...]
-rw-r--r-- 1 root root 9644250 Nov 19  2021 libc.a
[...]
-rw-r--r-- 1 root root    1444 Nov 19  2021 libm.a

I only have a picolibc-based environment on a Ubuntu 22.04 VM - I'll create a different environment to see if this is still the case elsewhere.

My changes to py/dynruntime.mk are as such, by the way:

diff --git i/py/dynruntime.mk w/py/dynruntime.mk
index 5592db5fa..95ddbfad1 100644
--- i/py/dynruntime.mk
+++ w/py/dynruntime.mk
@@ -112,7 +112,8 @@ CFLAGS_ARCH += -march=rv32imac -mabi=ilp32 -mno-relax
 # workarounds, always select Picolibc if available.
 PICOLIBC_SPECS = $(shell $(CROSS)gcc --print-file-name=picolibc.specs)
 ifneq ($(PICOLIBC_SPECS),picolibc.specs)
-CFLAGS_ARCH += --specs=$(PICOLIBC_SPECS)
+CFLAGS_ARCH += -specs=$(PICOLIBC_SPECS)
+USE_PICOLIBC := 1
 endif
 
 MICROPY_FLOAT_IMPL ?= none
@@ -125,8 +126,13 @@ MICROPY_FLOAT_IMPL_UPPER = $(shell echo $(MICROPY_FLOAT_IMPL) | tr '[:lower:]' '
 CFLAGS += $(CFLAGS_ARCH) -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER)
 
 ifeq ($(LINK_RUNTIME),1)
+ifeq ($(USE_PICOLIBC),1)
+LIBM_NAME := libc.a
+else
+LIBM_NAME := libm.a
+endif
 LIBGCC_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-libgcc-file-name))
-LIBM_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-file-name=libm.a))
+LIBM_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-file-name=$(LIBM_NAME)))
 MPY_LD_FLAGS += $(addprefix -l, $(LIBGCC_PATH) $(LIBM_PATH))
 endif

@agatti
Copy link
Contributor

agatti commented Mar 5, 2025

OK, unless I manage to get the specs-driven path resolution working in the CI environment (and if somebody can help...) I can at least get things to compile with this:

diff --git a/py/dynruntime.mk b/py/dynruntime.mk
index 5592db5fa..807befb46 100644
--- a/py/dynruntime.mk
+++ b/py/dynruntime.mk
@@ -110,9 +110,12 @@ CFLAGS_ARCH += -march=rv32imac -mabi=ilp32 -mno-relax
 # bare metal RISC-V toolchain with Picolibc rather than Newlib, and the default
 # is "nosys" so a value must be provided.  To avoid having per-distro
 # workarounds, always select Picolibc if available.
-PICOLIBC_SPECS = $(shell $(CROSS)gcc --print-file-name=picolibc.specs)
+PICOLIBC_SPECS := $(shell $(CROSS)gcc --print-file-name=picolibc.specs)
 ifneq ($(PICOLIBC_SPECS),picolibc.specs)
-CFLAGS_ARCH += --specs=$(PICOLIBC_SPECS)
+CFLAGS_ARCH += -specs=$(PICOLIBC_SPECS)
+USE_PICOLIBC := 1
+PICOLIBC_ARCH := rv32imac
+PICOLIBC_ABI := ilp32
 endif
 
 MICROPY_FLOAT_IMPL ?= none
@@ -125,8 +128,42 @@ MICROPY_FLOAT_IMPL_UPPER = $(shell echo $(MICROPY_FLOAT_IMPL) | tr '[:lower:]' '
 CFLAGS += $(CFLAGS_ARCH) -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER)
 
 ifeq ($(LINK_RUNTIME),1)
+# All of these picolibc-specific directives are here to work around a
+# limitation of Ubuntu 22.04's RISC-V bare metal toolchain.  In short, the
+# specific version of GCC in use (10.2.0) does not seem to take into account
+# extra paths provided by an explicitly passed specs file when performing name
+# resolution via `--print-file-name`.
+#
+# If Picolibc is used and libc.a fails to resolve, then said file's path will
+# be computed by searching the Picolibc libraries root for a libc.a file in a
+# subdirectory whose path is built using the current `-march` and `-mabi`
+# flags that are passed to GCC.  The `PICOLIBC_ROOT` environment variable is
+# checked to override the starting point for the library file search, and if
+# it is not set then the default value is used, assuming that this is running
+# on an Ubuntu 22.04 machine.
+#
+# This should be revised when the CI base image is updated to a newer Ubuntu
+# version (that hopefully contains a newer RISC-V compiler) or to another Linux
+# distribution.
+ifeq ($(USE_PICOLIBC),1)
+LIBM_NAME := libc.a
+else
+LIBM_NAME := libm.a
+endif
 LIBGCC_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-libgcc-file-name))
-LIBM_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-file-name=libm.a))
+LIBM_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-file-name=$(LIBM_NAME)))
+ifeq ($(USE_PICOLIBC),1)
+ifeq ($(LIBM_PATH),)
+# The CROSS toolchain prefix usually ends with a dash, but that may not be
+# always the case.  If the prefix ends with a dash it has to be taken out as
+# Picolibc's architecture directory won't have it in its name.  GNU Make does
+# not have any facility to perform character-level text manipulation so we
+# shell out to sed.
+CROSS_PREFIX := $(shell echo $(CROSS) | sed -e 's/-$$//')
+PICOLIBC_ROOT ?= /usr/lib/picolibc/$(CROSS_PREFIX)/lib
+LIBM_PATH := $(PICOLIBC_ROOT)/$(PICOLIBC_ARCH)/$(PICOLIBC_ABI)/$(LIBM_NAME)
+endif
+endif
 MPY_LD_FLAGS += $(addprefix -l, $(LIBGCC_PATH) $(LIBM_PATH))
 endif

Some assumptions are made, but they are at least documented. Is this approach good enough for this situation?

If the path resolution issues I'm seeing depend on the compiler version then my workaround won't be used, and if this happens to be run on a system that uses a "broken" compiler but with Picolibc installed elsewhere this should still be recoverable somewhat - I assume the user will look at the Makefile to at least edit the hardcoded path, see the workaround, and act accordingly in that case.

@dpgeorge
Copy link
Member

dpgeorge commented Mar 5, 2025

Some assumptions are made, but they are at least documented. Is this approach good enough for this situation?

Yes, that looks acceptable for a temporary solution. It won't affect newlib (right?) so that's good.

@agatti
Copy link
Contributor

agatti commented Mar 5, 2025

Yes, the workaround kicks in only if USE_PICOLIBC is set to 1 from inside the makefile itself, and right now only the RV32 target does that.

@dpgeorge
Copy link
Member

Merged via 16863, see commit 5197611

Thanks @vshymanskyy for this great addition, and @agatti for making the final steps!

@dpgeorge dpgeorge closed this Mar 17, 2025
@dubiousjim
Copy link
Contributor

dubiousjim commented Mar 19, 2025

Cool, I was just coming to this forum to ask into implementing this functionality. Glad that someone's already done the work of building it, and that it's already been reviewed and committed. Before I start experimenting with it, can I just ask for someone more familiar with the changes to say: is it known how this will work with musl? (I'm using Alpine Linux/armv7.)

Here's the issue that led me to hankering for this functionality.

@agatti
Copy link
Contributor

agatti commented Mar 19, 2025

is it known how this will work with musl? (I'm using Alpine Linux/armv7.)

You may need to modify py/dynruntime.mk to enable the same behaviour that is currently followed for Picolibc. Picolibc needs to process libc.a for making math functions available, but maybe you can leverage that to let the linker pick stack protection symbols up instead - there is no limitation on which symbols are linked from external libraries, as far as I know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
py-core Relates to py/ directory in source
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Software floating point unsupported in dynamic native .mpy modules
7 participants