GCC生成的ARM和x86汇编代码的差异
让我们用一个简单的C代码来设置寄存器: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边
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