CPU架构的llvm后端
Creating an LLVM Backend for the Cpu0 Architecture
Backend structure
? TargetMachine structure
? Add AsmPrinter
? Add Cpu0DAGToDAGISel class
? Handle return register $lr
? Add Prologue/Epilogue functions
o Concept
o Prologue and Epilogue functions
o Handle stack slot for local variables
o Large stack
? Data operands DAGs
? Summary of this Chapter
Fig. 14 Cpu0 backend class access link
圖 14 Cpu0 后端類訪問鏈接
添加了大多數(shù) Cpu0 后端類,代碼可以概括為圖14。類 Cpu0Subtarget 提供接口 getInstrInfo(),getFrameLowering(),…,獲取其它 Cpu0 類。大多數(shù)類(如 Cpu0InstrInfo,Cpu0RegisterInfo 等),都有 Subtarget 引用成員,允許通過 Cpu0Subtarget 接口,訪問其它類。如果后端模塊沒有 Subtarget 引用,這些類仍然可以通過 static_cast<Cpu0TargetMachine &>?.getSubtargetImpl(),通過 Cpu0TargetMachine(通常使用 TM 作為符號)訪問 Subtarget 類。一旦獲取到 Subtarget 類,后端代碼就可以訪問其它類。對于 Cpu0SExx 類的名稱,表示標(biāo)準(zhǔn)32 位類。遵循 llvm 3.5 Mips 后端風(fēng)格。Mips 后端使用 Mips16,MipsSE 和 Mips64 文件/類名稱,分別為 16,32 和 64 位架構(gòu)定義類。
圖15顯示了 Cpu0 TableGen 的繼承關(guān)系。后端類可以包含 TableGen 生成的類并從中繼承。Cpu0后端的所有TableGen生成的類,都在build/lib/Target/Cpu0/*.inc中。通過 C++ 繼承機(jī)制,TableGen 為后端程序員,提供了一種靈活的方式,使用生成的代碼。如果需要,程序員有機(jī)會(huì)覆蓋此功能。
圖 15繼承自 TableGen 生成文件的 Cpu0 類
Fig. 15 Cpu0 classes inherited from TableGen generated files
由于llvm有很深的繼承樹,這里就不深挖了。受益于繼承樹結(jié)構(gòu),不需要在指令,幀/堆棧和選擇 DAG 類中,實(shí)現(xiàn)太多代碼,很多代碼是由父類實(shí)現(xiàn)的。llvm-tblgen 根據(jù)Cpu0InstrInfo.td 的信息,生成 Cpu0GenInstrInfo.inc。Cpu0InstrInfo.h 通過定義“#define GET_INSTRINFO_HEADER”,從 Cpu0GenInstrInfo.inc 中,提取需要的代碼。使用TabelGen,通過編譯器開發(fā)的模式匹配理論,減少了后端的代碼量。這在 “DAG”和“指令選擇”中,都有解釋。
To make the registration clearly, summary as the following diagram, Fig. 16.
圖 16 Tblgen 為 Cpu0 后端生成文件
Fig. 16 Tblgen generate files for Cpu0 backend
createCpu0MCAsmInfo() 為目標(biāo) TheCpu0Target 和 TheCpu0elTarget,注冊了類 Cpu0MCAsmInfo 的對象。TheCpu0Target 用于大端,TheCpu0elTarget 用于小端。Cpu0MCAsmInfo 派生自 MCAsmInfo,一個(gè) llvm 內(nèi)置類。大多數(shù)代碼在父級中實(shí)現(xiàn),后端通過繼承重用這些代碼。
createCpu0MCInstrInfo() 實(shí)例化 MCInstrInfo 對象X,通過 InitCpu0MCInstrInfo(X) ,進(jìn)行初始化。由于 InitCpu0MCInstrInfo(X) 是在 Cpu0GenInstrInfo.inc 中定義的,所以這個(gè)函數(shù)會(huì)添加指定的 Cpu0InstrInfo.td 中的信息。
createCpu0MCInstPrinter() 實(shí)例化 Cpu0InstPrinter,支持打印功能的說明。
createCpu0MCRegisterInfo()類似于“MC指令信息的注冊函數(shù)”,初始化了Cpu0RegisterInfo.td中,指定的寄存器信息。共享來自指令/寄存器 td 描述的一些值,如果與 td 描述文件一致,無需在 Initialize 例程中,再次指定。
createCpu0MCSubtargetInfo() 實(shí)例化 MCSubtargetInfo 對象,使用 Cpu0.td 信息,進(jìn)行初始化。
根據(jù)“目標(biāo)注冊部分” ,可以通過動(dòng)態(tài)注冊機(jī)制,在 LLVMInitializeCpu0TargetMC() 按需注冊 Cpu0 后端類,如上述函數(shù) LLVMInitializeCpu0TargetMC()。
現(xiàn)在,可以使用 AsmPrinter,如下所示,
Summary above translation into Table: Chapter 3 .bc IR instructions.
Table 10 Chapter 3 .bc IR instructions
以上摘要翻譯成表:第 3 章 .bc IR 指令。
表 10第 3 章 .bc IR 指令
? 下層:初始選擇 DAG(Cpu0ISelLowering.cpp,LowerReturn(…))
? ISel:指令選擇
? RVR:重寫虛擬寄存器,刪除 CopyToReg
? AsmP:Cpu0 Asm 打印
? Post-RA:Post-RA 偽指令擴(kuò)展pass
從上面的llc -print-before-all -print-after-all顯示,ret在stage Optimized legalized selection DAG中,翻譯成 Cpu0ISD::Ret,最后翻譯成Cpu0指令ret。由于 ret 使用常量 0(在此示例中為ret i32 0),因此常量 0通過Cpu0InstrInfo.td 中定義的以下模式,轉(zhuǎn)換為“addiu $2, $zero, 0”。
Cpu0ISelLowering.cpp 的函數(shù)LowerReturn() 正確處理返回變量。Chapter3_4/Cpu0ISelLowering.cpp在LowerReturn()中創(chuàng)建Cpu0ISD::Ret節(jié)點(diǎn),當(dāng)llvm系統(tǒng)遇到C的return關(guān)鍵字時(shí)調(diào)用。創(chuàng)建 DAG(Cpu0ISD::Ret (CopyToReg %X, %V0, %Y), %V0, Flag)。由于 V0 寄存器,在 CopyToReg 中分配,Cpu0ISD::Ret 使用 V0,帶有 V0 寄存器的 CopyToReg,繼續(xù)存在,不會(huì)在任何后續(xù)優(yōu)化步驟中刪除。如果使用“return DAG.getNode(Cpu0ISD::Ret, DL, MVT::Other, Chain, DAG.getRegister(Cpu0::LR, MVT::i32));”,不是“返回 DAG.getNode (Cpu0ISD::Ret, DL, MVT::Other, &RetOps[0], RetOps.size());”,V0 寄存器將不會(huì)生效,DAG(CopyToReg %X, %V0, %Y)將在以后的優(yōu)化步驟中刪除。
添加序言/尾聲功能
概念
以下來自 tricore_llvm.pdf 部分“4.4.2 非靜態(tài)寄存器信息”。
對于某些目標(biāo)架構(gòu),目標(biāo)架構(gòu)的寄存器集的某些方面,取決于可變因素,必須在運(yùn)行時(shí)確定。不能從 TableGen,描述靜態(tài)生成——盡管在 TriCore 后端,大部分是可能的。有以下幾點(diǎn):
? 調(diào)用者保存的寄存器。通常,ABI 指定一組寄存器,如果內(nèi)容在執(zhí)行期間可能被修改,函數(shù)必須在進(jìn)入時(shí),保存這些寄存器,在返回時(shí),恢復(fù)這些寄存器。
? 保留寄存器。盡管 TableGen 文件中,已經(jīng)定義了一組不可用的寄存器,TriCoreRegisterInfo 包含一個(gè)方法,用于在位向量中,標(biāo)記所有不可分配的寄存器編號。
實(shí)現(xiàn)了以下方法:
? emitPrologue() 在函數(shù)的開頭,插入序言代碼。由于 TriCore 的上下文模型,這是一項(xiàng)微不足道的任務(wù),不需要手動(dòng)保存任何寄存器。唯一需要做的,通過遞減堆棧指針,為函數(shù)的堆棧幀保留空間。如果函數(shù)需要一個(gè)幀指針,幀寄存器 %a14 被預(yù)先設(shè)置為堆棧指針的舊值。
? emitEpilogue() 旨在發(fā)出指令,在從函數(shù)返回之前,銷毀堆棧幀,恢復(fù)所有先前保存的寄存器。由于 %a10(堆棧指針),%a11(返回地址)和 %a14(幀指針,如果有),都是上層上下文的一部分,根本不需要結(jié)尾代碼。所有清理操作,都由 ret 指令隱式執(zhí)行。
? 對于引用堆棧槽中,一個(gè)數(shù)據(jù)字的每條指令,都調(diào)用消除幀索引()。代碼生成器之前的所有過程,都通過抽象幀索引和立即偏移量,尋址堆棧槽。此函數(shù)的目的,將這樣的引用轉(zhuǎn)換為寄存器-偏移對。根據(jù)包含指令的機(jī)器函數(shù),是否具有固定或可變堆棧幀,使用堆棧指針 %a10,或幀指針 %a14,作為基址寄存器。相應(yīng)計(jì)算偏移量。圖 17展示了兩種情況下,堆棧槽的尋址方式。
如果受影響指令的尋址模式,由于偏移量太大,無法處理該地址(偏移字段對于 BO 尋址模式,有 10 位,對于 BOL 模式,有 16 位),發(fā)出一系列指令,顯式計(jì)算有效地址。臨時(shí)結(jié)果,放入一個(gè)未使用的地址寄存器。如果沒有可用的,清除已占用的地址寄存器。LLVM 的框架提供了一個(gè)名為 RegScavenger 的類,負(fù)責(zé)處理所有細(xì)節(jié)。
able 11 Handle return register lr
表 11處理返回寄存器 lr
圖 17位于堆棧上的變量 a 的尋址。如果堆棧幀具有可變大小,必須相對于幀指針尋址槽
Fig. 17 Addressing of a variable a located on the stack. If the stack frame has a variable size, slot must be addressed relative to the frame pointer
Table 12 Backend functions called in PrologEpilogInserter.cpp
表 12 PrologEpilogInserter.cpp 中調(diào)用的后端函數(shù)
File PrologEpilogInserter.cpp includes the calling of backend functions spillCalleeSavedRegisters(), emitProlog(), emitEpilog() and eliminateFrameIndex() as follows,
文件 PrologEpilogInserter.cpp,包括調(diào)用后端函數(shù),spillCalleeSavedRegisters(), emitProlog(), emitEpilog() ,eliminateFrameIndex()。
Table 13 Cpu0 stack adjustment instructions before replace addiu and shl with lui instruction
Cpu0AnalyzeImmediate.cpp遞歸方式編寫,邏輯上有點(diǎn)復(fù)雜。不過前端編譯,用到了遞歸技巧。不跟蹤代碼,列出“表:用lui指令替換addiu和shl之前的Cpu0堆棧,調(diào)整指令”和“表:用lui指令替換addiu和shl之后的Cpu0堆棧,調(diào)整指令”中的堆棧大小和指令。
表13用lui指令,替換addiu和shl前的cpu0棧調(diào)整指令。
Table 14 Cpu0 stack adjustment instructions after replace addiu and shl with lui instruction
由于 Cpu0 堆棧是 8 字節(jié)對齊,從 0x7ff9 到 0x7fff 的地址,不可能存在的。
假設(shè) sp = 0xa0008000,stack size = 0x90008000, (0xa0008000 - 0x90008000) => 0x10000000。使用 Cpu0 Prologue 說明,進(jìn)行驗(yàn)證,如下所示,
- “addiu $1, $zero, -9” => ($1 = 0 + 0xfffffff7) => $1 = 0xfffffff7.
- “shl $1, $1, 28;” => $1 = 0x70000000.
- “addiu $1, $1, -32768” => $1 = (0x70000000 + 0xffff8000) => $1 = 0x6fff8000.
- “addu $sp, $sp, $1” => $sp = (0xa0008000 + 0x6fff8000) => $sp = 0x10000000.
使用 sp = 0x10000000,堆棧大小stack size = 0x90008000 的 Cpu0 Epilogue 指令,進(jìn)行驗(yàn)證。 - “addiu $1, $zero, -28671” => ($1 = 0 + 0xffff9001) => $1 = 0xffff9001.
- “shl $1, $1, 16;” => $1 = 0x90010000.
- “addiu $1, $1, -32768” => $1 = (0x90010000 + 0xffff8000) => $1 = 0x90008000.
- “addu $sp, $sp, $1” => $sp = (0x10000000 + 0x90008000) => $sp = 0xa0008000.
Cpu0AnalyzeImmediate::GetShortestSeq() ,將調(diào)用 Cpu0AnalyzeImmediate:: ReplaceADDiuSHLWithLUi() ,僅用單個(gè)指令 lui,替換 addiu 和 shl。
假設(shè) sp = 0xa0008000 和堆棧大小 = 0x90008000,那么 (0xa0008000 - 0x90008000) => 0x10000000。使用 Cpu0 Prologue 說明進(jìn)行驗(yàn)證,如下所示, - “l(fā)ui $1, 28671” => $1 = 0x6fff0000。
- “ori $1, $1, 32768” => $1 = (0x6fff0000 + 0x00008000) => $1 = 0x6fff8000。
- “addu $sp, $sp, $1” => $sp = (0xa0008000 + 0x6fff8000) => $sp = 0x10000000。
使用 sp = 0x10000000 和堆棧大小 = 0x90008000 的 Cpu0 Epilogue 指令進(jìn)行驗(yàn)證,如下所示, - “l(fā)ui $1, 36865” => $1 = 0x90010000。
- “addiu $1, $1, -32768” => $1 = (0x90010000 + 0xffff8000) => $1 = 0x90008000。
- “addu $sp, $sp, $1” => $sp = (0x10000000 + 0x90008000) => $sp = 0xa0008000。
表 15 llvm 后端階段的函數(shù)
able 15 Functions for llvm backend stages
在“添加 Cpu0DAGToDAGISel 類”部分的指令,添加了一個(gè)pass。可以將代碼嵌入到其它類似的pass中。有關(guān)信息,請查看 CodeGen/Passes.h。根據(jù)llc -debug-pass=Structure 指示的功能單元,調(diào)用pass。
已經(jīng)完成了一個(gè)簡單的 cpu0 編譯器,只支持ld, st,addiu,ori,lui,addu,shl和ret 8 條指令。
可能會(huì)想“在編寫了這么多代碼之后,只需得到這 8 條指令!”。重點(diǎn)是已經(jīng)為 Cpu0 目標(biāo)機(jī),創(chuàng)建了一個(gè)框架(llvm 后端結(jié)構(gòu)類繼承樹)。有超過 3000 行帶有注釋的源代碼,包括文件 .cpp,.h,.td 和 CMakeLists.txt。可以通過命令計(jì)數(shù)wc find dir -name *.cpp對于文件 .cpp,.h,.td,*.txt。LLVM 前端,總共有 700 行源代碼,沒有注釋。實(shí)際上,編寫后端是啟動(dòng)緩慢,但運(yùn)行很快。Clang 在 clang/lib 目錄中,有超過 500,000 行,帶有注釋的源代碼,包括 C++ 和 Obj C 支持。llvm 3.1 的 Mips 后端,只有 15,000 行,帶有注釋。即使是復(fù)雜的X86 CPU,外有CISC,內(nèi)有RISC(微指令),在llvm 3.1中,也只有45000行注釋。
Fig. 20 Code generation and execution flow
圖20的上半部分,生成和執(zhí)行計(jì)算機(jī)程序,工作流程和軟件包。IR代表中間表示。中間部分是工作流程。除了clang,其它塊都需要擴(kuò)展,進(jìn)行新的后端開發(fā)(許多后端也擴(kuò)展clang,Cpu0后端沒有這個(gè)需求)。實(shí)現(xiàn)了黃框部分。該圖的綠色部分,用于 Cpu0 后端的 lld 和 elf2hex,可以在http://jonathan2251.github.io/lbt/index.html上找到 。十六進(jìn)制是 ascii 文件格式,使用“0”到“9”和“a”到“f”,表示十六進(jìn)制值,因?yàn)?Verilog 語言機(jī)器,用作輸入文件。
參考鏈接:
http://jonathan2251.github.io/lbd/ctrlflow.html
總結(jié)
以上是生活随笔為你收集整理的CPU架构的llvm后端的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LLVM编译器基础架构与DragonEg
- 下一篇: CPU0 处理器的架构及应用