1. 程式人生 > >編譯器架構的王者LLVM——(12)使用JIT引擎

編譯器架構的王者LLVM——(12)使用JIT引擎

LLVM平臺,短短几年間,改變了眾多程式語言的走向,也催生了一大批具有特色的程式語言的出現,不愧為編譯器架構的王者,也榮獲2012年ACM軟體系統獎 —— 題記

使用JIT引擎

LLVM從設計之初就考慮瞭解釋執行的功能,這非常其作為一款跨平臺的中間位元組碼來使用,可以方便地跨平臺執行。又具有編譯型語言的優勢,非常的方便。

我們使用的LLVM3.6版,移除了原版JIT,改換成了新版的MCJIT,性格有了不小的提升,本文就MCJIT的使用和注意事項,進行簡要的介紹。

JIT技術

Just-In-Time Compiler,是一種動態編譯中間程式碼的方式,根據需要,在程式中編譯並執行生成的機器碼,能夠大幅提升動態語言的執行速度。

像Java語言,.net平臺,luajit等,廣泛使用jit技術,使得程式達到了非常高的執行效率,逐漸接近原生機器語言程式碼的效能了。

JIT引擎的工作原理並沒有那麼複雜,本質上是將原來編譯器要生成機器碼的部分要直接寫入到當前的記憶體中,然後通過函式指標的轉換,找到對應的機器碼並進行執行。

但實踐中往往需要處理許多頭疼的問題,例如記憶體的管理,符號的重定向,處理外部符號,相當於要處理編譯器後端的諸多複雜的事情,真正要設計一款能用的JIT引擎還是非常困難的。

使用LLVM的MCJIT能開發什麼

當然基本的功能是提供一款直譯器的底層工具,將LLVM位元組碼解釋執行,具體能夠做的事,例如可以製作一款跨平臺的C++外掛系統,使用clang將C/C++程式碼一次編譯到.bc

位元組碼,然後在各個平臺上解釋執行。也可以製作一款雲除錯系統,聯網遠端向系統註冊方法,獲取C++客戶端的debug資訊等等。當然,還有很多其他的用法等著大家來開發。

使用MCJIT做一款直譯器

製作LLVM位元組碼的直譯器還是非常簡單的,最棒的示例應該是LLVM原始碼中的工具:lli

一共700行左右的C++程式碼,呼叫LLVM工具集實現了LLVM位元組碼JIT引擎,如果想很好的學習llvm中的直譯器和JIT,可以參考其在github上的原始碼

初始化系統

使用LLVM的JIT功能,需要呼叫幾條初始化語句,可以放在main函式開始時。

InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();
InitializeNativeTargetAsmParser();

這幾句呼叫,主要是在處理JIT的TargetMachine,初始化機器相關編譯目標。

引用相關的標頭檔案

這裡的稍稍有點多餘的,不去管了。,llvm的標頭檔案是層次組織的,像執行引擎,都在llvm/ExecutionEngine/下,而IR相關的,也都在llvm/IR/下,初用LLVM往往搞不清需要哪些,這時就需要多查相關的文件,瞭解LLVM的各個模組的功能。

#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/ExecutionEngine/Interpreter.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/IR/Verifier.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include <llvm/IRReader/IRReader.h>
#include <llvm/Support/SourceMgr.h>
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/TargetSelect.h"
#include <llvm/Support/MemoryBuffer.h>
#include "llvm/Support/raw_ostream.h"
#include <llvm/Support/DynamicLibrary.h>
#include "llvm/Support/Debug.h"

主要說要注意的幾個細節,首先是

#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/ExecutionEngine/Interpreter.h"

C++編譯時,這兩個標頭檔案居然不是必須的,如果你不注意時,編譯不會報錯。因為執行引擎是一個介面的模式,不對外暴露子類的細節,我們必須注意引用其中一個或兩個都引用,否則會連結不到對應的引擎。

會報如下錯誤:

Create Engine Error
JIT has not been linked in.

類結構

使用EngineBuilder構建JIT引擎

