32位模式下使用64位暫存器注意事項
阿新 • • 發佈:2019-01-05
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 選項的。