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

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

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

函数的调用及基本运算符

之前我们提到了函数的定义,那么,定义好的函数如何调用才行呢?今天我们就来了解一下,函数的调用。

函数调用的宏形式

我们去读之前对函数调用的语法树翻译形式:

    printf("%d\n", y);

会被翻译为:

            Node
                String call
                String printf
                String %d\n
                ID y

这个宏的名字是call,是个不定参数的:

    (call 函数名 参数表... )

于是我们就需要扫描参数表,获取全部调用参数。

调用宏的基本形式

调用宏其实很简单,就是不断循环判断有多少参数即可。

static Value* call_macro(CodeGenContext* context, Node* node) {
    // 参数一 函数名

    // 其余参数 要传入的参数
    for (Node* p = node->getNext(); p != NULL; p = p->getNext()) {
        // 循环获取参数
    }
}

另外我们查阅一下LLVM的文档,找到其中CallInst这个指令,LLVM的指令都派生自Instruction,可以发现构建的方法很简单:

    static CallInst * Create (Value *Func, ArrayRef< Value * > Args, const Twine &NameStr, BasicBlock *InsertAtEnd)

但是我们发现,Value中要传输的是一个Function对象,如何获取呢?当然还是从符号表中获取,我们下次会讲符号表的实现,这次也和上节一样,将接口先写出来。

    // 参数一 函数名
    Value* func = context->getFunction(node);
    if (func == NULL) {
        errs() << "找不到函数的定义:";
        errs() << node->getStr().c_str() << "\n";
        exit(1);
    }

函数调用在生成时,如果这个函数还没有被扫描到,那么在生成时会报函数定义找不到的问题,这就是我们为什么要用多遍扫描。只有充分的多次扫描语法树,才能获取每个函数后面的函数定义。虽然像C语言那样强制声明也可以,但我个人不大喜欢这种风格。

至于参数的获取,就十分简单的,但有一点要注意,参数是递归生成的,例如:

    printf("%d", add(3, 5));

这时,我们在获取参数时,就会发现,其中一个参数是表达式,那么我们就要先对其进行处理:

    // 其余参数 要传入的参数
    std::vector<Value*> args;
    for (Node* p = node->getNext(); p != NULL; p = p->getNext()) {
        Value* v = p->codeGen(context); // 递归地生成参数
        if (v != NULL)
            args.push_back(v);
    }

Node类下面有实现codeGen方法,其作用就是重新调用了完整的对当前节点的代码生成,方便递归调用:

Value* Node::codeGen(CodeGenContext* context) {
    return context->MacroMake(this); // MacroMake是核心的代码生成接口
}

于是我们递归地生成了这些代码,就可以产生一条Call语句,那么别忘记将其返回给上一层:

static Value* call_macro(CodeGenContext* context, Node* node) {
    // 参数一 函数名
    Value* func = context->getFunction(node);
    if (func == NULL) {
        errs() << "找不到函数的定义:";
        errs() << node->getStr().c_str() << "\n";
        exit(1);
    }

    // 其余参数 要传入的参数
    std::vector<Value*> args;
    for (Node* p = node->getNext(); p != NULL; p = p->getNext()) {
        Value* v = p->codeGen(context);
        if (v != NULL)
            args.push_back(v);
    }

    CallInst *call = CallInst::Create(func, args, "", context->getNowBlock());
    return call;
}

简单运算符计算

对于计算机,加减乘除这些基本运算,就是几个指令而已,但对于编译器,却也要分好几种情况讨论,因为,全部的运算符有这么多:

// Standard binary operators...
 FIRST_BINARY_INST( 8)
HANDLE_BINARY_INST( 8, Add  , BinaryOperator)
HANDLE_BINARY_INST( 9, FAdd , BinaryOperator)
HANDLE_BINARY_INST(10, Sub  , BinaryOperator)
HANDLE_BINARY_INST(11, FSub , BinaryOperator)
HANDLE_BINARY_INST(12, Mul  , BinaryOperator)
HANDLE_BINARY_INST(13, FMul , BinaryOperator)
HANDLE_BINARY_INST(14, UDiv , BinaryOperator)
HANDLE_BINARY_INST(15, SDiv , BinaryOperator)
HANDLE_BINARY_INST(16, FDiv , BinaryOperator)
HANDLE_BINARY_INST(17, URem , BinaryOperator)
HANDLE_BINARY_INST(18, SRem , BinaryOperator)
HANDLE_BINARY_INST(19, FRem , BinaryOperator)