由於JIT引擎我們不需要建立多個,我們這裡使用單例類的方式,使用一個LLVM中的Module進行初始化,如果引擎已經建立過,我們可以使用addModule方法,將LLVM的Module新增到引擎的Module集合中。

finalizeObject函式,是一個關鍵的函式,對應JIT引擎很重要,我們要保障我們在呼叫JIT編譯後的程式碼前,要呼叫過該函式

ExecutionEngine* EE = NULL;
RTDyldMemoryManager* RTDyldMM = NULL;

void initEE(std::unique_ptr<Module> Owner) {
    string ErrStr;
    if (EE == NULL) {
        RTDyldMM = new SectionMemoryManager();
        EE = EngineBuilder(std::move(Owner))
            .setEngineKind(EngineKind::JIT)
            .setErrorStr(&ErrStr)
            .setVerifyModules(true)
            .setMCJITMemoryManager(std::unique_ptr<RTDyldMemoryManager>(RTDyldMM))
            .setOptLevel(CodeGenOpt::Default)
            .create();

    } else
        EE->addModule(std::move(Owner));
    if (ErrStr.length() != 0)
        cerr << "Create Engine Error" << endl << ErrStr << endl;
    EE->finalizeObject();
}

這裡是finalizeObject的文件解釋:

finalizeObject - ensure the module is fully processed and is usable.

It is the user-level function for completing the process of making the object usable for execution. It should be called after sections within an object have been relocated using mapSectionAddress. When this method is called the MCJIT execution engine will reapply relocations for a loaded object. This method has no effect for the interpeter.

setEngineKind可選的有JITInterpreter,如果預設的話,則是優先JIT,檢測到哪個引擎能用就用哪個。

setMCJITMemoryManager是一個關鍵的管理器,當然貌似預設不寫也會構建,這裡我們為了清晰所見,還是添加了這條配置,這個記憶體管理器在執行引擎中很重要,一般本地的應用我們要選擇SectionMemoryManager類,而lli中甚至還包含著遠端呼叫的相關類。

setOptLevel是設定程式碼的優化等級,預設是O2,可以修改為下面列舉值:

  • None
  • Less
  • Default
  • Aggressive

MCJIT架構圖

MCJIT架構圖

編寫核心的呼叫方法


typedef void (*func_type)(void*);

// path是bc檔案的路徑,func_name是要執行的函式名
void Run(const std::string& path, const std::string& func_name) {

    // 首先要讀取要執行的bc位元組碼
    SMDiagnostic error;
    std::unique_ptr<Module> Owner = parseIRFile(path, error, context);
    if(Owner == nullptr) {
        cout << "Load Error: " << path << endl;
        Owner->dump();
        return;
    }

    // 單例的方法進行初始化,暫未考慮多執行緒
    initEE(std::move(Owner));

    // 獲取編譯後的函式指標並執行
    uint64_t func_addr = EE->getFunctionAddress(func_name.c_str());
    if (func_addr == 0) {
        printf("錯誤, 找不到函式: %s\n", func_name.c_str());
        return;
    }
    func_type func = (func_type) func_addr;
    func(NULL); // 需要傳引數時可以從這裡傳遞
}

直譯器版本

直譯器效率稍低一下,不過能夠做到惰性的一下程式碼載入和執行工作,有時也很有用途。下面我們就在jit的基礎上,介紹一下簡單的直譯器功能。

介紹器最主要需要做的就是將生成引擎改變:

EE = EngineBuilder(std::move(Owner))
    // 這裡改完直譯器
    .setEngineKind(EngineKind::Interpreter)
    .setErrorStr(&ErrStr)
    .setVerifyModules(true)
    .setMCJITMemoryManager(std::unique_ptr<RTDyldMemoryManager>(RTDyldMM))
    .setOptLevel(CodeGenOpt::Default)
    .create();

另外直譯器可以使用getLazyIRFileModule函式可以替換parseIRFile實現.bc檔案的惰性載入。

