1. 程式人生 > >32位模式下使用64位暫存器注意事項

32位模式下使用64位暫存器注意事項



1. 彙編環境

龍芯2E平臺32位OS模式下,要使用64位暫存器可以在彙編程式碼裡直接 用,運算時使用d開頭的指令(double-word, 64bit),作用於暫存器即可。如:dadd, dsub, dmult, dmultu, ddiv, dsll, dsrl, dsra 等等。

訪問儲存器可以直接使用ld/sd, ldc1/sdc1

使用這些指令前,先用偽操作 .set mips3 告訴彙編器下面的指令是MIPS IV(64位指令集,相容32位指令)中的指令。


2. C語言環境

可以內嵌彙編,在彙編中使用dadd, dsub, dmult, dmultu, ddiv, dsll, dsrl, dsra 等指令

內嵌彙編與C語言之間的資料方式要特別注意,如讀取CP0 25 暫存器(64位,高低32位各為一個計數器)的值,使用如下程式碼,獲取的高32位的值是不正確的:

  long long counter;
  int c0, c1;

  asm(
        ".set mips3/n/t"
        "dmfc0 %0, $25/n/t"
        ".set mips1/n/t"
        : "=r" (counter)
    );

  c0 = counter;
  c1 = counter >> 32;

  printf("low is 0x%x/n", c0);
  printf("high is 0x%x/n", c1);


可以與如下程式碼獲取的值比較下就知道了:

unsigned int counter0, counter1;
unsigned int control;

void read_pmc()
{
  __asm__ volatile (
        ".set mips3   /n/t"
        "dmfc0 %0, $24 /n/t"
        ".set mips0   /n/t"
        :"=r"(control)
        );

  __asm__ volatile (
        ".set mips3   /n/t"
        "dmfc0 %0, $25 /n/t"
        ".set mips0   /n/t"
        :"=r"(counter0)
        );

  __asm__ volatile (
        ".set mips3   /n/t"
        "dmfc0 $8, $25 /n/t"
        "dsrl %0, $8, 32 /n/t"
        ".set mips0   /n/t"
        :"=r"(counter1)
        );
}


int main()
{
  read_pmc();

  printf("control register is(cp0_24): 0x%016x/n", control);
  printf("counter0 register is(cp0_25_low): 0x%08x/n", counter0);
  printf("counter1 register is(cp0_25_high): 0x%08x/n", counter1);

}


如果一定要把CP0_25的值放在一個long long 型別的變數裡,則:

  long long counter;
  int c0, c1;

  asm(
    ".set mips3/n/t"
    "dmfc0 %M0, $25/n/t"
    "dsll   %L0, %M0, 32/n/t"
    "dsrl   %M0, %M0, 32/n/t"
    "dsrl   %L0, %L0, 32/n/t"
    ".set mips0/n/t"
    : "=r" (counter)
  );

  c0 = counter;
  c1 = counter >> 32;

  printf("low is 0x%x/n", c0);
  printf("high is 0x%x/n", c1);


同樣的要將一個long long的值寫入,需用如下程式碼方能正確寫入:

asm(
    ".set mips3/n/t"
    "dsll   %L0, %L0, 32/n/t"
    "dsrl   %L0, %L0, 32/n/t"
    "dsll   %M0, %M0, 32/n/t"
    "or   %L0, %L0, %M0/n/t"
    "dmtc0 %L0, $25/n/t"
    ".set mips0/n/t"
    ::"r" (counter)
  );

其中counter得定義為 long long

如果要用上面的程式碼寫入0,則一定要這樣 long long counter = 0 才可,直接傳常數給他是不行的。


3. 常見問題

試看如下程式碼:

1   long long counter;
2   int c0, c1;

3   asm(
4     ".set mips3/n/t"
5     "dmfc0 %M0, $25/n/t"
6     "dsll   %L0, %M0, 32/n/t"
7     "dsrl   %M0, %M0, 32/n/t"
8     "dsrl   %L0, %L0, 32/n/t"
9     ".set mips0/n/t"
10     : "=r" (counter)
11   );

12   c0 = counter;
13   c1 = counter >> 32;

14   if(c0 < 0)
15     printf("low is 0x%x/n", c0);

16   if(c1 < 0)
17     printf("high is 0x%x/n", c1);


其意圖是在任何一個計數器溢位時(最高位為1,則補碼錶示即為負數),列印其值。

gcc 不帶優化選項 -O 編譯之,執行是沒有問題的。

倘若加-O 編譯之,執行時並非如我們所期待的行為,最高位為1時,他也不列印,邏輯關係完全錯了。



這個問題我們來對比下gcc 生成的彙編碼就清楚了:

不加-O 選項生成的程式碼為:

  .set mips3
  dmfc0 $3, $25
  dsll   $2, $3, 32
  dsrl   $3, $3, 32
  dsrl   $2, $2, 32
  .set mips0

  sw $2,32($fp)
  sw $3,36($fp)
  lw $2,32($fp)

  sw $2,28($fp)
  lw $4,36($fp)
  lw $5,36($fp)

  sra $2,$4,0
  sra $3,$5,31

  sw $2,24($fp)
  lw $2,28($fp)

  bgez   $2,$L2
  lw $2,%got($LC0)($28)

  addiu   $4,$2,%lo($LC0)
  lw $5,28($fp)
  lw $25,%call16(printf)($28)

  jalr   $25
  lw $28,16($fp)
  .....

加 -O 選項生成的程式碼為:

  .set mips3
  dmfc0 $17, $25
  dsll   $16, $17, 32
  dsrl   $17, $17, 32
  dsrl   $16, $16, 32
  .set mips0

  bgez   $16,$L2
  lw $4,%got($LC0)($28)

  addiu   $4,$4,%lo($LC0)
  lw $25,%call16(printf)($28)
  .set   noreorder
  .set   nomacro
  jalr   $25
  move   $5,$16
  .set   macro
  .set   reorder
  .....


可 以看到使用 -O 時,gcc 將原兩條 sra 指令合併入 dsll-dsrl-dsrl 中了,那麼由dsrl 作用得到的counter0 (對應$16)、counter1 (對應$17),其高32位是都為0的,如果counter0的值為 0x8000 0012,則此時它在$16中的全值為0x0000 0000 8000 0012,而按照運行於32位相容模式下的定義,低32位的值應該高位符號擴充套件的,$16中的值是這樣 0xffff ffff 8000 0012 才表示為負數。故而在下面 bgez 判斷的時候就會出現差錯。

從這一點可以看到,分支轉移指令的作用範圍還是整個64位的。

解決的方法是將嵌入的彙編中的 dsrl 換成 dsra。

程式碼中的這種問題極其隱蔽,且很難發現,需要謹慎在意才是。


另外,核心中 include/asm-mips/mipsregs.h 中定義的__read_64bit_c0_split 巨集就有這個問題,使用時要小心在意,因為核心編譯時,是開啟 -O 選項的。