GCC生成的ARM和x86汇编代码的差异

GCC生成的ARM和x86汇编代码的差异,gcc,assembly,compiler-construction,x86,arm,Gcc,Assembly,Compiler Construction,X86,Arm,让我们用一个简单的C代码来设置寄存器: int main() { int *a = (int*)111111; *a = 0x1000; return 0; } 当我使用1级优化为ARM(ARM none eabi gcc)编译此代码时,汇编代码如下所示: mov r2, #4096 mov r3, #110592 str r2, [r3, #519] mov r0, #0 bx lr 看起来地址111111解析为最近的4K边

让我们用一个简单的C代码来设置寄存器:

int main()
{
    int *a = (int*)111111;
    *a = 0x1000;
    return 0;
}
当我使用1级优化为ARM(ARM none eabi gcc)编译此代码时,汇编代码如下所示:

mov     r2, #4096
mov     r3, #110592
str     r2, [r3, #519]
mov     r0, #0
bx      lr
看起来地址111111解析为最近的4K边界(110592)并移动到r3,然后通过将519添加到110592(=111111)来存储值4096(0x1000)。为什么会发生这种情况

在x86中,组装非常简单:

movl    $4096, 111111
movl    $0, %eax
ret

编译器可能正在利用这一点来减少代码大小。基本上,110592是
0x1B这种编码背后的原因是x86具有可变大小的指令——从1字节到16字节(甚至可能更多带有前缀)

ARM指令的宽度为32位(不包括Thumb模式),这意味着不可能在单个操作码中编码所有32位宽度的常量(立即数)

固定大小的体系结构通常使用几种方法加载大常量:

1)  movi  #r1, Imm8  ; // Here Imm8 or ImmX is simply X least significant bits
2)  movhi #r1, Imm16 ; // Here Imm16 loads the 16 MSB of the register
3)  load  #r1, (PC + ImmX);  // use PC-relative address to put constant in code
4)  movn  #r1, Imm8 ;  // load the inverse of Imm8 (for signed constants) 
5)  mov(i/n) #1, Imm8 << N;       // where N=0,8,16,24

地址必须分成两部分,因为这个特定常量不能用一条指令加载到寄存器中

指定了某些指令(例如
MOV
)中允许的立即数常量的限制:

在ARM指令中,常量可以有任何可以产生的值 通过将8位值向右旋转任意偶数个位 32位字

在32位Thumb-2指令中,常数可以是:

通过将8位值左移 32位字中的任意位数

0x00XY00XY形式的任何常数。
0xXY00XY00形式的任何常数。
0xXYXYXYXY形式的任何常数

111111
1B207
十六进制)不能表示为上述任何值,因此编译器必须将其拆分

110592
1B000
,因此它满足第一个条件(8位值0x1B向左旋转12位),可以使用
MOV
指令加载

另一方面,
STR
指令对使用的偏移量进行了调整。具体而言,519(0x207)属于ARM模式下字存储/加载所允许的-4095到4095范围


在这种特定的情况下,编译器设法将常量分成两部分。如果立即数有更多的位,它可能需要生成更多的指令,或者使用文字池加载。例如,如果我使用
0xABCDEF78
,我会得到以下结果(对于ARMv7):

对于没有MOVW/MOVT的体系结构(例如ARMv4),GCC似乎会退回到文字池:

    mov     r2, #4096
    ldr     r3, .L2
    str     r2, [r3, #-135]
    mov     r0, #0
    bx      lr
.L3:
    .align  2
.L2:
    .word   -1412567041

我想提醒您不要根据生成的汇编指令的数量来评估这些体系结构。不同的汇编指令的数量不会告诉您使用了多少字节的代码,也不会告诉您任何关于执行时间的信息。x86 ISA是在人类仍在进行大量汇编编码的时代设计的,因此,即使指令在其他方面效率低下,也必须使用“直接”指令。x86使用可变字长指令,因此您可以使用一条指令来执行任意32或64位立即数。Arm是固定指令长度的,因此您不能有任何32位或64位的立即数,它需要多条指令和/或具有该值的位置的pc相对负载。编译器将选择一个或另一个选项。
mov[dword],dword
C7 05 dword dword
,顺便说一句(在32位模式下),这并没有减少代码大小:另一种方法是从内存加载地址。编译器在这里选择使用立即数常量来形成地址,以便保存加载—这可能会丢失缓存,并且可能比加载立即数常量所用的单个周期更长。该立即数常量使用12位,而不是32位。它支持在完整的32位指令中对常量进行编码。因此,一条32位指令替换32位内存,其中包含指令的常量加上16位(甚至在ARM模式下为32位)。
movw    r3, #61439
movt    r3, 43981
mov     r2, #4096
str     r2, [r3, #-135]
mov     r0, #0
bx      lr
    mov     r2, #4096
    ldr     r3, .L2
    str     r2, [r3, #-135]
    mov     r0, #0
    bx      lr
.L3:
    .align  2
.L2:
    .word   -1412567041