天天看點

ARM64基礎4:在C語言中嵌入ARM64彙編代碼

如題,有時為了提高部分代碼運作性能,可以将部分C代碼實作,用彙編改寫(自己編寫常比編譯器優化效果更好)

1.先看案例:

#include <stdio.h>

static int compare_data(int a, int b)
{
  int val;
  __asm__ __volatile__ (
      "cmp %1, %2\n"
      "csel %0, %1, %2, hi\n"
      : "+r" (val)
      : "r" (a), "r" (b) 
      : "memory");

  return val;
}

int main()
{
  int val;

  val = compare_data(5, 6); 
  printf("big data: %d\n", val);

  val = compare_data(6, 4); 
  printf("big data: %d\n", val);
  
  return 0;
}      

編譯運作:

aarch64-linux-gnu-gcc -o main main.c --static -g
# ./main
big data: 6
big data: 6      

2.文法解析:

C/C++中嵌入彙編,常用格式如下:

__asm__ [__volatile__] ( assembler template 
           : [output operand list]             /* optional */
           : [input operand list]              /* optional */
           : [clobbered register list]         /* optional */
           );      

關鍵字__asm__:其實也可以寫成“asm”。但是“asm”并不是所有版本的GCC編譯器都支援的,而且可能有命名沖突問題,是以用“asm”的話,相容性更好。

關鍵字__volatile__:也可以寫“volatile”,理由同上;__volatile__是可選的,作用是禁止編譯器對後面彙編指令再進行優化。一般自己寫的彙編,考慮性能,已經做過優化,編譯器再優化的話,可能效果反而更差,是以通常還是帶上這個關鍵字;

括号裡:是真正的彙編代碼,主要有四部分組成,第一部分是具體的彙編代碼,是必須的;其他三個為輔助參數,可選;各部分之間用冒号“:”分割,即使參數為空,也要加冒号;

2.1 assembler template:

所有彙編代碼必須用雙引号括起來,如果多行彙編代碼,每一條語句都要用雙引号括起來,代碼後面加上換行符("\n"或"\n\t"),具體形式如下:

__asm__ __volatile__ ( "instruction 1\n\t" 
           "instruction 2\n\t"
           ......
           "last instruction"
           );      

注:即使一行彙編代碼也沒有,也要傳入空字元串"",否則會報錯;

2.2 [output/input operand list] :輸出/入參數清單;

分别對應彙編與C語言互動的C表達式,若有多個,用逗号隔開;

在前面彙編中,還可以用"%n",直接引用後面的操作數;

__asm__("mov %0, %1, ror #1" 
            : "=r"(result) 
            : "r"(value)
            );      

這裡%0代表第一個操作數,result變量;

%1代表第二個操作數,即value變量;

操作數格式:

每一個操作數,最多由四部分組成:

[name]"modifier+constraint"(C expression)      

name:别名,可選;

modifier:修改符,要用雙引号括起來;

constraint:限定符,要用雙引号括起來;

c表達式:用小括号括起來;

(1)如果指定了别名的話,那在彙編模闆中,引用該變量,就可以使用别名,增加可讀性,例如:

int x=10, y;
__asm__ ("mov %[in],%[out]"
   : [out]"=r"(y)
   : [in]"r"(x)
   :
);      

(2) 說說限定符,操作數在這裡的作用是将C語言定義的變量與彙編中使用的變量進行一 一對應,但并不是所有的彙編指令都可以接受任何類型變量,是以彙編器需要知道這些變量到底用在什麼地方,傳遞前做一些轉換。常用限定符如下表

限定符 ARM指令集含義
r 通用寄存器
f 浮點寄存器
m 記憶體位址

(3)修改符号,在限定符之前,可選,為空的話,表明該操作數是隻讀的。

GCC定義了三個修改符,分别是:

修改符 含義
= 隻寫操作數,一般用于輸出操作數中
+ 可讀且可寫
& 寄存器隻能用于輸出

如果想讓一個C變量既作為輸入操作數,也作為輸出操作數的話,可以使用“+”限定符,并且這個操作數隻需要在輸出操作數清單中列出就行了。例如:

__asm__("mov %0, %0, ror #1" 
        : "=r"(y)
        : "0"(y)
        );      

“&”:為了避免編譯器優化輸出和輸入使用同一個寄存器,可在輸出操作數中使用“&”修改符,明确告訴編譯器,代表輸出操作數的寄存器一定不能使用輸入操作數已經使用過的寄存器。

2.3 修改寄存器清單

為保持寄存器,記憶體資料一緻性,提供三個類型

類型 作用
r0…r15 告訴編譯器彙編代碼中修改了通用寄存器r0…r15
cc 告訴編譯器彙編代碼會導緻CPU狀态位的改變
memory

繼續閱讀