ARM 内联汇编

ARM 内联汇编

ARM-GCC-Inline-Assembler-Cookbook.pdf

基本结构

asm volatile(
    "汇编指令" 
    : 输出操作数 
    : 输入操作数 
    : 被修改的寄存器
);

Input and Output Operands

修饰符 限定
= 只写操作数,通常用于所有输出操作数。
+ 读写操作数(内联汇编器不支持)
& 寄存器只能用于输出

=:表示该操作数是输出操作数,指示编译器为其分配寄存器,并将结果存入该寄存器。

&:确保输出操作数分配一个新的寄存器,避免与其他操作数冲突。

为什么需要 &

  • 确保寄存器不会作为输入和输出的寄存器& 确保寄存器被分配为仅用于输出。在没有 & 的情况下,编译器可能会选择一个已经作为输入使用的寄存器来作为输出寄存器,这样会导致寄存器冲突或者覆盖输入数据。

    例如,假设我们不使用 &

    asm("add %0, %1, %2" : "=r"(c) : "r"(a), "r"(b));

    在这种情况下,编译器可能会选择 ab 的寄存器来存储 c,而这可能会覆盖其中一个输入寄存器的值。如果不希望发生这种情况,可以使用 & 来强制为输出分配一个新的寄存器。

在汇编代码中引用操作数

方法一: "0""1""2" 等约束符

int a = 5, b = 3, result;

asm volatile (
    "ADD %0, %1, %2\n"   // result = a + b
    : "=r" (result), "=r" (b)  // 输出部分:result 和 b 都是输出,且要绑定到同一寄存器
    : "0" (a), "r" (b)    // 输入部分:a 使用第一个输出操作数的寄存器,b 使用不同寄存器
);

在这个例子中:

  • "0" 约束符表示 a 和第一个输出操作数(result)共享同一个寄存器。
  • b 会被分配到一个独立的寄存器中,因为它在输入部分使用了 "r" 约束符。

方法二:%[操作数名称]

int res = 0;
// result,input_i,input_j 就是操作数名称
__asm ("ADD %[result], %[input_i], %[input_j]"
    : [result] "=r" (res)
    : [input_i] "r" (i), [input_j] "r" (j)
);

clobber 列表

在内联汇编中,memory 关键字用于告知编译器:

  • 汇编代码可能会对内存进行 读写操作,并且这些操作可能会影响外部变量。
  • 编译器需要对内存进行适当的优化,避免错误的寄存器重排(register reordering)或缓存(cache)行为,确保内存操作的正确性。

通常,memory 关键字在以下情况下使用:

  • 当汇编代码显式地 修改内存,例如通过 store(存储)或 load(加载)指令。
  • 当内联汇编代码可能会改变程序中某些变量的值,尤其是那些 未直接出现在汇编代码中的变量

使用 memory 时,编译器会考虑到 内存屏障数据依赖性,并确保汇编操作与其上下文的一致性。

memory 强制 gcc 编译器假设 RAM 所有内存单元均被汇编指令修改,这样 cpu 中的 registers 和 cache 中已缓存的内存单元中的数据将作废。cpu 将不得不在需要的时候重新读取内存中的数据。这就阻止了 cpu 又将 registers, cache 中的数据用于去优化指令,而避免去访问内存。

语法示例:

asm volatile (
    "prfm   pldl1keep, [%0, #512]   \n"
    "ld1    {v0.4s, v1.4s, v2.4s, v3.4s}, [%0] \n"
    "fabs   v0.4s, v0.4s            \n"
    "fabs   v1.4s, v1.4s            \n"
    "fabs   v2.4s, v2.4s            \n"
    "fabs   v3.4s, v3.4s            \n"
    "st1    {v0.4s, v1.4s, v2.4s, v3.4s}, [%0], #64 \n"
    : "=r"(ptr) // 输出部分
    : "0"(ptr)  // 输入部分
    : "memory", "v0", "v1", "v2", "v3"  // clobber 部分
);

在这个例子中:

  • "memory" 表示内联汇编中 会修改内存。在这里,内存的修改是通过 ld1(加载)和 st1(存储)指令完成的,它们会访问 ptr 指向的内存。
  • "v0", "v1", "v2", "v3" 表示这些寄存器会被修改,编译器需要确保这些寄存器不会被错误地优化。

此外

  • cc 表示内联汇编代码修改了标志寄存器