码迷,mamicode.com
首页 > 其他好文 > 详细

C编译器剖析_5.1 中间代码生成及优化_布尔表达式的翻译

时间:2015-04-10 15:40:23      阅读:172      评论:0      收藏:0      [点我收藏+]

标签:c编译器   布尔表达式的翻译   短路运算   

5.2  中间代码生成与优化_布尔表达式的翻译

    我们仍然按照语法分析和语义检查时的思路,先讨论表达式的翻译,再处理语句。表达式从概念上来说,可分为算术表达式和布尔表达式,在一些编程语言(例如Java)中对这两者是有严格区分的,算术表达式的结果是整数或浮点数,而布尔表达式的结果是逻辑上的真或假。布尔是英国数学家,由于布尔较早进行了关于“与或非”逻辑运算的研究,为了纪念这位先驱,在Java中引入了关键字boolean,而在C++中引入了bool关键字来表达逻辑上的真或假。C语言中并没有专门的布尔类型,而是把非0的值当作真,把0当作假。

int a, b;

if(a+b){// Java中非法,a+b为算术值,非布尔值;C中合法

}

if(a == b){//a == b为布尔值,在C或Java中都合法

}

    为了讨论的方便,我们不妨把C语言看成是存在布尔表达式,例如a&&b,a||b或者a>b等,也存在算术表达式,例如a+b或者a&b,但是不存在C程序员可见的布尔类型关键字bool或者boolean。对于算术表达式而言,C编译器需要对整个表达式进行求值,例如,我们要计算出“a和b相加的值”,或者“a和b按位与的值”,并把运算结果保存到一个临时变量中,这个临时变量的值就代表了整个表达式的值。而对于布尔表达式来说,我们并不会对整个表达式进行求值,而是生成相应的跳转指令,对于a&&b而言,当a为假时,不论b为何值,a&&b的值都为假,所以此时我们并不需要对b进行求值。若把“a&&b”换成“f()&&g()”,则当f()的返回值为假时,我们不需要对函数g()进行求值,此时g()函数根本就没有被调用,这就是C语言中的短路运算。

     我们先举个简单的例子来说明这些概念,如图5.2.1所示,第1至21行给出了一个简单的C程序,第22至60行是与之对应的中间代码。对于第4至8行的C代码来说,与其对应的中间代码在第24至31行,我们需要先对第4行的算术表达式a+b进行求值,第25行的中间代码“t0:a+b”表示把“a+b”求值后的结果存于临时变量t0中,对表达式进行求值的工作由ucl\tranexpr.c中的TranslateExpression()函数来完成。但对于第9行的布尔表达式a&&b来说,我们只是生成第33行和第35行的条件跳转指令,并没有对整个布尔表达式a&&b进行求值,但a为假时,控制流直接由第33行转移到基本块BB6处,即不再需要判断b的值。产生这些跳转指令的工作由ucl\tranexpr.c中的TranslateBranch()函数来实现。在X86汇编中,跳转指令对应的助记符为Jump,缩写为J;但在Arm汇编中,跳转指令对应的是Branch,缩写为B,单词Branch是分支的意思(也有些人写为分枝,哪个是错别字,whoknows!),这意味着如果是“有条件跳转”的话,控制流就会“开叉”,相当于一根树枝分成若干个细枝了,例如图5.2.1第35行的有条件跳转,控制流可能流向第37行,也可能流向第40行。

技术分享

