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

2021-12-29 20:52:55 字數 2924 閱讀 9862

llvm平台,短短幾年間,改變了眾多程式語言的走向,也催生了一大批具有特色的程式語言的出現,不愧為編譯器架構的王者,也榮獲2023年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) 獲取乙個字串代表的型別,往往不是一件容易的事,尤其在存在結構體和類的情況下,這時,我們往往需要查一下符號表,檢查這個字串是否為型別,以及是什麼樣的型別,是基本型別、結構體,還是函式指標或者指向其他結構的指標等等。

獲取型別,往往是llvm中非常重要的一步。

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

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

static value* function_type_macro(codegencontext* context, node* node) 接下來的參數列也許是很不好實現的一部分,因為其巢狀比較複雜,不過思路還好,就是不斷的去掃瞄節點,這樣我們就可以寫出如下的**:

// 第三個引數, 參數列

node* args_node = node = node->getnext();

std::vector type_vec; // 型別列表

std::vector<:string> arg_name; // 引數名列表

if (args_node->getchild() != null)

}其實有了前三個引數,我們就可以構建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(node);

// 處理塊結尾

bb = context->getnowblock();

if (bb->getterminator() == null)

returninst::create(*(context->getcontext()), bb);

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

C 編譯器的函式編譯流程

c 中的型別查詢過程相對簡單,基本上就是名字查詢,這裡不再介紹。對於 cpp 檔案中呼叫的乙個函式 或成員函式 編譯器主要做了下面三件事情 1 名字查詢 先在所在編譯單元中可見名字實體中進行名字查詢 1 類成員函式優先 物件所在的類 基類 一 經找到就停止查詢 2 如果沒有 在相應的名字空間中做進一...

C 編譯器的函式編譯流程

c 中的型別查詢過程相對簡單,基本上就是名字查詢,這裡不再介紹。對於 cpp 檔案中呼叫的乙個函式 成員函式 編譯器主要做了下面三件事情 1 名字查詢 先在所在編譯單元中可見名字實體中進行名字查詢 1 類成員函式優先 物件所在的類 基類 一經找到就停止查詢 如果沒有 2 在相應的名字空間中做進一步的...

編譯器預設生成的函式

拷貝控制函式包括 拷貝建構函式 拷貝賦值函式 移動建構函式 移動賦值函式 析構函式。1.建構函式 如果我們沒有定義任何建構函式,編譯器會為我們生成乙個預設的建構函式。如果定義了,則沒有預設建構函式,即不能以class item來定義物件了。因此,不管有沒有定義建構函式,最好自己定義下預設建構函式。2...