The Decaf Book
  • Introduction
  • 语言规范
  • Java 框架分阶段指导
    • 本章导引
    • PA1-A:语法分析器的自动构造
      • 阶段任务
      • 词法分析
      • 抽象语法树
      • 文法分析
    • PA1-B:基于 LL(1) 的语法分析器半自动构造
      • 阶段任务
      • LL(1) 文法分析
      • 错误恢复
    • PA2:语义分析
      • 阶段任务
      • 类型、符号和作用域
      • 访问者模式
      • 符号表构建
      • 类型检查
    • PA3:中间代码生成
      • 阶段任务
      • TAC 程序
      • 面向对象机制
      • 控制流的翻译
    • PA4:中间代码优化
      • 实验内容
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
  • Scala 框架分阶段指导
    • 本章导引
    • PA1:语法分析器的自动构造
      • 阶段任务
      • 词法分析
      • 抽象语法树
      • 文法分析
      • 访问者模式
    • PA2:语义分析
      • 阶段任务
      • 类型、符号和作用域
      • 符号表构建
      • 类型检查
    • PA3:中间代码生成
      • 阶段任务
      • TAC 程序
      • 面向对象机制
      • 控制流的翻译
    • PA3-JVM:JVM 字节码生成
      • 阶段任务
      • JVM 字节码简介
      • 翻译过程
    • PA4:中间代码优化
      • 实验内容
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
  • Rust 框架分阶段指导
    • 本章导引
    • PA1-A:语法分析器的自动构造
      • 实验内容
      • lalr1使用指导
        • 编写lexer
        • impl块的可选属性
        • 产生式和语法动作
        • 解决冲突
        • 一个完整的例子
      • 抽象语法树
      • 框架中部分实现的解释
      • 文件结构
    • PA1-B:基于 LL(1) 的语法分析器半自动构造
      • 实验内容
      • lalr1使用指导
      • 错误恢复
      • 文件结构
    • PA2:语义分析
      • 实验内容
      • 语义分析
      • 符号表
      • visitor模式
    • PA3:中间代码生成
      • 实验内容
      • 中间代码
      • 中间代码中的类型信息
      • 运行时存储布局
      • 面向对象机制
      • tacvm简述
    • PA4:中间代码优化
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
    • PA5:寄存器分配
      • 实验内容
      • 图着色基本原理
      • 着色算法
      • 预着色节点
      • 干涉图节点合并
      • 调用约定
Powered by GitBook
On this page
  • 分阶段设计
  • 代码结构
  • 项目构建

Was this helpful?

  1. Java 框架分阶段指导

本章导引

在这一章中,我们将介绍 Java 框架与各阶段实验相关的内容。包括各阶段实验的任务、实验框架中已有代码的设计以及实验中需要注意的事项。

分阶段设计

作为系统软件的重要一员,编译器是连接用户与机器的桥梁。编译器的核心功能是: 将某种对人类(相对)友好的高级语言语义保持地(但很多编译器并不能完全做到)转换为机器可执行的低级语言。 随着计算机学科的飞速发展,编译器的功能已远不止于此,代码优化 (optimization)、静态分析 (static analysis/lint),乃至属性验证 (verification) 和程序综合 (synthesis),已逐渐成为现代工业级编译器的标配。

编译器是一个复杂的软件,复杂到即便用上很多现代软件工程的技术手段,也远不是任何普通团队和个人能轻易掌控的。 但是,编译器也不神秘,它不就是个把高级语言翻译成低级语言的程序吗?这里最大的挑战是:高级语言往往很高级,低级语言往往很低级。 如何打破这跨越时代的变迁?分层!

分层这个概念大家并不陌生:计算机的软硬件分层、计算机网络的 OSI 七层模型与 TCP/IP 五层模型、商业网站的开后端分离、甚至深度学习框架也开始搞开后端分离。 既然高级语言和低级语言差异很大,不如设计一系列中间语言,逐个击破,多次翻译,那么就在有限的步数内解决问题。 Decaf 的编译器也是如此,在 Decaf 源码和 Mips 汇编之间,我们设计了抽象语法树、带类型标注的抽象语法树、TAC 这三种中间语言。

  • 在语法分析阶段,Decaf 源码被转换为抽象语法树(PA1)

  • 在语义分析阶段,抽象语法树被转换为带类型标注的抽象语法树(若程序无类型错误)(PA2)

  • 在中间代码生成阶段,带类型标注的抽象语法树被转换为 TAC(PA3)

  • 在汇编码生成阶段,TAC 被最终转换为 Mips 汇编(PA5)

