LLVM平臺,短短幾年間,改變了眾多編程語言的走向,也催生了一大批具有特色的編程語言的出現,不愧為編譯器架構的王者,也榮獲2012年ACM軟件系統獎 —— 題記
### 用代碼生成代碼
LLVM的開發思路很簡單,就是用C++代碼去不斷生成llvm字節碼。
### RedApple語言示例
這是我花了兩周多的時間制作的一門實驗型語言,主要是想驗證一個編譯器的設計思路,宏翻譯系統。
它的架構和一般的編譯器很不一樣,首先,編譯器前端會先將語法轉換為很通用的AST語法樹節點,一般的編譯器,往往是直接在這些節點上進行語義分析,然后進行代碼生成。
這次我采用了類似lisp的表示方法,將源文件轉換為語法樹,然后遍歷整棵語法樹,根據上面標注的宏提示,去按照各個宏的規則進行翻譯工作。
整個編譯器1500行左右的代碼,非常的小巧,不過功能也比較有限,而且好多地方還不完善,主要支持的就是函數的定義,結構體的定義,函數調用,結構體訪問,分配內存,基本邏輯控制語句這些基本的特性。
大家可以作為學習llvm的一個示例吧。
Github地址:[https://github.com/sunxfancy/RedApple](https://github.com/sunxfancy/RedApple)
同樣,非常精品的示例還推薦大家看以下兩位網友寫的:
構建Toy編譯器:基于Flex、Bison和LLVM
[http://lesliezhu.github.io/public/write-your-toy-compiler.html](http://lesliezhu.github.io/public/write-your-toy-compiler.html)
用LLVM來開發自己的編譯器系列
[http://my.oschina.net/linlifeng/blog/97457](http://my.oschina.net/linlifeng/blog/97457)
當然,這些示例不是說要大家一下都看懂,那么也就沒有教程的意義了,下面我會繼續介紹各個關鍵的LLVM平臺API以及相關工具鏈。大家可以將以上三個項目和LLVM官網example中的作為參考,在實踐中加以印證。
### 工具鏈簡介
| 工具 | 功能 |
|-----|-----|
| clang -emit-llvm | 指令,可以生成.bc的字節碼文件 |
| lli | llvm解釋器,直接執行.bc字節碼文件 |
| llc | llvm編譯器,將.bc編譯成.o |
以上三個最常用,其他小工具備用
| 工具 | 功能 |
|-----|-----|
| llvm-as | 匯編器 |
| llvm-dis | 反匯編器 |
| llvm-ar | 打包器 |
| llvm-link | 字節碼鏈接器 |
唉,太多了,好多我也木有用過,還有需要的請查看官方文檔:
[http://llvm.org/docs/CommandGuide/index.html](http://llvm.org/docs/CommandGuide/index.html)
### 常用類
| LLVM類 | 功能 |
|-----|-----|
| LLVMContext | 上下文類,基本是最核心的保存上下文符號的類 |
| Module | 模塊類,一般一個文件是一個模塊,里面有函數列表和全局變量表 |
| Function | 函數類,函數類,生成出來就是一個C函數 |
| Constant | 常量類,各種常量的定義,都是從這里派生出來的 |
| Value | 各值類型的基類,幾乎所以的函數、常量、變量、表達式,都可以轉換成Value型 |
| Type | 類型類,表示各種內部類型或用戶類型,每一個Value都有個getType方法來獲取其類型。 |
| BasicBlock | 基本塊,一般是表示一個標簽,注意這個塊不是嵌套形式的結構,而是每個塊結尾可以用指令跳轉 到其他塊,類似C語言中的標簽的功能 |
### 嘗試先來生成個小函數
就拿printf開練吧,這個函數第一有用,第二簡單,第三只要聲明不要內容。
~~~
void register_printf(llvm::Module *module) {
std::vector<llvm::Type*> printf_arg_types; // 這里是參數表
printf_arg_types.push_back(llvm::Type::getInt8PtrTy(module->getContext()));
llvm::FunctionType* printf_type =
llvm::FunctionType::get(
llvm::Type::getInt32Ty(module->getContext()), printf_arg_types, true);
// 這里的true表示后面接不定參數
llvm::Function *func = llvm::Function::Create(
printf_type, llvm::Function::ExternalLinkage,
llvm::Twine("printf"),
module
);
func->setCallingConv(llvm::CallingConv::C); // 一定注意調用方式的正確性
}
~~~
怎么樣,是不是也很簡單?
### 編寫主函數和調試上下文
下面我們來編寫一個主函數,來測試一下我們的函數是否正確,這里,也是LLVM最核心的啟動和調試流程。
~~~
int main(){
InitializeNativeTarget();
LLVMContext Context;
Module* M = new Module("main", Context);
register_printf(M);
// 校驗問題, 這個函數需要一個輸出流來打印錯誤信息
if (verifyModule(*M, &errs())) {
errs() << "構建LLVM字節碼出錯!\n";
exit(1);
}
// 輸出llvm字節碼
outs() << "LLVM module:\n\n" << *M;
outs() << "\n\n";
outs().flush();
// 輸出二進制BitCode到.bc文件
std::error_code ErrInfo;
raw_ostream *out = new raw_fd_ostream("a.bc", ErrInfo, sys::fs::F_None);
WriteBitcodeToFile(M, *out);
out->flush(); delete out;
// 關閉LLVM釋放內存
llvm_shutdown();
return 0;
}
~~~
運行效果:

對了,我們好像沒有提該引用哪些頭文件,請見附錄
### 附:完整示例
只是頭文件有點長,具體功能有的我也記不清了,一般我是習慣性把一片粘過去 →_→
~~~
/*
* @Author: sxf
* @Date: 2015-11-06 20:37:15
* @Last Modified by: sxf
* @Last Modified time: 2015-11-06 20:46:43
*/
#include "llvm/IR/Verifier.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/ExecutionEngine/Interpreter.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Bitcode/ReaderWriter.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/IR/ValueSymbolTable.h"
using namespace llvm;
void register_printf(llvm::Module *module) {
std::vector<llvm::Type*> printf_arg_types;
printf_arg_types.push_back(llvm::Type::getInt8PtrTy(module->getContext()));
llvm::FunctionType* printf_type =
llvm::FunctionType::get(
llvm::Type::getInt32Ty(module->getContext()), printf_arg_types, true);
llvm::Function *func = llvm::Function::Create(
printf_type, llvm::Function::ExternalLinkage,
llvm::Twine("printf"),
module
);
func->setCallingConv(llvm::CallingConv::C);
}
int main(){
InitializeNativeTarget();
LLVMContext Context;
Module* M = new Module("main", Context);
register_printf(M);
// 校驗問題, 這個函數需要一個輸出流來打印錯誤信息
if (verifyModule(*M, &errs())) {
errs() << "構建LLVM字節碼出錯!\n";
exit(1);
}
// 輸出llvm字節碼
outs() << "LLVM module:\n\n" << *M;
outs() << "\n\n";
outs().flush();
// 輸出二進制BitCode到.bc文件
std::error_code ErrInfo;
raw_ostream *out = new raw_fd_ostream("a.bc", ErrInfo, sys::fs::F_None);
WriteBitcodeToFile(M, *out);
out->flush(); delete out;
// 關閉LLVM釋放內存
llvm_shutdown();
return 0;
}
~~~