擴展 LLVM:新增指令、內建函數、類型等

介紹與警告

在使用 LLVM 的過程中,您可能希望為了您的研究專案或實驗而自訂它。此時,您可能會意識到您需要向 LLVM 新增一些東西,無論是新的基礎類型、新的內建函數,還是一個全新的指令。

當您意識到這一點時,請停下來思考。您真的需要擴展 LLVM 嗎?這是一個 LLVM 目前版本不支援的新基礎功能,還是可以從現有的 LLVM 元素合成出來?如果您不確定,請在 LLVM 論壇 上提問。原因是擴展 LLVM 會牽涉到您需要更新所有您打算用於擴展的不同 Pass,而且有 many LLVM 分析和轉換,因此這可能需要相當多的工作。

新增內建函數比新增指令容易得多,並且對於優化 Pass 是透明的。如果您的新增功能可以表示為函數呼叫,則內建函數是 LLVM 擴展的首選方法。

在您投入大量精力進行重要的擴展之前,請在列表上詢問您想要做的事情是否可以使用現有的基礎架構完成,或者是否可能已經有人在做了。這樣做將為您節省大量的時間和精力。

新增內建函數

向 LLVM 新增內建函數比新增指令容易得多。幾乎所有對 LLVM 的擴展都應該從內建函數開始,然後在有需要時轉變為指令。

  1. llvm/docs/LangRef.html:

    記錄內建函數。決定它是否特定於程式碼產生器,以及有哪些限制。與其他人討論,以確保這是一個好主意。

  2. llvm/include/llvm/IR/Intrinsics*.td:

    為您的內建函數新增一個條目。描述其記憶體存取特性以進行優化(這控制它是否會被 DCE、CSE 等處理)。如果任何參數需要是立即值,則必須使用 ImmArg 屬性指示。請注意,任何使用 llvm_any*_ty 類型作為參數或回傳類型的內建函數,都會被 tblgen 視為多載,並且需要在內建函數的名稱上加上相應的後綴。

  3. llvm/lib/Analysis/ConstantFolding.cpp:

    如果可以對您的內建函數進行常數折疊,請在 canConstantFoldCallToConstantFoldCall 函數中新增對它的支援。

  4. llvm/test/*:

    為您的測試案例新增測試案例到測試套件中

一旦將內建函數新增到系統中,您必須為其新增程式碼產生器支援。一般來說,您必須執行以下步驟

lib/Target/*/*.td 中為您選擇的目標新增對 .td 檔案的支援。

這通常是向 .td 檔案新增一個模式來匹配內建函數的問題,儘管這顯然可能需要新增您想要產生的指令。在 PowerPC 和 X86 後端中有很多範例可以參考。

新增 SelectionDAG 節點

