Skip to content

Commit 5197611

Browse files
vshymanskyyagatti
authored andcommitted
tools/mpy_ld.py: Allow linking static libraries.
This commit introduces an additional symbol resolution mechanism to the natmod linking process. This allows the build scripts to look for required symbols into selected libraries that are provided by the compiler installation (libgcc and libm at the moment). For example, using soft-float code in natmods, whilst technically possible, was not an easy process and required some additional work to pull it off. With this addition all the manual (and error-prone) operations have been automated and folded into `tools/mpy_ld.py`. Both newlib and picolibc toolchains are supported, albeit the latter may require a bit of extra configuration depending on the environment the build process runs on. Picolibc's soft-float functions aren't in libm - in fact the shipped libm is nothing but a stub - but they are inside libc. This is usually not a problem as these changes cater for that configuration quirk, but on certain compilers the include paths used to find libraries in may not be updated to take Picolibc's library directory into account. The bare metal RISC-V compiler shipped with the CI OS image (GCC 10.2.0 on Ubuntu 22.04LTS) happens to exhibit this very problem. To work around that for CI builds, the Picolibc libraries' path is hardcoded in the Makefile directives used by the linker, but this can be changed by setting the PICOLIBC_ROOT environment library when building natmods. Signed-off-by: Volodymyr Shymanskyy <[email protected]> Co-authored-by: Alessandro Gatti <[email protected]>
1 parent f187c77 commit 5197611

File tree

7 files changed

+440
-82
lines changed

7 files changed

+440
-82
lines changed

docs/develop/natmod.rst

+7
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ The known limitations are:
7070
So, if your C code has writable data, make sure the data is defined globally,
7171
without an initialiser, and only written to within functions.
7272

73+
The native module is not automatically linked against the standard static libraries
74+
like ``libm.a`` and ``libgcc.a``, which can lead to ``undefined symbol`` errors.
75+
You can link the runtime libraries by setting ``LINK_RUNTIME = 1``
76+
in your Makefile. Custom static libraries can also be linked by adding
77+
``MPY_LD_FLAGS += -l path/to/library.a``. Note that these are linked into
78+
the native module and will not be shared with other modules or the system.
79+
7380
Linker limitation: the native module is not linked against the symbol table of the
7481
full MicroPython firmware. Rather, it is linked against an explicit table of exported
7582
symbols found in ``mp_fun_table`` (in ``py/nativeglue.h``), that is fixed at firmware

examples/natmod/features2/Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@ SRC = main.c prod.c test.py
1010
# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin)
1111
ARCH = x64
1212

13+
# Link with libm.a and libgcc.a from the toolchain
14+
LINK_RUNTIME = 1
15+
1316
# Include to get the rules for compiling and linking the module
1417
include $(MPY_DIR)/py/dynruntime.mk

examples/natmod/features2/main.c

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* This example demonstrates the following features in a native module:
22
- using floats
3+
- calling math functions from libm.a
34
- defining additional code in Python (see test.py)
45
- have extra C code in a separate file (see prod.c)
56
*/
@@ -10,6 +11,9 @@
1011
// Include the header for auxiliary C code for this module
1112
#include "prod.h"
1213

