Avoid a software 64-bit divide in pico_printf %ll on RP2350 Printing a long long did value % base and value /= base, which on RP2350 pulls libgcc's 64-bit divide into every program using printf, and libgcc runs from RAM (Arm: 880 bytes; RISC-V: separate divide and modulo, 3.2KB). Divide by the small base in 16-bit pieces with 32-bit divides instead. RP2040 keeps the plain divide, as pico_divider's 64-bit divide is in flash and often already linked. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
diff --git a/src/rp2_common/pico_printf/printf.c b/src/rp2_common/pico_printf/printf.c index 2a1022b..6cc00b6 100644 --- a/src/rp2_common/pico_printf/printf.c +++ b/src/rp2_common/pico_printf/printf.c
@@ -283,6 +283,26 @@ // internal itoa for 'long long' type #if PICO_PRINTF_SUPPORT_LONG_LONG +// RP2040 has a fast/small software implementation for C divide using the hardware divider already +#if PICO_32BIT && !PICO_RP2040 +#define USE_DIVMOD_U64_BY_SMALL 1 +#endif + +#if USE_DIVMOD_U64_BY_SMALL +// divide by a small base using 32-bit divides, avoiding a software 64-bit divide +static unsigned int _divmod_u64_by_small(unsigned long long *value, unsigned int base) { + uint32_t hi = (uint32_t) (*value >> 32u); + uint32_t lo = (uint32_t) *value; + uint32_t q_hi = hi / base; + uint32_t t = ((hi % base) << 16u) | (lo >> 16u); + uint32_t q_mid = t / base; + t = ((t % base) << 16u) | (lo & 0xffffu); + uint32_t q_lo = t / base; + *value = ((unsigned long long) q_hi << 32u) | (q_mid << 16u) | q_lo; + return t % base; +} +#endif + static size_t _ntoa_long_long(out_fct_type out, char *buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) { @@ -297,9 +317,13 @@ // write if precision != 0 and value is != 0 if (!(flags & FLAGS_PRECISION) || value) { do { +#if !USE_DIVMOD_U64_BY_SMALL const char digit = (char) (value % base); - buf[len++] = (char)(digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10); value /= base; +#else + const char digit = (char) _divmod_u64_by_small(&value, (unsigned int) base); +#endif + buf[len++] = (char)(digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10); } while (value && (len < PICO_PRINTF_NTOA_BUFFER_SIZE)); }