使用llvm實現一門語言 —— cava

摘要:
本文將介紹如何使用llvm+bison+flex技術實現一門編程語言。 以咱們實現的cava語言爲例,介紹編譯器各階段,詞法分析 -> 語法分析 -> 語義分析 -> 中間代碼優化 -> 目標代碼生成,最終生成彙編指令,再由彙編語言根據不一樣的指令集生成對應的可執行程序是如何實現的。

背景

cava 產生的背景,是因爲ha3業務方對插件定製及版本兼容需求,要求咱們基於llvm開發一種性能與c++至關的類java腳本語言。html

通過咱們的調查發現:java

可備選項由例如sp上的lua,elasticsearch上的groovy等,但最終得出的結論是現有的腳本語言都不能很好的知足ha3的需求。c++

groovy是jvm語言,它和用java開發的elasticsearch比較配。ha3是用c++開發的,ha3上插件的內存管理模式很固定,插件中的內存分配能夠和請求session的pool綁定,請求結束整個pool釋放,不需引入gc;另外jni比較重和c++交互的效率不高,因jvm語言不知足要求。express

公認和c++結合比較好的是lua,它在遊戲領域被普遍使用,lua自己比較輕量,它經過lua棧和c++交互,lua有個非官方版的jit實現luajit,不考慮和c++交互的話,luajit的性能很是不錯。可是在ha3算分過濾等場景,腳本和c++交互的次數能達到百萬級別,c++交互上的開銷是一個不能忽略的因素,lua在這種場景性能仍是知足不了咱們的要求。編程

最終,咱們決定本身實現一門類java腳本語言——cava。後端

本文將分享下如何使用 LLVM 來實現一門語言,以cava做爲例子來具體講述編譯器各個階段的實現:數組

編譯器經典流程

編譯器經過詞法分析 -> 語法分析 -> 語義分析 -> 中間代碼優化 -> 目標代碼生成,最終生成彙編指令,再由彙編語言根據不一樣的指令集生成對應的可執行程序bash

cava使用Bison和flex來實現詞法語法分析,使用llvm來實現中間代碼到編譯執行session

詞法 & 語法

基於 Bison flex 實現詞法語法分析器oracle

token

token定義

%token BOOLEAN // primitive_type
%token CHAR BYTE SHORT INT LONG UBYTE USHORT UINT ULONG // integral_type
%token DOUBLE FLOAT // floating_point_type
%token NULL_LITERAL
%token LBRACK RBRACK // array_type
%token DOT // qualified_name
%token SEMICOLON MULT COMMA LBRACE RBRACE EQ // separators
%token LPAREN RPAREN COLON // more separators
...複製代碼

token識別,使用flex

"(" { updateLocation(yylloc, YYLeng()); return token::LPAREN;    }
")" { updateLocation(yylloc, YYLeng()); return token::RPAREN;    }
"{" { updateLocation(yylloc, YYLeng()); return token::LBRACE;    }
"}" { updateLocation(yylloc, YYLeng()); return token::RBRACE;    }
"[" { updateLocation(yylloc, YYLeng()); return token::LBRACK;    }
"]" { updateLocation(yylloc, YYLeng()); return token::RBRACK;    }
token_type boolLiteral(semantic_type *yylval, location_type *yylloc, bool val) {
    updateLocation(yylloc, YYLeng());
    yylval->booleanLiteral = val;
    return token::BOOLEAN_LITERAL;
}
token_type intLiteral(semantic_type *yylval, location_type *yylloc) {
    yylval->integerLiteral = atoi(YYText());
    updateLocation(yylloc, YYLeng());
    return token::INTEGER_LITERAL;
}
...複製代碼

位置信息

cava在詞法分析階段就透出了位置信息,記錄下了全部token所在文件的行列號,用於後續報錯處理時可以準確的定位錯誤位置

void updateLocation(location_type *yylloc, int width) {
    updateBegin(yylloc, 0);
    updateEnd(yylloc, width);
}複製代碼

