1. 程式人生 > >編譯器架構的王者LLVM——(7)函式的翻譯方法

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

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

函式的翻譯方法

前面介紹了許多編譯器架構上面的特點,如何組織語法樹、如果多遍掃描語法樹。今天開始,我們就要設計本編譯器中最核心的部分了,如何設計一個編譯時巨集,再利用LLVM按順序生成模組。

設計巨集

我們的編譯器可以說是巨集驅動的,因為我們掃描每個語法節點後,都會考察當前是不是一個合法的巨集,例如我們來分析一下上一章的示例程式碼:

void hello(int k, int g) {
    ......
}   

我暫時隱藏了函式體部分,讓大家先關注一下函式頭

        String function
        String void
        String hello
        Node
            Node
                String set
                String int
                String k

            Node
                String set
                String int
                String g

        Node
            ......

我們的語法樹的每一層相當於是連結串列組織的,通過next指標都可以找到下一個元素。
而語法樹的開頭部分,是一個“function”的巨集名稱,這個部分就是提示我們用哪個巨集函式來翻譯用的。

接下來的節點就是: 返回型別,函式名,引數表,函式體

例如引數表,裡面的內容很多,但我們掃描時,它們是一個整體,進行識別。

所以我們的巨集的形式實際上就是這樣:

    (function 返回型別 函式名 (形參表) (函式體))

括號括起來的部分表示是一個列表,而不是一個元素。

巨集函式的編寫

我們之前已經定義了巨集的函式形式,我們需要傳入的有我們自己的上下文類和當前要處理的Node節點,返回的是LLVM的Value型別(各個語句的抽象基類)

typedef Value* (*CodeGenFunction)(CodeGenContext*, Node*);

於是我們將這個函式實現出來:

static Value* function_macro(CodeGenContext* context, Node* node) {
    // 第一個引數, 返回型別


    // 第二個引數, 函式名
    node = node->getNext();


    // 第三個引數, 引數表
    Node* args_node = node = node->getNext();


    // 第四個引數, 程式碼塊
    node = node->getNext();

    return F;
}

獲取一個字串代表的型別,往往不是一件容易的事,尤其在存在結構體和類的情況下,這時,我們往往需要查一下符號表,檢查這個字串是否為型別,以及是什麼樣的型別,是基本型別、結構體,還是函式指標或者指向其他結構的指標等等。
獲取型別,往往是LLVM中非常重要的一步。

我們這裡先寫一下查符號表的介面,不做實現,接下來的章節中,我們會介紹經典的棧式符號表的實現。

第二個引數是函式名,我們將其儲存在臨時變數中待用:

static Value* function_type_macro(CodeGenContext* context, Node* node) {
    // 第一個引數, 返回型別
    Type* ret_type = context->FindType(node);

    // 第二個引數, 函式名
    node = node->getNext();
    std::string function_name = node->getStr();

    // 第三個引數, 引數表
    Node* args_node = node = node->getNext();

    // 第四個引數, 程式碼塊
    node = node->getNext();

    return F;
}

接下來的引數表也許是很不好實現的一部分,因為其巢狀比較複雜,不過思路還好,就是不斷的去掃描節點,這樣我們就可以寫出如下的程式碼:

    // 第三個引數, 引數表
    Node* args_node = node = node->getNext();
    std::vector<Type*> type_vec;   // 型別列表
    std::vector<std::string> arg_name; // 引數名列表
    if (args_node->getChild() != NULL) {
        for (Node* pC = args_node->getChild(); 
             pC != NULL; pC = pC->getNext() ) 
        {
            Node* pSec = pC->getChild()->getNext();
            Type* t = context->FindType(pSec);
            type_vec.push_back(t);
            arg_name.push_back(pSec->getNext()->getStr());  
        }
    }

其實有了前三個引數,我們就可以構建LLVM中的函式聲明瞭,這樣是不用寫函式體程式碼的。
LLVM裡很多物件都有這個特點,函式可以只宣告函式頭,解析完函式體後再將其填回去。結構體也一樣,可以先宣告符號,回頭再向裡填入型別資訊。這些特性都是方便生成宣告的實現,並且在多遍掃描的實現中也會顯得很靈活。

