5.3.2.Switch语句的翻译

在这一小节中,我们来讨论一下switch语句的翻译,switch语句的产生式如下所示。

SwitchStatement:

switch( expr ) statement

当C程序员编写出如下代码时,UCC编译器会在语义检查阶段进行报错“error:The  break shall  appear  in  a  switch  or loop”,从语法上来看,以下“case  3: b = 30;”是case语句,而“break;”并不是case语句的一部分。按switch语句的产生式,以下“switch(a)  case 3: b = 30;”构成一条完整的switch语句,而“break;”则不在switch语句内。

switch(a)

case  3:  b =30;  break;

虽然按switch语句的产生式,其中的statement部分并不一定是复合语句,但通常C程序员在编写代码时,会将此部分写为一个复合语句,例如:

switch(a){

case  3: b = 30; break;

}

此时,复合语句中的“break;”就包含在swich语句中,这就可以符合C语言的语义要求,即“break语句要被包含在switch语句或者循环语句”中。到了中间代码生成阶段,我们面对的已经是“没有语法或语义错误”的语法树。我们还是举一个例子来说明switch语句的翻译,如图5.3.3所示,第4至17行为一个switch语句,与之对应的中间代码在第23至40行。我们注意到,第28行是一条“间接跳转”指令,我们根据(a-1)的值来索引跳转表“[BB4,BB8,BB6,BB12,BB10,]”,从而得到相应的跳转目标,例如当a为1时,我们得到的跳转目标为BB4,当a的值为4时,由于4并不落在{1,3,2,5}中,对应的跳转目标BB12用于跳出switch语句。通过跳转表的技术来翻译switch语句,可以使得生成的汇编代码在运行时不需要进行过多的比较,从而加快代码的执行速度,不过,跳转表本身需要占用内存空间,这是一种“以空间换时间”的方法。第44至53行是对应的部分汇编代码,第44至49行的跳转表switchTable1在数据区中,其中存放的是跳转目标的首地址(代码区中的地址),而第50至53行则根据(a-1)的值来查表,再进行跳转。

图5.3.3  switch语句的例子

在图5.3.3的例子中,各case语句中的常量为{1,3,2,5},这些整数在数值上相差不大,如果我们遇到的是{1,2,20000,50},相应的跳转表就得有20000个表项,其中大部分的表项存放的是形如“BB12”这样的用于跳出switch语句的地址,这需要耗费相当大的内存,此时我们不再构造一张跳转表,而是想把{1,2,20000,50}分成几段,每一段内的各个数值彼此接近,例如我们可把{1,2,20000,50}分成3段{{1,2},{50},{20000}}。我们制定一个标准,来衡量各数值彼此接近的程度,UCC编译器引入了一个“Case语句密度”的概念,

Case语句密度 =Case语句数量/ 区间大小

例如,对于{1,2,20000,50}来说,其密度为(4/(20000-1)),这有点像人口密度的概念,反映了人口的密集程度,而{1,2}的密度为2/(2-1)。为了方便代码生成,UCC编译器在语法分析时,会把在同一switch语句的各case语句,按出现的先后顺序进行排列。而为了统计“Case语句密度”,在语义检查时,会按各case语句的常量表达式数值,从小到大进行排列。因此,每条case语句实际上会出两个链表中,UCC编译器用结构体astCaseStatement来描述一条case语句,其中的next和nextCase域用于构造这样的两个链表。

struct  astCaseStatement{

//按case语句在源代码中出现的先后顺序排列

struct astNode *next;

//按case语句的表达式数值从小到大排列

structastCaseStatement *nextCase;

…..

}

当面对从小到大排列的{1,2,50,20000}时,UCC编译器要求每段的密度要大于

1/2,根据这个经验值,我们可以按以下步骤进行分段,

(1) 第1个case语句就是一段,即{1}

(2) 把第2个case语句加入{1},则有{1,2},其密度为2

(3) 若把第3个case语句加入{1,2},则有{1,2,50},其密度为3/49,小于1/2,因此我新创建一段来存放50,即有了{{1,2},{50}}

(4) 若把第4个case语句加入{50},则有{50,20000},其密度也小于1/2,因此我们再新创建一段来存放20000,即有了{{1,2},{50},{20000}}

若有一个整数val,我们要判断val是落在哪一段中,为了减少比较的次数,我们可以

