aarch64 架构通过 mov 和 movk 指令实现立即数装载

对 64 位数据装载到寄存器这一操作不同架构实现不同,x86 能直接通过 mov 指令实现,而 aarch64 的 mov 指令一步只能装载 16 位,对于 64 位立即数必须分四部分进行装载,下面就是 x86 和 aarch64 对比验证过程。

x86 能够通过 mov 指令实现 64 位立即数装载到寄存器,测试过程如下,通过 mov 指令对寄存器 r9 进行赋值,然后通过嵌汇编获取 r9 的值并且打印出来。

测试用例 test_asm_x86.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

void print_r9(unsigned long long *r9) {
__asm__ __volatile__ (
"mov %%r9, %0"
:"=m"(*r9)
::"memory");
printf("r9 = 0x%llx\n", *r9);
}

int main()
{
unsigned long long r9;
__asm__("mov $0xa1b2, %r9");
print_r9(&r9);
__asm__("mov $0xa1b2c3d4, %r9");
print_r9(&r9);
__asm__("mov $0xa1b2c3d4e5f6f7f8, %r9");
print_r9(&r9);
return 0;
}
````

**结果为:**

r9 = 0xa1b2
r9 = 0xa1b2c3d4
r9 = 0xa1b2c3d4e5f6f7f8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

## **测试用例test_asm_aarch64.c:**

```c
#include <stdio.h>
// x19-x28 is callee-saved
void print_x19() {
unsigned long long x19_val;
__asm__ __volatile__(
"mov %0, x19"
:"=r"(x19_val)
::);
printf("x19 = 0x%llx\n", x19_val);
}

int main()
{
__asm__("mov x19, #0xaaaa");
print_x19();
// Error: immediate cannot be moved by a single instruction
__asm__("mov x19, #0xbbbbaaaa");
print_x19();
return 0;
}
```

**编译报错:**

```
Assembler messages:
Error: immediate cannot be moved by a single instruction

报错来源于__asm__("mov x19, #0xbbbbaaaa"); 这条汇编,查阅资料发现 aarch64 不能通过 mov 指令实现超过 16 位的立即数装载,即 mov 指令不能一次将 64 位立即数装入寄存器,需要借助 movk 指令分四次装载。movk 指令意思是 move and keep,可以将一个 16 位立即数偏移存入指定寄存器,并且保留寄存器其他位的数值。

若想把 0xddddccccbbbbaaaa64 位立即数装入,则需要把立即数分割成四部分依次装载(对于 64 位指针也是这样分四次装载):

1
2
3
4
__asm__("mov x19, #0xaaaa");
__asm__("movk x19, #0xbbbb, lsl #16");
__asm__("movk x19, #0xcccc, lsl #32");
__asm__("movk x19, #0xdddd, lsl #48");

结果为:

1
2
3
4
x19 = 0xaaaa
x19 = 0xbbbbaaaa
x19 = 0xccccbbbbaaaa
x19 = 0xddddccccbbbbaaaa

如果想单独装载 16 位数据到高位,可以根据 mov 规则,对立即数先进行 16、32、48 移位,然后再根据需要往对应位置装载 16 位立即数,下面四条嵌汇编都能正确执行:

1
2
3
4
__asm__("mov x19, #0xaaaa");
__asm__("mov x19, #0xbbbb0000");
__asm__("mov x19, #0xcccc00000000");
__asm__("mov x19, #0xdddd000000000000");

能够发现,上述四条汇编只是提前进行了 lsl 移位,所以本质上是一样的。如果没有根据规定进行 16、32、48 特定的移位就会报错,比如立即数 0xbbbb000 只是把 0xbbbb 左移 12 位,此时就会报错:

1
__asm__("mov x19, #0xbbbb000"); //Error: immediate cannot be moved by a single instruction