語法 parser

利用Bison定義語法規則,維護token之間的排列關係

expression : assignment_expression {
    @$ = @1;
    $$ = $1;
}

assignment_expression : conditional_expression {
    @$ = @1;
    $$ = $1;
}
| assignment { $$ = $1; }

conditional_expression : conditional_or_expression {
    @$ = @1;
    $$ = $1;
}
| conditional_or_expression QUESTION expression COLON conditional_expression {
    @$ = @1 + @2 + @3 + @4 + @5;
    $$ = NodeFactory::createConditionalExpr(ctx, @$, $1, $3, $5);
}

conditional_or_expression : conditional_and_expression {
    @$ = @1;
    $$ = $1;
}
| conditional_or_expression OROR conditional_and_expression {
    @$ = @1 + @2 + @3;
    $$ = NodeFactory::createBinaryOpExpr(ctx, @$, $1, BinaryOpExpr::OT_COND_OR, $3);
}

conditional_and_expression : inclusive_or_expression {
    @$ = @1;
    $$ = $1;
}
| conditional_and_expression ANDAND inclusive_or_expression {
    @$ = @1 + @2 + @3;
    $$ = NodeFactory::createBinaryOpExpr(ctx, @$, $1, BinaryOpExpr::OT_COND_AND, $3);
}複製代碼

AST

AST 生成

在語法分析的時候,cava利用NodeFactory類生成對應的AST,把token鏈接成語法樹

AST設計參考

以上文中 BinaryOpExpr 爲例, binaryOpExpr 表示二元表達式。先建立對應的BinaryOpExpr 類,繼承Expr類,裏面包含成員左表達式 _left,右表達式 _right, 以及 表達式類型 _op。

class BinaryOpExpr : public Expr
{
public:
      enum OpType {
        // logic
        OT_COND_OR,         // ||
        OT_COND_AND,        // &&
        // bit
        OT_BIT_OR,          // |
        OT_BIT_XOR,         // ^
        OT_BIT_AND,         // &
        // relational
        OT_EQ,              // ==
        OT_NE,              // !=

        OT_LT,              // <
        OT_GT,              // >
        OT_LE,              // <=
        OT_GE,              // >=
        // shift
        OT_SHL,             // <<
        OT_SHR,             // >>
        // arithmetic
        OT_ADD,             // +
        OT_SUB,             // -
        OT_MUL,             // *
        OT_DIV,             // /
        OT_MOD,             // %

        OP_NONE
    };
private:
    Expr *_left;
    Expr *_right;
    OpType _op;
    // CGClassInfo *_promotionClassInfo; // set by typeinfer
    CAVA_LOG_DECLARE();
};複製代碼

建立節點並將左右子表達式及Op類型填入後,填充對應的位置信息,維護ASTContext(用於記錄全部的AST信息)

// createBinaryOpExpr
CREATE_NODE_IMPL_ARG3(BinaryOpExpr,
                      Expr *,
                      BinaryOpExpr::OpType,
                      Expr *);

#define CREATE_NODE_IMPL_ARG3(T, T1, T2, T3) \
T *NodeFactory::create##T(ASTContext &astCtx, Location &location, \
                          T1 arg1, T2 arg2, T3 arg3)                \
CREATE_NODE_IMPL_BODY(T, arg1, arg2, arg3)

#define CREATE_NODE_IMPL_BODY(T, ...) \
{                                                                   \
    T *val = new T(__VA_ARGS__);                                    \
    val->setLocation(location);                                     \
    astCtx.addNode<T>(val);                                         \
    astCtx.addNode<TypeNode>(val);                                  \
    astCtx.addNode<ASTNode>(val);                                   \
    val->beParent(__VA_ARGS__);                                     \
    return val;                                                     \
}複製代碼

cava AST 設計大綱