采用二分查找的方法,即先看看val是否落在中间的那一段{50},如果比中间段更小,就再看看是否能在左侧的段{1,2}中;若更大,则看看是否落在右侧的段{20000}中。按这样思路,我们可以产生如图5.3.4第23至38行的比较和跳转操作,而第39至50行的中间代码仍然保持C源程序中case语句的先后顺序。我们还注意到,当某段中的case语句多于1条时,UCC编译器会通过跳转表来进行跳转,如第34行所示。如果某段中只有一条case语句且其左侧或右侧的段还没有被比较过,例如第23行的{50},我们可产生形如第23至27行的代码,即要处理“小于”、“大于”和“落在段中”这3种情况;而如果该段的左侧和右侧的其他段都已被处理过,例如第36行的{20000},我们可产生形如第36至38行的代码即可,此时只要处理“等于”和“不等于”这2种情况。

图5.3.4 Case语句密度与二分查找

我们再举一个例子来说明分段中可能出现的情况,例如当UCC编译器面对从小到大排列的各case语句{0, 1, 4, 9, 10, 11}时,我们可以按以下步骤进行分段:

(1)    加入case  0,得到{0};

(2)    加入case  1,得到{0,1},密度为2;

(3)    加入case  4,得到{0,1,4},密度为3/4;

(4)    case  9单独构成一段,得到{{0,1,4},{9}};

(5)    加入case  10,得到{{0,1,4},{9,10}};

(6)    加入case  11,先得到{{0,1,4},{9,10,11}},由于6/11仍大于1/2,因此我们把这两段合并为                {0,1,4,9,10,11}。

为了描述“段”的概念,UCC编译器引入了switchBucket结构体,Bucket

是“桶”的意思,我们用“Bucket”来存放处于同一段中的各条case语句,由于存在多个分段,就需要多个桶,我们可用链表结构来管理这些桶,而每个桶内又有一条由case语句构成的链表,switchBucket结构体如下所示:

typedef  struct  switchBucket{//用于描述形如{0,1,4}这样的段

int ncase;               //桶内case语句的条数,例如3

int    minVal;           //桶内case语句表达式的最小值,例如0

int maxVal;              //桶内case语句表达式的最大值,例如4

AstCaseStatement cases;  //桶内case语句链表的链首

AstCaseStatement*tail;   //指向链尾,便于插入操作

structswitchBucket *prev;//用于组成由各个“桶”对象构成的链

} *SwitchBucket;

为了方便对各SwitchBucket进行二分查找,UCC编译器除了把各switchBucket对象通过上述prev域构成一条链表外,还会用一个数组来存放各switchBucket的首地址。例如,对于{{1,2},{50},{20000}}来说,我们可用以下伪代码来表示相关结构:

SwitchBucket  ptr1 = {1,2};

SwitchBucket  ptr2 = {50};

SwitchBucket  ptr3 = {20000}

其链表结构为:

{1,2} ---> {50} --->{20000}

其数组结构为:

SwitchBucket bucketArray[] = {ptr1,ptr2,ptr3};

有了这样的基础后,我们就可以来看一下switch语句的翻译,如图5.3.5所示,第7行用于翻译switch语句中的表达式,第11至31行把各case语句加入到相应的桶中。由于每个case语句都是控制流的跳转目标,因此每个case语句都对应一个基本块,第14行调用CreateBlock()函数创建了这些基本块。第16行的if条件用于判断“当前桶中的case语句密度是否大于1/2”,第21行通过调用MergeSwitchBucket函数进行相邻桶的合并,由此可把前文的{0,1,4}和{9,10,11}这两个桶的合并为{0,1,4,9,10,11}。当Case语句密度小于或等于1/2时,我们通过第23至28行创建一个新桶。第32至39行用于创建桶指针数组,便于进行二分查找。当Switch语句的表达式的值不与任何case匹配时,控制流要么进入default语句(在C程序员提供default语句时),要么跳出switch语句(在C程序员没有编写default语句时),第40至46行会对此进行处理。图5.3.5第48行调用TranslateSwitchBuckets函数,用于产生形如“图5.3.4第23至38行的用于比较和跳转”的中间代码。如果switch语句确实包含case或者default语句,第54行就递归地调用TranslateStatement函数,来翻译候选式“switch(expr)statement”中的statement。

图5.3.5 TranslateSwitchStatement()

接下来,我们来分析一下用于产生比较和跳转语句的TranslateSwitchBuckets函数,其函数接口如下所示:

static void TranslateSwitchBuckets(

//对bucketArray[left]至bucketArray[right]这几个桶进行处理

SwitchBucket *bucketArray,   int  left,          int  right,

//符号choice代表了switch(expr)statement中表达式的值

Symbol  choice,

//指向当前要处理的SwichBucket桶或者为NULL

BBlock  currBB,

//参数defBB要么是default语句对应的基本块(存在default语句时)

//要么是switch语句之后的基本块nextBB(当default语句不存在时)

BBlock  defBB

);