我們下面來宣告這個函式:

    // 先合成一個函式
    FunctionType *FT = FunctionType::get(ret_type, type_vec, 
        /*not vararg*/false);

    Module* M = context->getModule();
    Function *F = Function::Create(FT, Function::ExternalLinkage, 
        function_name, M);

這裡,我們使用了函式型別,這也是派生自Type的其中一個類,函式型別也可以getPointerTo來獲取函式指標型別。
另外,如果構建函式時,添加了Function::ExternalLinkage引數,就相當於C語言的extern關鍵字,確定這個函式要匯出符號。這樣,你寫的函式就能夠被外部連結,或者作為外部函式的宣告使用。

函式的特殊問題

接下來我們要建立函式的程式碼塊,但這部分程式碼實際上和上面的不是在同一個函式中實現的,應該說,他們不是在一趟掃描中。
我們知道,如果要讓一個函式內的程式碼塊能夠呼叫在任意位置宣告的函式,那麼我們就必須對所有函式都先處理剛才講過的前三個引數,這樣函式的宣告就有了,在之後的正式掃描中,才有瞭如下的程式碼塊生成部分:

    // 第四個引數, 程式碼塊
    node = node->getNext();
    BasicBlock* bb = context->createBlock(F); // 建立新的Block

    // 特殊處理引數表, 這個地方特別坑,你必須給每個函式的引數
    // 手動AllocaInst開空間,再用StoreInst存一遍才行,否則一Load就報錯
    // context->MacroMake(args_node->getChild());
    if (args_node->getChild() != NULL) {
        context->MacroMake(args_node);
        int i = 0;
        for (auto arg = F->arg_begin(); i != arg_name.size(); ++arg, ++i) {
            arg->setName(arg_name[i]);
            Value* argumentValue = arg;
            ValueSymbolTable* st = bb->getValueSymbolTable();
            Value* v = st->lookup(arg_name[i]);
            new StoreInst(argumentValue, v, false, bb);
        }
    }
    context->MacroMake(node);

    // 處理塊結尾
    bb = context->getNowBlock();
    if (bb->getTerminator() == NULL)
        ReturnInst::Create(*(context->getContext()), bb);
    return F;

這個地方問題非常多,我先保留一個懸念,在接下來程式碼塊和變數儲存與載入的講解中,我會再次提到這個部分的特殊處理。

相關推薦

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

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

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

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

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

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

Javascript高階程式設計學習筆記7—— 函式

前幾天有事耽擱了,今天繼續更新 今天的主要內容是JS中的函式 這一篇主要講函式的定義等內容,至於變數提升、執行環境、閉包、記憶體回收等內容在後面講,高玩們可以不用看下面的正文了。   函式 首先來講,函式對於任何程式語言都是一個十分核心的概念。 Js中的函式通過function關鍵字來宣

mathematica的學習及應用題目——函式使用方法提升

題目 1、應用Mathematica完成下列題目的運算求解或繪圖 (1)求解方程ax2+bx+c=0 (2)求解方程x3+5x+6=0 (3)求解方程x2-3x+2=0 (4)求解方程3cosx=lnx (5)解方程組  (6)從方程組        中消去未知

Python基礎7函式

函式:被組織好的,可重複使用的,用來實現單一功能或相關聯功能的程式碼塊。 合理利用函式,可以增強應用的模組性,提高程式碼的重複使用率。 比如常用的求絕對值,最大值,字串長度等這些模組,都已經被封裝成內建函式abs(),max(),len()。 當然有一些只對於你個人的專案而言常用的模組,在找不

淺談C++類7--解構函式

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

吳恩達深度學習筆記7--邏輯迴歸的代價函式Cost Function

邏輯迴歸的代價函式(Logistic Regression Cost Function) 在上一篇文章中,我們講了邏輯迴歸模型,這裡,我們講邏輯迴歸的代價函式(也翻譯作成本函式)。 吳恩達讓我轉達大家:這一篇有很多公式,做好準備,睜大眼睛!代價函式很重要! 為什麼需要代價函式: 為

手把手教你構建 C 語言編譯器7

