对 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 位立即数偏移存入指定寄存器,并且保留寄存器其他位的数值。
若想把 0xddddccccbbbbaaaa
64 位立即数装入,则需要把立即数分割成四部分依次装载(对于 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" );