14+
// Include standard library header
15+
#include <math.h>
16+
1317
// Automatically detect if this module should include double-precision code.
1418
// If double precision is supported by the target architecture then it can
1519
// be used in native module regardless of what float setting the target
@@ -41,6 +45,12 @@ static mp_obj_t add_d(mp_obj_t x, mp_obj_t y) {
4145
static MP_DEFINE_CONST_FUN_OBJ_2(add_d_obj, add_d);
4246
#endif
4347

48+
// A function that uses libm
49+
static mp_obj_t call_round(mp_obj_t x) {
50+
return mp_obj_new_float_from_f(roundf(mp_obj_get_float_to_f(x)));
51+
}
52+
static MP_DEFINE_CONST_FUN_OBJ_1(round_obj, call_round);
53+
4454
// A function that computes the product of floats in an array.
4555
// This function uses the most general C argument interface, which is more difficult
4656
// to use but has access to the globals dict of the module via self->globals.
@@ -74,6 +84,7 @@ mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *a
7484
#if USE_DOUBLE
7585
mp_store_global(MP_QSTR_add_d, MP_OBJ_FROM_PTR(&add_d_obj));
7686
#endif
87+
mp_store_global(MP_QSTR_round, MP_OBJ_FROM_PTR(&round_obj));
7788

7889
// The productf function uses the most general C argument interface
7990
mp_store_global(MP_QSTR_productf, MP_DYNRUNTIME_MAKE_FUNCTION(productf));

py/dynruntime.mk

+62-19
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@ CFLAGS += -Wall -Werror -DNDEBUG
2929
CFLAGS += -DNO_QSTR
3030
CFLAGS += -DMICROPY_ENABLE_DYNRUNTIME
3131
CFLAGS += -DMP_CONFIGFILE='<$(CONFIG_H)>'
32-
CFLAGS += -fpic -fno-common
33-
CFLAGS += -U _FORTIFY_SOURCE # prevent use of __*_chk libc functions
34-
#CFLAGS += -fdata-sections -ffunction-sections
32+
33+
CFLAGS_ARCH += -fpic -fno-common
34+
CFLAGS_ARCH += -U_FORTIFY_SOURCE # prevent use of __*_chk libc functions
35+
#CFLAGS_ARCH += -fdata-sections -ffunction-sections
3536

3637
MPY_CROSS_FLAGS += -march=$(ARCH)
3738

3839
SRC_O += $(addprefix $(BUILD)/, $(patsubst %.c,%.o,$(filter %.c,$(SRC))) $(patsubst %.S,%.o,$(filter %.S,$(SRC))))
3940
SRC_MPY += $(addprefix $(BUILD)/, $(patsubst %.py,%.mpy,$(filter %.py,$(SRC))))
4041

41-
CLEAN_EXTRA += $(MOD).mpy
42+
CLEAN_EXTRA += $(MOD).mpy .mpy_ld_cache
4243

4344
################################################################################
4445
# Architecture configuration
@@ -47,72 +48,74 @@ ifeq ($(ARCH),x86)
4748

4849
# x86
4950
CROSS =
50-
CFLAGS += -m32 -fno-stack-protector
51+
CFLAGS_ARCH += -m32 -fno-stack-protector
5152
MICROPY_FLOAT_IMPL ?= double
5253

5354
else ifeq ($(ARCH),x64)
5455

5556
# x64
5657
CROSS =
57-
CFLAGS += -fno-stack-protector
58+
CFLAGS_ARCH += -fno-stack-protector
5859
MICROPY_FLOAT_IMPL ?= double
5960

6061
else ifeq ($(ARCH),armv6m)
6162

6263
# thumb
6364
CROSS = arm-none-eabi-
64-
CFLAGS += -mthumb -mcpu=cortex-m0
65+
CFLAGS_ARCH += -mthumb -mcpu=cortex-m0
6566
MICROPY_FLOAT_IMPL ?= none
6667

6768
else ifeq ($(ARCH),armv7m)
6869

6970
# thumb
7071
CROSS = arm-none-eabi-
71-
CFLAGS += -mthumb -mcpu=cortex-m3
72+
CFLAGS_ARCH += -mthumb -mcpu=cortex-m3
7273
MICROPY_FLOAT_IMPL ?= none
7374

7475
else ifeq ($(ARCH),armv7emsp)
7576

7677
# thumb
7778
CROSS = arm-none-eabi-
78-
CFLAGS += -mthumb -mcpu=cortex-m4
79-
CFLAGS += -mfpu=fpv4-sp-d16 -mfloat-abi=hard
79+
CFLAGS_ARCH += -mthumb -mcpu=cortex-m4
80+
CFLAGS_ARCH += -mfpu=fpv4-sp-d16 -mfloat-abi=hard
8081
MICROPY_FLOAT_IMPL ?= float
8182

8283
else ifeq ($(ARCH),armv7emdp)
8384

8485
# thumb
8586
CROSS = arm-none-eabi-
86-
CFLAGS += -mthumb -mcpu=cortex-m7
87-
CFLAGS += -mfpu=fpv5-d16 -mfloat-abi=hard
87+
CFLAGS_ARCH += -mthumb -mcpu=cortex-m7
88+
CFLAGS_ARCH += -mfpu=fpv5-d16 -mfloat-abi=hard
8889
MICROPY_FLOAT_IMPL ?= double
8990

9091
else ifeq ($(ARCH),xtensa)
9192

9293
# xtensa
9394
CROSS = xtensa-lx106-elf-
94-
CFLAGS += -mforce-l32
95+
CFLAGS_ARCH += -mforce-l32
9596
MICROPY_FLOAT_IMPL ?= none
9697

9798
else ifeq ($(ARCH),xtensawin)
9899

99100
# xtensawin
100101
CROSS = xtensa-esp32-elf-
101-
CFLAGS +=
102102
MICROPY_FLOAT_IMPL ?= float
103103

104104
else ifeq ($(ARCH),rv32imc)
105105

106106
# rv32imc
107107
CROSS = riscv64-unknown-elf-
108-
CFLAGS += -march=rv32imac -mabi=ilp32 -mno-relax
108+
CFLAGS_ARCH += -march=rv32imac -mabi=ilp32 -mno-relax
109109
# If Picolibc is available then select it explicitly. Ubuntu 22.04 ships its
110110
# bare metal RISC-V toolchain with Picolibc rather than Newlib, and the default
111111
# is "nosys" so a value must be provided. To avoid having per-distro
112112
# workarounds, always select Picolibc if available.
113-
PICOLIBC_SPECS = $(shell $(CROSS)gcc --print-file-name=picolibc.specs)
113+
PICOLIBC_SPECS := $(shell $(CROSS)gcc --print-file-name=picolibc.specs)
114114
ifneq ($(PICOLIBC_SPECS),picolibc.specs)
115-
CFLAGS += --specs=$(PICOLIBC_SPECS)
115+
CFLAGS_ARCH += -specs=$(PICOLIBC_SPECS)
116+
USE_PICOLIBC := 1
117+
PICOLIBC_ARCH := rv32imac
118+
PICOLIBC_ABI := ilp32
116119
endif
117120

118121
MICROPY_FLOAT_IMPL ?= none
@@ -122,7 +125,47 @@ $(error architecture '$(ARCH)' not supported)
122125
endif
123126

124127
MICROPY_FLOAT_IMPL_UPPER = $(shell echo $(MICROPY_FLOAT_IMPL) | tr '[:lower:]' '[:upper:]')
125-
CFLAGS += -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER)
128+
CFLAGS += $(CFLAGS_ARCH) -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER)
129+
130+
ifeq ($(LINK_RUNTIME),1)
131+
# All of these picolibc-specific directives are here to work around a
132+
# limitation of Ubuntu 22.04's RISC-V bare metal toolchain. In short, the
133+
# specific version of GCC in use (10.2.0) does not seem to take into account
134+
# extra paths provided by an explicitly passed specs file when performing name
135+
# resolution via `--print-file-name`.
136+
#
137+
# If Picolibc is used and libc.a fails to resolve, then said file's path will
138+
# be computed by searching the Picolibc libraries root for a libc.a file in a
139+
# subdirectory whose path is built using the current `-march` and `-mabi`
140+
# flags that are passed to GCC. The `PICOLIBC_ROOT` environment variable is
141+
# checked to override the starting point for the library file search, and if
142+
# it is not set then the default value is used, assuming that this is running
143+
# on an Ubuntu 22.04 machine.
144+
#
145+
# This should be revised when the CI base image is updated to a newer Ubuntu
146+
# version (that hopefully contains a newer RISC-V compiler) or to another Linux
147+
# distribution.
148+
ifeq ($(USE_PICOLIBC),1)
149+
LIBM_NAME := libc.a
150+
else
151+
LIBM_NAME := libm.a
152+
endif
153+
LIBGCC_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-libgcc-file-name))
154+
LIBM_PATH := $(realpath $(shell $(CROSS)gcc $(CFLAGS) --print-file-name=$(LIBM_NAME)))
155+
ifeq ($(USE_PICOLIBC),1)
156+
ifeq ($(LIBM_PATH),)
157+
# The CROSS toolchain prefix usually ends with a dash, but that may not be
158+
# always the case. If the prefix ends with a dash it has to be taken out as
159+
# Picolibc's architecture directory won't have it in its name. GNU Make does
160+
# not have any facility to perform character-level text manipulation so we
161+
# shell out to sed.
162+
CROSS_PREFIX := $(shell echo $(CROSS) | sed -e 's/-$$//')
163+
PICOLIBC_ROOT ?= /usr/lib/picolibc/$(CROSS_PREFIX)/lib
164+
LIBM_PATH := $(PICOLIBC_ROOT)/$(PICOLIBC_ARCH)/$(PICOLIBC_ABI)/$(LIBM_NAME)
165+
endif
166+
endif
167+
MPY_LD_FLAGS += $(addprefix -l, $(LIBGCC_PATH) $(LIBM_PATH))
168+
endif
126169

127170
CFLAGS += $(CFLAGS_EXTRA)
128171

@@ -165,7 +208,7 @@ $(BUILD)/%.mpy: %.py
165208
# Build native .mpy from object files
166209
$(BUILD)/$(MOD).native.mpy: $(SRC_O)
167210
$(ECHO) "LINK $<"
168-
$(Q)$(MPY_LD) --arch $(ARCH) --qstrs $(CONFIG_H) -o $@ $^
211+
$(Q)$(MPY_LD) --arch $(ARCH) --qstrs $(CONFIG_H) $(MPY_LD_FLAGS) -o $@ $^
169212

170213
# Build final .mpy from all intermediate .mpy files
171214
$(MOD).mpy: $(BUILD)/$(MOD).native.mpy $(SRC_MPY)

0 commit comments

Comments
 (0)