直譯器的執行方式和JIT有一些不同,要使用FindFunctionNamed函式來尋找對應的函式物件,直譯器能夠獲取更全的LLVM位元組碼的中間資訊,例如一些屬性和元資料,在做一些靈活的動態語言直譯器時是非常有用的。

// 給直譯器使用的部分
Function* func = EE->FindFunctionNamed(func_name.c_str());
if (func == NULL) {
    printf("忽略, 找不到函式: %s\n", func_name.c_str());
    return;
}
// 如果需要傳引數的話
std::vector<GenericValue> args;
args.push_back(GenericValue(NULL));
EE->runFunction(func, args);

建立測試的C程式碼

我在是Elite編譯器工程下開發的,所以會有介面呼叫的測試,大家可以,建立簡單的C函式進行呼叫測試:

extern void
test2_elite_plugin_init(CodeGenContext* context) {
    printf("test2_elite_plugin_init\n");
    if (context == NULL) printf("Error for context\n");
    else context->AddOrReplaceMacros(macro_funcs);
}

執行結果:

執行結果

相關推薦

編譯器架構王者LLVM——12使用JIT引擎

LLVM平臺,短短几年間,改變了眾多程式語言的走向,也催生了一大批具有特色的程式語言的出現,不愧為編譯器架構的王者,也榮獲2012年ACM軟體系統獎 —— 題記 使用JIT引擎 LLVM從設計之初就考慮瞭解釋執行的功能,這非常其作為一款跨平臺的中間位

編譯器架構王者LLVM——7函式的翻譯方法

LLVM平臺,短短几年間,改變了眾多程式語言的走向,也催生了一大批具有特色的程式語言的出現,不愧為編譯器架構的王者,也榮獲2012年ACM軟體系統獎 —— 題記 函式的翻譯方法 前面介紹了許多編譯器架構上面的特點,如何組織語法樹、如果多遍掃描語法樹。今天

編譯器架構王者LLVM——4簡單的詞法和語法分析

LLVM平臺,短短几年間,改變了眾多程式語言的走向,也催生了一大批具有特色的程式語言的出現,不愧為編譯器架構的王者,也榮獲2012年ACM軟體系統獎 —— 題記 簡單的詞法和語法分析 Lex和Yacc真是太好用了,非常方便我們構建一門語言的分析程式。

java架構之路-12JVM垃圾回收演算法和垃圾回收器

  接上次JVM虛擬機器堆記憶體模型來繼續說,上次我們主要說了什麼時候可能把物件直接放在老年代,還有我們的可能性分析,提出GCroot根的概念。這次我們主要來說說垃圾回收所使用的的演算法和我們的垃圾回收器,需要了解我們的可達性分析GCroot根是什麼,還有我們的動態年齡判斷和老年代分配擔保機制,還不清楚咋回事

軟件架構設計學習總結12:大型網站技術架構網站的伸縮性架構

可用性 name 偶數 發送 得到 合並 linux vi 可謂 性能 網站系統的伸縮性架構最重要的技術手段就是使用服務器集群功能,通過不斷地向集群中添加服務器來增強整個集群的處理能力。“伸”即網站的規模和服務器的規模總是在不斷擴大。 1、網站架構的伸縮性設計 網站的伸縮性