图5.2.1 布尔表达式和算术表达式

    对于图5.2.1第14行的算术表达式a&b来说,我们需要先对整个表达式a&b进行求值,再根据其结果进行跳转,如图第42和43行所示。比较特殊的是第19行的布尔表达式a||b,按我们之前的介绍,翻译布尔表达式时,我们只是产生一些跳转指令来改变控制流,如第50行和第52行所示。但按照第19行赋值语句的语义,我们需要在a||b为真时,给变量c赋值1;在a||b为假时,给变量赋值0。因此,C编译器需要产生第54至57行的代码。这些工作由ucl\tranexpr.c中的函数TranslateBranchExpression来完成。

    简而言之,如果我们需要显式地求出表达式的值,就调用函数TranslateExpression();如果只是需要生成一些跳转指令来改变当前的控制流,则使用TranslateBranch()。如果我们在调用TranslateExpression()来对表达式求值时,遇到的表达式是形如图5.2.1第19行的赋值语句中的布尔表达式a||b,此时我们就再调用TranslateBranchExpression()函数来处理,进而产生如图5.2.1第50至57行的代码。

    趁热打铁,我们现在就来看一下函数TranslateBranchExpression的代码,如图5.2.2第1至18行所示。第4行通过调用CreateTemp函数创建了一个临时变量,不妨设这个临时变量为t。我们需要根据布尔表达式的控制流来执行t=0或者t=1的代码,第11行调用GenerateMove函数产生t = 0的代码,第15行则用于产生t = 1。当表达式expr为假时,我们在执行完t=0后,还要通过第12行的GenerateJump函数,产生一条无条件跳转指令,跳过t=1这个指令。中间代码t=0是基本块falseBB的第一条指令,而t=1为基本块trueBB的第一条指令。在调用第8行的TranslateBranch函数来产生跳转指令时,我们需要把trueBB和falseBB作为跳转目标传递给TranslateBranch函数,这样我们才能生成形如如图5.2.1第50至52行的跳转指令。

技术分享

图5.2.2 TranslateBranchExpression()

    当我们遇到形如“c = expression;”的语句时,由上下文可知,我们需要先对赋值号右侧的表达式进行求值,之后再结果赋给左侧的c。对右侧表达式进行求值的运算,由图5.2.2第45行的TranslateExpression()来实现。在第46行,我们又遇到了非常熟悉的套路,通过查表来调用相应的处理函数,对应的表格如图第29至44行所示,分别是第30行的逗号表达式、第31行的赋值表达式、第33行的条件表达式、第35行的二元运算表达式、第37行的一元运算表达式、第39行的后缀表达式和第41行的基本表达式。我们会在后续章节分别对这些处理函数进行讨论。当我们遇到”c= a || b”时,赋值号右侧的“a ||b”实际上是个布尔表达式,运算符||属于二元运算符,通过第46行的查表操作,我们实际上会调用第19行的TranslateBinaryExpression函数来处理。由于当前运算符为||,即OP_OR,此时第21行的条件会成立,我们就会在第23行调用TranslateBranchExpression函数来生成以下代码:

BB10:       // c = a || b

        if (a) goto BB13;

BB11:

        if (b) goto BB13;

BB12:

        t = 0;

         goto BB14;

BB13:

        t = 1;

BB14:

         c = t;

    当然,上述最末尾的中间代码“c = t;”并不在TranslateBranchExpression函数中生成。经过中间代码的优化,我们可以删去多余的临时变量t,就可以得到如图5.2.1第50至57行的代码。如果想观察未经优化过的中间代码,可把第5.1节“图5.1.3 Translate()”中第48行的Optimize()函数调用给注释掉,然后重新make编译器UCC的源代码,并make install之。不过,更好的办法是像GCC或Clang那样,加入一些新的命令行参数,来指定优化的级别。

当然,如果我们在TranslateBinaryExpression中遇到的是形如”a<<b”或”a+b”这样的二元算术表达式,此时图5.2.2第21行的if条件就不会成立,我们会调用第27行的Simplify函数来做一些简单的优化,例如对于a<<0,我们根本就没有必要产生左移指令,因为a左移0位仍然是a本身。我们在第48至63行中给出了函数Simplify的部分代码,第62行调用的TryAddValue()函数主要是用于处理公共子表达式,我们在第2章的“图2.5.9 TryAddValue()

