Arm In-line Assembly

Someone2022年12月3日
大约 3 分钟

__asm

Example[1]:

#include <stdio.h>

int add(int i, int j)
{
  int res = 0;
  __asm ("ADD %[result], %[input_i], %[input_j]"
    : [result] "=r" (res)
    : [input_i] "r" (i), [input_j] "r" (j)
  );
  return res;
}

int main(void)
{
  int a = 1;
  int b = 2;
  int c = 0;

  c = add(a,b);

  printf("Result of %d + %d = %d\n", a, b, c);
}

我们仔细研究上述的例子,可以看到,其内嵌了一条 ADD 指令,其语法如下所示:

__asm [volatile] (code); /* Basic inline assembly syntax */

其中的 code 就是我们需要内嵌的汇编代码,其中 [volatile] 是可选的,后续我们再对此进行说明。

如果将 code 展开的话,如下所示:

/* Extended inline assembly syntax */ 
__asm [volatile] (code_template 
       : output_operand_list 
      [: input_operand_list 
      [: clobbered_register_list]] 
  );

我们总共有 3 个 ”:“, 每一个后面都有不同的含义,下面对其进行具体说明。(注意 [] 符号包含住表示的是这个参数是可选的)

output

第一个 :冒号后面跟汇编代码的输出;有几个细节需要注意:

  • "=r" 而不是 "r", 在输出中
  • %[variable] 的 % 是在最前面的

input

第二个后面跟汇编代码的输入,如下所示:

__asm ("ADD R0, %[input_i], %[input_j]"
    :  /* This is an empty output operand list */
    : [input_i] "r" (i), [input_j] "r" (j)
  );

在这个例子中,我们将 input_iinput_j 的值相加放入寄存器 R0 中,每一个 input 都使用逗号分隔开,三个字段 [input_i] "r" (i) 的含义分别是符号名称,约束字符串和 C 表达式。

clobbered_register_list

这个里面指定寄存器,嵌入式汇编代码中指定的寄存器可能会产生冲突,因此需要把这些寄存器列举出来,表示其可以在编译的时候被重命名。

Real Example

prfm

下面例子是实战中写的汇编示例,使用了 prfm 指令:

  #if defined(__arm__)
  int i=0, j=0, res=0;
    __asm__ __volatile__(
      "add %[result], %[input_i], %[input_j]\n\t"
      : [result] "+r" (res)
      : [input_i] "r" (i), [input_j] "r" (j)
    );
    
  __asm__ __volatile__(
    "prfm pldl2strm, [%[ptr], #256]"
    :
    : [ptr] "r" (ref)
  );
  #endif

mrs pmu

下面这个是读取 PMU 寄存器中的数据的示例:

static inline uint64_t arch_counter_get_cntpct() {
    uint64_t cval = 0;
#if defined(__aarch64__)
  __asm__ __volatile__(
    // "mrs %[res], PMCCNTR_EL0"
    "mrs %[res], CNTVCT_EL0"
    : [res] "=r" (cval)
    :
  );
  // LOG(INFO) << "[PREFETCH], COUNTER IS " << cval;
#endif
  return cval;
}

CNTVCT_EL0 寄存器为一个不需要开启用户态访问权限也能访问到的寄存器。

memcpy

下面这个是嵌入 memecpy 的例子:

#if defined(__aarch64__)
  if (obj_size == 24) { // copy len 16
    __asm__ __volatile__(
    "ldp x5, x4 ,[%[src], #8]\n\t"
    "stp x5, x4 ,[%[dst], #8]\n\t"
    :
    : [src] "r" (from_ref), [dst] "r" (to_ref)
    : "x4", "x5"
    );
  } else if (obj_size == 32) { // copy length 24
    __asm__ __volatile__(
    "ldp x5, x4 ,[%[src], #8]\n\t"
    "ldr x6 ,[%[src], #24]\n\t"
    "stp x5, x4 ,[%[dst], #8]\n\t"
    "str x6 ,[%[dst], #24]\n\t"
    :
    : [src] "r" (from_ref), [dst] "r" (to_ref)
    : "x4", "x5", "x6"
    );
  } else if (obj_size == 40) { // copy length 32
    __asm__ __volatile__(
    "ldp x5, x4 ,[%[src], #8]\n\t"
    "ldp x7, x6 ,[%[src], #24]\n\t"
    "stp x5, x4 ,[%[dst], #8]\n\t"
    "stp x7, x6 ,[%[dst], #24]\n\t"
    :
    : [src] "r" (from_ref), [dst] "r" (to_ref)
    : "x4", "x5", "x6", "x7"
    );
  } else {
    memcpy(reinterpret_cast<uint8_t*>(to_ref) + kObjectHeaderSize,
          reinterpret_cast<const uint8_t*>(from_ref) + kObjectHeaderSize,
          obj_size - kObjectHeaderSize);
  }

例子比较长,但是可以供参考,这是比较完备的举例。


  1. ARM 官方文档open in new window ↩︎

Loading...