此外,在生成汇编码前,我们还可以对 TAC 进行一系列优化,以生成执行效率更高的 TAC(PA4)。

在 Java 框架中,上述每个阶段都被定义为一个 Phase:

public abstract class Phase<In, Out> implements Task<In, Out>, ErrorIssuer

每个阶段都是一个可能失败的函数 Function<In, Optional<Out>>,其中 In 为该阶段作为输入的某种语言表示,而 Out 为该阶段作为输出的某种语言表示。 例如,文法分析阶段就是从某个输入流读取 Decaf 源码,输出语法树:

public Task<InputStream, Tree.TopLevel> parse() {
  return new JaccParser(config);
}

而一次完整的编译过程,无非就是把这些阶段像流水线一样给拼接 (pipe) 起来。由于每个阶段都是一个有可能失败的函数,这种拼接就对应于函数的某种组合:

public interface Task<T, R> extends Function<T, Optional<R>> {
    default <V> Task<T, V> then(Task<R, V> next) {
        return t -> this.apply(t).flatMap(next);
    }
}

具体来说,f.then(g) 就是,先做 f,如果成功,把输出的结果作为输入传给 g,返回 g 的结果(成功或者失败)。 如果 f 已经失败了(执行 f 的过程中产生错误),那么直接终止整个流程,返回失败。 用 Haskell 的话讲,这 (then) 不就是个 Kleisli composition 吗?

借助这个 then 函数,我们能方便地定义出各阶段编译器要执行的任务:

public class TaskFactory {

    public Task<InputStream, Tree.TopLevel> parse() { // PA1-A
        return new JaccParser(...);
    }

    public Task<InputStream, Tree.TopLevel> parseLL() { // PA1-B
        return new LLParser(...);
    }

    public Task<InputStream, Tree.TopLevel> typeCheck() { // PA2
        return parse().then(new Namer(...)).then(new Typer(...));
    }

    public Task<InputStream, TacProg> tacGen() { // PA3
        return typeCheck().then(new TacGen(...));
    }

    public Task<InputStream, TacProg> optimize() { // PA4
        return tacGen().then(new Optimizer(...));
    }

    public Task<InputStream, String> mips() { // PA5
        var emitter = new MipsAsmEmitter();
        return tacGen().then(new Asm(...));
    }
}

代码结构

本框架源码部分的包组织如下:

decaf
    .driver         主控(命令行选项解析、任务调度)
        .error      错误定义及处理
    .frontend       前端
        .tree       语法树定义
        .parsing    语法分析
        .type       类型定义
        .symbol     符号定义
        .scope      作用域定义
        .typecheck  语义分析(符号表构建、类型检查)
        .tacgen     中间代码 TAC 生成
    .backend        后端
        .dataflow   数据流分析
        .opt        TAC 优化
        .reg        寄存器分配
        .asm        汇编码生成
            .mips   Mips 平台相关
    .printing       格式化打印
    .lowlevel       底层工具链(未来会分离出来)
        .instr      (伪)指令定义
        .label      标签定义
        .tac        TAC 工具链
        .log        简单 Logger(供调试使用)

此外:

  • src/main/jflex 包含了 JFlex 需要使用的词法描述文件(PA1 共用)

  • src/main/jacc 包含了 Jacc 需要使用的文法描述文件(PA1-A)

  • src/main/ll1pg 包含了 ll1pg 需要使用的文法描述文件(PA1-B)

项目构建

工程引入了两个插件:

  • JFlex 插件能在编译时进行词法分析器代码生成

  • Jacc 插件能在编译时进行文法分析器代码生成

请使用 gradle jar 生成独立的 .jar 包。运行时请加上 --enable-preview 选项以开启 JDK 12 的新特性。

此外,你还可以使用 gradle javadoc 生成 JavaDoc 风格的 API 文档,以便查阅和搜索。

PreviousJava 框架分阶段指导NextPA1-A:语法分析器的自动构造

Last updated 5 years ago

Was this helpful?

目前本框架基于 构建。该工具以 Groovy(默认)或 Kotlin(可配置)作为脚本语言。 由于 Groovy/Kotlin 已经是通用编程语言,因此你可以在脚本中编写任意复杂的业务逻辑。 项目中提供的 gradle.build 已经完成了基本配置。你可以自行添加新的库依赖 (如果不是 maven central 仓库请设置 repositories 并保证该库可公开下载),或者按需求增加自定义的 task。

gradle