From b9f52068d05ec2495fa33e50e6a742210b2fa5a1 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 9 Jan 2026 12:28:39 +0100 Subject: [PATCH 1/3] tools/ci.sh: Use an i686 cross-compiler for Unix/x86 bit builds. This commit forces usage of an i686 cross-compiler for 32-bits x86 Unix port builds, instead of relying on the "-m32" flag passed to the currently installed GCC version. Before this change it was not possible to build an x86 native module on anything but an x86/x64 machine, since the scripts used the generic "gcc" compiler installed on the system the code was built on. Recent Ubuntu versions (at least five years old now) provide a 32-bits x86 cross-compiler on all supported machines ("gcc-i686-linux-gnu" and "g++-i686-linux-gnu"), and since the CI uses Ubuntu as its base OS, that compiler is now used for x86 builds. Installing said cross-compiler, though, conflicts with the "gcc-multilib" and "g++-multilib" packages that are installed on the CI image as part of the 32-bits jobs setup procedure. This means that all 32-bits x86 builds have to be migrated to the new cross-compiler as well. Signed-off-by: Alessandro Gatti --- ports/unix/Makefile | 2 ++ py/dynruntime.mk | 4 ++-- tools/ci.sh | 27 ++++++++++++++++----------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 7d71fc3f955eb..8746d0b5efece 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -240,8 +240,10 @@ CFLAGS += -DMPZ_DIG_SIZE=16 # force 16 bits to work on both 32 and 64 bit archs endif ifeq ($(MICROPY_FORCE_32BIT),1) +ifeq ($(RUN_TESTS_MPY_CROSS_FLAGS),) RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-march=x86' endif +endif ifeq ($(CROSS_COMPILE),arm-linux-gnueabi-) # Force disable error text compression when compiling for ARM as the compiler diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 3902acbd0b32a..a8233fdfe7a5e 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -47,8 +47,8 @@ CLEAN_EXTRA += $(MOD).mpy .mpy_ld_cache ifeq ($(ARCH),x86) # x86 -CROSS = -CFLAGS_ARCH += -m32 -fno-stack-protector +CROSS = i686-linux-gnu- +CFLAGS_ARCH += -fno-stack-protector MICROPY_FLOAT_IMPL ?= double else ifeq ($(ARCH),x64) diff --git a/tools/ci.sh b/tools/ci.sh index b14d96d2ca47e..610c69324c582 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -625,7 +625,11 @@ CI_UNIX_OPTS_REPR_B=( CFLAGS_EXTRA="-DMICROPY_OBJ_REPR=MICROPY_OBJ_REPR_B -DMICROPY_PY_UCTYPES=0 -Dmp_int_t=int32_t -Dmp_uint_t=uint32_t" MICROPY_FORCE_32BIT=1 RUN_TESTS_MPY_CROSS_FLAGS="--mpy-cross-flags=\"-march=x86 -msmall-int-bits=30\"" +) +CI_UNIX_OPTS_X86=( + CROSS_COMPILE=i686-linux-gnu- + RUN_TESTS_MPY_CROSS_FLAGS=${RUN_TESTS_MPY_CROSS_FLAGS:-"--mpy-cross-flags=\"-march=x86\""} ) function ci_unix_build_helper { @@ -766,20 +770,20 @@ function ci_unix_coverage_run_native_mpy_tests { function ci_unix_32bit_setup { sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 + sudo apt-get install gcc-i686-linux-gnu g++-i686-linux-gnu patchelf libffi-dev:i386 python -m pip install pyelftools python -m pip install ar - gcc --version + i686-linux-gnu-gcc --version python3 --version } function ci_unix_coverage_32bit_build { - ci_unix_build_helper VARIANT=coverage MICROPY_FORCE_32BIT=1 - ci_unix_build_ffi_lib_helper gcc -m32 + ci_unix_build_helper VARIANT=coverage MICROPY_FORCE_32BIT=1 "${CI_UNIX_OPTS_X86[@]}" + ci_unix_build_ffi_lib_helper i686-linux-gnu-gcc } function ci_unix_coverage_32bit_run_tests { - ci_unix_run_tests_full_helper coverage MICROPY_FORCE_32BIT=1 + ci_unix_run_tests_full_helper coverage MICROPY_FORCE_32BIT=1 "${CI_UNIX_OPTS_X86[@]}" } function ci_unix_coverage_32bit_run_native_mpy_tests { @@ -787,8 +791,8 @@ function ci_unix_coverage_32bit_run_native_mpy_tests { } function ci_unix_nanbox_build { - ci_unix_build_helper VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1" - ci_unix_build_ffi_lib_helper gcc -m32 + ci_unix_build_helper VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1" "${CI_UNIX_OPTS_X86[@]}" + ci_unix_build_ffi_lib_helper i686-linux-gnu-gcc } function ci_unix_nanbox_run_tests { @@ -796,7 +800,8 @@ function ci_unix_nanbox_run_tests { } function ci_unix_longlong_build { - ci_unix_build_helper VARIANT=longlong "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" + ci_unix_build_helper VARIANT=longlong "${CI_UNIX_OPTS_SANITIZE_UNDEFINED[@]}" "${CI_UNIX_OPTS_X86[@]}" + patchelf --add-rpath "/usr/i686-linux-gnu/lib" ports/unix/build-longlong/micropython } function ci_unix_longlong_run_tests { @@ -969,14 +974,14 @@ function ci_unix_qemu_riscv64_run_tests { } function ci_unix_repr_b_build { - ci_unix_build_helper "${CI_UNIX_OPTS_REPR_B[@]}" - ci_unix_build_ffi_lib_helper gcc -m32 + ci_unix_build_helper "${CI_UNIX_OPTS_REPR_B[@]}" "${CI_UNIX_OPTS_X86[@]}" + ci_unix_build_ffi_lib_helper i686-linux-gnu-gcc } function ci_unix_repr_b_run_tests { # ci_unix_run_tests_full_no_native_helper is not used due to # https://github.com/micropython/micropython/issues/18105 - ci_unix_run_tests_helper "${CI_UNIX_OPTS_REPR_B[@]}" + ci_unix_run_tests_helper "${CI_UNIX_OPTS_REPR_B[@]}" "${CI_UNIX_OPTS_X86[@]}" } ######################################################################################## From e734fc80696626131e6072f3495e60eacdba9daa Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 9 Jan 2026 13:28:36 +0100 Subject: [PATCH 2/3] unix: Remove MICROPY_FORCE_32BIT flag usage. This commit removes support for forced 32-bits builds of the Unix port, as it only worked on x64 machines and x86 usage is low enough these days to allow making changes on how the latter target is built. Passing such flag to the Unix makefile will stop the build, as the flag itself will be removed from MicroPython entirely (right now only the MinGW Windows builds use it) in subsequent commits. CI build scripts had to be updated to manually pass the necessary flags that were once implied by MICROPY_FORCE_32BIT, for all targets that are meant to build 32-bits binaries. Finally, documentation for the Unix port has been updated with a section explaining how to make x86 builds on x64 hosts work again. Given that the 32-bits builds now use a cross-compiler, then other machines for which such a compiler is available can build x86 binaries too (eg. AArch64 or RISC-V 64). Signed-off-by: Alessandro Gatti --- ports/unix/Makefile | 20 ++---- ports/unix/README.md | 70 +++++++++++++++++-- .../unix/variants/longlong/mpconfigvariant.mk | 4 +- ports/unix/variants/nanbox/mpconfigvariant.mk | 3 +- tools/ci.sh | 5 +- 5 files changed, 77 insertions(+), 25 deletions(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 8746d0b5efece..0eee7b6984060 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -16,6 +16,10 @@ endif # If the build directory is not given, make it reflect the variant name. BUILD ?= build-$(VARIANT) +ifneq ($(MICROPY_FORCE_32BIT),) +$(warning *** The MICROPY_FORCE_32BIT flag is no longer affecting builds, please update your environment ***) +endif + include ../../py/mkenv.mk -include mpconfigport.mk include $(VARIANT_DIR)/mpconfigvariant.mk @@ -107,11 +111,7 @@ endif # while cross-compile ports require gcc, so we test here for OSX and # if necessary override the value of 'CC' set in py/mkenv.mk ifeq ($(UNAME_S),Darwin) -ifeq ($(MICROPY_FORCE_32BIT),1) -CC = clang -m32 -else CC = clang -endif # Use clang syntax for map file LDFLAGS_ARCH = -Wl,-map,$@.map -Wl,-dead_strip else @@ -174,11 +174,7 @@ ifeq ($(MICROPY_STANDALONE),1) GIT_SUBMODULES += lib/libffi DEPLIBS += libffi LIBFFI_CFLAGS := -I$(shell ls -1d $(BUILD)/lib/libffi/include) - ifeq ($(MICROPY_FORCE_32BIT),1) - LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib32/libffi.a - else - LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib/libffi.a - endif +LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib/libffi.a else # Use system version of libffi. LIBFFI_CFLAGS := $(shell pkg-config --cflags libffi) @@ -239,12 +235,6 @@ ifneq ($(FROZEN_MANIFEST),) CFLAGS += -DMPZ_DIG_SIZE=16 # force 16 bits to work on both 32 and 64 bit archs endif -ifeq ($(MICROPY_FORCE_32BIT),1) -ifeq ($(RUN_TESTS_MPY_CROSS_FLAGS),) -RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-march=x86' -endif -endif - ifeq ($(CROSS_COMPILE),arm-linux-gnueabi-) # Force disable error text compression when compiling for ARM as the compiler # cannot optimise out the giant strcmp list generated for MP_MATCH_COMPRESSED. diff --git a/ports/unix/README.md b/ports/unix/README.md index ee983a882cc83..5bb2479e18da0 100644 --- a/ports/unix/README.md +++ b/ports/unix/README.md @@ -5,9 +5,10 @@ The "unix" port runs in standard Unix-like environments including Linux, BSD, macOS, and Windows Subsystem for Linux. The x86 and x64 architectures are supported (i.e. x86 32- and 64-bit), as well -as ARM and MIPS. Extending the unix port to another architecture requires -writing some assembly code for the exception handling and garbage collection. -Alternatively, a fallback implementation based on setjmp/longjmp can be used. +as ARM, MIPS, and RISC-V. Extending the unix port to another architecture +requires writing some assembly code for the exception handling and garbage +collection. Alternatively, a fallback implementation based on setjmp/longjmp +can be used. Building -------- @@ -18,7 +19,7 @@ To build the unix port locally then you will need: * git command line executable, unless you downloaded a source .tar.xz file from https://micropython.org/download/ -* gcc (or clang for macOS) toolchain +* an appropriate gcc (or clang for macOS) toolchain for your target * GNU Make * Python 3.x @@ -181,3 +182,64 @@ Several classes of checks are disabled via compiler flags: check is intended to make sure locals in a "returned from" stack frame are not used. However, this mode interferes with various assumptions that MicroPython's stack checking, NLR, and GC rely on. + +### Notes about x86 (i686) support + +It used to be possible to create an x86 (32-bits) build on a x64 (64-bits) host +by passing `MICROPY_FORCE_32BIT=1` to `make`. That option was retired: x86 +usage has declined quite a bit in the past few years, and MicroPython now +supports at least one more mixed 32/64-bits target architecture for which +enabling `MICROPY_FORCE_32BIT` would make builds fail. + +x86 will be treated as a cross-compilation target from now on. This means you +will need to install a suitable compiler (on Ubuntu you can install the +`gcc-i686-linux-gnu` and `g++-i686-linux-gnu` packages, for example) and pass +the toolchain's command prefix to the `CROSS_COMPILE` command line variable. + +This change is also extended to native modules, for which you may need to pass +the toolchain's command prefix to the `CROSS` command line variable if your +x86 compiler cannot be invoked with `i686-linux-gnu-gcc`. + +Or, as an example: + +```bash +$ printf "%s %s %s %s\n" $(lsb_release -d | cut -f 2) $(uname -m) +Ubuntu 24.04.3 LTS x86_64 + +$ i686-linux-gnu-gcc --version | head -n 1 +i686-linux-gnu-gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 + +$ CROSS_COMPILE=i686-linux-gnu- make -C ports/unix +make: Entering directory '/ports/unix' +Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity. +... +LINK build-standard/micropython + text data bss dec hex filename + 718479 36016 2092 756587 b8b6b build-standard/micropython +make: Leaving directory '/ports/unix' + +$ file -b ports/unix/build-standard/micropython | cut -d, -f-2 +ELF 32-bit LSB pie executable, Intel 80386 + +# `i686-linux-gnu-` is the default prefix for x86 native modules now, it has +# been explicitly mentioned here in case you want to see how to set it. + +$ make -C examples/natmod/features0 ARCH=x86 CROSS=i686-linux-gnu- +make: Entering directory '/examples/natmod/features0' +GEN build/features0.config.h +CC features0.c +LINK build/features0.o +arch: EM_386 +text size: 196 +bss size: 0 +GOT entries: 4 +GEN features0.mpy +make: Leaving directory '/examples/natmod/features0' + +$ cd examples/natmod/features0 && ../../../ports/unix/build-standard/micropython +MicroPython v1.28.0-preview.63.g8189e9935a.dirty on 2026-01-09; linux [GCC 13.3.0] version +Type "help()" for more information. +>>> import features0 +>>> features0.factorial(10) +3628800 +``` diff --git a/ports/unix/variants/longlong/mpconfigvariant.mk b/ports/unix/variants/longlong/mpconfigvariant.mk index 2d2c3706469fb..9fe20cd584dee 100644 --- a/ports/unix/variants/longlong/mpconfigvariant.mk +++ b/ports/unix/variants/longlong/mpconfigvariant.mk @@ -1,7 +1,7 @@ # build interpreter with "bigints" implemented as "longlong" -# otherwise, small int is essentially 64-bit -MICROPY_FORCE_32BIT := 1 +# This needs to be built for a 32-bits target, otherwise small ints will +# essentially be 64-bit wide. MICROPY_PY_FFI := 0 diff --git a/ports/unix/variants/nanbox/mpconfigvariant.mk b/ports/unix/variants/nanbox/mpconfigvariant.mk index e588e657efc5b..6d63a86e9b0a6 100644 --- a/ports/unix/variants/nanbox/mpconfigvariant.mk +++ b/ports/unix/variants/nanbox/mpconfigvariant.mk @@ -1,3 +1,4 @@ # build interpreter with nan-boxing as object model (object repr D) -MICROPY_FORCE_32BIT = 1 +# This needs to be built for a 32-bits target, as object representation D is +# only meant to work on 32-bits machines. diff --git a/tools/ci.sh b/tools/ci.sh index 610c69324c582..63e902a791f29 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -623,7 +623,6 @@ CI_UNIX_OPTS_SANITIZE_UNDEFINED=( CI_UNIX_OPTS_REPR_B=( VARIANT=standard CFLAGS_EXTRA="-DMICROPY_OBJ_REPR=MICROPY_OBJ_REPR_B -DMICROPY_PY_UCTYPES=0 -Dmp_int_t=int32_t -Dmp_uint_t=uint32_t" - MICROPY_FORCE_32BIT=1 RUN_TESTS_MPY_CROSS_FLAGS="--mpy-cross-flags=\"-march=x86 -msmall-int-bits=30\"" ) @@ -778,12 +777,12 @@ function ci_unix_32bit_setup { } function ci_unix_coverage_32bit_build { - ci_unix_build_helper VARIANT=coverage MICROPY_FORCE_32BIT=1 "${CI_UNIX_OPTS_X86[@]}" + ci_unix_build_helper VARIANT=coverage "${CI_UNIX_OPTS_X86[@]}" ci_unix_build_ffi_lib_helper i686-linux-gnu-gcc } function ci_unix_coverage_32bit_run_tests { - ci_unix_run_tests_full_helper coverage MICROPY_FORCE_32BIT=1 "${CI_UNIX_OPTS_X86[@]}" + ci_unix_run_tests_full_helper coverage "${CI_UNIX_OPTS_X86[@]}" } function ci_unix_coverage_32bit_run_native_mpy_tests { From 095b38104676dc31a9137aa706a39184424590d6 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 9 Jan 2026 13:44:21 +0100 Subject: [PATCH 3/3] py/py.mk: Move MICROPY_FORCE_32BIT to the Windows port's Makefile. This commit moves the definition of the "MICROPY_FORCE_32BIT" build flag away from the core's Makefile definition, migrating it to the Windows port's Makefile instead. Both Windows and Unix ports used this flag, which was intrinsically x64 specific. Since the Unix port is no longer using it and the Windows port is currently limited to x86/x64 builds it makes more sense to move that flag's support away from MicroPython's core. This assures that MinGW builds will still operate as usual without any changes. Support for this flag will probably be removed from the Windows port (and therefore from MicroPython) when Windows/Arm builds will show up. Signed-off-by: Alessandro Gatti --- ports/windows/Makefile | 7 +++++++ py/py.mk | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ports/windows/Makefile b/ports/windows/Makefile index 6e206e7f23cb1..82fa25f21a43f 100644 --- a/ports/windows/Makefile +++ b/ports/windows/Makefile @@ -29,6 +29,13 @@ PROG ?= micropython QSTR_DEFS += ../unix/qstrdefsport.h QSTR_GLOBAL_DEPENDENCIES += $(VARIANT_DIR)/mpconfigvariant.h +# Enable building 32-bit code on 64-bit hosts. +ifeq ($(MICROPY_FORCE_32BIT),1) +CC += -m32 +CXX += -m32 +LD += -m32 +endif + # include py core make definitions include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk diff --git a/py/py.mk b/py/py.mk index b37e3cf579800..4f17bdc29da13 100644 --- a/py/py.mk +++ b/py/py.mk @@ -21,13 +21,6 @@ QSTR_GLOBAL_REQUIREMENTS += $(HEADER_BUILD)/mpversion.h # some code is performance bottleneck and compiled with other optimization options CSUPEROPT = -O3 -# Enable building 32-bit code on 64-bit host. -ifeq ($(MICROPY_FORCE_32BIT),1) -CC += -m32 -CXX += -m32 -LD += -m32 -endif - # External modules written in C. ifneq ($(USER_C_MODULES),) # pre-define USERMOD variables as expanded so that variables are immediate