Overall

  • 編譯模塊(Module): has a module
  • 編譯單元(CompilationUnit): has Package Impot and ClassDecl (0 1 or more)
  • 類(ClassDecl): has className and ClassBody
  • ClassBodyDecl: has ClassMemberDecl (0 1 or more)
  • 類成員(ClassMemberDecl): is a

    • 構造(ConstructorDecl): has className, a list of Formal and a block
    • Formal: has name and TypeNode
    • block: has a CompoundStmt Stmt
  • 字段(FieldDecl): has TypeNode and VarDecl

    • VarDecl: has a valName and maybe with a Expr as initializer
    • 方法(MethodDecl): has methodName a list of Formal and a block
  • 類型(TypeNode) 語句(Stmt) 表達式(Expr)

TypeNode

  • 基礎類型(CanonicalTypeNode): boolean, char, int, double ...
  • class類型(AmbTypeNode): unresolved class type (除了基礎類型,和數組類型,其他都是class類型)
  • 數組類型(ArrayTypeNode): has a TypeNode and dims

Stmt

  • CompoundStmt: { ...; ...; } contain multi Stmts
  • EmptyStmt
  • ExprStmt: has a Expr
  • BreakStmt
  • ContinueStmt
  • ReturnStmt
  • LocalVarDeclStmt: has TypeNode and VarDecl
  • IfStmt: has a ifExpr ifStmt and may has elseStmt
  • ForStmt: a list of initStmt, stopStmt, updateStmt and bodyStmt
  • DoWhileStmt
  • WhileStmt
  • SwitchStmt

Expr

  • ArrayInitExpr: {1, 2, 3}
  • ConditionalExpr: a > b ? 0 : 1
  • BinaryOpExpr: && || | & ^ == >= <= > < != >> << + - * / % ...
  • UnaryOpExpr: ++ -- !
  • LiteralExpr: 1 1.1 'a' "abc" null ...
  • NewExpr: new class
  • NewArrayExpr
  • FieldAccessExpr
  • AssignExpr: = += -= ...
  • ArrayAccessExpr
  • CallExpr
  • AmbExpr: a.b.c.d

AST改寫插件

cava支持多種用戶自定義的插件,其中重要的一類是自定義AST改寫插件,因爲在AST層面上,可以拿到整顆語法樹的信息,能夠很方便的進行一些改寫語法樹的操做,使得腳本語言更加靈活,能夠在用戶代碼無感知的狀況下作一些改寫工做,好比能夠更好的作到版本兼容問題,幫助用戶完成一些代碼邏輯。AST插件的執行位置在生成AST以後。如下介紹幾種插件:

自動生成return語句的插件

用於檢測用戶在函數的函數中未實現return語句,插件自動填充return語句,該功能僅限返回值爲void使用,其他類型沒法肯定返回值,所以加入了檢測分支爲實現return即報錯。

默認構造函數插件

用於對爲實現構造函數的類自動生成的默認構造函數

bool AddDefaultCtor::process(ASTContext &astCtx) {
    for (auto classDecl : astCtx.getClassDecls()) {  // for all class
        if (!classDecl->getCtors().empty() ||
            ASTUtil::hasNativeFunc(classDecl))    // check has ctor func
        {
            continue;
        }
        // use NodeFactory build Ctor
        auto modifier = NodeFactory::allocModifier(astCtx);
        auto name = &classDecl->getClassName();
        auto formals = astCtx.allocFormalVec();
        Location loc;
        auto type = NodeFactory::createCanonicalTypeNode(astCtx,
            loc, CanonicalTypeNode::CT_VOID);  //create return type
        auto stmtVec = astCtx.allocStmtVec();
        auto returnStmt = NodeFactory::createReturnStmt(astCtx, loc, NULL);
        stmtVec->push_back(returnStmt);
        auto body = NodeFactory::createCompoundStmt(astCtx, loc, stmtVec);
        auto ctor = NodeFactory::createConstructorDecl(astCtx,
            loc, modifier, name, formals, type, body);
        classDecl->addCtor(ctor);
    }
    return true;
}複製代碼

報錯信息

