擴充 LLVM:加入指令、內建函式、型別等等

簡介與警告

在使用 LLVM 的過程中,您可能會希望針對您的研究專案或實驗進行客製化。此時,您可能會意識到您需要在 LLVM 中添加一些東西,無論是新的基本型別、新的內建函式,還是全新的指令。

當您意識到這一點時,請停下來思考。您真的需要擴充 LLVM 嗎?它是 LLVM 目前版本不支援的新基本功能,還是可以從現有的 LLVM 元素合成?如果您不確定,請在 LLVM 論壇 上詢問。原因是擴充 LLVM 會變得複雜,因為您需要更新所有打算與您的擴充一起使用的不同 pass,而 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 節點

與內部函式一樣,將新的 SelectionDAG 節點添加到 LLVM 比添加新的指令容易得多。添加新節點通常是為了表示許多目標通用的指令。這些節點通常映射到 LLVM 指令(加法、減法)或內部函式(位元組交換、位元計數)。在其他情況下,添加新節點是為了允許多個目標執行常見任務(在浮點數和整數表示法之間轉換)或在單個節點中捕獲更複雜的行為(旋轉)。

  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 相同的檔案中)。 目標的預設行為是假設您的新節點對於該目標合法的任何型別都是合法的。 如果此目標本身不支援您的節點,請指示目標將其 Promote(如果在較大型別支援)或 Expand 它。 這將導致您在上面 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 是一個很好的例子。

新增指令

警告

新增指令會改變位元碼格式,並且需要付出一些努力才能與先前的版本保持相容性。 僅在絕對必要時才新增指令。

  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:

    為您的指令新增一個案例,以及如何從位元碼中解析它

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

    為您的指令新增一個案例,以及如何從位元碼中解析它

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

    為您的指令如何輸出到組譯新增一個案例

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

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

  10. 測試您的指令

  11. llvm/lib/Target/*:

    為程式碼產生器新增對指令的支援,或新增 lowering pass。

  12. llvm/test/*:

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

此外,您需要實作(或修改)您想要理解這個新指令的任何分析或 pass。

新增新的型別

警告

新增新的型別會改變位元碼格式,並且會破壞與現有 LLVM 安裝的相容性。 僅在絕對必要時才新增新的型別。

新增基本型別

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

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

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

    新增從 TypeIDType* 的映射;初始化靜態 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 TypeID 以及 isget 方法。

  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) 以輸出新的衍生類型