手把手带你写一门编程语言
宫文学
北京原点代码 CEO
7534 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
起步篇:让一门超简单的语言跑起来 (21讲)
结束语 (1讲)
手把手带你写一门编程语言
15
15
1.0x
00:00/00:00
登录|注册

17|生成本地代码第2关:变量存储、函数调用和栈帧维护

你好,我是宫文学。
在上一节课里,我们已经初步生成了汇编代码和可执行文件。不过,很多技术细节我还没有来得及给你介绍,而且我们支持的语言特性也比较简单。
那么,这一节课,我就来给你补上这些技术细节。比如,我们要如何把逻辑寄存器映射到物理寄存器或内存地址、如何管理栈桢,以及如何让程序符合调用约定等等。
好了,我们开始吧。先让我们解决逻辑寄存器的映射问题,这其中涉及一个简单的寄存器分配算法。

给变量分配物理寄存器或内存

在上一节课,我们在生成汇编代码的时候,给参数、本地变量和临时变量使用的都是逻辑寄存器,也就是只保存了变量的下标。那么我们要怎么把这些逻辑寄存器对应到物理的存储方式上来呢?
我们还是先来梳理一下实现思路吧。
其实,我们接下来要实现的寄存器分配算法,是一个比较初级的算法。你如果用 clang 或 gcc 把一个 C 语言的文件编译成汇编代码,并且不带 -O1、-O2 这样的优化选项,生成出来的汇编代码就是采用了类似的寄存器分配算法。现在我们就来看看这种汇编代码在实际存储变量上的特点。
首先,程序的参数都被保存到了内存里。具体是怎么来保存的呢?你可以先看看示例程序param.c
void println(int a);
int foo(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8){
int x1 = p1*p2;
int x2 = p3*p4;
return x1 + x2 + p5*p6 + p7*p8;
}
int main(){
int a = 10;
int b = 12;
int c = a*b + foo(a,b,1,2,3,4,5,6) + foo(b,a,7,8,9,10,11,12);
println(c);
return 0;
}
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了生成本地代码的技术细节,包括变量存储、函数调用和栈帧维护。文章详细解释了参数、本地变量和临时变量在内存中的存储方式,以及如何处理临时变量的物理寄存器分配。通过示例代码和汇编代码展示了参数和本地变量在栈桢中的存储位置,以及临时变量分配物理寄存器的过程。文章还分析了寄存器的冲突可能性,并介绍了如何保护寄存器的值。最后,梳理了栈桢中的内容,以及如何进行栈桢的维护。整篇文章深入浅出地介绍了本地代码生成的技术细节,对读者深入理解本地代码生成过程提供了有益的指引。 文章还介绍了栈桢的维护,包括栈桢的结构和内存布局设计,以及栈桢的大小计算和维护代码。此外,还总结了函数调用的约定和寄存器的使用规则。在最后的思考题中,提出了关于临时变量使用Caller保护的寄存器的问题,引发读者思考。 通过本文,读者可以深入了解本地代码生成的技术细节,包括栈桢的维护和内存布局设计,函数调用的约定以及寄存器的使用规则,为读者提供了全面的技术指导。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《手把手带你写一门编程语言》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(6)

  • 最新
  • 精选
  • ifelse
    学习打卡
    2022-09-17归属地:浙江
  • 有学识的兔子
    这些临时变量在调用callee之前需要caller进行spill从寄存器写入内存,在调用完成callee之后需要caller进行reload从内存加载到寄存器。这个过程对于callee来说是不可见的,寄存器的保护交给callee,它也没法完成。
    2021-09-20
  • chris
    使用callee保护的寄存器则方法在prolog之后需要对它做保留, epilog中做恢复; 使用caller-save寄存器, 则由当前函数自己决定是否需要在调用前后对其做保留恢复, 如果寄存器的值不跨调用活跃, 就不需要保留恢复了, 这样性能更好
    2021-09-15
  • qinsi
    个人理解:Caller保护的寄存器在函数返回之后会由Caller恢复,所以在函数执行时可以随便改;Callee保护的寄存器需要确保进入函数时和函数返回时寄存器中的值是不变的,所以要么函数执行时不要去动它们,要动的话Callee就要负责保存和恢复。相比之下用Caller保护的寄存器成本更低,优先使用,不够用的时候再用Callee保护的寄存器。
    2021-09-15
  • 奋斗的蜗牛
    优先使用caller保护的寄存器,可能可以在函数内联优化时,去掉callee的寄存器保护代码
    2021-09-15
  • 罗 乾 林
    使用Callee保护的寄存器作为临时变量必定涉及寄存器的保存和恢复,存在内存访问开销 优先使用Caller保护的寄存器,如果Caller中未使用该寄存器将不用保存和恢复
    2021-09-15
收起评论
显示
设置
留言
6
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部