報錯信息中定義了錯誤類型,報錯的位置信息,以及具體的錯誤內容,錯誤信息須要分佈在編譯的各個階段產生,如詞法語法錯誤,插件報錯,類型系統的錯誤,類型推導階段錯誤,codegen報錯,jit報錯等。也須要思考如何才能報錯精準,可以讓用戶清晰的知道本身的錯誤在哪裏,cava的報錯會向java靠近,目前的實現還不盡如人意,後續版本中會逐漸完善報錯內容的精準度,以及覆蓋全部錯誤分支的測試。

類型系統

類型分爲基礎類型,數組類型和class類型三個大類。

咱們引入了類型系統來管理全部的基礎類型,數組類型以及class類型,提供了註冊類型,管理類型的功能。

基礎類型

基礎類型是cava原生的一些類型,如void,boolean,byte,int,double等,與java不一樣的一點是,咱們引入了unsigned類型,方便與c++作交互,基礎類型間容許相互之間的自動轉換以及強制轉換,如int類型的a,能夠轉成(long)a。cava經過TypePromotion定義轉換規則,參考java promotion實現。

class類型

由class 定義的類型均稱爲cava的class類型,class類型中包含每一個類型所屬的module,package等信息,可以記錄類型的生命週期,做用域,類型間的關係等功能。

與java一致,咱們引入了package概念,每一個class類型都有對應的package,用以區分不一樣的類。

cava是以module形式管理代碼的,類型的註冊和生命週期都是基於module產生的,module分爲external和internal兩類,external容許外部module調用本module中的類型,用於作跨模塊的連接,而internal設計爲不容許外部module使用,屬於私有module。

數組類型

數組類型由數組的維數和其基類型(class類型或基礎類型)共同組成,cava定義數組類型,數組能夠顯示的調用length:

template<typename T>
class CavaArrayType
{
public:
    int64_t length;
    T *getData() { return _data; }
    void setData(T *data) { _data = data; }
private:
    T *_data;
}複製代碼

能夠看出,不一樣維數的數組是不同的類型,所以,當生成n維數組的時候,咱們會遞歸的生成n-1維到1維數組類型。

類型推導 & 檢測

類型推導和檢測是在codegen前作的一步重要工做,因爲cava是一門強類型語言。在生成IR前,cava會遍歷AST肯定全部變量表達式的類型。所以,在全部的表達式中所包含的參數類型都有嚴格的要求,好比boolean類型不能作加減等運算,int + long 返回的類型通過向上轉型原則爲long等。

所以,cava會遍歷整顆語法樹中的全部變量常量等作類型的推導檢測,以保證符合語法。

IR & Codegen

在經歷完以上步驟後的語法樹,咱們正式用到了llvm,接下來咱們將使用llvm生成語法樹對應的LLVM IR(LLVM 自帶的中間碼)。

llvm IR 從Module -> Function -> Basic Block -> Instruction,分爲不一樣的層次,囊括了一門語言基本結構。llvm Module是llvm的基本編譯單元。

image

cava生成IR的步驟:

  • 生成Module

llvm Module構造,傳入llvm::Context,llvm Context初始構造能夠爲空

llvm::Module(moduleName, llvmContext); // string name, llvm::Context *context複製代碼
  • 遍歷Module中的全部的類(Module -> cava Class)
  • 生成類中全部的構造及方法,構造函數是特殊的方法(cava Class -> cava Ctor, Function), cava Ctor 是特殊的Function

llvm Function構造,須要在對應的llvm Module中插入,傳入function Name以及llvm::FunctionType

llvm::FunctionType 構造須要傳入返回值類型和參數列表和是否變參

llvm::FunctionType::get(retLLVMType, params, false); // Type *Result, ArrayRef<Type *> Params, bool isVarArg

llvm::cast<llvm::Function>(_module->getOrInsertFunction(funcName, funcType)); // string name, llvm::FunctionType *funcType複製代碼
  • 生成方法對應的代碼塊(Function -> Basic Block)