”中介绍过这个函数,这里不再重复。公共子表达式的出发点在于,当我们计算出a+b的值后,如果再次遇到a+b,且a和b的值没有发生变化,此时我们没有必要重新计算a+b的值,如下所示。

         c = a + b;

         d = a + b;

         // 对应的中间代码

         t0 : a + b;

         c = t0;

         d = t0;

     当布尔表达式a||b出现在if(a||b)中时,其翻译的方法就与c = a||b有所不同,此时我们只需要产生跳转指令即可,不必生成t=0或t=1这样的指令。接下来,我们来分析一下用于生成跳转指令的TranslateBranch函数,我们先举个简单的例子来说明一下,如图5.2.3所示。对于图5.2.3第1至5行的代码而言,我们可以翻译为第6至15行的中间代码,在第7行和第8行,我们需要产生2条跳转指令,当a为真时,控制流跳往BB_T1,当a为假时,第7行的条件不成立,不会进行跳转,我们接着执行第8行,跳往BB_F1,这是一个完全正确的翻译方案,不妨称之为“方案1”。当然,我们也可以按图5.3.2第16至24行进行翻译,当a为假(即!a为真)时,我们跳往BB_F2,但当a为真时,第17行的条件不成立,不会进行跳转,接着执行第19行的指令,这正是我们在第1行想要的结果,即表达式a为真时执行BB_T2的代码,而表达式a为假时,执行BB_F2处的代码,不妨称此方案为“方案2”。方案2比方案1不仅减少了代码所占用的空间(减少了一条指令),同时也加快了执行的速度(当a为假时,没有必要像方案1那样需要执行第7和第8行这两条跳转指令)。

技术分享

图5.2.3 生成跳转指令

    函数TranslateBranch为表达式expr产生跳转指令时,是按照方案2来处理的。我们把表达式分为布尔表达式和算术表达式,对算术表达式而言,我们先对其求值,其求值后的结果就相当于第1行中的a。而对于布尔表达式中的关系运算表达式来说,例如if(a>b)中的a>b,我们只要把第17行的条件改为if(!(a>b)) gotoBB_F2即可,即

         if(a <= b) goto BB_F2;

    换言之,如果函数TranslateBranch要处理的是形如a+b这样的算术表达式,或者a>b这样的关系运算表达式,由图5.2.3第17行可知,我们只需要生成一条跳转指令,给函数TranslateBranch传递一个跳转目标作为参数即可,比如第17行的BB_F2。比较麻烦的是复杂的短路运算,如图5.2.4所示。

技术分享

图5.2.4 短路运算

    当图5.2.4第4行的表达式((a&&b)||c)为真时,我们需要跳往第18行的BB3;为假时,我们需要跳往第21行的BB4。而且我们确实在第15行用到了跳转目标BB3,又在第17行用到了跳转目标BB4。此时,用于产生第13至17行各跳转指令的函数TranslateBranch就需要两个跳转目标作为其参数。因此,其函数接口如下所示,其中bt和bn是两个跳转目标。         

void   TranslateBranch(AstExpression expr, BBlock  bt, BBlock  bn);        

    当参数expr对应的只是形如a+b的算术表达式或者形如a>b的关系运算表达式,我们只需要一个跳转目标bt,即只生成一条跳转指令“If(expr)  goto bt”,如下所示。我们期望在条件expr不成立时,我们会接着执行参数bn对应的基本块,这就要求bn对应的基本块紧挨着“if(expr)  goto  bt;”这条中间代码,如下所示。

         if(expr)  goto  bt

bn:

         …

bt:   

         …

    不难发现,图5.2.3第17至24行的“方案2”完全符合这个模式,其中图5.2.3第17行的表达式(!a)对应上述expr,第17行的BB_F2对应上述bt,而第18行的BB_T2对应上述bn。

    当参数expr为复杂的短路运算表达式时,我们也期望bn对应的基本块紧挨着“短路运算对应的跳转指令”。不难发现,图5.2.4第18行的bb3对应如下的bn,而第19行的bb4对应如下的bt基本块,第13至17行的跳转指令就是短路运算表达式((a&&b)||c)对应的跳转指令。

翻译方案

         “短路运算((a&&b)||c)对应的跳转指令”

bn:

         …

