5.2.1 中间代码生成与优化_布尔表达式的翻译
我们仍然按照语法分析和语义检查时的思路,先讨论表达式的翻译,再处理语句。表达式从概念上来说,可分为算术表达式和布尔表达式,在一些编程语言(例如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行的跳转指令。