// Logical operators (integer operands)
HANDLE_BINARY_INST(20, Shl  , BinaryOperator) // Shift left  (logical)
HANDLE_BINARY_INST(21, LShr , BinaryOperator) // Shift right (logical)
HANDLE_BINARY_INST(22, AShr , BinaryOperator) // Shift right (arithmetic)
HANDLE_BINARY_INST(23, And  , BinaryOperator)
HANDLE_BINARY_INST(24, Or   , BinaryOperator)
HANDLE_BINARY_INST(25, Xor  , BinaryOperator)

这些定义很难找,在文档中并没有真正写出来,而是在头文件的llvm/IR/Instruction.def里面,这是宏定义的专属部分。

这些还仅仅是数值运算,还不算比较运算的部分呢。

当然,这和计算机体系结构有关,浮点数的运算和整数肯定是不一样的,而我们知道,右移位也分算数右移和逻辑右移。所以必然,会有大量不同的运算符。

创建指令则很简单:

static BinaryOperator * Create (BinaryOps Op, Value *S1, Value *S2, const Twine &Name, BasicBlock *InsertAtEnd)

两个运算数,可以是常量,也可以是变量load出值后,还可以是表达式返回值,只要两个Value调用getType,符合运算规则,就可以。

注意,浮点数不能直接和整数运算,必须先将整形转为浮点才可以。

于是以下是简单的运算符操作,我只写了整数的运算操作:

static Value* opt2_macro(CodeGenContext* context, Node* node) {
    std::string opt = node->getStr();

    Node* op1 = (node = node->getNext());
    if (node == NULL) return NULL;
    Node* op2 = (node = node->getNext());
    if (node == NULL) return NULL;

    Instruction::BinaryOps instr;
    if (opt == "+") { instr = Instruction::Add;  goto binOper; }
    if (opt == "-") { instr = Instruction::Sub;  goto binOper; }
    if (opt == "*") { instr = Instruction::Mul;  goto binOper; }
    if (opt == "/") { instr = Instruction::SDiv; goto binOper; }

    // 未知运算符
    return NULL;

binOper:
    return BinaryOperator::Create(instr, op1->codeGen(context),
        op2->codeGen(context), "", context->getNowBlock());

附:文档参考及源代码

CallInst类参考

BinaryOperator类参考

github源码-函数调用及基本运算符

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

时间: 2024-10-13 19:53:28

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

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

LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 -- 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 http://blog.csdn.net/xfxyy_sxfancy 函数的翻译方法 前面介绍了许多编译器架构上面的特点,如何组织语法树.如果多遍扫描语法树.今天开始,我们就要设计本编译器中最核心的部分了,如何设计一个编译时宏,再利用LLVM按顺序生成模块. 设计宏 我们

编译器架构的王者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

[转载]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 <<

函数的调用优化

函数的调用优化 在类中有四大成员函数,构造函数,拷贝构造函数,赋值函数和析构函数,在类外进行实例化时,若调用函数的方法不当则会产生时间和空间的浪费. 以下,将用几个小栗子来说明函数的调用优化的常见办法 类: class Test { public: Test(int d = 0) : _data(d) { cout << "Create Test Object:"<<this << endl; } Test(const Test&x) :_d

关于编译型语言函数的调用(三)

接上文:http://blog.csdn.net/prsniper/article/details/40653235 类delete就不说了,有兴趣的朋友自己跟踪看看吧,提示一下:析构函数也有参数和返回值. 下面我们看下裸函数,裸函数从执行效率上是可以与汇编语言媲美的,然而它不太好逆过来说,我们就顺着说吧 前面说得构造函数的临时堆栈,恢复寄存器等等,有人给了个名称叫prolog和epilog 而裸函数并没有自动编译这些部分,其实说白了,裸函数相当于汇编语言中的一个标签,其调用受前面几种约定约束