Skip to content

unix: Remove usages of the MICROPY_FORCE_32BIT flag.#18667

Open
agatti wants to merge 3 commits into
micropython:masterfrom
agatti:retire-force32bit
Open

unix: Remove usages of the MICROPY_FORCE_32BIT flag.#18667
agatti wants to merge 3 commits into
micropython:masterfrom
agatti:retire-force32bit

Conversation

@agatti
Copy link
Copy Markdown
Contributor

@agatti agatti commented Jan 9, 2026

Summary

This PR removes usage of the MICROPY_FORCE_32BIT build flag for the Unix port, and forces cross-compilation for x86 targets in doing so.

MICROPY_FORCE_32BIT has always been x64-specific and right now there is support for one more mixed 32/64-bits architecture (RISC-V) on which such flag would make builds fail. Since x86 usage has declined over the years, this PR also treats x86 as a cross-compilation target, for which a separate toolchain is needed.

Now x86 builds on non-x86 hosts require having a CROSS_COMPILE variable passed to the makefile to force building an x86 binary. This applies to native modules as well, so an i686 cross-compilation toolchain is now needed to build x86 native modules. This also allows building x86 native modules on non-x86/x64 hosts, as long as the proper toolchain is installed. Documentation for the Unix port has updated accordingly to show how to create x86 builds with these changes.

CI jobs had to be updated to force usage of the new cross-compiler for 32-bits builds. This means that on Ubuntu the gcc-i686-linux-gnu and g++-i686-linux-gnu packages need to be installed instead of gcc-multilib and g++-multilib.

MICROPY_FORCE_32BIT hasn't been completely removed from MicroPython, though. It is also used by the Windows port to create MinGW builds, and since it is the last remaining target needing such a thing and being limited to x86/x64 targets anyway it is not much of a deal to limit the flag support to that specific port.

This is one less roadblock in having the Unix port CI test suite run on AArch64-based runners (the only build-related issue is forcing usage of x86_64-linux-gnu- as the compiler for x64 native modules and on non-x64 hosts for an x64 QEMU-based target).

Testing

Besides manual builds, this was successfully built and tested by CI on my own branch.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.47%. Comparing base (44a569b) to head (095b381).

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #18667   +/-   ##
=======================================
  Coverage   98.47%   98.47%           
=======================================
  Files         176      176           
  Lines       22845    22845           
=======================================
  Hits        22497    22497           
  Misses        348      348           

☔ 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.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jan 9, 2026

Code size report:

Reference:  rp2/CMakeLists.txt: Require boards to define PICO_FLASH_SIZE_BYTES. [44a569b]
Comparison: py/py.mk: Move MICROPY_FORCE_32BIT to the Windows port's Makefile. [merge of 095b381]
  mpy-cross:    +0 +0.000% 
   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
      esp32:    +0 +0.000% ESP32_GENERIC
     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

@dlech
Copy link
Copy Markdown
Contributor

dlech commented Jan 10, 2026

Why can't we remove it on Windows as well?

@agatti
Copy link
Copy Markdown
Contributor Author

agatti commented Jan 10, 2026

Why can't we remove it on Windows as well?

All the supported build methods mentioned in ports/windows/README.md for which MICROPY_FORCE_32BIT actually does something are only for x86/x64, and MICROPY_FORCE_32BIT works only on x86/x64. Whilst it's true that MinGW for Arm64 exists (https://github.com/mstorsjo/llvm-mingw/releases has native toolchains for Arm64), I don't think it's available as a prepackaged cross-compiler that can be installed on a Ubuntu host right now.

If somebody adds support for MinGW/Cygwin/MSYS2 Arm64 builds, then this option stops being relevant and it should be retired. Until then, it's doing what it's supposed to, so why removing it? :)

@dpgeorge
Copy link
Copy Markdown
Member

dpgeorge commented Jun 1, 2026

This looks like a very sensible thing to do, to generalize x86 (32-bit) support to a cross-compilation target. That cleans things up nicely.

