编译原理实战课
宫文学
北京原点代码 CEO
26065 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 55 讲
真实编译器解析篇 (19讲)
编译原理实战课
15
15
1.0x
00:00/00:00
登录|注册

08 | 代码生成:如何实现机器相关的优化?

参考链接
一课一思
后端处理的整体过程
调用约定的影响
生成目标代码时的优化工作
生成针对不同CPU的目标代码
生成目标代码
编译器后端的工作

该思维导图由 AI 生成,仅供参考

你好,我是宫文学。我们继续来学习编译器后端的技术。
在编译过程的前几个阶段之后,编译器生成了 AST,完成了语义检查,并基于 IR 运行了各种优化算法。这些工作,基本上都是机器无关的。但编译的最后一步,也就是生成目标代码,则必须是跟特定 CPU 架构相关的。
这就是编译器的后端。不过,后端不只是简单地生成目标代码,它还要完成与机器相关的一些优化工作,确保生成的目标代码的性能最高。
这一讲,我就从机器相关的优化入手,带你看看编译器是如何通过指令选择、寄存器分配、指令排序和基于机器代码的优化等步骤,完成整个代码生成的任务的。
首先,我们来看看编译器后端的任务:生成针对不同架构的目标代码。

生成针对不同 CPU 的目标代码

我们已经知道,编译器的后端要把 IR 翻译成目标代码,那么要生成的目标代码是什么样子的呢?
我以 foo.c 函数为例:
int foo(int a, int b){
return a + b + 10;
}
执行“clang -S foo.c -o foo.x86.s”命令,你可以得到对应的 x86 架构下的汇编代码(为了便于你理解,我进行了简化):
#序曲
pushq %rbp
movq %rsp, %rbp #%rbp是栈底指针
#函数体
movl %edi, -4(%rbp) #把第1个参数写到栈里第一个位置(偏移量为4)
movl %esi, -8(%rbp) #把第2个参数写到栈里第二个位置(偏移量为8)
movl -4(%rbp), %eax #把第1个参数写到%eax寄存器
addl -8(%rbp), %eax #把第2个参数加到%eax
addl $10, %eax #把立即数10加到%eax,%eax同时是放返回值的地方
#尾声
popq %rbp
retq
小提示:上述汇编代码采用的是 GNU 汇编器的代码格式,源操作数在前面,目的操作数在后面。
我在第 1 讲中说过,要翻译成目标代码,编译器必须要先懂得目标代码,就像做汉译英一样,我们必须要懂得英语。可是,通常情况下,我们会对汇编代码比较畏惧,觉得汇编语言似乎很难学。其实不然。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

编译器后端的关键任务是生成针对不同CPU架构的目标代码。在这一过程中,编译器通过指令选择、寄存器分配、指令排序和基于机器代码的优化等步骤,完成代码生成的任务。指令选择的作用是选择代价更低的指令组合来完成相同功能,例如使用lea指令代替多条mov和add指令。寄存器分配则通过算法将寄存器分配给使用最频繁的变量,以提高性能。指令排序利用流水线技术实现指令级并行,减少总的执行时间。这些优化措施都旨在充分发挥硬件性能,确保生成的目标代码性能最高。文章还提到了不同的算法和技术,如树覆盖算法、寄存器染色算法和基于数据依赖图的List Scheduling算法,用于实现指令选择、寄存器分配和指令排序。这些技术和算法对编译器后端的性能提升起着重要作用。文章还介绍了窥孔优化的思路,调用约定对目标代码生成的影响,以及后端处理的整体过程。总的来说,本文深入介绍了编译器后端的工作原理和优化技术,对于想深入了解编译器技术的读者具有重要参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《编译原理实战课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(7)

  • 最新
  • 精选
  • 蹦哒
    老师请问: 1.iOS中OC语言的内存管理采用引用计数管理,编译器会给对象调用retain或release方法来增加或者减少引用计数,这个过程是在编译器哪个阶段进行的呢?是在代码优化或者代码生成阶段,由LLVM完成的吗 2.JavaScript的垃圾回收,应该就不属于编译器的职责范围了吧?应该是运行之后单独的垃圾回收程序来负责垃圾回收了吧

    作者回复: 我在32讲,重点讲了编译器和垃圾回收的关系。 总的来说,垃圾回收属于运行时机制。但垃圾回收机制的运行,需要编译器的支持。比如,在合适的时候(安全点)停下程序的运行,去垃圾回收机制去运行,这就需要编译器生成这样的代码,去和gc做协调。 另外,通常,编译器还要告诉gc,gc root(包括全局变量、本地变量、存放在寄存器中的变量等)都是哪些,便于垃圾收集机制的运行。 为了配合一些特定的垃圾收集算法,编译器还要插入一些其他的代码,比如读写屏障。 对于采用引用计数方法的gc,就更需要编译器的配合。编译器要插入相应的代码(机器码),增减引用计数。在引用计数为零的时候,还要调用回收内存的代码。

    2020-07-22
    3
    2
  • 阿木
    老师,我用clang -target armv7a-none-eabi 想交叉编译生成一个打印hello world的程序,老是报错,找不到头文件,请问在mac上还需要配置什么吗

    作者回复: 看看这篇文档对你有没有用:https://clang.llvm.org/docs/CrossCompilation.html 我的习惯,是在本机自行编译一下LLVM,里面也带了clang等工程。这有几个好处: 首先,可以拥有一个debug版本的LLVM,方便用一些针对开发者的工具和参数。 第二,可以选择合适的LLVM/Clang版本。 第三,可以不依赖本机原来带的clang,所有的配置,包括头文件什么的,都用自己的这一套。

    2020-06-27
    2
  • 请教一下,为什么特别地以-O2为例介绍优化后的代码,是有什么典型性吗

    作者回复: -O1、-O2什么的,代表不同的优化深度。优化越多,编译时间越长。 你可以对比一下-O1和-O2生成的汇编代码的不同,可以加深理解。

    2020-12-16
  • A君
    循环展开可以减少count++和判断指令,另外还可以增加计算指令数,增加通过指令排序优化的机会。
    2022-04-23
  • A君
    指令选择,一个IR可以生成多条指令,我们应该选择代价最小的那条指令。
    2022-04-23
  • Self-devourer
    老师,关于流水线,指令并行那里有个问题想请教下。 我理解的是,每条指令虽然被拆分多个阶段,但一条指令的所有阶段还是由一个功能单元去执行。 假设指令间没有依赖性,那为什么会出现错位的情况,不能让所有功能单元都从指令的第一个阶段开始执行呢?
    2022-01-13
  • Laputa
    “因为每个时钟周期都可以开始执行一条新指令,所以虽然一条指令需要 5 个时钟周期才能执行完,但在同一个时刻,却可以有 5 条指令并行执行。” 老师,一条指令需要5时钟周期,这块的依据是什么呢,任何指令都需要5个时钟周期吗,还是说不通指令需要的时钟周期不一样呢?
    2021-11-30
收起评论
显示
设置
留言
7
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部