2. 實驗 2/2 - 使用 LLVM JIT ExecutionEngine 是一個關鍵的類別。它能把 Module 中的特定 Function 動態編譯成 host binary。此外,它還提供介面供使用者提取 JIT-ted function (host binary) 的資訊。 本實驗補上前篇實驗的後半部。附件是完整的代碼。 --------------------- tut2.cpp (cont'd) ----------------------------- #include <llvm/LLVMContext.h> #include <llvm/Module.h> #include <llvm/Function.h> #include <llvm/PassManager.h> #include <llvm/Analysis/Verifier.h> #include <llvm/Assembly/PrintModulePass.h> #include <llvm/Support/FormattedStream.h> #include <llvm/Support/IRBuilder.h> // LLVM JIT 所需的頭文件 #include <llvm/ExecutionEngine/JIT.h> #include <llvm/ExecutionEngine/GenericValue.h> #include <llvm/Target/TargetSelect.h> #include <llvm/Support/ManagedStatic.h> using namespace llvm; Module* makeLLVMModule(LLVMContext& ctx); int main(int argc, char**argv) { // 根據 host 初始化 LLVM target,即針對哪一個 target 生成 binary。 InitializeNativeTarget(); LLVMContext Context; Module* Mod = makeLLVMModule(Context); verifyModule(*Mod, PrintMessageAction); PassManager PM; PM.add(createPrintModulePass(&outs())); PM.run(*Mod); // 根據給定的 Module 生成 ExecutionEngine。 ExecutionEngine* EE = EngineBuilder(Mod).create(); // 在 Module 中插入新的函式 (Function)。若該函式已存在,返回該函式。 // 因此這邊取回 makeLLVMModule 所插入的 gcd 函式。 Function *GCD = cast<Function>(Mod->getOrInsertFunction("gcd", /* 返回型別 */ Type::getInt32Ty(Context), /* 參數 */ Type::getInt32Ty(Context), /* 參數 */ Type::getInt32Ty(Context), /* 結尾 */ (Type *)0)); // 準備傳給 GCD 的參數。 std::vector<GenericValue> Args(2); Args[0].IntVal = APInt(32, 17); Args[1].IntVal = APInt(32, 4); // 將參數傳給 gcd。將其 JIT 成 host binary 並執行。取得執行後的結果。 GenericValue GV = EE->runFunction(GCD, Args); outs() << "---------\nstarting GCD(17, 4) with JIT...\n"; outs() << "Result: " << GV.IntVal << "\n"; // 釋放 ExecutionEngine 中相對於 GCD JIT'ed machine code 的內存。 EE->freeMachineCodeForFunction(GCD); delete EE; llvm_shutdown(); // 釋放用於 LLVM 內部的資料結構。 return 0; } --------------------------------------------------------------------- 眼尖的你應該會發現我似乎忘了 delete Mod。很好! 但是你一但加上 delete Mod,執行之後 會出現 segmentation fault。試試看! ;-) 答案在 EngineBuilder 的註解裡 "the created engine takes ownership of the module"。 --- /// EngineBuilder - Constructor for EngineBuilder. If create() is called and /// is successful, the created engine takes ownership of the module. EngineBuilder(Module *m) : M(m) { InitEngine(); } --- 我還漏了底下這一行沒講清楚。 verifyModule(*Mod, PrintMessageAction); 什麼時候 Module 是非法的? 舉例來說,假設 Module 裡面有底下這樣一條 LLVM 指令。 %x = add i32 1, %x ; x = 1 + x 直覺上來看很正常。但是 LLVM IR 滿足 SSA (Static Single Assignment) 的形式,亦即 每條賦值指令都會生成一個新的變數。所以上面這條指令應該是底下這樣: %x1 = add i32 1, %x ; x1 = 1 + x SSA 能夠簡化資料流 (data flow) 的分析,有助於後續的優化。大部分的編譯器 (我只確定 GCC 和 LLVM) 的 IR 主要都是採 SSA 的形式。 最後,各位要注意 LLVM API 不穩定。看各位是要緊跟 svn 版本進行開發,又或是只跟 release 版本開發。這其中各有利弊,但是別忘了 LLVM 算是很熱心的一個 community, 我想有問題都可以到郵件列表或聊天室詢問。[1][2] [1] http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev [2] http://llvm.org/docs/#irc -- Wei-Ren Chen (陳韋任) Computer Systems Lab, Institute of Information Science, Academia Sinica, Taiwan (R.O.C.) Tel:886-2-2788-3799 #1667
/* * $ g++ tut2.cpp `llvm-config --cxxflags --ldflags --libs all` -o tut2 * $ ./tut2 */ #include <llvm/LLVMContext.h> #include <llvm/Module.h> #include <llvm/Function.h> #include <llvm/PassManager.h> #include <llvm/Analysis/Verifier.h> #include <llvm/Assembly/PrintModulePass.h> #include <llvm/Support/FormattedStream.h> #include <llvm/Support/IRBuilder.h> #include <llvm/ExecutionEngine/JIT.h> #include <llvm/ExecutionEngine/GenericValue.h> #include <llvm/Target/TargetSelect.h> #include <llvm/Support/ManagedStatic.h> using namespace llvm; Module* makeLLVMModule(LLVMContext& Context); // 負責創建 LLVM Module。 int main(int argc, char**argv) { // 根據 host 初始化 LLVM target,即針對哪一個 target 生成 binary。 InitializeNativeTarget(); // LLVMContext 是較晚期才加入 LLVM 的類別。 // 其作用是管理 LLVM core infrastructure 中的 global data。 // 多執行緒的情況下,每個執行緒都應該要有自己的 LLVMContext。 // 請見 llvm/LLVMContext.h。 LLVMContext Context; Module* Mod = makeLLVMModule(Context); // 呼叫 makeLLVMModule 取得 LLVM Module。 verifyModule(*Mod, PrintMessageAction); // 驗證此 Module 是否合法。 PassManager PM; // 所有的轉換或是優化均被視為 pass,由 PassManager 進行調度。 PM.add(createPrintModulePass(&outs())); // 加入打印 LLVM Module 內容的 pass。 PM.run(*Mod); // 於該 Module 運行 pass。 // 根據給定的 Module 生成 ExecutionEngine。 ExecutionEngine* EE = EngineBuilder(Mod).create(); // 在 Module 中插入新的函式 (Function)。若該函式已存在,返回該函式。 // 因此這邊取回 makeLLVMModule 所插入的 gcd 函式。 Function *GCD = cast<Function>(Mod->getOrInsertFunction("gcd", /* 返回型別 */ Type::getInt32Ty(Context), /* 參數 */ Type::getInt32Ty(Context), /* 參數 */ Type::getInt32Ty(Context), /* 結尾 */ (Type *)0)); // 準備傳給 GCD 的參數。 std::vector<GenericValue> Args(2); Args[0].IntVal = APInt(32, 17); Args[1].IntVal = APInt(32, 4); // 將參數傳給 gcd。將其 JIT 成 host binary 並執行。取得執行後的結果。 GenericValue GV = EE->runFunction(GCD, Args); outs() << "---------\nstarting GCD(17, 4) with JIT...\n"; outs() << "Result: " << GV.IntVal << "\n"; // 釋放 ExecutionEngine 中相對於 GCD JIT'ed machine code 的內存。 EE->freeMachineCodeForFunction(GCD); delete EE; llvm_shutdown(); // 釋放用於 LLVM 內部的資料結構。 return 0; } Module* makeLLVMModule(LLVMContext& Context) { Module* M = new Module("tut2", Context); // 在 Module 中插入新的函式 (Function)。若該函式已存在,返回該函式。 Function *gcd = cast<Function>(M->getOrInsertFunction("gcd", /* 返回型別 */ Type::getInt32Ty(Context), /* 參數 */ Type::getInt32Ty(Context), /* 參數 */ Type::getInt32Ty(Context), /* 結尾 */ (Type *)0)); // 分別將 gcd 的參數命名為 x 和 y。使將來的輸出較易懂。 Function::arg_iterator args = gcd->arg_begin(); Value* x = args++; x->setName("x"); Value* y = args++; y->setName("y"); // 在函式中插入基本塊 (BasicBlock)。基本塊內含 LLVM IR,並以 // terminator instruction 結尾,請見 // http://llvm.org/docs/LangRef.html#terminators // // 底下建立的基本塊,請參照 // http://llvm.org/releases/2.6/docs/tutorial/JITTutorial2.html // 中的 CFG。 BasicBlock* entry = BasicBlock::Create(Context, "entry", gcd); BasicBlock* ret = BasicBlock::Create(Context, "return", gcd); BasicBlock* cond_false = BasicBlock::Create(Context, "cond_false", gcd); BasicBlock* cond_true = BasicBlock::Create(Context, "cond_true", gcd); // 即使我們賦予 cond_false 和 cond_false_2 相同的名稱,LLVM 會將 // 之替換成不同名稱。如此可省去我們命名的麻煩。 BasicBlock* cond_false_2 = BasicBlock::Create(Context, "cond_false", gcd); /* 開始填入 LLVM IR */ // IRBuilder 提供一組一致的介面生成 LLVM IR。 IRBuilder<> builder(entry); // 於基本塊 entry 填入 LLVM IR。 // // %tmp = icmp eq i32 %x, %y // br i1 %tmp, label %return, label %cond_false // Value* xEqualsY = builder.CreateICmpEQ(x, y, "tmp"); builder.CreateCondBr(xEqualsY, ret, cond_false); builder.SetInsertPoint(ret); // 於基本塊 ret 填入 LLVM IR。 // // ret i32 %x // builder.CreateRet(x); builder.SetInsertPoint(cond_false); // 於基本塊 cond_false 填入 LLVM IR。 // // 注意! LLVM 中的 integer type 不帶有 signed 或是 unsigned 的資訊。 // icmp 需要顯式的對其 integer type 運算元做 signed 或是 unsigned // 的解釋。請見 http://llvm.org/docs/LangRef.html#i_icmp // // %tmp2 = icmp ult i32 %x, %y // br i1 %tmp2, label %cond_true, label %cond_false1 // Value* xLessThanY = builder.CreateICmpULT(x, y, "tmp"); builder.CreateCondBr(xLessThanY, cond_true, cond_false_2); builder.SetInsertPoint(cond_true); // 於基本塊 cond_true 填入 LLVM IR。 // // %tmp3 = sub i32 %y, %x // %tmp4 = call i32 @gcd(i32 %x, i32 %tmp3) // ret i32 %tmp4 // Value* yMinusX = builder.CreateSub(y, x, "tmp"); std::vector<Value*> args1; args1.push_back(x); args1.push_back(yMinusX); Value* recur_1 = builder.CreateCall(gcd, args1.begin(), args1.end(), "tmp"); builder.CreateRet(recur_1); builder.SetInsertPoint(cond_false_2); // 於基本塊 cond_false_2 填入 LLVM IR。 // // %tmp5 = sub i32 %x, %y // %tmp6 = call i32 @gcd(i32 %tmp5, i32 %y) // ret i32 %tmp6 // Value* xMinusY = builder.CreateSub(x, y, "tmp"); std::vector<Value*> args2; args2.push_back(xMinusY); args2.push_back(y); Value* recur_2 = builder.CreateCall(gcd, args2.begin(), args2.end(), "tmp"); builder.CreateRet(recur_2); return M; }