llvm BasicBlock 構造須要llvm Context,BasicBlock name,所屬function等信息,代碼塊至關於{}

llvm::BasicBlock *createBasicBlock(const llvm::Twine &name = "",
    llvm::Function *parent = nullptr,
    llvm::BasicBlock *before = nullptr)
{
    return llvm::BasicBlock::Create(_context, name, parent, before);
}複製代碼
  • 生成代碼塊中的語句(Basic Block -> cava Stmt)

Stmt和Expr 對應到llvm中均爲llvm::Instructions

引入llvm::IRBuilder 用於輔助生成llvm Instructions,插入到對應的 Basic Block 中。

irBuilder(context); // llvm::Context *context
irBuilder.SetInsertPoint(entryBB); // llvm::BasicBlock *entryBB複製代碼

分支語句的生成,以if語句爲例:

bool CodeGenFunction::handleIfStmt(IfStmt *ifStmt) {
    llvm::BasicBlock *thenBlock = createBasicBlock("if.then");
    llvm::BasicBlock *contBlock = createBasicBlock("if.end");
    llvm::BasicBlock *elseBlock = contBlock;
    if (ifStmt->getElse()) {
        elseBlock = createBasicBlock("if.else");
    }
    emitBranchOnBoolExpr(ifStmt->getCond(), thenBlock, elseBlock);
    if (_error) {
        emitBlock(thenBlock, true);
        emitBlock(contBlock, true);
        if (ifStmt->getElse()) {
            emitBlock(elseBlock, true);
        }
        return false;
    }
    // if.then
    emitBlock(thenBlock);
    handleStmt(ifStmt->getThen());
    emitBranch(contBlock);
    // else
    if (ifStmt->getElse()) {
        emitBlock(elseBlock);
        handleStmt(ifStmt->getElse());
        emitBranch(contBlock);
    }
    // Handle the continuation block for code after the if.
    emitBlock(contBlock, true);
    return true;
}複製代碼
  • 生成表達式的IR(cava Stmt -> cava Expr)

同理,使用 llvm::IRBuilder 工具生成Expr對應的指令集。

異常檢測

目前cava的異常檢測還比較弱小,不支持用戶try,catch邏輯

現有的異常檢測實現方法是在全部的數組下標調用前,除法前以及對象下標訪問前,進行斷定是否合法,將if語句IR植入到代碼中,使用if語句判斷實現,遇到異常進行標記,並逐層返回。目前支持的異常檢測包括:

  • 數組邊界檢測
  • 除0檢測
  • null對象下標訪問檢測

內存管理

cava不提供相似JVM的GC機制,做爲一門腳步語言,採用容許用戶自定義的內存分配方式。目前默認的簡單內存實現是使用mem pool,做爲腳步語言內存的持有一直到cava生命週期結束。

void *_cava_alloc_(CavaCtx *ctx, size_t size, int flag) {
    if (size == 0) {
        ++size;
    }
    CavaAlloc *cavaAlloc = (CavaAlloc *)ctx->userCtx;
    void *ret = cavaAlloc->alloc(size);
    if (flag && ret) {
        memset(ret, 0, size);
    }
    return ret;
}
void *alloc(size_t size) {
    return _pool.allocate(size);
}
autil::mem_pool::Pool _pool;複製代碼

在CavaCtx 類中包含了可自定義的內存管理工具userCtx,全部的cava函數的第一項非this指針參數,均爲 *CavaCtx,用於在每一個方法中管理內存和異常信息。

以ha3調用cava舉例,ha3使用mem pool自定義了Ha3CavaAllocator用於cava內存管理,在每一個線程開始時建立cavaCtx的Ha3CavaAllocator,在調用插件的接口處傳入cavaCtx,用於執行cava腳本
score = _scorerModuleInfo->scoreFunc(_scorerObj, _cavaCtx, doc);在線程結束前析構Ha3CavaAllocator,釋放資源。

Pass

