编译器架构的王者LLVM——(7)函数的翻译方法

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 —— 题记

版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy

函数的翻译方法

前面介绍了许多编译器架构上面的特点,如何组织语法树、如果多遍扫描语法树。今天开始,我们就要设计本编译器中最核心的部分了,如何设计一个编译时宏,再利用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;

这个地方问题非常多,我先保留一个悬念,在接下来代码块和变量存储与加载的讲解中,我会再次提到这个部分的特殊处理。

版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy

时间: 2024-10-18 19:14:59

编译器架构的王者LLVM——(7)函数的翻译方法的相关文章

编译器架构的王者LLVM——(8)函数的调用及基本运算符

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 函数的调用及基本运算符 之前我们提到了函数的定义,那么,定义好的函数如何调用才行呢?今天我们就来了解一下,函数的调用. 函数调用的宏形式 我们去读之前对函数调用的语法树翻译形式: printf("%d\

编译器架构的王者LLVM——(11)深入理解GetElementPtr

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 深入理解GetElementPtr LLVM平台,和C语言极为类似,强类型,需要复杂的指针操作,基于系统的符号调用等.而LLVM的指针操作指令,GetElementPtr,几乎是所有指针计算的关键,而理

编译器架构的王者LLVM——(10)变量的存储与读取

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 变量的存储与读取 变量是一款编程语言中的核心,说编译语言是一种符号处理工具,其实是有些道理的.栈式符号表可以方便的记录编译过程中的变量和语法符号,我们上节已经了解了其中的实现方法.那么,还有没有其他的办

编译器架构的王者LLVM——(9)栈式符号表的构建

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 栈式符号表的构建 栈式符号表对于一款编译器,无疑是核心的组件. 无论你在做什么符号扫描,那么都离不开符号表,如何得知一个符号是否定义,以及它的类型,那么唯有查看符号表中的记录. 栈式符号表并不复杂,但思

编译器架构的王者LLVM——(5)语法树模型的基本结构

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 语法树模型的基本结构 上次我们看了Lex和Yacc的翻译文件,可能一些朋友并不了解其中的执行部分,而且,对这个抽象语法树是怎么构建起来的还不清楚.今天我们就再详细介绍一下如果方便的构建一棵抽象语法树(A

strdup函数的使用方法

函数名: strdup 功  能: 将串复制到新建的位置处 用  法: char *strdup(char *str): 这个函数在linux的man手冊里解释为: The strdup() function returns a pointer toa new string which is a duplicate of the string s. Memory for thenew string is obtained with malloc(3), and can be freed with

[转载]Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取

原文地址:点击打开链接 为什么要写这篇文章 1.      因为最近在学习<软件调试>这本书,看到书中的某个调试历程中讲了Windows的系统调用的实现机制,其中讲到了从Ring3跳转到Ring0之后直接进入了KiFastCallEntry这个函数. 2.      碰巧前天又在网上看到了一篇老文章介绍xxx安全卫士对Windows系统调用的Hook,主要就是Hook到这个函数 3.      刚刚做完毕业设计,对使用中断来实现系统调用的方式记忆犹新. 以上原因导致我最近眼前总是出现系统调用这

c/c++ 编译器提供的默认6个函数

c/c++ 编译器提供的默认6个函数 1,构造函数 2,拷贝构造函数 3,析构函数 4,=重载函数 5,&重载函数 6,const&重载函数 #include <iostream> using namespace std; class Test{ public: Test(int d = 0):data(d){ cout << "c" << endl; } Test(const Test &t){ cout <<

inline函数的使用方法

 inline关键字用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义. 表达式形式的宏定义如下: #define ExpressionName(Var1,Var2) ((Var1)+(Var2))*((Var1)-(Var2)) 为什么要取代这种形式呢,且听我道来: 1. 首先谈一下在C中使用这种形式宏定义的原因,C语言是一个效率很高的语言,这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈,代码生成等一系列的操作,因此,效率很高,这是它在C中