You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何ARM架构中间接跳转被编译为两条而非单条指令?

关于GCC/Clang对ARM间接跳转指令生成的疑问

示例代码

#include <stdlib.h>

#define NEXT goto **ip++
#define guard(n) asm("#" #n)

int main() {
  static void  *prog[] = {&&next1,&&next2,&&next1,&&next3,&&next1,&&next4,&&next1,&&next5,&&next1,&&loop};
  void ** ip=prog;
  int    count = 100000000;
  NEXT;

 next1: guard(1); NEXT;
 next2: guard(2); NEXT;
 next3: guard(3); NEXT;
 next4: guard(4); NEXT;
 next5: guard(5); NEXT;
 loop:
  if (count) {
    count--;
    ip=prog;
    NEXT;
  }
  exit(0);
}

编译指令差异

编译后,代码中每个next#处的间接跳转被生成了两条ARM指令:

ldr     r2, [r3], #4
mov     pc, r2    @ indirect register jump

但从指令集角度,完全可以优化为更紧凑的单条指令:

ldr pc, [r3], #4

核心疑惑

此前查阅GCC Bugzilla讨论得知,Cortex-A8的分支预测器不会将ldr pc, [r3, #0]这类指令识别为函数调用,会导致返回预测错误。但当前代码中的跳转是间接goto,并非函数调用,因此产生两个疑问:

  • 这是GCC和Clang的优化遗漏吗?
  • 还是单条ldr pc, [r3], #4指令存在未知性能劣势,导致编译器刻意不生成?

原因分析

编译器选择拆分指令是针对ARM架构分支预测特性的有意设计,而非优化遗漏:

1. 分支预测器的行为绑定

在Cortex-A8及部分ARMv7处理器中,ldr pc, [reg], #offset这类指令会被分支预测器默认判定为函数返回操作(类似bx lr或栈上弹出返回地址的操作),处理器会动用返回栈缓冲器(RSB)来预测跳转目标。如果实际场景是普通间接goto而非函数返回,这种错误的预测归类会导致频繁的分支预测失败,反而大幅降低性能。

2. 保守优化的稳定性

将跳转拆分为ldr加载地址到通用寄存器、再mov pc完成跳转的组合,会让处理器将其识别为普通间接分支,转而使用全局历史缓冲器(GHB)等通用分支预测机制。这种处理方式的预测准确率更稳定,能避免RSB误判带来的性能损失。

3. 强制生成单条指令的方案

如果确实需要生成单条ldr pc指令,可以通过手动内联汇编改写NEXT宏,示例如下:

#define NEXT do { \
    asm volatile("ldr pc, [%0], #4" : "+r"(ip)); \
} while(0)

但这种写法需要自行承担分支预测错误带来的性能风险,仅在确认当前场景不会触发RSB误判时适用。


内容的提问来源于stack exchange,提问作者Renee Cousins

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 19:46:01