[hellogcc] [投稿] LLVM Introduction - How to use JIT 3/3

  • From: 陳韋任 <chenwj@xxxxxxxxxxxxxx>
  • To: hellogcc@xxxxxxxxxxxxx
  • Date: Sat, 22 Oct 2011 04:14:25 +0800

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;
}

Other related posts: