- 一.C數(shù)據(jù)類型和對齊
- 二.RVG調(diào)用約定
- 三.Soft-Float調(diào)用約定
- 四.總結(jié)
- 使用編譯器生成匯編代碼分析調(diào)用過程
- 五.參考
一.C數(shù)據(jù)類型和對齊
所有數(shù)據(jù)保持自然對齊。
ILP32,LP64
C type | Description | Bytes in RV32 | Bytes in RV64 |
---|---|---|---|
char/unsigned char | 8-bit unsigned integer,zero-extended | 1 | 1 |
signed char | 8-bit signed integer,sign-extended | ||
short | 16-bit signed integer,sign-extended | 2 | 2 |
unsigned short | 16-bit unsigned integer,zeroextended | ||
int | int都是32位 | 4 | 4 |
long | 指針和long和整數(shù)寄存器一樣寬 | 4 | 8 |
long long | long long都是64位 | 8 | 8 |
void * | 指針和long和整數(shù)寄存器一樣寬 | 4 | 8 |
float | 32-bit IEEE 754-2008 | 4 | 4 |
double | 64-bit IEEE 754-2008 | 8 | 8 |
long double | 128-bit IEEE floating-point | ||
IEEE floating-point | 16 | 16 |
在RV64中,32位類型不管是int還是unsigned都是符號擴展到64位。
二.RVG調(diào)用約定
a0-a7
,fa0-fa7
:用于函數(shù)傳遞參數(shù),其中0-1
用于返回值,a
表示arguments
。
都是調(diào)用者負責(zé)保存,因為是傳參肯定是在函數(shù)調(diào)用前就要準備好,所部不可能是被調(diào)用者去負責(zé)保存。
- 如果函數(shù)參數(shù)為結(jié)構(gòu)體的字段,每一個都是指針對齊的,則參數(shù)寄存器是結(jié)構(gòu)體前面8個指針字
pointer-words
的影子shadow
。
如果是小于8個的浮點值,則使用fai
傳遞;小于8個的整數(shù)則使用ai
傳遞。
如果浮點參數(shù)是聯(lián)合體unions
的字段,或者結(jié)構(gòu)體的數(shù)組字段,則使用整數(shù)寄存器傳遞。
另外可變參數(shù)函數(shù)中除了顯示指定的參數(shù)外的參數(shù),如果是浮點數(shù)也是使用整數(shù)寄存器傳遞。
- 小于指針字
pointer-word
的參數(shù)使用低位傳遞,子指針字sub-pointer-word
的參數(shù)通過棧傳遞時,使用指針字pointer-word
的低地址,因為RISC-V
是小端的存儲系統(tǒng)。 - 當(dāng)原始參數(shù)兩倍于指針字
pointer-word
時通過棧傳遞,使用自然對齊。當(dāng)它們使用整數(shù)寄存器傳遞時,使用對齊的偶-奇
寄存器對,偶寄存器存低位。比如RV32
的void foo(int, long long)
使用a0
傳遞第一個參數(shù),a2-a3
傳遞第二個參數(shù),因為由偶寄存器對齊,且a2
存低位,返回值通過a0
傳遞。 - 兩倍于指針字
pointer-word
的參數(shù)通過引用傳遞。 - 結(jié)構(gòu)體中部分參數(shù)未使用整數(shù)寄存器傳遞的使用棧傳遞,棧指針
sp
指向第一個未使用整數(shù)寄存器傳遞的參數(shù)。 a0,a1
,fa0,fa1
用于函數(shù)返回值。只有結(jié)構(gòu)體成員只有一個或者兩個浮點成員,或者primitives
時才使用浮點寄存器返回;其他的由a0-a1
組成的兩倍指針字的two pointer-words
大小返回;更大的返回值通過內(nèi)存?zhèn)鬟f;調(diào)用者負責(zé)分配這個內(nèi)存,并傳遞指向該內(nèi)存的指針,隱含的作為第一個參數(shù)傳遞給被調(diào)用者。- 標準
RISC-V
調(diào)用中,棧向下生長,并且保持16字節(jié)對齊。 - 7個臨時整數(shù)寄存器
t0-t6
,12個臨時浮點寄存器ft0-ft11
在調(diào)用過程是可變的,如果后面需要使用則必須由調(diào)用者負責(zé)保存。其中t
表示Temporaries
。
- 這里有點疑惑,臨時寄存器是被調(diào)用者使用的,只有被調(diào)用者才知道自己要用哪些寄存器,為什么不是被調(diào)用者負責(zé)保存?
這樣理解,因為這些寄存器是可變的,對于被調(diào)用者來說既然是可變的則可以隨便使用,也就是可能被被調(diào)用者修改,所以對于調(diào)用者來說,如果這些寄存器的值不能被破壞則自己需要負責(zé)保存。
- 12個整數(shù)寄存器
s0-s11
,12個浮點寄存器fs0-fs11
在調(diào)用過程是必須保持的,所以如果被調(diào)用者需要使用則必須由被調(diào)用者保存。
實際上上面的8
和9.
,t
和s
寄存器的可變volatile
和保持preserved
是對被調(diào)用者來說的,也就是對被調(diào)用者申明,告訴被調(diào)用者,
t
這些寄存器是可變的,那么被調(diào)用者可以隨便使用,此時調(diào)用者則必須考慮被被調(diào)用者隨便使用而修改,需要調(diào)用者保存;
s
這些寄存器是保持的,那么被調(diào)用者不能隨便使用,如果要用就要負責(zé)保存。
所以對于a
寄存器也可以這樣理解,因為a
寄存器用于傳遞參數(shù),所以是被調(diào)用者隨便使用的,即不保持的,所以需要調(diào)用者負責(zé)保存,并賦參數(shù)值。
Register | ABI Name | Description | Saver |
---|---|---|---|
x0 | zero | 硬件固定為0 | / |
x1 | ra | 返回地址 | Caller調(diào)用者 |
x2 | sp | 棧指針 | Callee被調(diào)用者 |
x3 | gp | 全局指針 | / |
x4 | tp | 線程指針 | / |
x5-x7 | t0-t2 | 臨時使用 | Caller調(diào)用者 |
x8 | s0/fp | 保存寄存器/幀指針 | Callee被調(diào)用者 |
x9 | s1 | 保存寄存器 | Callee被調(diào)用者 |
x10-x11 | a0-a1 | 函數(shù)參數(shù)/返回值 | Caller調(diào)用者 |
x12-x17 | a2-a7 | 函數(shù)參數(shù) | Caller調(diào)用者 |
x18-x27 | s2-s11 | 保存寄存器 | Callee被調(diào)用者 |
x28-x31 | t3-t6 | 臨時使用 | Caller調(diào)用者 |
f0-f7 | ft0-ft7 | FP臨時使用 | Caller調(diào)用者 |
f8-f9 | fs0-fs1 | FP保存寄存器 | Callee被調(diào)用者 |
f10-f11 | fa0-fa1 | FP函數(shù)參數(shù)/返回值 | Caller調(diào)用者 |
f12-f17 | fa2-fa7 | FP參數(shù) | Caller調(diào)用者 |
f18-f27 | fs2-fs11 | FP保存寄存器 | Callee被調(diào)用者 |
f28-f31 | ft8-ft11 | FP臨時使用 | Caller調(diào)用者 |
三.Soft-Float調(diào)用約定
在沒有浮點硬件,或者不使用F
,D
,Q
擴展的硬件浮點,不使用浮點寄存器,完全由軟件實現(xiàn)浮點。
整數(shù)參數(shù)的傳入和返回值和RVG一樣。
浮點參數(shù)和返回值,通過整數(shù)寄存器傳遞,原則是使用大小相同的整數(shù)寄存器傳遞。
比如RV32
的
double foo(int, double, long double)
則第一個參數(shù)通過a0
傳遞;
第二個參數(shù)通過a2
和a3
傳遞;
第三個參數(shù)通過a4
傳引用傳遞;
結(jié)果通過a0
和a1
傳遞。
如果是RV64
則
則第一個參數(shù)通過a0
傳遞;
第二個參數(shù)通過a1
傳遞;
第三個參數(shù)通過a2-a3
傳遞;
結(jié)果通過a0
傳遞。
動態(tài)舍入模式和產(chǎn)生的異常標志通過C99
的fenv.h
提供的接口訪問。
四.總結(jié)
從以下幾個部分去理解
- 寄存器
理解函數(shù)參數(shù)的傳遞與返回值,a0-a1,a2-a7
,fa0-fa1,fa2-fa7
,0-1
用于返回值。
理解ra
寄存器,函數(shù)的返回地址
理解SP
棧指針,理解棧的向下生長,理解進入子函數(shù)時減少sp
分配空間,分配的空間用于存儲s
寄存器和局部變量使用,和退出子函數(shù)時增加sp
恢復(fù)sp
。也就是調(diào)用完子函數(shù)返回后sp
要保持不變。
理解t0-t6
,ft0-ft11
;s0-s11
.fs0-fs11
,這里重點站在被調(diào)用者角度去理解可變和保持,進而理解誰負責(zé)保存寄存器。
- 函數(shù)調(diào)用
jal ra label
或者jal ra rd imm
簡化為偽指令jal label
或者jalr rd
(立即數(shù)為0)。jal
跳轉(zhuǎn)即將PC + 4
存儲到ra
寄存器,即函數(shù)返回后的下一條執(zhí)行的指令。jalr
類似只是設(shè)置PC
為rd + imm
。
注意與無條件跳轉(zhuǎn)jal x0 label
和jalr x0 rd imm
,偽指令j label
,jr rd
(立即數(shù)為0)的區(qū)別,無條件跳轉(zhuǎn)是不返回了的所以不保存返回地址到ra
,而是保存到了x0
寄存器,而x0
寄存器是硬件固定為0的,所以相當(dāng)于不保存,
兩者指令是統(tǒng)一的,這也體現(xiàn)了RISC-V指令設(shè)計的簡潔統(tǒng)一的美學(xué)。
其中jal
的l
可以理解為link
,類似于ARM
的LR
寄存器的L
。
- 進入和退出函數(shù)
除非使用棧傳遞參數(shù),否則子函數(shù)返回后sp
必須保持不變。
所有的s
寄存器在子函數(shù)返回后必須保持,這也是其保持的含義,也是為什么被調(diào)用者需要負責(zé)保存。
子函數(shù)退出時返回ra
處執(zhí)行
函數(shù)進入時的處理:減少sp
,s
寄存器個數(shù)和局部變量大小的空間,存儲使用到的s
寄存器到棧中。如果還有子函數(shù)調(diào)用則存儲ra
到棧中(因為子函數(shù)的子函數(shù)的返回值要存到ra
會覆蓋ra
)。
函數(shù)退出時的處理:恢復(fù)棧中保存的s
寄存器,更新sp
值。如果有需要恢復(fù)ra
值,恢復(fù)sp
值到函數(shù)進入之前的值,返回到ra
處執(zhí)行。
最好通過編寫c
代碼,使用編譯工具生成匯編代碼,對照c
和匯編代碼的方式去理解。
五.參考
-
代碼
+關(guān)注
關(guān)注
30文章
4779瀏覽量
68521 -
編譯器
+關(guān)注
關(guān)注
1文章
1623瀏覽量
49108 -
數(shù)據(jù)類型
+關(guān)注
關(guān)注
0文章
236瀏覽量
13618 -
RISC-V
+關(guān)注
關(guān)注
45文章
2270瀏覽量
46129
發(fā)布評論請先 登錄
相關(guān)推薦
評論