LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 —— 题记
版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy
函数的翻译方法
前面介绍了许多编译器架构上面的特点,如何组织语法树、如果多遍扫描语法树。今天开始,我们就要设计本编译器中最核心的部分了,如何设计一个编译时宏,再利用LLVM按顺序生成模块。
设计宏
我们的编译器可以说是宏驱动的,因为我们扫描每个语法节点后,都会考察当前是不是一个合法的宏,例如我们来分析一下上一章的示例代码:
1 2 3
| void hello(int k, int g) { ...... }
|
我暂时隐藏了函数体部分,让大家先关注一下函数头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| String function String void String hello Node Node String set String int String k
Node String set String int String g
Node ......
|
我们的语法树的每一层相当于是链表组织的,通过next指针都可以找到下一个元素。
而语法树的开头部分,是一个“function”的宏名称,这个部分就是提示我们用哪个宏函数来翻译用的。
接下来的节点就是: 返回类型,函数名,参数表,函数体
例如参数表,里面的内容很多,但我们扫描时,它们是一个整体,进行识别。
所以我们的宏的形式实际上就是这样:
1
| (function 返回类型 函数名 (形参表) (函数体))
|
括号括起来的部分表示是一个列表,而不是一个元素。
宏函数的编写
我们之前已经定义了宏的函数形式,我们需要传入的有我们自己的上下文类和当前要处理的Node节点,返回的是LLVM的Value类型(各个语句的抽象基类)
1
| typedef Value* (*CodeGenFunction)(CodeGenContext*, Node*);
|
于是我们将这个函数实现出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static Value* function_macro(CodeGenContext* context, Node* node) { // 第一个参数, 返回类型
// 第二个参数, 函数名 node = node->getNext();
// 第三个参数, 参数表 Node* args_node = node = node->getNext();
// 第四个参数, 代码块 node = node->getNext();
return F; }
|
获取一个字符串代表的类型,往往不是一件容易的事,尤其在存在结构体和类的情况下,这时,我们往往需要查一下符号表,检查这个字符串是否为类型,以及是什么样的类型,是基本类型、结构体,还是函数指针或者指向其他结构的指针等等。
获取类型,往往是LLVM中非常重要的一步。
我们这里先写一下查符号表的接口,不做实现,接下来的章节中,我们会介绍经典的栈式符号表的实现。
第二个参数是函数名,我们将其保存在临时变量中待用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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; }
|
接下来的参数表也许是很不好实现的一部分,因为其嵌套比较复杂,不过思路还好,就是不断的去扫描节点,这样我们就可以写出如下的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // 第三个参数, 参数表 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里很多对象都有这个特点,函数可以只声明函数头,解析完函数体后再将其填回去。结构体也一样,可以先声明符号,回头再向里填入类型信息。这些特性都是方便生成声明的实现,并且在多遍扫描的实现中也会显得很灵活。
我们下面来声明这个函数:
1 2 3 4 5 6 7
| // 先合成一个函数 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关键字,确定这个函数要导出符号。这样,你写的函数就能够被外部链接,或者作为外部函数的声明使用。
函数的特殊问题
接下来我们要创建函数的代码块,但这部分代码实际上和上面的不是在同一个函数中实现的,应该说,他们不是在一趟扫描中。
我们知道,如果要让一个函数内的代码块能够调用在任意位置声明的函数,那么我们就必须对所有函数都先处理刚才讲过的前三个参数,这样函数的声明就有了,在之后的正式扫描中,才有了如下的代码块生成部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| // 第四个参数, 代码块 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;
|
这个地方问题非常多,我先保留一个悬念,在接下来代码块和变量存储与加载的讲解中,我会再次提到这个部分的特殊处理。