2. 實驗 1/2 - 建立 LLVM Module 我們來動手一試吧! 本實驗參考底下連結,但使用的是 LLVM 2.9 Release。注意! LLVM API 較不穩定,請根據 LLVM 實際版本參照相對應的文件。 http://llvm.org/releases/2.6/docs/tutorial/JITTutorial2.html 本實驗是要用 LLVM IR 實現一個 Greatest Common Divisor (GCD) 演算法,最後使用 LLVM JIT 生成 binary 並執行。GCD 演算法的 C 代碼如下: --------------- gcd.c ---------------------- unsigned gcd(unsigned x, unsigned y) { if ( x == y ) { return x; } else if ( x < y ) { return gcd( x, y - x ); } else { return gcd( x - y, y); } } -------------------------------------------- 上述代碼的 control flow graph (CFG) 請見上述連結。好! 我們開始吧。:-) 使用 LLVM JIT 基本上有底下幾個步驟: 1. 創建或是讀入 LLVM Module。Module 是 LLVM IR 的容器。先在 Module 中建立函式,再 於函式中建立基本塊,最後在基本塊中填入 LLVM IR。[1][2][3] 2. 於我們所建立的 LLVM IR 上施行優化 (可選)。在 LLVM 中,優化的過程稱為 pass。pass 也可以是僅將 LLVM IR 做些轉換而非優化。[4] 3. 呼叫 LLVM JIT,將 LLVM IR 編譯成 host binary。 4. 取得被編譯成 host binary 的 function pointer。透過呼叫該 function pointer 執行 並取得結果。 本實驗做到步驟 2,下一個實驗會補上 3 和 4。因為 LLVM API 較不穩定,請善用 [5] 查詢 API 使用方法。 --------------------- tut2.cpp ----------------------------- /* * $ 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> using namespace llvm; Module* makeLLVMModule(LLVMContext& ctx); // 負責創建 LLVM Module。 int main(int argc, char**argv) { // 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。 delete Mod; return 0; } Module* makeLLVMModule(LLVMContext& ctx) { /* 尚未實作 */ } ---------------------------------------------------------- 現在我們知道大致的架構,接著我們來看怎麼實現 makeLLVMModule。這裡做點弊, 我們先來看最後 LLVM IR 的輸出,先有個感覺。;-) ------------------------------- tut2 -------------------------------------- ; ModuleID = 'tut2' define i32 @gcd(i32 %x, i32 %y) { entry: %tmp = icmp eq i32 %x, %y br i1 %tmp, label %return, label %cond_false return: ; preds = %entry ret i32 %x cond_false: ; preds = %entry %tmp2 = icmp ult i32 %x, %y br i1 %tmp2, label %cond_true, label %cond_false1 cond_true: ; preds = %cond_false %tmp3 = sub i32 %y, %x %tmp4 = call i32 @gcd(i32 %x, i32 %tmp3) ret i32 %tmp4 cond_false1: ; preds = %cond_false %tmp5 = sub i32 %x, %y %tmp6 = call i32 @gcd(i32 %tmp5, i32 %y) ret i32 %tmp6 } -------------------------------------------------------------------------- 開始囉! --------------------- makeLLVMModule ------------------------------------- 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; } --- 請注意! 生成 LLVM IR 時,請遵守 http://llvm.org/docs/LangRef.html 上的規範。 某些情況被 LLVM 視為未定義 (undefined),這代表 LLVM 想怎麼做都可以。千萬不要 憑直覺解讀運算後的結果,否則你怎麼死的都不知道。 舉例: shl 將第一個運算元往左移位指定的位數。你先想想底下的結果為何。 shl i32 1, 32 ; 把長度為 32-bit 的 1 往左移 32 位 是 0 嗎? 我們來看規格怎麼說。:-) Semantics: The value produced is ... . If op2 is (statically or dynamically) negative or equal to or larger than the number of bits in op1, the result is undefined. 是的,shl i32 1, 32 所得結果是 undef。這代表你的程序可能能正確執行、當機或是任何 事都可能發生。請嚴格遵守 http://llvm.org/docs/LangRef.html 上的規範。不要自作聰明。 [1] http://llvm.org/docs/ProgrammersManual.html#Module [2] http://llvm.org/docs/ProgrammersManual.html#Function [3] http://llvm.org/docs/ProgrammersManual.html#BasicBlock [4] http://llvm.org/docs/WritingAnLLVMPass.html [5] http://llvm.org/doxygen/ [6] http://llvm.org/docs/LangRef.html#i_shl -- Wei-Ren Chen (陳韋任) Computer Systems Lab, Institute of Information Science, Academia Sinica, Taiwan (R.O.C.) Tel:886-2-2788-3799 #1667