3. 建置 JIT:每個函式的延遲編譯¶
本教學文件仍在積極開發中。內容尚未完整,細節可能會頻繁變更。 然而,我們邀請您以現狀試用,並歡迎任何回饋意見。
3.1. 第 3 章 簡介¶
警告:由於 ORC API 更新,本文目前已過時。
範例程式碼已更新,可以使用。本文將在 API 變動趨緩後更新。
歡迎來到「在 LLVM 中建置基於 ORC 的 JIT」教學文件的第 3 章。本章討論延遲 JIT 編譯,並示範如何透過從第 2 章的 JIT 中加入 ORC CompileOnDemand 層來啟用它。
3.2. 延遲編譯¶
當我們將模組加入第 2 章的 KaleidoscopeJIT 類別時,IRTransformLayer、IRCompileLayer 和 RTDyldObjectLinkingLayer 會分別立即為我們最佳化、編譯和連結它。這種預先完成所有使模組可執行的工作方案,簡單易懂,並且其效能特性很容易推斷。然而,如果要編譯的程式碼量很大,則會導致非常高的啟動時間,而且如果只有少數編譯後的函式在執行時期被呼叫,也可能進行許多不必要的編譯。一個真正的「即時 (just-in-time)」編譯器應該允許我們延遲任何給定函式的編譯,直到該函式首次被呼叫的那一刻,從而縮短啟動時間並消除冗餘工作。事實上,ORC API 為我們提供了一個延遲編譯 LLVM IR 的層:CompileOnDemandLayer。
CompileOnDemandLayer 類別符合第 2 章中描述的層介面,但其 addModule 方法的行為與我們目前看到的層非常不同:它不會預先執行任何工作,而是掃描正在加入的模組,並安排在每個函式第一次被呼叫時才編譯它們。為了做到這一點,CompileOnDemandLayer 為它掃描的每個函式建立兩個小工具:一個樁 (stub) 和一個編譯回呼 (compile callback)。樁是一對函式指標(一旦函式被編譯,它將指向函式的實作)和一個透過該指標的間接跳轉。透過在程式的生命週期內固定間接跳轉的位址,我們可以給予函式一個永久的「有效位址」,即使函式的實作從未被編譯,或者即使它被多次編譯(例如,由於以更高的最佳化層級重新編譯函式)並且位址發生變化,這個位址也可以安全地用於間接和函式指標比較。第二個工具,編譯回呼,代表從程式重新進入編譯器的進入點,它將觸發函式的編譯,然後執行。透過將函式的樁初始化為指向函式的編譯回呼,我們啟用了延遲編譯:第一次嘗試呼叫函式將會追蹤函式指標並觸發編譯回呼。編譯回呼將編譯函式,更新樁的函式指標,然後執行該函式。在所有後續的函式呼叫中,函式指標將指向已經編譯的函式,因此不會再有來自編譯器的額外負擔。我們將在本教學文件的下一章中更詳細地探討這個過程,但現在我們將信任 CompileOnDemandLayer 為我們設定所有樁和回呼。我們只需要將 CompileOnDemandLayer 加入到我們的堆疊頂部,我們就可以獲得延遲編譯的好處。我們只需要對原始碼做一些修改
...
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/ExecutionEngine/Orc/CompileOnDemandLayer.h"
#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
...
...
class KaleidoscopeJIT {
private:
std::unique_ptr<TargetMachine> TM;
const DataLayout DL;
RTDyldObjectLinkingLayer ObjectLayer;
IRCompileLayer<decltype(ObjectLayer), SimpleCompiler> CompileLayer;
using OptimizeFunction =
std::function<std::shared_ptr<Module>(std::shared_ptr<Module>)>;
IRTransformLayer<decltype(CompileLayer), OptimizeFunction> OptimizeLayer;
std::unique_ptr<JITCompileCallbackManager> CompileCallbackManager;
CompileOnDemandLayer<decltype(OptimizeLayer)> CODLayer;
public:
using ModuleHandle = decltype(CODLayer)::ModuleHandleT;
首先,我們需要包含 CompileOnDemandLayer.h 標頭檔,然後加入兩個新的成員:一個 std::unique_ptr<JITCompileCallbackManager> 和一個 CompileOnDemandLayer 到我們的類別中。CompileCallbackManager 成員被 CompileOnDemandLayer 用於建立每個函式所需的編譯回呼。
KaleidoscopeJIT()
: TM(EngineBuilder().selectTarget()), DL(TM->createDataLayout()),
ObjectLayer([]() { return std::make_shared<SectionMemoryManager>(); }),
CompileLayer(ObjectLayer, SimpleCompiler(*TM)),
OptimizeLayer(CompileLayer,
[this](std::shared_ptr<Module> M) {
return optimizeModule(std::move(M));
}),
CompileCallbackManager(
orc::createLocalCompileCallbackManager(TM->getTargetTriple(), 0)),
CODLayer(OptimizeLayer,
[this](Function &F) { return std::set<Function*>({&F}); },
*CompileCallbackManager,
orc::createLocalIndirectStubsManagerBuilder(
TM->getTargetTriple())) {
llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr);
}
接下來,我們必須更新我們的建構子以初始化新的成員。為了建立適當的編譯回呼管理器,我們使用 createLocalCompileCallbackManager 函式,它接受一個 TargetMachine 和一個 ExecutorAddr,如果它收到編譯未知函式的請求,則呼叫該 ExecutorAddr。在我們簡單的 JIT 中,這種情況不太可能發生,所以我們將作弊並在此處傳遞 '0'。在生產品質的 JIT 中,您可以提供一個函式的位址,該函式會拋出例外,以便展開 JIT 編譯程式碼的堆疊。
現在我們可以建構我們的 CompileOnDemandLayer。遵循先前層的模式,我們首先傳遞對我們堆疊中下一層 – OptimizeLayer 的參考。接下來,我們需要提供一個「分割函式 (partitioning function)」:當一個尚未編譯的函式被呼叫時,CompileOnDemandLayer 將呼叫這個函式來詢問我們想要編譯什麼。至少我們需要編譯正在被呼叫的函式(由分割函式的引數給出),但我們也可以請求 CompileOnDemandLayer 編譯從正在被呼叫的函式中無條件呼叫(或極有可能被呼叫)的其他函式。對於 KaleidoscopeJIT,我們將保持簡單,只請求編譯被呼叫的函式。接下來,我們傳遞對我們的 CompileCallbackManager 的參考。最後,我們需要提供一個「間接樁管理器建構器 (indirect stubs manager builder)」:一個實用工具函式,用於建構 IndirectStubManagers,而 IndirectStubManagers 又用於為每個模組中的函式建置樁。CompileOnDemandLayer 將為每次呼叫 addModule 呼叫間接樁管理器建構器一次,並使用產生的間接樁管理器為集合中所有模組中的所有函式建立樁。如果/當模組集合從 JIT 中移除時,間接樁管理器將被刪除,釋放分配給樁的任何記憶體。我們透過使用 createLocalIndirectStubsManagerBuilder 實用工具提供此函式。
// ...
if (auto Sym = CODLayer.findSymbol(Name, false))
// ...
return cantFail(CODLayer.addModule(std::move(Ms),
std::move(Resolver)));
// ...
// ...
return CODLayer.findSymbol(MangledNameStream.str(), true);
// ...
// ...
CODLayer.removeModule(H);
// ...
最後,我們需要替換我們的 addModule、findSymbol 和 removeModule 方法中對 OptimizeLayer 的參考。完成這些,我們就開始運作了。
待辦事項
** 章節結論。**
3.3. 完整程式碼列表¶
以下是我們執行範例的完整程式碼列表,其中加入了 CompileOnDemand 層以啟用每個函式延遲編譯。要建置此範例,請使用
# Compile
clang++ -g toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core orcjit native` -O3 -o toy
# Run
./toy
這是程式碼
//===- KaleidoscopeJIT.h - A simple JIT for Kaleidoscope --------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.dev.org.tw/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Contains a simple JIT definition for use in the kaleidoscope tutorials.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H
#define LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H
#include "llvm/ADT/StringRef.h"
#include "llvm/ExecutionEngine/Orc/CompileOnDemandLayer.h"
#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
#include "llvm/ExecutionEngine/Orc/Core.h"
#include "llvm/ExecutionEngine/Orc/EPCIndirectionUtils.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h"
#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h"
#include "llvm/ExecutionEngine/Orc/IRPartitionLayer.h"
#include "llvm/ExecutionEngine/Orc/IRTransformLayer.h"
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/InstCombine/InstCombine.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Scalar/GVN.h"
#include <memory>
namespace llvm {
namespace orc {
class KaleidoscopeJIT {
private:
std::unique_ptr<ExecutionSession> ES;
std::unique_ptr<EPCIndirectionUtils> EPCIU;
DataLayout DL;
MangleAndInterner Mangle;
RTDyldObjectLinkingLayer ObjectLayer;
IRCompileLayer CompileLayer;
IRTransformLayer OptimizeLayer;
IRPartitionLayer IPLayer;
CompileOnDemandLayer CODLayer;
JITDylib &MainJD;
static void handleLazyCallThroughError() {
errs() << "LazyCallThrough error: Could not find function body";
exit(1);
}
public:
KaleidoscopeJIT(std::unique_ptr<ExecutionSession> ES,
std::unique_ptr<EPCIndirectionUtils> EPCIU,
JITTargetMachineBuilder JTMB, DataLayout DL)
: ES(std::move(ES)), EPCIU(std::move(EPCIU)), DL(std::move(DL)),
Mangle(*this->ES, this->DL),
ObjectLayer(*this->ES,
[]() { return std::make_unique<SectionMemoryManager>(); }),
CompileLayer(*this->ES, ObjectLayer,
std::make_unique<ConcurrentIRCompiler>(std::move(JTMB))),
OptimizeLayer(*this->ES, CompileLayer, optimizeModule),
IPLayer(*this->ES, OptimizeLayer),
CODLayer(*this->ES, IPLayer, this->EPCIU->getLazyCallThroughManager(),
[this] { return this->EPCIU->createIndirectStubsManager(); }),
MainJD(this->ES->createBareJITDylib("<main>")) {
MainJD.addGenerator(
cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(
DL.getGlobalPrefix())));
}
~KaleidoscopeJIT() {
if (auto Err = ES->endSession())
ES->reportError(std::move(Err));
if (auto Err = EPCIU->cleanup())
ES->reportError(std::move(Err));
}
static Expected<std::unique_ptr<KaleidoscopeJIT>> Create() {
auto EPC = SelfExecutorProcessControl::Create();
if (!EPC)
return EPC.takeError();
auto ES = std::make_unique<ExecutionSession>(std::move(*EPC));
auto EPCIU = EPCIndirectionUtils::Create(*ES);
if (!EPCIU)
return EPCIU.takeError();
(*EPCIU)->createLazyCallThroughManager(
*ES, ExecutorAddr::fromPtr(&handleLazyCallThroughError));
if (auto Err = setUpInProcessLCTMReentryViaEPCIU(**EPCIU))
return std::move(Err);
JITTargetMachineBuilder JTMB(
ES->getExecutorProcessControl().getTargetTriple());
auto DL = JTMB.getDefaultDataLayoutForTarget();
if (!DL)
return DL.takeError();
return std::make_unique<KaleidoscopeJIT>(std::move(ES), std::move(*EPCIU),
std::move(JTMB), std::move(*DL));
}
const DataLayout &getDataLayout() const { return DL; }
JITDylib &getMainJITDylib() { return MainJD; }
Error addModule(ThreadSafeModule TSM, ResourceTrackerSP RT = nullptr) {
if (!RT)
RT = MainJD.getDefaultResourceTracker();
return CODLayer.add(RT, std::move(TSM));
}
Expected<ExecutorSymbolDef> lookup(StringRef Name) {
return ES->lookup({&MainJD}, Mangle(Name.str()));
}
private:
static Expected<ThreadSafeModule>
optimizeModule(ThreadSafeModule TSM, const MaterializationResponsibility &R) {
TSM.withModuleDo([](Module &M) {
// Create a function pass manager.
auto FPM = std::make_unique<legacy::FunctionPassManager>(&M);
// Add some optimizations.
FPM->add(createInstructionCombiningPass());
FPM->add(createReassociatePass());
FPM->add(createGVNPass());
FPM->add(createCFGSimplificationPass());
FPM->doInitialization();
// Run the optimizations over all functions in the module being added to
// the JIT.
for (auto &F : M)
FPM->run(F);
});
return std::move(TSM);
}
};
} // end namespace orc
} // end namespace llvm
#endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H