But from a practical point of view I wonder why we need to install yet another toolchain when -m32 does the job? Being on Arch Linux this is going to be a little awkward, eg it looks like it needs the AUR package https://aur.archlinux.org/packages/i686-elf-gcc (and maybe related binutils). I'll have to check how to actually get this PR working on Arch.

@agatti
Copy link
Copy Markdown
Contributor Author

agatti commented Jun 1, 2026

As far as I know, an x86_64 toolchain is not guaranteed to also compile code for x86 since it can be built to target a limited subset of CPUs (ie. not having -m32). Granted, this is probably not the case for commonly packaged toolchains right now, but with a dedicated i686 toolchain the problem is simply not there.

Another possibility is to replace i686-linux-gnu- with x86_64-linux-gnu- in CI but that simply moves the complexity elsewhere to conditionally add -m32 to the Unix port Makefiles if the compiler actually supports such flag. I'd say the relative lack of demand for x86 code outweighs starting to implement compiler checks on every make invocation.

A separate i686 toolchain is also just one step towards having host-neutral CI environments, which would have been a follow-up PR. -m32 only works for x86_64-linux-gnu-, which is also invoked as raw gcc in the CI scripts. Also, Arm64 hosts are getting increasingly popular and VPS boxes running on Arm64 can be significantly cheaper than their x64 counterparts if you're not chasing the performance :)

agatti added 3 commits June 1, 2026 19:53
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 <a.gatti@frob.it>
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 <a.gatti@frob.it>
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 <a.gatti@frob.it>
@agatti agatti force-pushed the retire-force32bit branch from c24f8b6 to 095b381 Compare June 1, 2026 17:54
@agatti
Copy link
Copy Markdown
Contributor Author

agatti commented Jun 1, 2026

I've rebased the PR to remove conflicts, just in case.

@dpgeorge
Copy link
Copy Markdown
Member

dpgeorge commented Jun 4, 2026

I've tried to get this PR working on Arch Linux. First I tried building the i686-elf-gcc AUR package, but I ran into C++ build errors like this:

../../gcc-15.2.0/libcody/buffer.cc: In member function ‘void Cody::Detail::MessageBuffer::LexedLine(std::string&)’:
../../gcc-15.2.0/libcody/buffer.cc:381:33: error: no matching function for call to ‘S2C(const char8_t [2])’
  381 |         if (buffer[pos-1] == S2C(u8"\n"))
      |                              ~~~^~~~~~~~
  • there is 1 candidate
    • candidate 1: ‘template<unsigned int I> constexpr char Cody::Detail::S2C(const char (&)[I])’
      ../../gcc-15.2.0/libcody/cody.hh:51:16:
         51 | constexpr char S2C (char const (&s)[I])
            |                ^~~
      • template argument deduction/substitution failed:
        •   mismatched types ‘const char’ and ‘const char8_t’
          ../../gcc-15.2.0/libcody/buffer.cc:381:33:
            381 |         if (buffer[pos-1] == S2C(u8"\n"))
                |                              ~~~^~~~~~~~

That's not very interesting, and I really don't know where to start trying to fix that.

So I moved on and thought to use clang instead, which comes already with support for many targets. That worked well:

$ make CC=clang CFLAGS_EXTRA='--target=i686-unknown-linux-gnu' LDFLAGS_EXTRA='--target=i686-unknown-linux-gnu' -j8
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
 847317	  34128	   2020	 883465	  d7b09	build-standard/micropython
$ file ./build-standard/micropython
./build-standard/micropython: ELF 32-bit LSB pie executable, Intel i386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=576b84222cf6936d8547b7b7faeea058b97630bb, for GNU/Linux 4.4.0, stripped

Then running it looks good:

$ ./build-standard/micropython
MicroPython v1.29.0-preview.348.g2007e6124d.dirty on 2026-06-04; linux [Clang 22.1.6] version
Type "help()" for more information.
>>> import sys
>>> sys.platform
'linux'
>>> sys.implementation
(name='micropython', version=(1, 29, 0, 'preview'), _machine='linux [Clang 22.1.6] version', _mpy=1798, _build='standard', _thread='unsafe')
>>> sys.maxsize
2147483647
>>>