bt:

         …

    简而言之,我们在调用函数TranslateBranch(expr, bt, bn)时,有这么两个约定:

    (1).   当expr为真时,跳往bt基本块

    (2). 紧随函数TranslateBranch所生成的跳转指令之后的基本块为bn。

    有了这样的基础后,我们就可以来看一下用于生成跳转指令的函数TranslateBranch了,如图5.2.5所示,第46至61行用于处理形如a+b这样的算术表达式,第46行调用TranslateExpression函数来对表达式进行求值,其结果存于符号src1中,当结果为非0常数时,则第1行的参数expr的值为永真,我们在第49行通过GenerateJump生成一条无条件跳转指令,跳往参数trueBB所对应的基本块。当src1不为常数时,若其类型为小于int的整型(例如short或者char),则先调用第55行的TranslateCast函数来提升为int型,之后再通过第58行的GenerateBranch函数生成有条件跳转指令,按之前的约定,此处要进行的跳转为“if(expr) goto trueBB”,即当expr不为0时,跳往trueBB,第58行的JNZ是JumpIfNotZero的助记符,第60行的注释再次提醒我们,紧接下来就是基本块falseBB的中间代码。第21至28行的代码用于翻译关系运算表达式(>,>=,<,<=,==和!=这6个关系运算),第25行通过调用GenerateBranch函数生成有条件跳转指令,跳往trueBB基本块。第29至42行的代码用于处理形如“!!!!! kids0”的表达式,我们先统计!运算符的个数,若为偶数,则可简化为“kids0”;若为奇数,则可简化为“! kids0”。我们在第33行调用TranslateExpression函数对表达式kids0进行求值,其结果存于符号src1中,之后再根据!运算符的奇偶来选择跳转指令JZ或者JNZ,第34至40行的注释,对为何选择JZ或JNZ做了解释。需要注意的是,按照我们之前的约定,我们在此处生成的有条件跳转指令要跳往trueBB基本块。第41行的注释又一次的提醒我们,接一下的基本块就是参数falseBB对应的基本块。

技术分享

图5.2.5 TranslateBranch()

     图5.2.5第7至13行用于对形如“kids0 && kids1”的短路运算进行翻译,其中kids0或者kids1本身又是表达式,这就需要递归地对其进行处理。按TranslateBranch函数接口的约定,当实际的函数调用为TranslateBranch (kids0 && kids1, tBB,fBB)时,所产生的中间代码仍然要满足以下模式。

kids0 &&kids1为真时,跳往tBB

// 上述跳转指令由TranslateBranch(kids0&& kids1, tBB,fBB)来产生

fBB:

         ….

tBB:

         …

     按短路运算的语义要求,当kids0为假时,整个表达式(kids0&& kids1)肯定为假,此时我们要生成跳转指令,跳往fBB,图5.2.5第9行用于此目的。如果kids0为真,则我们还要接着判断kids1是否为真,当kids1为真时,整个表达式”kids0&& kids1”就为真,此时我们需要跳往tBB,图5.2.5第11行用于产生相应的跳转指令;若kids1为假,则按照调用TranslateBranch时的约定,接下的基本块就是fBB,此时控制流会直接流入fBB基本块。我们在图5.2.5第7至13行所产生的中间代码的模式如下所示:    

         “当kids0为假时,即!kids0为真时,跳往fBB

//上述跳转指令由TranslateBranch(Not(expr->kids[0]),fBB, rBB);来产生

rBB:

         “当kids1为真时,跳往tBB

         //上述跳转指令由TranslateBranch(expr->kids[1],tBB, fBB);来产生

fBB:

         ….

tBB:

         …

     函数TranslateBranch有3个参数,在上述代码模式中,我们特意为第3个实参添加了下划线,可以发现,调用TranslateBranch函数产生跳转指令后,接下的基本块总是第3个实参所对应的基本块,这正是调用TranslateBranch函数时我们要遵守的约定。函数TranslateBranch是一个递归函数,而图5.2.5第21至61行的代码就充当了递归调用的出口,这些代码通过调用GenerateBranch来产生“有条件跳转指令”,例如图5.2.5第25行所示,或者通过调用GenerateJump来产生“无条件跳转指令”,如图5.2.5第49行所示,但自始自终,我们都遵守另一个约定,即由GenerateBranch或GenerateJump所生成的跳转指令,其跳转目标都是TranslateBranch函数的第2个参数trueBB。

     同理,我们不难理解图5.2.5第14至20行的用于处理”kids0 || kids1”的代码,这里不再啰嗦。至此,我们实现了对布尔表达式的翻译,也初步介绍了用于翻译算术表达式的函数TranslateExpression,在下一节中,我们会对后缀表达式和一元表达式等的表达式的翻译进行讨论。

C编译器剖析_5.1 中间代码生成及优化_布尔表达式的翻译

标签:c编译器   布尔表达式的翻译   短路运算   

原文地址:http://blog.csdn.net/sheisc/article/details/44979085

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!