理论教育 编译器设计之路:存储分配实现与优化

编译器设计之路:存储分配实现与优化

时间:2023-11-04 理论教育 版权反馈
【摘要】:运行时的存储分配是独立存在于IR优化与目标代码生成之间的。否则,存储分配将不得不面临多次迭代的命运。在存储分配之前,这项工作是非常有意义的。通常,存储寻址都是向上扩展的,而系统栈的扩展却恰好相反,为了便于处理,Neo Pascal以局部变量存储区的最低地址为基准逐一向上分配。关于存储分配的实现,就暂且讨论至此。笔者还要说明一点,在Neo Pascal中,存储分配算法与存储优化是密不可分的,严格地说,存储优化是必不可少的。

编译器设计之路:存储分配实现与优化

运行时的存储分配是独立存在于IR优化与目标代码生成之间的。通常,关于存储空间方面的IR优化应该是在存储分配之前基本完成的。换句话说,本阶段涉及的有效变量、常量的集合是相对稳定的,至少在IR层次上是不再进行任何相关优化的。否则,存储分配将不得不面临多次迭代的命运。事实上,这是完全没有必要的。

通过前面的学习,读者应该已经理解栈式分配的基本思想了。编译器可以通过计算符号相对过程局部数据块首的偏移,最终实现寻址。

程序8-2 CodeGen.cpp

第5、6行:调用CalcTypeSize函数,重新计算各种类型所需的存储空间大小。在存储分配之前,这项工作是非常有意义的。在处理记录类型时,必须考虑字段布局的问题。因为不同的字段布局对最终的空间大小是有一定影响的。

第7行:生成数据段声明的汇编代码。

第9~49行:遍历常量信息表,生成常量声明的汇编代码。

第11~12行:略过未引用的常量符号。

第13~48行:根据常量的类型,生成常量声明代码。

第15~17行:生成集合类型常量的声明代码。应该注意两点:

(1)常量对应的汇编符号是以“ConstXXX”形式命名的,其中,XXX即为常量符号的位序号

(2)在标准Pascal中,集合常量可以用连续32个字节的值表示。这里,SetValue函数就是将集合常量的字面值转换为由32个独立字节形式组成的字符串。

第18~23行:生成布尔类型常量的声明代码。在Neo Pasca1中,布尔类型常量只有两种取值,即TRUE和FALSE。根据Pasca1的标准,TRUE用数值“1”表示,而FALSE则用“0”表示。

第24~26行:生成指针类型常量的声明代码。在Neo Pascal中,指针类型常量只有NIL,其取值为“0”。

第27~37行:生成字符串类型常量的声明代码。应该注意以下三点:

(1)字符串结束符即数值“0”。

(2)为了统一字符串的寻址处理,编译器为字符串常量分配了一个指针存储区。该指针指向的存储区才是实际的字符串值。这样,无论是字符串常量还是变量寻址,都必须进行一次间接寻址。虽然这种处理不得不以牺牲一些存储空间为代价,但显著减轻了程序设计的负担。

(3)处理字符串中的换行符。在MASM宏汇编声明中,不含换行符的字符串是允许出现在数据段声明中的,当然,并不支持C语言的转义字符。因此,有必要对符号表中的字符串常量中的换行符作特殊处理。

以字符串“aa\nbb”为例,其对应的汇编声明如下:

在Neo Pascal中, Constl单元存储的值是由初始化程序统一设置的。(www.daowen.com)

第38~41行:枚举类型、整型常量只需直接获得其实际取值即可。

第42~45行:实型常量只需要直接获得其实际取值即可。不过,值得注意的是,在实践中,很多汇编器是不支持实型的,经常需要编译器完成实型值的内码计算。其中IEEE 754是最常见的实数内码标准。

第52~78行:遍历过程信息表,生成形参的声明代码。分配算法关键就是计算变量相对于AR的偏移,AR是以过程为单位讨论的。

第54、55行:未被调用的过程不参与存储分配。

第56行:初始化偏移基值。根据系统栈的布局,形参的初始偏移基值就是8。

第57、58行:匿名过程信息不参与存储分配。

第59~78行:遍历变量信息表,生成当前过程所属形参的声明。

第61、62行:判断当前变量是否为本过程所属的形参。

第64行:为变量符号分配存储区。这里,只需在符号信息中记录偏移值即可。

第65行:获得当前变量符号所需存储空间的大小。以计算下一个符号的偏移。

第67、68行:处理整字对齐。这里,只考虑4字节的整字对齐情况。

第69~74行:生成符号的汇编声明。当i等于0时,即表示该符号为主程序的形参,应该作为全局变量进行静态分配。当然,目前笔者并没有为主程序提供形参机制。

第75行:更新偏移计数变量iTmp。

第79~91行:遍历变量信息表,生成不可优化的变量符号的声明。这里,值得注意的是,并非所有的有效符号都需要分配存储空间。实际上,在分配存储空间之前,编译器还会进行一次存储空间方面的优化,以实现空间的共享。如果变量符号的m_iMemoryAlloc的值为一1,则表示该符号不需要分配空间。

第87--89行:计算各过程的局部变量符号的偏移值,并生成汇编声明。通常,存储寻址都是向上扩展的,而系统栈的扩展却恰好相反,为了便于处理,Neo Pascal以局部变量存储区的最低地址为基准逐一向上分配。这里,m_iMemoryAlloc的值是变量相对于0的偏移,而m_ValSize是整个过程的局部变量存储区的大小。这两个值都是由存储优化算法计算得到的。

第92~108行:生成全局变量符号的静态声明。前面已经讲述了全局变量是静态分配存储空间的。因此,这里只需根据变量所需空间大小分配即可。

关于存储分配的实现,就暂且讨论至此。笔者还要说明一点,在Neo Pascal中,存储分配算法与存储优化是密不可分的,严格地说,存储优化是必不可少的。然而,这个观点并不适用于一般化的情形,在很多编译器中,存储优化完全是可选的。不过,存储优化本身并不影响调试,因此,可选或必选并不太重要。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