Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
Expand All @@ -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]"
Expand Down
9 changes: 8 additions & 1 deletion bricks/_common/common.mk
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion bricks/_common/sources.mk
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions bricks/cityhub/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions bricks/movehub/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 80 additions & 0 deletions lib/aeabi_div/aeabi_div.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

// 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")));
1 change: 1 addition & 0 deletions tools/codeformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

codeformat.PATHS = [
"bricks/**/*.[ch]",
"lib/aeabi_div/**/*.[ch]",
"lib/pbio/**/*.[ch]",
"lib/lego/**/*.[ch]",
"py/*.[ch]",
Expand Down