例如,在图5.3.5第48行我们按以下方式来调用TranslateSwitchBuckets函数,其中的swtchStmt->nbucket代表switch语句中case的个数,此处待翻译的各桶的下标从0至(swtchStmt->nbucket –1)。

TranslateSwitchBuckets(bucketArray,  0,  swtchStmt->nbucket- 1,

sym, NULL,swtchStmt->defBB);

仍以图5.3.4为例,对于{{1,2},{50},{20000}}而言,按二分查找的算法,我们最先处理的是位于中间的桶{50},我们会为之产生以下用于比较和跳转的代码:

if (a < 50) goto BB4;                   //再去与桶{1,2}比较

BB1:

if (a > 50) goto BB8;                   //再去与桶{20000}比较

BB2:

goto BB17;                //跳往case  50

图5.3.6给出了TranslateSwitchBuckets函数的代码,第15至18行用于构造长度为len的跳转表,第19至26行用于对跳转表进行初始化,从而得到形如“(BB11,BB13,)”的表格,当桶中只有一个case语句,且该桶左侧或右侧的其他桶都已被处理完,例如当我们处理桶{20000}时,我们可通过第31至32行产生一条比较指令“if (a !=20000) goto BB19;”,而当我们面对{50}或{1,2}时,则需要通过第34至39行产生形如“if (a < 50)goto BB4; BB1: if (a > 50) gotoBB8;”这样的中间代码。

图5.3.6 TranslateSwitchBuckets()

图5.3.6第40至50行用于产生跳入相应case语句的代码,例如上述用于跳入case  50的指令“BB2:  goto BB17;”,当跳转表的大小为1时,我们只有一个跳转目标,通过第49行产生一条无条件跳转指令即可;否则在第43至46行,通过跳转表来进行跳转,例如我们在图5.3.4 中为桶{1,2}产生以下跳转代码:

BB6:

t0 : a - 1;

goto (BB11,BB13,)[t0];    //跳往case 1或case 2

图5.3.6第52至53递归地调用TranslateSwitchBuckets,对位于当前桶的左侧的各个桶进行处理,而第54至55行则递归地对右侧进行翻译。

Switch语句的翻译是所有控制流语句中最复杂的。为了讨论的完整性,我们再给出UCC编译器中for语句、while语句和do语句的翻译方案,如图5.3.7第2至30行所示,其他编译器的翻译方案可能与此有所不同,但都要实现相同的语义。图5.3.7第32至41行用于翻译break语句,break可跳出循环语句或switch语句,第36和38行通过产生无条件跳转指令跳出这些语句。第42至47行用于翻译continue语句,这可通过在第45行产生跳转语句,跳入循环语句的contBB基本块来实现,例如图5.3.7第6行、第15行和第23行所示的contBB。第48至56行用于处理return语句,如果有返回值,我们可在第52行调用TranslateExpression函数来翻译相应的表达式,并把结果存于RET指令中。在UCC编译器中,RET指令并不改变控制流,UCC编译器为简化处理,使每个待翻译的函数都只有唯一的入口基本块entryBB,及唯一的出口基本块exitBB,第54行会产生无条件跳转指令跳入exitBB基本块,即相当于要从函数返回。

图5.3.7   循环语句的翻译方案

至此,我们完成了对语句的翻译,为了得到运行时更高效的代码,我们还需要对已生成的中间代码进行优化,例如在图5.3.4第49至52行,我们可以看到以下代码:

b = 50;

goto BB19;

BB19:

b = 60;

经过优化,我们可删除其中无用的跳转指令“goto BB19;”,从而得到:

b = 50;

BB19:

b = 60;

优化是编译相关领域研发的热点,与优化相关的理论和技术不仅可用于编译器,还经常被用于信息安全等领域。UCC编译器只进行了一些简单的代码优化,我们会在下一节中进行讨论。