Also, all tests pass (using just ./run-tests.py) and the test running detects the platform as x86.


Anyway, I do really like this PR, and I think that's acceptable in the case you want to use clang instead of gcc to cross compile it. But maybe those instructions for clang and the --target=i686-unknown-linux-gnu options could be added to the README? After all, we did prior to this PR support using clang and the MICROPY_FORCE_32BIT option.

@agatti
Copy link
Copy Markdown
Contributor Author

agatti commented Jun 4, 2026

Glad you got it to work in the end. Updating the documentation to also mention clang is a more than reasonable request and in fact I've done so in my unpublished branch.

However, natmods won't build with it :| The compiler is hardcoded to expect a GCC-style toolchain triplet cross-compilation string and expects to use GCC as a compiler [1]. That can be made it work with Clang and a similar command line set with [2], but then it complains about symbol redefinitions [3].

If you're OK with it, I'll tackle the py/dynruntime.mk changes and symbols redefinition issue in a different PR as that's probably just a minor change in tools/mpy_ld.py (just wrap all affected typedefs with ifndef/endif guards), and the makefile changes have already been written anyway. Then I'll get back to this so it'll have more accurate docs.


[1]

$ make CC=clang CFLAGS_EXTRA='--target=i686-unknown-linux-gnu' LDFLAGS_EXTRA='--target=i686-unknown-linux-gnu' ARCH=x86 -C examples/natmod/features0 V=1
make: Entering directory '/micropython/examples/natmod/features0'
CC features0.c
i686-linux-gnu-gcc -I. -I../../.. -std=c99 -Os -Wall -Werror -DNDEBUG -DNO_QSTR -DMICROPY_ENABLE_DYNRUNTIME -DMP_CONFIGFILE='<build/features0.config.h>' -fpic -fno-common -U_FORTIFY_SOURCE  -fno-stack-protector -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_DOUBLE --target=i686-unknown-linux-gnu -o build/features0.o -c features0.c
i686-linux-gnu-gcc: error: unrecognized command-line option ‘--target=i686-unknown-linux-gnu’
make: *** [../../../py/dynruntime.mk:227: build/features0.o] Error 1
make: Leaving directory '/micropython/examples/natmod/features0'

[2]

diff --git i/py/dynruntime.mk w/py/dynruntime.mk
index a8233fdfe..39e1187fd 100644
--- i/py/dynruntime.mk
+++ w/py/dynruntime.mk
@@ -2,6 +2,7 @@
 # MPY_DIR must be set to the top of the MicroPython source tree
 
 BUILD ?= build
+CC ?= gcc
 
 ECHO = @echo
 RM = /bin/rm
@@ -47,7 +48,7 @@ CLEAN_EXTRA += $(MOD).mpy .mpy_ld_cache
 ifeq ($(ARCH),x86)
 
 # x86
-CROSS = i686-linux-gnu-
+CROSS ?= i686-linux-gnu-
 CFLAGS_ARCH += -fno-stack-protector
 MICROPY_FLOAT_IMPL ?= double
 
@@ -110,7 +111,7 @@ 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)$(CC) --print-file-name=picolibc.specs)
 ifneq ($(PICOLIBC_SPECS),picolibc.specs)
 CFLAGS_ARCH += -specs=$(PICOLIBC_SPECS)
 USE_PICOLIBC := 1
@@ -129,7 +130,7 @@ CFLAGS_ARCH += -march=rv64imac -mabi=lp64 -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)$(CC) --print-file-name=picolibc.specs)
 ifneq ($(PICOLIBC_SPECS),picolibc.specs)
 CFLAGS_ARCH += -specs=$(PICOLIBC_SPECS)
 USE_PICOLIBC := 1
@@ -143,7 +144,7 @@ else
 $(error architecture '$(ARCH)' not supported)
 endif
 
-ifneq ($(findstring -musl,$(shell $(CROSS)gcc -dumpmachine)),)
+ifneq ($(findstring -musl,$(shell $(CROSS)$(CC) -dumpmachine)),)
 USE_MUSL := 1
 endif
 