與內建函數一樣,向 LLVM 新增 SelectionDAG 節點比新增指令容易得多。新增節點通常是為了幫助表示許多目標通用的指令。這些節點通常對應到 LLVM 指令(add、sub)或內建函數(byteswap、population count)。在其他情況下,新增節點是為了允許許多目標執行常見任務(在浮點數和整數表示法之間轉換)或在單一節點中捕捉更複雜的行為(rotate)。

  1. include/llvm/CodeGen/ISDOpcodes.h:

    為新的 SelectionDAG 節點新增一個列舉值。

  2. lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp:

    新增程式碼以在 getOperationName 中印出節點。如果您的新節點可以在編譯時期在給定常數參數的情況下進行評估(例如常數與另一個常數的加法),請找到採用適當參數數量的 getNode 方法,並為您的節點在 switch 語句中新增一個 case,該語句對與您的新節點採用相同參數數量的節點執行常數折疊。

  3. lib/CodeGen/SelectionDAG/LegalizeDAG.cpp:

    新增程式碼以合法化、提升和擴展節點(如有必要)。至少,您需要在 LegalizeOp 中為您的節點新增一個 case 語句,該語句對節點的運算元呼叫 LegalizeOp,並在任何運算元由於合法化而發生更改時回傳一個新節點。SelectionDAG 框架支援的並非所有目標都會原生支援新節點。在這種情況下,您還必須在 LegalizeOp 中節點的 case 語句中新增程式碼,以將您的節點擴展為更簡單、合法的操作。ISD::UREM 將餘數擴展為除法、乘法和減法的 case 是一個很好的範例。

  4. lib/CodeGen/SelectionDAG/LegalizeDAG.cpp:

    如果目標可能僅在某些大小下支援新增的節點,您還需要在 LegalizeOp 中節點的 case 語句中新增程式碼,以將節點的運算元提升到更大的尺寸,並執行正確的操作。您還需要在 PromoteOp 中新增程式碼來執行此操作。有關好的範例,請參閱 ISD::BSWAP,它將其運算元提升到更寬的尺寸,執行位元組交換,然後將正確的位元組右移,以在更寬的類型中模擬較窄的位元組交換。

  5. lib/CodeGen/SelectionDAG/LegalizeDAG.cpp:

    ExpandOp 中為您的節點新增一個 case,以教導合法化器如何在值已分成高半部和低半部的情況下執行新節點表示的動作。此 case 將用於在 32 位元目標上支援具有 64 位元運算元的節點。

  6. lib/CodeGen/SelectionDAG/DAGCombiner.cpp:

    如果您的節點可以與自身或其他現有節點以窺孔式的方式組合,請為其新增一個訪問函數,並從中呼叫該函數。您可以進行幾個簡單組合的好範例;visitFABSvisitSRL 是很好的起點。

  7. lib/Target/PowerPC/PPCISelLowering.cpp:

    每個目標都有 TargetLowering 類別的實作,通常在其自己的檔案中(儘管有些目標將其包含在與 DAGToDAGISel 相同的檔案中)。目標的預設行為是假設您的新節點對於該目標合法的所有類型都是合法的。如果此目標原生不支援您的節點,則告訴目標將其提升(如果在更大的類型中支援)或擴展。這將導致您在上面 LegalizeOp 中編寫的程式碼將您的新節點分解為此目標的其他合法節點。

  8. include/llvm/Target/TargetSelectionDAG.td:

    LLVM 支援的大多數目前目標都使用 DAGToDAG 方法產生程式碼,其中 SelectionDAG 節點與特定於目標的節點進行模式匹配,這些節點表示個別指令。為了讓目標將指令與您的新節點匹配,您必須在此檔案中的列表中為該節點新增一個定義,並帶有適當的類型約束。請查看 addbswapfadd 的範例。

  9. lib/Target/PowerPC/PPCInstrInfo.td:

    每個目標都有一個 tablegen 檔案,用於描述目標的指令集。對於使用 DAGToDAG 指令選擇框架的目標,為您的新節點新增一個模式,該模式使用一個或多個目標節點。有關此文件的說明目前有點稀疏,但是有一些不錯的範例。請參閱 PPCInstrInfo.tdrotl 的模式。

  10. TODO:記錄複雜模式。

  11. llvm/test/CodeGen/*:

    為您的新節點新增測試案例到測試套件中。llvm/test/CodeGen/X86/bswap.ll 是一個很好的範例。

新增指令

警告

新增指令會變更 bitcode 格式,並且需要一些努力來維護與先前版本的相容性。僅在絕對必要時才新增指令。

  1. llvm/include/llvm/IR/Instruction.def:

    為您的指令新增一個數字和一個列舉名稱

  2. llvm/include/llvm/IR/Instructions.h:

    為將表示您的指令的類別新增一個定義

  3. llvm/include/llvm/IR/InstVisitor.h:

    為訪問您的新指令類型的訪問者新增一個原型

  4. llvm/lib/AsmParser/LLLexer.cpp:

    新增一個新的符記,以便從組語文字檔中解析您的指令

  5. llvm/lib/AsmParser/LLParser.cpp:

    新增關於如何讀取您的指令以及它將建構什麼結果的文法

  6. llvm/lib/Bitcode/Reader/BitcodeReader.cpp:

    為您的指令新增一個 case,以及它將如何從 bitcode 中解析

  7. llvm/lib/Bitcode/Writer/BitcodeWriter.cpp:

    為您的指令新增一個 case,以及它將如何從 bitcode 中解析

  8. llvm/lib/IR/Instruction.cpp:

    為您的指令如何印出到組語新增一個 case

  9. llvm/lib/IR/Instructions.cpp:

    實作您在 llvm/include/llvm/Instructions.h 中定義的類別

  10. 測試您的指令

  11. llvm/lib/Target/*:

    為您的指令新增程式碼產生器支援,或新增 Lowering Pass。

  12. llvm/test/*:

    將您的測試案例新增到測試套件中。

此外,您需要實作(或修改)您希望理解此新指令的任何分析或 Pass。

新增類型

警告

新增類型會變更 bitcode 格式,並會破壞與目前現有的 LLVM 安裝的相容性。僅在絕對必要時才新增類型。

新增基礎類型

  1. llvm/include/llvm/IR/Type.h:

    為新類型新增列舉;為此類型新增靜態 Type*

  2. llvm/lib/IR/Type.cppllvm/lib/CodeGen/ValueTypes.cpp

    新增從 TypeID => Type* 的映射;初始化靜態 Type*

  3. llvm/include/llvm-c/Core.hllvm/lib/IR/Core.cpp

    為新類型新增列舉 LLVMTypeKind 並修改 LLVMTypeKind LLVMGetTypeKind(LLVMTypeRef Ty)

  4. llvm/lib/AsmParser/LLLexer.cpp:

    新增從文字組語中解析類型的能力

  5. llvm/lib/AsmParser/LLParser.cpp:

    為該類型新增符記

  6. llvm/lib/Bitcode/Writer/BitcodeWriter.cpp:

    修改 void ModuleBitcodeWriter::writeTypeTable() 以序列化您的類型

  7. llvm/lib/Bitcode/Reader/BitcodeReader.cpp:

    修改 Error BitcodeReader::parseTypeTableBody() 以讀取您的資料類型

  8. include/llvm/Bitcode/LLVMBitCodes.h:

    為新類型新增列舉 TypeCodes

新增衍生類型

  1. llvm/include/llvm/IR/Type.h:

    為新類型新增列舉;也新增類型的正向宣告

  2. llvm/include/llvm/IR/DerivedTypes.h:

    新增新類別以表示層次結構中的新類別;將正向宣告新增到 TypeMap 值類型

  3. llvm/lib/IR/Type.cppllvm/lib/CodeGen/ValueTypes.cpp

    新增對衍生類型的支援,尤其 enum TypeIDisget 方法。

  4. llvm/include/llvm-c/Core.hllvm/lib/IR/Core.cpp

    為新類型新增列舉 LLVMTypeKind 並修改 LLVMTypeKind LLVMGetTypeKind(LLVMTypeRef Ty)

  5. llvm/lib/AsmParser/LLLexer.cpp:

    修改 lltok::Kind LLLexer::LexIdentifier() 以新增從文字組語中解析類型的能力

  6. llvm/lib/Bitcode/Writer/BitcodeWriter.cpp:

    修改 void ModuleBitcodeWriter::writeTypeTable() 以序列化您的類型

  7. llvm/lib/Bitcode/Reader/BitcodeReader.cpp:

    修改 Error BitcodeReader::parseTypeTableBody() 以讀取您的資料類型

  8. include/llvm/Bitcode/LLVMBitCodes.h:

    為新類型新增列舉 TypeCodes

  9. llvm/lib/IR/AsmWriter.cpp:

    修改 void TypePrinting::print(Type *Ty, raw_ostream &OS) 以輸出新的衍生類型