C编译器剖析_5.3.2 中间代码生成及优化_switch语句的翻译相关推荐

  1. 【编译原理笔记14】中间代码生成:布尔表达式的回填,控制流语句的回填,switch语句的翻译,过程调用语句的翻译

    本次笔记内容: 6-8 布尔表达式的回填 6-9 控制流语句的回填 6-10 SWITCH语句的翻译 6-11 过程调用语句的翻译 本节课幻灯片,见于我的 GitHub 仓库:第14讲 中间代码生成_ ...

  2. 【编译原理笔记11】中间代码生成:类型表达式,声明语句的翻译

    本次笔记内容: 6-1 类型表达式 6-2 声明语句的翻译 本节课幻灯片,见于我的 GitHub 仓库:第11讲 中间代码生成_1.pdf 文章目录 类型表达式 Type Expression 举例 ...

  3. 从零写一个编译器(十二):代码生成之生成逻辑

    项目的完整代码在 C2j-Compiler 前言 在上一篇解释完了一些基础的Java字节码指令后,就可以正式进入真正的代码生成部分了.但是这部分先说的是代码生成依靠的几个类,也就是用来生成指令的操作. ...

  4. 纯c语言编译器pelloc,大规模并行粒子模拟系统代码级优化研究和实现.pdf

    大规模并行粒子模拟系统代码级优化研究和实现.pdf 第25卷第9期 计算机与应用化学 V01.25.No.9 2008年9月28日 and ComputersAppIiedChemistry 大规模并 ...

  5. Simulink自动代码生成3——优化生成的代码(optimizing generated code)

    代码优化综述 使用simulink代码生成之后,如果需要进一步对执行效率或者内存优化,可以看下面提到的方法.具体可从以下几个方面考虑: remove initialization code remov ...

  6. oracle临时表经常被锁_5.性能测试 - Oracle体系结构和性能优化简介

    体系结构 Oracle体系结构示意图 Oracle服务器: Oracle服务器是一个数据库管理系统,它为信息管理提供了开放.综合和集成的方法,包括Oracle实例和 Oracle数据库. Oracle ...

  7. oracle awr报告生成_5.性能测试 - Oracle体系结构和性能优化简介

    体系结构 Oracle体系结构示意图 Oracle服务器: Oracle服务器是一个数据库管理系统,它为信息管理提供了开放.综合和集成的方法,包括Oracle实例和 Oracle数据库. Oracle ...

  8. 国外精选视频课:编译原理入门1

    编译原理的概述 编译指的是将程序员用某种高级语言的源代码转换成目标代码,即计算机能够人认识的可执行机器代码 编译是由一个叫编译器的程序完成的 因为程序需要被编译运行在特定类型的处理器上,所以,具体如何 ...

  9. 哈工大编译原理期末复习(完整版)

    文章目录 本文PDF下载 一.绪论 1.1 什么是编译 1.2 编译系统的结构 1.3 编译程序的生成 1.4 为什么要学习编译原理 1.5 编译技术的应用 二.语言及其文法 2.1 基本概念 2.2 ...

最新文章

  1. 学python买什么电脑-程序员,买了台破Apple电脑,用来学Python
  2. P3356 火星探险问题(网络流)
  3. java 方法注解_使用Java注解不正确的方法
  4. 《深入理解JVM.2nd》笔记(三):垃圾收集器与垃圾回收策略
  5. Java类型转换工具类(十六进制—bytes互转、十进制—十六进制互转,String—Double互转)
  6. CCF201909-2 小明种苹果(续)(100分)【序列处理】
  7. C++中的万能头文件
  8. c语言 大数开方,c语言求一个数的平方根
  9. web前端技术课程作业
  10. 微信公众号引流的十种方法
  11. Python 一维数据
  12. Abaqus应力结点数据导出与处理
  13. 词根词缀sinu/sist/soci/sol/somn等词根衍生单词
  14. 聊聊Dubbox(一):为何选择
  15. python自动发邮件富文本_Python自动化测试发送邮件太麻烦?!一起聊一聊 Python 发送邮件的3种方式...
  16. html后代选择器简单代码,css:not(),选择器和选择后代
  17. Linux--开发工具
  18. Mac电脑使用:查看本机已连接Wi-Fi密码的方法
  19. AEB三维数据分析图 TTC对AEB相对动能减少量的影响 xyz轴分别是TTC、自车速度、相对动能减少量
  20. apk文件在部分浏览器打不开

热门文章

  1. 自定义报表是这样实现的
  2. 不忍了!自己来搭建一个网盘
  3. 本关任务:设圆半径r,圆柱高h , 求圆周长C1,半径为r的圆球表面积Sb,圆半径r,圆柱高为h的圆柱体积Vb。 用scanf输入数据,输出计算结果,输出时取小数点后两位数字。请编程序。 P=3.14
  4. 常用封装电阻的常用电阻阻值
  5. 挺住! “6·18” | 万亿消费狂欢背后的IT构建
  6. 技术类电子书网站-影印文字版(https://itbook.download/)
  7. ofo 共享单车链接
  8. 【Bug修复】yuv生成mp4格式文件帧数(时间)与原视频不一致
  9. LaTeX打出字母上标、下标等
  10. 让你欲罢不能的十部电影推荐!