Skip to content

CLIR: 0x1 Design Goals, Compiler Overview and Dev Rhythm

Posted on:October 15, 2023 at 03:56 PM (9 min)

compiler map

Table of Contents

Open Table of Contents

Design Goals

Rua 是 Lua 的 Rust 实现, ruac 是其编译器部分, 负责从源代码生成 lua bytecode. ruac 的设计目标包含以下几点:

Compiler Overview

Official Implementation: Luac Overview

在我的理解中, luac 其实是 lua VM 的一部分, 因为使用前者需要先创建后者, 前者在工作时候使用的内存以及创建的对象 (Lua Obeject), 也是从 VM 的 GC 堆上分配出来的. luac 工作时单趟 (Single Pass) 编译, 只扫描一次源代码, 词法分析, 语法分析, 代码生成同时进行. 这样的好处是更快, 代码体积更小. 缺点就是糅合的代码无法复用 Compiler 的某一部分做额外工作, 也无法做多趟配合才能完成的优化功能.

在 lua 诞生的年代, 内存还是一种需要节约的资源, 做出如今看来美中不足(此处应当标记为存疑或作者主观意见)的取舍是合情合理的. 至于其他编译器应该关注的指标, 如代码生成质量, 编译时间等, 因为要么是与指令集强相关而我学识尚浅无法评价, 要么是我并非专业用户使用经验不足所以评价没有意义. 在我以初学者角度看来, 经过了三十年的迭代和现实世界的考验, 整体上 luac 的实现相当精巧, 没有明显的不足.

具体到各个部分, 按照编译流程来说 luac 中值得关注的点有如下部分:

以上这些概念都能在 ruac 中找到对应的概念, 不了解以上概念不影响后续文章阅读.

CLIR Implementation: Ruac Overview

而对于 ruac 来说, 其值得关注的点基本在 Design Goals 中描述过了, 将这些目标体现在设计上则可以描述为以下几点:

Dev Rhythm

根据以往观察到以及体验过的『立下 flag 但因为开发中缺少正反馈最终不得不鸽了』的实践经验, 在开一个新坑并尝试填上的过程中, 优先构建可以运行的原型并在此基础上逐步添加功能是较好的开发节奏.

ruac 整体上不会违反这条原则, 但考虑到对 ruac 的前端和中端部分有十足的信心, 所以在 Tokenizer, Parser 这一局部, 我会以 F2+A 的方式构建出完整的功能, 不必在将来回头检查对语法的支持或者改某个 Corner Case 的错误. 后端代码生成部分则是按照敏捷模式, 优先支持少数指令并且不考虑优化. 随着 VM 对指令集的支持而逐步完善并添加优化. 在这个过程中一旦前, 中, 后端的某一部分设计稳定, 我会暂停新功能的开发转而添加与该部分有关的基础设施, 以像 Parser 一样直接做出成品的方式进行, 例如中端的常量折叠, AST Dumper, 后端的 Binary Dumper. 在某一部分的设计稳定以及配套设施均完成后, 我会将其记录在本系列内, 同时进行局部的 Code Review, 增加单元测试以及修补文档等工作.

对于开发工作本身来说, 这样的安排可以将开发工作中的关注点分离到较小的局部, 减小开发者记忆和认知的负担. 对于《CLIR》则是内容更加紧凑, 不会让相关的主题均匀分散到系列各处. 对于我自己来说还有额外的机会可以立刻进行 Design Review 以免思路和实现细节在后续开发中被忘记. 这样安排的缺点也很明显, 如果给出了一个糟糕的 AST 定义并且无法在 Parser 开工时就意识到其问题所在, 那么在发现错误之后, 完成度拉满的 Parser 反而成了负担. 我只能在希望自己运气好一点不要撞见这种情况的的同时保证: 如果真的撞见这种情况, 我会回来改掉上一段的第一句话的.