Cortex-M0平台下ARM GCC常量除法为何调用__udivsi3
问题核心结论
这既不是你编译参数配置错误,也不是GCC的bug,是GCC基于Cortex-M0的硬件特性做的合理优化权衡,且未识别到你代码中操作数的实际取值范围,最终选择调用库函数的决策。
具体原因拆解
- Cortex-M0属于ARMv6-M架构,硬件层面没有整数除法指令,也不支持生成64位结果的乘法指令,仅能执行输出32位结果的
MULS指令。在带有64位乘法指令的Cortex-M3及以上内核上,GCC会直接内联生成常数除法的乘移位序列,不会调用库函数;但Cortex-M0缺失64位乘法指令,要实现32位全范围的魔数除法,需要用多条32位乘法指令拼接模拟64位乘法来计算魔数乘积的高32位,整体代码体积、执行周期算下来,反而不如直接复用通用的__udivsi3库函数划算(库函数代码可被全工程复用,整体Flash占用更低,执行周期相近),因此GCC默认会选择调用库函数。 - 你的代码中
uint16_t类型的变量num,会触发C语言的整数提升规则,被自动提升为32位整型参与除法运算。GCC因此默认按照32位全范围的无符号除法来做优化决策,没有识别到实际操作数仅为0~65535的16位范围,自然不会选择更短、效率更高的16位对应魔数优化序列。
优化方案
如果希望让GCC内联生成无函数调用的乘移位除法代码,可以通过以下方式调整:
- 显式提示GCC操作数范围,避免优化范围放大
在代码中加入范围提示,告诉GCC操作数不存在高16位,无需按32位全范围做优化决策:
调整后GCC会直接生成适配16位操作数的短乘移位序列,不再调用int main(void) { uint16_t num = (uint16_t) ADC1->DR; // 提示GCC num取值范围为0~UINT16_MAX __builtin_assume((uint32_t)num <= UINT16_MAX); ADC1->DR = num / 7; }__udivsi3。 - 手动替换为适配16位范围的魔数运算
针对0~65535范围的无符号数除以7,可以直接用单条32位乘法实现等价运算,完全避免函数调用开销:
该序列仅需2~3条指令即可完成运算,远快于int main(void) { uint16_t num = (uint16_t) ADC1->DR; // 16位无符号数除以7的精确魔数序列,等价于整数除法 ADC1->DR = (uint16_t)(((uint32_t)num * 0x2493U) >> 16); }__udivsi3的调用开销。 - 调整GCC内联阈值(可选)
如果希望GCC对常数除法更倾向于内联实现,可以在编译参数中适当提高内联允许的最大指令数阈值:-Ofast --param max-inline-insns-single=32
内容的提问来源于stack exchange,提问作者Dan Sandberg
相关产品推荐
相关产品推荐

