内联汇编知识点earlyclobber=
arm64内联汇编格式:
asm volatile (
"汇编指令1\n\t"
"汇编指令2\n\t"
"汇编指令3"
: 输出操作数列表
: 输入操作数列表
: 可能被修改的寄存器列表
);
示例1:简单的寄存器操作
uint64_t add_numbers(uint64_t a, uint64_t b) {
uint64_t result;
asm volatile (
"add %0, %1, %2"
: "=r" (result)
: "r" (a), "r" (b)
);
return result;
}
示例2:读取系统寄存器
uint64_t read_timer_counter(void) {
uint64_t val;
asm volatile("mrs %0, cntvct_el0" : "=r" (val));
return val;
}
示例3:内存屏障
static inline void memory_barrier(void) {
asm volatile("dmb ish" ::: "memory");
}
示例4:使用多条指令
void swap_values(int *a, int *b) {
asm volatile (
"ldr x0, [%0]\n\t"
"ldr x1, [%1]\n\t"
"str x0, [%1]\n\t"
"str x1, [%0]"
:
: "r" (a), "r" (b)
: "x0", "x1", "memory"
);
}
内联汇编中=&的含义
在内联汇编中,=&
是一种操作数约束修饰符的组合,它由两部分组成:
=
表示这是一个输出操作数,即汇编代码会向这个操作数写入值。&
表示这个操作数是早期改写(early-clobber)操作数。
早期改写(&)的具体含义
&
修饰符告诉编译器,这个输出操作数会在指令执行过程中被修改,而且这种修改可能发生在所有输入操作数被完全使用之前。
这意味着编译器不能将这个输出操作数分配到与任何输入操作数相同的寄存器中,因为这可能导致输入值在被使用前就被覆盖。
使用示例
int a = 5, b = 10, result;
asm volatile (
"add %0, %1, %2\n\t" // 将a和b相加,结果存入result
"add %1, #1\n\t"
"add %2, #1\n\t"
: "=&r" (result) // 输出操作数,使用=&约束
: "r" (a), "r" (b) // 输入操作数
);
假如没有&,那么%0和%1可能用相同寄存器,那么第二个汇编就把第一个结果覆盖,出现问题。
在这个例子中,=&r
告诉编译器:
result
是一个输出操作数(=
)- 它会在指令执行过程中被早期修改(
&
) - 它应该被放入一个通用寄存器中(
r
)
何时使用=&
在以下情况下,你应该使用=&
约束:
- 当一个输出操作数在所有输入操作数被完全读取之前就被修改时
- 在多步操作的汇编代码中,如果输出寄存器在中间步骤就被写入
- 当你使用的汇编指令有特殊的寄存器使用规则时
实际应用场景
在ARM64架构中,一些复杂的指令序列可能需要使用=&
约束,例如:
uint64_t a = 5, b = 10, result1, result2;
asm volatile (
"add %0, %2, %3\n\t" // result1 = a + b
"mul %1, %0, %3" // result2 = result1 * b
: "=&r" (result1), "=r" (result2) // 注意result1使用=&
: "r" (a), "r" (b)
);
在这个例子中,result1
必须使用=&
约束,因为它先被写入,然后在计算result2
时又被读取。
参考:
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
http://akaedu.github.io/book/ch19s05.html
http://blog.chinaunix.net/uid-20543672-id-3194385.html