Skip to content

优化器做了什么

先明确一点

这个项目有优化器,但它不是那种“什么都做”的大优化框架。

当前优化器的目标挺克制:

  • 先做安全、容易解释的优化
  • 不追求复杂到难维护的算法
  • 让优化前后仍然容易人工检查

这跟第一代项目的气质很一致:先把容易看懂、容易验证的东西做好。

当前优化器主要做哪几件事

主要有三类:

  • 常量传播
  • 常量折叠
  • 删除明显不可达代码

另外还有一件很实用的事:

  • 压缩临时变量名字,减少无意义的一次性临时名扩散

什么是常量传播

如果某个临时变量已经确定是常量,那么后面引用它时,就可以直接利用这个事实。

例如:

t0 = 3
t1 = t0

优化后可以把 t1 也直接视作 3

当前项目故意只在“临时变量这一层”做这件事,因为临时变量在 IR 里比较老实,通常是一次生成、一路往下用,不像普通变量那样容易反复改值。

什么是常量折叠

如果一个表达式在编译期就能算出来,那就没必要等到运行期再算。

例如:

t0 = 3
t1 = 4
t2 = t0 + t1

就可以直接折成:

t2 = 7

这种优化不一定让你热血沸腾,但它非常直观,也很适合当第一批优化手段。

什么是不可达代码删除

如果经过常量传播和折叠后,一个条件跳转已经能在编译期确定方向,那么另一个分支就可能永远走不到。

这时就可以把明显不可达的指令删掉。

例如:

  • 条件恒真,只保留 true 分支
  • 条件恒假,只保留 false 分支

这个项目就是顺着显式控制流往下看,把明显走不到的地方剪掉。

为什么控制流显式化让优化更容易

因为 IR 里已经有:

  • 标签
  • 跳转
  • 条件跳转

这意味着优化器不用再从高层语法树里猜执行流,而是可以直接根据控制流图样式判断哪些指令可能被执行。

这也是 IR 比直接抱着 AST 干活省心的地方之一。

临时变量压缩是在干什么

这一步还谈不上正式寄存器分配,但脑回路已经有点像了。

优化器会观察:

  • 某个临时变量最后一次使用在什么时候
  • 之后是否可以把它的名字复用给新的临时变量

这样一来,大量 t0t1t2 这类一次性名字,会被压缩成少量更紧凑的虚拟寄存器名,比如 r0r1

这有两个直接好处:

  • 生成结果更紧凑
  • 给未来更低层的后端优化留出接口

为什么优化器没有做更多

因为更多不一定更好,尤其在第一代项目里。

如果一上来就引入很复杂的数据流分析、全局优化、别名分析,代价会很高:

  • 更难验证
  • 更难讲清楚
  • 更难维护

当前项目现在走的是“小步快跑但别乱跑”的路线,不贪多。

当前优化器的边界

当前优化器只在 IR 路径上工作。

如果程序走的是 AST Backend 路径,那么这套 IR 优化不会介入。

所以别一看到“有优化器”就以为所有程序都统一走这套。现在还不是,走 AST Backend 的那部分不会经过这里。

如何理解它的工程价值

这个优化器最重要的地方,不是马上把性能榨干,而是说明:

  • 编译链已经不仅仅是直通翻译
  • 项目已经具备插入中间优化阶段的架构位置
  • 后续可以逐步增强,而不用推翻整体结构

这说明项目已经不只是“从前端直通后端”的玩具了,中间层开始真的有自己的工作要做。