IntelliJ IDEA 12.0搭建Maven Web SSH2架構專案示例

       用IDEA搭建Maven web專案,對於很多用慣了Eclipse的人可能會很不適應。在專案的目錄結構設定上,Eclipse和IDEA的確有些區別。這篇文章將在原來的基礎上更加詳細的介紹,最後會給出兩個示例來展示實際效果。文章將從5個方面來介紹:(文章貼圖較多

網站平臺架構演變史 - 水平拆分的查詢

頻率 條件查詢 期待 數量 平臺 演變 關聯查詢 如果 條件 之前在講表拆分的時候氛圍垂直拆分和水平拆分 垂直拆分的查詢其實不難,就是從單表變為了多表,而大部分情況下只是對主表的查詢多,從表的查詢會很少用到,這樣的情況下關聯查詢不需要太多的考慮 水平拆分之前講了大數據量的情

Android開發筆記12——ListView & Adapter

dba 只顯示一行 -1 ngs 而已 整理 adapt array xxx 轉載請註明:http://www.cnblogs.com/igoslly/p/6947225.html 下一章是關於ListFragment的內容,首先先介紹ListView的相關配置,理解L

201402567012 《嵌入式系統程序設計》第七周學習總結

數據 寫入 是否 當前 部分 沒有 如果 打開 嵌入式 對阻塞打開和非阻 塞打開的讀寫進行討論: (1)對於讀進程。 若該管道是阻塞打開,且當前 FIFO 內沒有數據,則對讀進程而言將一直阻塞到有

springBoot12:集成Druid

springboot 集成druid 一、添加依賴<!--mybatis-開始--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>myba

vue,一路走來12--父與子之間傳參

今天 component efault 之間 dword return his pre 傳參 今天想起一直沒有記錄父組件與子組件的傳參問題,這在項目中一直用到。 父向子組件傳參 Index.vue父組件中 <component-a :msgfromfa="(posi

LeetCode12Integer to Roman

題目 img code num mage roman div iii lee 題目如下: Python代碼 def intToRoman(self, num): """ :type num: int :rtype: str

C#學習筆記12——三種方法操作XML

結點 記得 ext 應用程序 eval 資源 特性 pla cells 說明(2017-7-11 16:56:13): 原文地址: C#中常用的幾種讀取XML文件的方法 XML文件是一種常用的文件格式,例如WinForm裏面的app.config以及Web程序中的web.c

api-gateway實踐12新服務網關 - 審批產生網關身份!

服務 1.2 界面 resource details sco grant .cn 後臺 一、創建網關側身份 1、client身份(oauth_client_details) 1.1、數據結構 1.2、界面代碼 <form id="formDto" cla

Web API應用架構設計分析2

最好 factor 狀態 是否 沒有 dot sel nal std Web API應用架構設計分析(2) 在上篇隨筆《Web API應用架構設計分析(1)》,我對Web API的各種應用架構進行了概括性的分析和設計,Web API 是一種應用接口框架,它能夠構建HTT

Web API應用架構設計分析1

人員管理 門面 guid orm 和平 ide 額外 簡化 響應 Web API應用架構設計分析(1) Web API 是一種應用接口框架,它能夠構建HTTP服務以支撐更廣泛的客戶端(包括瀏覽器,手機和平板電腦等移動設備)的框架, ASP.NET Web API 是一種

Zookeeper之Zookeeper底層客戶端架構實現原理轉載

一次 描述 綁定 機制 一個 ini fin 源碼 receive Zookeeper的Client直接與用戶打交道,是我們使用Zookeeper的interface。了解ZK Client的結構和工作原理有利於我們合理的使用ZK,並能在使用中更早的發現問題。本文將在研究源

NS3網絡仿真12: ICMPv4協議

normal fun rac sequence icmp veh abcde all protoc 快樂蝦http://blog.csdn.net/lights_joy/歡迎轉載,但請保留作者信息ICMP的全稱是 Internet ControlMessage Prot

深入淺出數據結構C語言版12——從二分查找到二叉樹

額外 最終 匹配 應對 點數據 隨機數 普通 釋放 三種   在很多有關數據結構和算法的書籍或文章中,作者往往是介紹完了什麽是樹後就直入主題的談什麽是二叉樹balabala的。但我今天決定不按這個套路來。我個人覺得,一個東西或者說一種技術存在總該有一定的道理,不是能解決某個

文件查找和壓縮——Linux基本命令12

linux1.文件查找在文件系統上查找符合條件的文件:locate, find非實時查找(數據庫查找):locate實時查找:find 2.locate(1)功能特點查詢系統上預建的文件索引數據庫/var/lib/mlocate/mlocate.db依賴於事先構建的索引 索引的構建是在系統較為空閑時自動進行