撰寫 LLVM Pass¶
簡介 — 什麼是 Pass?¶
警告
本文件介紹新的 Pass 管理器。LLVM 使用舊版 Pass 管理器來處理程式碼產生流程。如需詳細資訊,請參閱 撰寫 LLVM Pass(舊版 PM 版本) 和 使用新的 Pass 管理器。
LLVM Pass 架構是 LLVM 系統的重要組成部分,因為 LLVM Pass 是編譯器中大多數有趣部分的所在。Pass 執行組成編譯器的轉換和最佳化,它們建立這些轉換所使用的分析結果,最重要的是,它們是一種用於編譯器程式碼的結構化技術。
與舊版 Pass 管理器中透過繼承定義 Pass 介面不同,新版 Pass 管理器中的 Pass 依賴基於概念的多型性,這意味著沒有明確的介面(如需詳細資訊,請參閱 PassManager.h
中的註解)。所有 LLVM Pass 都繼承自 CRTP 混入 PassInfoMixin<PassT>
。Pass 應該有一個 run()
方法,該方法返回一個 PreservedAnalyses
並接收一些 IR 單元以及一個分析管理器。例如,一個函數 Pass 將有一個 PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
方法。
我們首先向您展示如何建構 Pass,從設定建置、建立 Pass 到執行和測試它。查看現有的 Pass 始終是學習細節的好方法。
快速入門 — 撰寫 hello world¶
在這裡,我們描述如何編寫 Pass 的“hello world”。“HelloWorld”Pass 旨在簡單地印出正在編譯的程式中存在的非外部函數的名稱。它根本不會修改程式,只是檢查它。
以下程式碼已存在;您可以隨意建立與 HelloWorld 原始檔同名的不同名稱的 Pass。
設定建置¶
首先,按照 LLVM 系統入門 中的說明設定和建置 LLVM。
接下來,我們要重複使用一個現有的目錄(創建新目錄會涉及到比我們想要的更多 CMake 文件的修改)。在這個例子中,我們將使用 llvm/lib/Transforms/Utils/HelloWorld.cpp
,它已經被創建好了。如果你想創建自己的 Pass,請在 llvm/lib/Transforms/Utils/CMakeLists.txt
中添加一個新的源文件(假設你想要你的 Pass 位於 Transforms/Utils
目錄中)。
現在我們已經為新的 Pass 設置好了建置環境,接下來需要編寫 Pass 本身的代碼。
基本代碼要求¶
現在已經為新的 Pass 設置好了建置環境,我們只需要編寫它即可。
首先,我們需要在標頭檔中定義 Pass。我們將創建 llvm/include/llvm/Transforms/Utils/HelloWorld.h
。該文件應該包含以下樣板代碼:
#ifndef LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H
#define LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H
#include "llvm/IR/PassManager.h"
namespace llvm {
class HelloWorldPass : public PassInfoMixin<HelloWorldPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
};
} // namespace llvm
#endif // LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H
這將創建 Pass 的類別,並聲明實際運行 Pass 的 run()
方法。繼承自 PassInfoMixin<PassT>
設置了更多樣板代碼,因此我們不必自己編寫。
我們的類別位於 llvm
命名空間中,因此我們不會污染全局命名空間。
接下來,我們將創建 llvm/lib/Transforms/Utils/HelloWorld.cpp
,從以下內容開始:
#include "llvm/Transforms/Utils/HelloWorld.h"
… 以包含我們剛才創建的標頭檔。
using namespace llvm;
… 是必需的,因為包含文件中的函數位於 llvm 命名空間中。這應該只在非標頭檔中完成。
接下來,我們有 Pass 的 run()
定義:
PreservedAnalyses HelloWorldPass::run(Function &F,
FunctionAnalysisManager &AM) {
errs() << F.getName() << "\n";
return PreservedAnalyses::all();
}
… 它只是將函數的名稱輸出到 stderr。Pass 管理器將確保 Pass 會在模組中的每個函數上運行。 PreservedAnalyses
返回值表示所有分析(例如支配樹)在這個 Pass 之後仍然有效,因為我們沒有修改任何函數。
這就是 Pass 本身。現在,為了「註冊」Pass,我們需要將其添加到幾個地方。在 FUNCTION_PASS
部分的 llvm/lib/Passes/PassRegistry.def
中添加以下內容:
FUNCTION_PASS("helloworld", HelloWorldPass())
… 以「helloworld」的名稱添加 Pass。
llvm/lib/Passes/PassRegistry.def
被 #include 到 llvm/lib/Passes/PassBuilder.cpp
中多次,原因有很多。由於它構建了我們的 Pass,因此我們还需要在 llvm/lib/Passes/PassBuilder.cpp
中添加適當的 #include:
#include "llvm/Transforms/Utils/HelloWorld.h"
這應該是我們的 Pass 所需的所有代碼,現在可以編譯並運行它了。
使用 opt
運行 Pass¶
現在你已經有了一個全新的 Pass,我們可以建置 opt 並使用它來通過 Pass 運行一些 LLVM IR。
$ ninja -C build/ opt
# or whatever build system/build directory you are using
$ cat /tmp/a.ll
define i32 @foo() {
%a = add i32 2, 3
ret i32 %a
}
define void @bar() {
ret void
}
$ build/bin/opt -disable-output /tmp/a.ll -passes=helloworld
foo
bar
我們的 Pass 已運行並按預期打印出函數的名稱!
測試 Pass¶
測試我們的 Pass 對於防止未來的回歸非常重要。我們將在 llvm/test/Transforms/Utils/helloworld.ll
中添加一個 lit 測試。有關測試的更多信息,請參閱 LLVM 測試基礎架構指南。
$ cat llvm/test/Transforms/Utils/helloworld.ll
; RUN: opt -disable-output -passes=helloworld %s 2>&1 | FileCheck %s
; CHECK: {{^}}foo{{$}}
define i32 @foo() {
%a = add i32 2, 3
ret i32 %a
}
; CHECK-NEXT: {{^}}bar{{$}}
define void @bar() {
ret void
}
$ ninja -C build check-llvm
# runs our new test alongside all other llvm lit tests
常見問題解答¶
必要遍歷¶
如果一個遍歷定義了一個靜態 isRequired()
方法並返回 true,則該遍歷為必要遍歷。例如:
class HelloWorldPass : public PassInfoMixin<HelloWorldPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
static bool isRequired() { return true; }
};
必要遍歷是不可跳過的遍歷。必要遍歷的例子是 AlwaysInlinerPass
,它必須始終運行以保留 alwaysinline
語義。遍歷管理器是必要的,因為它們可能包含其他必要遍歷。
跳過遍歷的一個例子是 optnone
函數屬性,它指定不應對函數運行優化。必要遍歷仍將在 optnone
函數上運行。
有關更多實現細節,請參閱 PassInstrumentation::runBeforePass()
。
將遍歷註冊為插件¶
LLVM 提供了一種機制,可以在各種工具(如 clang
或 opt
)中註冊遍歷插件。遍歷插件可以將遍歷添加到默認優化流程中,也可以通過 opt
等工具手動運行。有關更多信息,請參閱 使用新的遍歷管理器。
在倉庫的根目錄下創建一個 CMake 專案,與其他專案一起。該專案必須包含以下最小的 CMakeLists.txt
add_llvm_pass_plugin(MyPassName source.cpp)
有關更多 CMake 細節,請參閱 add_llvm_pass_plugin
的定義。
遍歷必須為新的遍歷管理器提供至少兩個入口點之一,一個用於靜態註冊,另一個用於動態加載的插件
llvm::PassPluginLibraryInfo get##Name##PluginInfo();
extern "C" ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() LLVM_ATTRIBUTE_WEAK;
遍歷插件預設情況下是動態編譯和鏈接的。將 LLVM_${NAME}_LINK_INTO_TOOLS
設置為 ON
會將專案轉換為靜態鏈接的擴展。
有關樹內範例,請參閱 llvm/examples/Bye/
。
要使 PassBuilder
察覺到靜態鏈接的遍歷插件
// Declare plugin extension function declarations.
#define HANDLE_EXTENSION(Ext) llvm::PassPluginLibraryInfo get##Ext##PluginInfo();
#include "llvm/Support/Extension.def"
...
// Register plugin extensions in PassBuilder.
#define HANDLE_EXTENSION(Ext) get##Ext##PluginInfo().RegisterPassBuilderCallbacks(PB);
#include "llvm/Support/Extension.def"
要使 PassBuilder
察覺到動態鏈接的遍歷插件
// Load plugin dynamically.
auto Plugin = PassPlugin::Load(PathToPlugin);
if (!Plugin)
report_error();
// Register plugin extensions in PassBuilder.
Plugin.registerPassBuilderCallbacks(PB);