5.2.2 再论符号symbol与公共子表达式
在介绍算术表达式的翻译前,让我们简单重温一下第2.5节中的“图2.5.4 公共子表达式”及“图2.5.5 valueDef和valueUse”。为阅读方便,我们再次给出这两张图,更详细的说明请参见第2.5节。对于图2.5.4第2行的a+b,我们会由第7行的中间代码来对a+b进行求值,其结果存于临时变量t1中,之后在第3行中再次遇到表达式a+b时,a和b的值并没有发生变化,我们可在第9行直接把t1赋给变量d。由于我们在第4行对a进行赋值,导致t1中保存的值不再有效,所以我们需要重新进行计算第5行的a+b。我们用形如第7行的“t1:a+b;”来表示临时变量t1由a和b相加产生,这与编译原理相关教材中使用“t1 = a+b;”的式子略微有所区别。
图2.5.4 公共子表达式
对公共子表达式进行重用的思路并不难理解,第2.5节的图2.5.5主要是用于说明要实现这样的想法,我们需要定义出相应的数据结构。图2.5.5中的struct valueDef结构体用来描述一个公共子表达式“t1:a+b”,其中临时变量t1、变量a和变量b各用一个struct variableSymbol符号对象来刻画。在变量a发生变化时,为了能让公共子表达式a+b失效,我们需要在a中记录“a在哪些公共子表达式中被使用”,图2.5.4中定义的结构体struct valueUse就用于此目的。由于变量a可在多个公共子表达式中被使用,因此我们需要一条由若干个struct valueUse对象构成的链表来记录这些表达式,其链首就存于图2.5.5的结构体对象struct variableSymbol的uses域中。当a发生变化时,我们要沿着uses域所指向的链表,使链表上的各个公共子表达式失效。当然,变量b中也有类似的结构,当变量b发生变化时,我们也要做类似的处理。
图2.5.5 valueDef和valueUse
图2.5.4第7行使用“t1:a+b”的一个原因是,这隐含着UCC编译器只对t1进行唯一的一次赋值(单赋值)。但对于C语句“b = a > 3? 50:60;”来说,与其对应的中间代码如下所示,我们可以发现临时变量t0会被赋值两次。为了与单赋值的情况有所区别,此处我们用MOV指令“t0 = 50”,其中用的是赋值号’=’,而非冒号’:’。在生成中间代码后,我们在进行优化时,可把以下对t0进行赋值的语句改为对b进行赋值。但由于UCC编译器的优化只在一个基本块内进行,而此处的t0显然出现在多个基本块中,这就需要我们在生成中间代码时做一些特殊处理,以便优化时可对“t0=50;”指令做修改。产生这样困境的原因在于UCC编译器在翻译表达式“a > 3? 50:60”时,并没有考虑其所处的上下文,而总是将条件表达式的结果先存放到一个临时变量中。