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

问题描述 投票:0回答:3

让我们看一个简单的 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
assembly gcc x86 arm
3个回答
7
投票

这种编码背后的原因是因为 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

可变大小的架构 OTOH 可以将所有常量放在一条指令中:

xx xx xx 00 10 00 00 11 11 11 00 ; // assuming that it takes 3 bytes to encode
                                 ; // the instruction and the addressing mode
; added with 4 bytes to encode the 4096 and 4 bytes to encode 0x00111111

3
投票

地址必须分成两部分,因为这个特定常量无法使用单个指令加载到寄存器中。

ARM 文档指定了某些指令中允许的立即常量的限制(例如

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    r3, #61439
movt    r3, 43981
mov     r2, #4096
str     r2, [r3, #-135]
mov     r0, #0
bx      lr

对于没有 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

1
投票

编译器可能利用 ARM 立即值编码来减少代码大小。基本上 110592 是

0x1B << 12
,这可以进行一些简化。查看程序
arm-none-eabi-objdump -d
的输出,检查每条指令的长度。

© www.soinside.com 2019 - 2024. All rights reserved.