截止到目前,已經生成了未通過Pass優化前的llvm IR代碼,經過llvm::errs() << module; 打印出llvm Module 對應的IR代碼:

cava代碼

class Example {
    static int add(int a, int b) {
        return a + b;
    }
    static int main() {
        int a = 3;
        int b = 4;
        if (a == 0)
            return 0;
        return add(a, b);
    }
}複製代碼

對應的未通過pass優化的IR,因爲cava有一些內置的異常檢測,以及未通過任何pass優化,因此會顯得複雜點,後續會將異常檢測從新設計,再也不程序中內置檢測,可以減小指令數,

define i32 @_ZN7Example3addEP7CavaCtxii(%class.CavaCtx* %"@cavaCtx@", i32 %a, i32 %b) {
    entry:
        %"@cavaCtx@1" = alloca %class.CavaCtx*
        store %class.CavaCtx* %"@cavaCtx@", %class.CavaCtx** %"@cavaCtx@1"
        %a2 = alloca i32
        store i32 %a, i32* %a2
        %b3 = alloca i32
        store i32 %b, i32* %b3
        %0 = load i32, i32* %a2
        %1 = load i32, i32* %b3
        %add = add i32 %0, %1
        ret i32 %add
}

define i32 @_ZN7Example4mainEP7CavaCtx(%class.CavaCtx* %"@cavaCtx@") {
    entry:
        %"@cavaCtx@1" = alloca %class.CavaCtx*
        store %class.CavaCtx* %"@cavaCtx@", %class.CavaCtx** %"@cavaCtx@1"
        %a = alloca i32
        store i32 3, i32* %a
        %b = alloca i32
        store i32 4, i32* %b
        %0 = load i32, i32* %a
        %eq = icmp eq i32 %0, 0
        %1 = zext i1 %eq to i8
        %tobool = icmp ne i8 %1, 0
        br i1 %tobool, label %if.then, label %if.end

    if.then:                                          ; preds = %entry
        ret i32 0

    if.end:                                           ; preds = %entry
        %2 = load %class.CavaCtx*, %class.CavaCtx** %"@cavaCtx@1"
        %3 = load i32, i32* %a
        %4 = load i32, i32* %b
        %5 = call i32 @_ZN7Example3addEP7CavaCtxii(%class.CavaCtx* %2, i32 %3, i32 %4)
        %6 = load %class.CavaCtx*, %class.CavaCtx** %"@cavaCtx@1"
        %exception = getelementptr inbounds %class.CavaCtx, %class.CavaCtx* %6, i32 0, i32 1
        %7 = load i32, i32* %exception
        %ne = icmp ne i32 %7, 0
        br i1 %ne, label %if.then2, label %if.end4

    if.then2:                                         ; preds = %if.end
        %8 = load %class.CavaCtx*, %class.CavaCtx** %"@cavaCtx@1"
        %exception3 = getelementptr inbounds %class.CavaCtx, %class.CavaCtx* %8, i32 0, i32 1
        store i32 1, i32* %exception3
        ret i32 0

    if.end4:                                          ; preds = %if.end
        ret i32 %5
}複製代碼

Pass 優化及編寫,這也是編譯語言的精髓之處,不幸的是,筆者還未深刻這一領域,cava參考了clang -O2 的優化pass,執行了FunctionPasses, ModulePasses, CodeGenPasses等優化,使得性能接近c++,不過c++ 的pass有些過於複雜,不適合JIT階段使用,以及JIT獨有的PGO,根據線上真實場景作codegen等優化還沒有實現,這裏面的性能提高空間仍是頗有潛力的,但願與你們一同探究。(題外話,隨着對pass的瞭解,能夠說未涉及pass的編譯器還只是初級階段。)

下文中會提到,處於性能考慮,咱們也仿照llvm 的Clone Module,相似的實現了一個能夠跨Module 的clone function Pass,用於將加載的bc module inline 到其餘module中,減小函數調用。