@@ -175,8 +176,8 @@ 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_NAME)))
+LIBGCC_PATH := $(realpath $(shell $(CROSS)$(CC) $(CFLAGS) --print-libgcc-file-name))
+LIBM_PATH := $(realpath $(shell $(CROSS)$(CC) $(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
@@ -224,12 +225,12 @@ $(CONFIG_H): $(SRC)
 # Build .o from .c source files
 $(BUILD)/%.o: %.c $(CONFIG_H) Makefile
 	$(ECHO) "CC $<"
-	$(Q)$(CROSS)gcc $(CFLAGS) -o $@ -c $<
+	$(Q)$(CROSS)$(CC) $(CFLAGS) -o $@ -c $<
 
 # Build .o from .S source files
 $(BUILD)/%.o: %.S $(CONFIG_H) Makefile
 	$(ECHO) "AS $<"
-	$(Q)$(CROSS)gcc $(CFLAGS) -o $@ -c $<
+	$(Q)$(CROSS)$(CC) $(CFLAGS) -o $@ -c $<
 
 # Build .mpy from .py source files
 $(BUILD)/%.mpy: %.py

[3]

$ CROSS= make CC=clang CFLAGS_EXTRA='--target=i686-unknown-linux-gnu' LDFLAGS_EXTRA='--target=i686-unknown-linux-gnu' ARCH=x86 -C examples/natmod/features0 V=1
make: Entering directory '/micropython/examples/natmod/features0'
CC features0.c
clang -I. -I../../.. -std=c99 -Os -Wall -Werror -DNDEBUG -DNO_QSTR -DMICROPY_ENABLE_DYNRUNTIME -DMP_CONFIGFILE='<build/features0.config.h>' -fpic -fno-common -U_FORTIFY_SOURCE  -fno-stack-protector -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_DOUBLE --target=i686-unknown-linux-gnu -o build/features0.o -c features0.c
In file included from features0.c:8:
In file included from ../../../py/dynruntime.h:40:
In file included from ../../../py/binary.h:30:
In file included from ../../../py/obj.h:31:
../../../py/mpconfig.h:195:18: error: redefinition of typedef 'mp_int_t' is a C11 feature [-Werror,-Wtypedef-redefinition]
  195 | typedef intptr_t mp_int_t;
      |                  ^
./build/features0.config.h:3:18: note: previous definition is here
    3 | typedef intptr_t mp_int_t;
      |                  ^
In file included from features0.c:8:
In file included from ../../../py/dynruntime.h:40:
In file included from ../../../py/binary.h:30:
In file included from ../../../py/obj.h:31:
../../../py/mpconfig.h:196:19: error: redefinition of typedef 'mp_uint_t' is a C11 feature [-Werror,-Wtypedef-redefinition]
  196 | typedef uintptr_t mp_uint_t;
      |                   ^
./build/features0.config.h:2:19: note: previous definition is here
    2 | typedef uintptr_t mp_uint_t;
      |                   ^
2 errors generated.
make: *** [../../../py/dynruntime.mk:228: build/features0.o] Error 1
make: Leaving directory '/micropython/examples/natmod/features0'

@dpgeorge
Copy link
Copy Markdown
Member

dpgeorge commented Jun 4, 2026

Updating the documentation to also mention clang is a more than reasonable request and in fact I've done so in my unpublished branch.

Great, thanks!

It might also be worth documenting the obvious fallback for x86-64 gcc toolchains:

$ make CFLAGS_EXTRA=-m32 LDFLAGS_EXTRA=-m32

I tested that with this PR and it works as expected, and the tests pass.

If you're OK with it, I'll tackle the py/dynruntime.mk changes and symbols redefinition issue in a different PR

Yes, that's a good idea, thank you.

@agatti
Copy link
Copy Markdown
Contributor Author

agatti commented Jun 4, 2026

#19308 should, in theory, add Clang support for natmods. It is far from being complete, but that should keep me busy for a little while.

Once sorted out it'll be interesting to see the size/speed difference of the two compilers for Arm and RV32/RV64. Hopefully one day we'll see https://github.com/espressif/llvm-project/ upstreamed so Xtensa could join in on the fun too.

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.

4 participants