diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 05e060997..e559a7237 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -7,6 +7,8 @@ on: paths: - ".github/workflows/format.yml" - "bricks/**/*.[ch]" + - "lib/aeabi_div/**/*.[ch]" + - "lib/lego/**/*.[ch]" - "lib/pbio/**/*.[ch]" - "py/*.[ch]" - "pybricks/**/*.[ch]" @@ -17,6 +19,8 @@ on: paths: - ".github/workflows/format.yml" - "bricks/**/*.[ch]" + - "lib/aeabi_div/**/*.[ch]" + - "lib/lego/**/*.[ch]" - "lib/pbio/**/*.[ch]" - "py/*.[ch]" - "pybricks/**/*.[ch]" diff --git a/bricks/_common/common.mk b/bricks/_common/common.mk index 95b46faf5..a13c0b375 100644 --- a/bricks/_common/common.mk +++ b/bricks/_common/common.mk @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2013, 2014 Damien P. George -# Copyright (c) 2019-2023 The Pybricks Authors +# Copyright (c) 2019-2026 The Pybricks Authors # This file is shared by all bare-metal Arm Pybricks ports. @@ -666,6 +666,13 @@ OBJ += $(addprefix $(BUILD)/, $(PBIO_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(LEGO_SPEC_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_LIBM:.c=.o)) +ifeq ($(PB_LIB_AEABI_DIV),1) +OBJ += $(addprefix $(BUILD)/, $(AEABI_DIV_SRC_C:.c=.o)) +# Overriding the libgcc division helpers only works when they are not +# compiled with LTO. See lib/aeabi_div/aeabi_div.c. +$(BUILD)/lib/aeabi_div/%.o: CFLAGS += -fno-lto +endif + ifeq ($(PB_LIB_BLUENRG),1) OBJ += $(addprefix $(BUILD)/, $(BLUENRG_SRC_C:.c=.o)) endif diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index 1f1215901..db2b53fe8 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -1,8 +1,12 @@ # SPDX-License-Identifier: MIT -# Copyright (c) 2019-2023 The Pybricks Authors +# Copyright (c) 2019-2026 The Pybricks Authors # This file contains the sources common to all Pybricks MicroPython ports. +# Compact AEABI division helpers + +AEABI_DIV_SRC_C = lib/aeabi_div/aeabi_div.c + # Ring buffer LWRB_SRC_C = lib/lwrb/src/lwrb/lwrb.c diff --git a/bricks/cityhub/Makefile b/bricks/cityhub/Makefile index feb1254cd..6a0283ef7 100644 --- a/bricks/cityhub/Makefile +++ b/bricks/cityhub/Makefile @@ -6,6 +6,7 @@ PB_MCU_FAMILY = STM32 PB_MCU_SERIES = F0 PB_CMSIS_MCU = STM32F030xC PB_MCU_EXT_OSC_HZ = 0 # uses internal oscillator +PB_LIB_AEABI_DIV = 1 PB_LIB_STM32_HAL = 1 PB_LIB_BLE5STACK = 1 PB_FROZEN_MODULES = 1 diff --git a/bricks/movehub/Makefile b/bricks/movehub/Makefile index 6f7df12cf..110dec053 100644 --- a/bricks/movehub/Makefile +++ b/bricks/movehub/Makefile @@ -6,6 +6,7 @@ PB_MCU_FAMILY = STM32 PB_MCU_SERIES = F0 PB_CMSIS_MCU = STM32F070xB PB_MCU_EXT_OSC_HZ = 0 # uses internal oscillator +PB_LIB_AEABI_DIV = 1 PB_LIB_STM32_HAL = 1 PB_LIB_BLUENRG = 1 PB_FROZEN_MODULES = 0 diff --git a/lib/aeabi_div/aeabi_div.c b/lib/aeabi_div/aeabi_div.c new file mode 100644 index 000000000..f340dc131 --- /dev/null +++ b/lib/aeabi_div/aeabi_div.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 The Pybricks Authors + +// Compact replacements for the AEABI integer division helpers. +// +// Cortex-M0 has no divide instruction, so the compiler turns every 32-bit +// integer division into a call to one of the helpers below. Since GCC 11, the +// v6-m libgcc only ships speed-optimized versions: __udivsi3 is fully +// unrolled and __divsi3 contains its own second copy of the unsigned core +// instead of sharing it, which together take around 730 bytes. The loop-based +// versions here produce identical results in around 110 bytes. +// +// The cost is speed: roughly 3 to 5 times slower per division. The heaviest +// user is motor control with around 10 divisions per 5 ms loop, which stays +// well below 1% of the CPU either way, in line with these hubs already +// trading speed for size elsewhere (CSUPEROPT = -Os). +// +// Defining these symbols in an object file makes the linker prefer them over +// the libgcc archive members. This file must be compiled without LTO: the +// compiler generates references to these helpers during LTO code generation, +// after LTO symbol resolution, so definitions inside an LTO object fail to +// link. +// +// Division by zero returns a quotient of 0 and a remainder equal to the +// dividend, the same as libgcc's default __aeabi_idiv0 behavior. + +#include + +// The *divmod helpers return the quotient in r0 and the remainder in r1, +// which matches how a uint64_t is returned (low word in r0, high in r1). + +uint64_t __aeabi_uidivmod(uint32_t n, uint32_t d) { + if (d == 0) { + return (uint64_t)n << 32; + } + + // Scale the divisor up to the dividend, then do schoolbook long division + // in base 2, one quotient bit per iteration. + uint32_t q = 0; + uint32_t bit = 1; + while (d < n && !(d & UINT32_C(0x80000000))) { + d <<= 1; + bit <<= 1; + } + while (bit) { + if (n >= d) { + n -= d; + q |= bit; + } + d >>= 1; + bit >>= 1; + } + return ((uint64_t)n << 32) | q; +} + +uint32_t __aeabi_uidiv(uint32_t n, uint32_t d) { + return (uint32_t)__aeabi_uidivmod(n, d); +} +uint32_t __udivsi3(uint32_t n, uint32_t d) __attribute__((alias("__aeabi_uidiv"))); + +uint64_t __aeabi_idivmod(int32_t n, int32_t d) { + uint64_t res = __aeabi_uidivmod(n < 0 ? -(uint32_t)n : (uint32_t)n, d < 0 ? -(uint32_t)d : (uint32_t)d); + uint32_t q = (uint32_t)res; + uint32_t r = (uint32_t)(res >> 32); + + // C99 division truncates toward zero: the quotient is negative when the + // signs differ and the remainder takes the sign of the dividend. + if ((n ^ d) < 0) { + q = -q; + } + if (n < 0) { + r = -r; + } + return ((uint64_t)r << 32) | q; +} + +int32_t __aeabi_idiv(int32_t n, int32_t d) { + return (int32_t)__aeabi_idivmod(n, d); +} +int32_t __divsi3(int32_t n, int32_t d) __attribute__((alias("__aeabi_idiv"))); diff --git a/tools/codeformat.py b/tools/codeformat.py index 3f508b130..ffa1b00ae 100755 --- a/tools/codeformat.py +++ b/tools/codeformat.py @@ -18,6 +18,7 @@ codeformat.PATHS = [ "bricks/**/*.[ch]", + "lib/aeabi_div/**/*.[ch]", "lib/pbio/**/*.[ch]", "lib/lego/**/*.[ch]", "py/*.[ch]",