优化器做了什么¶
先明确一点¶
这个项目有优化器,但它不是那种“什么都做”的大优化框架。
当前优化器的目标挺克制:
- 先做安全、容易解释的优化
- 不追求复杂到难维护的算法
- 让优化前后仍然容易人工检查
这跟第一代项目的气质很一致:先把容易看懂、容易验证的东西做好。
当前优化器主要做哪几件事¶
主要有三类:
- 常量传播
- 常量折叠
- 删除明显不可达代码
另外还有一件很实用的事:
- 压缩临时变量名字,减少无意义的一次性临时名扩散
什么是常量传播¶
如果某个临时变量已经确定是常量,那么后面引用它时,就可以直接利用这个事实。
例如:
优化后可以把 t1 也直接视作 3。
当前项目故意只在“临时变量这一层”做这件事,因为临时变量在 IR 里比较老实,通常是一次生成、一路往下用,不像普通变量那样容易反复改值。
什么是常量折叠¶
如果一个表达式在编译期就能算出来,那就没必要等到运行期再算。
例如:
就可以直接折成:
这种优化不一定让你热血沸腾,但它非常直观,也很适合当第一批优化手段。
什么是不可达代码删除¶
如果经过常量传播和折叠后,一个条件跳转已经能在编译期确定方向,那么另一个分支就可能永远走不到。
这时就可以把明显不可达的指令删掉。
例如:
- 条件恒真,只保留 true 分支
- 条件恒假,只保留 false 分支
这个项目就是顺着显式控制流往下看,把明显走不到的地方剪掉。
为什么控制流显式化让优化更容易¶
因为 IR 里已经有:
- 标签
- 跳转
- 条件跳转
这意味着优化器不用再从高层语法树里猜执行流,而是可以直接根据控制流图样式判断哪些指令可能被执行。
这也是 IR 比直接抱着 AST 干活省心的地方之一。
临时变量压缩是在干什么¶
这一步还谈不上正式寄存器分配,但脑回路已经有点像了。
优化器会观察:
- 某个临时变量最后一次使用在什么时候
- 之后是否可以把它的名字复用给新的临时变量
这样一来,大量 t0、t1、t2 这类一次性名字,会被压缩成少量更紧凑的虚拟寄存器名,比如 r0、r1。
这有两个直接好处:
- 生成结果更紧凑
- 给未来更低层的后端优化留出接口
为什么优化器没有做更多¶
因为更多不一定更好,尤其在第一代项目里。
如果一上来就引入很复杂的数据流分析、全局优化、别名分析,代价会很高:
- 更难验证
- 更难讲清楚
- 更难维护
当前项目现在走的是“小步快跑但别乱跑”的路线,不贪多。
当前优化器的边界¶
当前优化器只在 IR 路径上工作。
如果程序走的是 AST Backend 路径,那么这套 IR 优化不会介入。
所以别一看到“有优化器”就以为所有程序都统一走这套。现在还不是,走 AST Backend 的那部分不会经过这里。
如何理解它的工程价值¶
这个优化器最重要的地方,不是马上把性能榨干,而是说明:
- 编译链已经不仅仅是直通翻译
- 项目已经具备插入中间优化阶段的架构位置
- 后续可以逐步增强,而不用推翻整体结构
这说明项目已经不只是“从前端直通后端”的玩具了,中间层开始真的有自己的工作要做。