整個編譯器還剩下最後兩個部分:語句和表示式的解析。它們的內容比較多,主要涉及如何將語句和表示式編譯成彙編程式碼。這章講解語句的解析,相對於表示式來說它還是較為容易的。 手把手教你構建 C 語言編譯器系列共有10個部分: 語句 C 語言區分“語句”(statement)和“表示式”(expression)兩

Python2語法簡記7函式

7 函式:def printme(str1, str2): def printme(str1, str2): "列印傳入的字串" # 函式的第一行語句可以選擇性地使用文件字串—用於存放函式說明。 print str1, str2 r

軟考架構7——資訊系統基礎

一:資訊系統基礎 1:資訊 不確定性的減少,系統有序程度的度量 資訊理論:單位bit,   熵: 資訊的基本特徵: 客觀性,普遍性,無限性,動態性,依附性,變換性,傳遞性,層次性,系統性,轉換性,及時性,安全性 2:資訊系統 資訊系統的資料環境 1)資料檔案  2)應用資料庫

手把手教你做一個 C 語言編譯器7:語句

整個編譯器還剩下最後兩個部分:語句和表示式的解析。它們的內容比較多,主要涉及如何將語句和表示式編譯成彙編程式碼。這章講解語句的解析,相對於表示式來說它還是較為容易的。 本系列: 語句 C 語言區分“語句”(statement)和“表示式”(expression)兩

微服務架構--SpringCloud7

Hystrix *斷路器、熔斷器 Hystrix   避免單口呼叫導致全域性宕掉 *Hystrix是一個用於處理分散式系統的延遲和容錯的開源庫,在分散式系統裡,許多依賴不可避免的會呼叫失敗,比如超時、異常等,Hystrix能夠保證在一個依賴出問題的情況下,不會導致整體

機器學習7-SVM與核函式

1.SVM介紹 是一個類似於邏輯迴歸的方法,用於對不同因素影響的某個結果的分類。 但邏輯迴歸主要採用的是sigmoid函式,SVM有自己常用的核函式:linear線性核、rbf徑向基、poly多項式

Lua 學習7--Lua 函式

Lua 函式主要有兩種用途: 1.完成指定的任務,這種情況下函式作為呼叫語句使用; 2.計算並返回值,這種情況下函式作為賦值語句的表示式使用。 函式定義 Lua 程式語言函式定義格式如下: optional_function_scope fun

[JNI]開發之旅7JNI函式中呼叫java物件的方法

在jni函式中我們不僅要對java物件的資料域進行訪問,而且有時也需要呼叫java中類物件已經實現的方法。接下來我們對物件的方法呼叫,呼叫步驟與訪問資料域相似。 1.獲得例項對應的class類 2.根據class類獲得方法的method id 3.根據me

我看Java虛擬機器7---直譯器和JIT編譯器

Java是被定為為解釋性語言,JIT編譯器並不是強制需要的,也並非所有的虛擬機器都是用直譯器+編譯器的並存架構。但主流的商用虛擬機器如Hotspot、J9等都採用這種並存的架構。 直譯器和編譯器比較 直譯器優點:省去編譯時間,啟動速度快 編譯器優點:對程式

(重磅)深度強化學習系列之7-----強化學習《獎勵函式》的設計和設定reward shaping

概述 前面已經講了好幾篇關於強化學習的概述、演算法(DPG->DDPG),也包括對環境OpenAI gym的安裝,baseline演算法的執行和填坑,雖然講了這麼多,演算法也能夠正常執行還取得不錯的效果,但是一直以來忽略了一個非常重要的話題,那就是強化學

TensorFlow2.07:啟用函式

  注:本系列所有部落格將持續更新併發布在github上,您可以通過github下載本系列所有文章筆記檔案。   1 什麼是啟用函式¶

Vue躬行記7——渲染函式和JSX

  除了可通過模板建立HTML之外,Vue還提供了渲染函式和JSX,前者的編碼自由度很高,後者對於開發過React的人來說會很熟悉。注意,Vue的模板最終都會被編譯成渲染函式。 一、渲染函式   雖然在大部分場景中,都會選擇直觀而清晰的模板,但遇到一些複雜的場景時,就不得不使用渲染函式render()了。