bool CavaModule::cloneGlobals() {
    auto module = _bitCodeManager.getModule(); // 取出bc 中的moduke
    if (!module) {
        return true;
    }
    if (!createDsoHandle(module)) { // clone __dso_handle
        return false;
    }
    if (!createCxaAtExit(module)) { // clone __cxa_atexit
        return false;
    }
    if (!createGlobalVariables(module)) { // clone GV, 全局變量
        return false;
    }
    if (!createCxaGlobalCtors(module)) { // clone cxaGlobalCtor
        return false;
    }
    return true;
}複製代碼

__dso_handle,__cxa_atexit,用於c++連接

JIT & 執行

JIT

llvm 同時支持 AOT編譯和JIT編譯,JIT編譯依賴於llvm TargetMachine,targetMachine 做爲llvm 針對不一樣機器指令集的後端接口,能夠根據不一樣的指令集產出不一樣的機器碼,同時,也能夠根據不一樣指令集進行pass優化。targetMachine詳細的針對不一樣指令集的配置信息能夠參考clang,cava只支持了x86-64機型。

cava JIT經過llvm ORC來生成jit編譯, ORC編譯須要定義一個llvm::orc::IRCompileLayer

// 須要定義一個Compiler類,用於執行各種pass優化
_compileLayer.reset(new CompileLayerT(_objectLayer,
    CavaCompiler(_targetMachine.get(), _config.debugIR)));
auto resolver = llvm::orc::createLambdaResolver(
    [cavaModule, this](const std::string &name) {
        if (auto sym = findMangledSymbol(name, cavaModule))
            return sym;
        return llvm::JITSymbol(nullptr);
    },
    [](const std::string &S) { return nullptr; });
auto handle = _compileLayer->addModuleSet(
    singletonSet(cavaModule->getLLVMModule()),
    llvm::make_unique<llvm::SectionMemoryManager>(),
    std::move(resolver));複製代碼

執行代碼

執行代碼經過找到函數符號對應的地址,直接調用function便可

typedef int (*MainProtoType)(CavaCtx *);
llvm::JITSymbol jitSymbol = cavaJit->findSymbol(cavaModule->getMangleMainName());    // mangle後的name,下一章會詳細介紹
MainProtoType mainFunc = (MainProtoType) jitSymbol.getAddress();
if (!mainFunc) {
    cout << "no main found" << endl;
    return 0;
}
int ret = mainFunc(&cavaCtx);複製代碼

與c/c++的交互

cava 的設計之初就是追求高性能,尤爲是與c++的交互

mangle

cava經過生成與clang/gcc/intel編譯c++標準一致的函數符號,來直接調用c++的函數。具體實現參考clang 的mangle邏輯,詳情能夠查看 「llvm-src/lib/AST/ItaniumMangle.cpp」,將cava的函數名生成與c++ mangle規則一致的函數名,如

static int add(int a, int b) 轉換成 define i32 @_ZN7Example3addEP7CavaCtxii(%class.CavaCtx* %"@cavaCtx@", i32 %a, i32 %b),CavaCtx是上文提到的cava自帶的參數

bc加載

cava 與c++的高性能交互,來源於二者均基於llvm實現編譯器,擁有一致的中間代碼,既能夠採用mangle後調用符號名一致的函數。也能夠有更高性能的交互,那就是把c++代碼或者cava代碼提早編譯成bc文件,再經過llvm IRReader 加載整個module,實現進一步的聯合編譯,pass優化。c++如何生成bc參考下文的經常使用命令。


跨模塊inline編譯

有了加載bc後,能夠將cava原生代碼和c++代碼聯合在一塊兒編譯,可是仍未解決cava調用c++函數這一層函數調用的開銷,因而就有了跨模塊inline的pass設計。咱們利用IR定製了一個跨模塊clone function的Pass,將不一樣module的函數及全局變量等經過遞歸的形式clone到本module中,再進行inline 優化,從而減小了函數調用。

參考 & 工具

文章做者: tjmts

原文連接

相關文章
相關標籤/搜索