本章导引
本书的这一部分介绍 Scala 框架各阶段实验的主要任务以及框架中一些重要的实现方法。
分阶段设计
作为系统软件的重要一员,编译器是连接用户与机器的桥梁。编译器的核心功能是: 将某种对人类(相对)友好的高级语言语义保持地(但很多编译器并不能完全做到)转换为机器可执行的低级语言。 随着计算机学科的飞速发展,编译器的功能已远不止于此,代码优化 (optimization)、静态分析 (static analysis/lint),乃至属性验证 (verification) 和程序综合 (synthesis),已逐渐成为现代工业级编译器的标配。
编译器是一个复杂的软件,复杂到即便用上很多现代软件工程的技术手段,也远不是任何普通团队和个人能轻易掌控的。 但是,编译器也不神秘,它不就是个把高级语言翻译成低级语言的程序吗?这里最大的挑战是:高级语言往往很高级,低级语言往往很低级。 如何打破这跨越时代的变迁?分层!
分层这个概念大家并不陌生:计算机的软硬件分层、计算机网络的 OSI 七层模型与 TCP/IP 五层模型、商业网站的开后端分离、甚至深度学习框架也开始搞开后端分离。 既然高级语言和低级语言差异很大,不如设计一系列中间语言,逐个击破,多次翻译,那么就在有限的步数内解决问题。 Decaf 的编译器也是如此,在 Decaf 源码和 Mips 汇编之间,我们设计了抽象语法树、带类型标注的抽象语法树、TAC 这三种中间语言。
在语法分析阶段,Decaf 源码被转换为抽象语法树(PA1)
在语义分析阶段,抽象语法树被转换为带类型标注的抽象语法树(若程序无类型错误)(PA2)
在中间代码生成阶段,带类型标注的抽象语法树被转换为 TAC(PA3)
在汇编码生成阶段,TAC 被最终转换为 Mips 汇编(PA5)
此外,在生成汇编码前,我们还可以对 TAC 进行一系列优化,以生成执行效率更高的 TAC(PA4)。
在 Scala 框架中,上述每个阶段都被定义为一个 Phase
:
每个阶段都是一个可能失败的函数 In => Option[Out]
,其中 In
为该阶段作为输入的某种语言表示,而 Out
为该阶段作为输出的某种语言表示。 例如,文法分析阶段就是从某个输入流读取 Decaf 源码,输出语法树:
而一次完整的编译过程,无非就是把这些阶段像流水线一样给拼接 (pipe) 起来。由于每个阶段都是一个有可能失败的函数,这种拼接就对应于函数的某种组合:
具体来说,f |> g
就是,先做 f
,如果成功,把输出的结果作为输入传给 g
,返回 g
的结果(成功或者失败)。 如果 f
已经失败了(执行 f
的过程中产生错误),那么直接终止整个流程,返回失败。 用 Haskell 的话讲,这 (|>
) 不就是个 Kleisli composition 吗?
借助这个 |>
组合子,我们能方便地定义出各阶段编译器要执行的任务:
代码结构
本框架的源码部分的包组织如下:
此外,src/main/antlr4
包含了 Antlr 需要使用的词法和文法描述文件。
项目构建
目前本框架基于 Scala 的官方工具 sbt
构建。虽然它有时会很慢,但对于这个项目来说还算可以接受。 使用 sbt
的一个主要优点在于,你可以直接 sbt console
打开 Scala REPL,方便地对项目中编写好的类和方法进行调试和测试。 工程引入了两个插件:
Antlr 插件能在编译时进行代码生成
assembly 插件便于创建独立的
.jar
包(命令sbt assembly
)
此外,你可以使用 sbt doc
生成 ScalaDoc 风格的 API 文档,以便查阅和搜索。
与 Java 混合编程
本框架会涉及到与 Java 混合编程的部分有:
文法分析器:调用 Antlr 生成的 Java 方法
TAC 生成:调用
decaf.lowlevel.tac
中提供的ProgramWriter
辅助完成 TAC 的生成和模拟执行,未来将支持生成 TAC 字节码JVM 字节码生成:调用
org.objectweb.asm
提供的方法辅助完成 JVM 字节码生成目标平台定义:
decaf.lowlevel
中提供了 Mips 等目标平台的基本定义,如(会用到的)指令集和寄存器等
由于 Scala 在设计时就考虑了与 JVM 平台其他语言(以 Java 为代表)的兼容性,使用 Scala 和 Java 混合编程并不困难。 在混合编程时,如何在 Java 与 Scala 的数据结构之间转换是一个高频需求。 为此,Scala 在 scala.jdk.CollectionConverters
包中提供了一组辅助函数,但是你仍需通过显式调用 .asJava
/.asScala
的方式完成转换。 得益于 Scala 的隐式转换特性,我们在 decaf.util.Conversions
中封装了一组隐式转换方法。 你只要在代码中导入它们,就能实现 java.util.List
与 scala.List
,以及 java.util.Optional
与 scala.Option
之间的无缝转换。
Last updated