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
  • ASM 库
  • 类的翻译
  • 方法的翻译
  • 语句与表达式的翻译

Was this helpful?

  1. Scala 框架分阶段指导
  2. PA3-JVM:JVM 字节码生成

翻译过程

把带类型标注的 AST 翻译成 JVM 字节码的过程是:

  • 对于每个类,分别为其生成同名的 Class 文件,其中记录了该类的元信息和所有成员;

  • 对于类中每个变量,需要在 Class 文件中记录其类型,设置访问权限为 ACC_PROTECTED;

  • 对于类中每个方法,需要在 Class 文件中记录其类型,设置访问权限为 ACC_PUBLIC,静态方法还要设置 ACC_STATIC,然后把函数体翻译为 JVM 指令序列。

ASM 库

org.objectweb.asm 库提供了一些对于 JVM 字节码的包装。 使用该库,你可以在无需知道每条 JVM 指令字节码编码等细节的情况下,完成对 JVM 字节码的生成并写入到文件中。 该库提供了 ClassWriter 类用于管理一个 Class 文件,以及 MethodVisitor 类完成对于一个函数体的生成。 我们可以使用 MethodVisitor 类提供的一组 visit* 方法完成 JVM 指令的生成。 在 PA3 阶段使用的 decaf.lowlevel.tac.ProgramWriter 是仿照 asm 库实现的,因此你在使用时一定会有似曾相识的感觉。

类的翻译

多亏了 JVM 原生对于面向对象机制的支持,一个 Decaf 类能非常直白地对应到一个 Class 文件。这个功能实现在 JVMGen 类中的 emitClass 方法:

def emitClass(clazz: ClassDef): JVMClass = {
  implicit val cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS)

  // set up meta data
  cw.visit(...)

  // First add the default constructor:
  val mv = cw.visitMethod(Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null)
  mv.visitCode()
  mv.visitVarInsn(Opcodes.ALOAD, 0)
  mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClass, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, false) // call super
  mv.visitInsn(Opcodes.RETURN)
  mv.visitMaxs(-1, -1) // pass in random numbers, as COMPUTE_MAXS flag enables the computation
  mv.visitEnd()

  // Then generate every user-defined member:
  clazz.fields.foreach {
    case field: VarDef => cw.visitField(...)
    case method: MethodDef => emitMethod(method)
  }
  cw.visitEnd()

  JVMClass(clazz.name, cw.toByteArray)
}

它会最终返回一个 JVMClass,调用其 writeFile 方法能生成最终所需要的 .class 文件。

方法的翻译

针对于 Decaf 中的成员方法和静态方法,我们需要为其生成相应的 JVM 函数。这个功能由 JVMGen 类中的 emitMethod 实现:

def emitMethod(method: MethodDef)(implicit cw: ClassWriter): Unit = {
  implicit val mv = cw.visitMethod(...)

  // Allocate indexes (in JVM local variable array) for every argument.
  // For member methods, index 0 is reserved for `this`.
  implicit val ctx = new Context(method.isStatic)
  method.params.foreach { p => ctx.declare(p.symbol) }

  // Visit method body and emit bytecode.
  mv.visitCode()
  implicit val loopExits: List[Label] = Nil
  emitStmt(method.body)
  appendReturnIfNecessary(method)
  mv.visitMaxs(-1, -1)
  mv.visitEnd()
}

注意我们需要调用 cw 的成员方法 visitMethod,传入方法的元数据,才能得到一个真正的 MethodVisitor mv。 接下来,先保留临时变量数组的前面几个元素作为函数参数。注意对于成员方法,0 对应于 this。 然后,依次遍历所有方法体的语句,生成 JVM 指令。 若该方法没有返回值,且最后一条指令又不是返回指令的话,需要插入一条返回指令作为结束,否则 JVM 在运行时会报错(通过 appendReturnIfNecessary 辅助函数实现)。 最后,调用 visitMaxs 自动计算栈帧相关信息,包括临时数组的大小。 调用 visitEnd 退出。

语句与表达式的翻译

语句的翻译实现在

def emitStmt(stmt: Stmt)(implicit mv: MethodVisitor, loopExits: List[Label], ctx: Context): Unit

表达式的翻译实现在

def emitExpr(expr: Expr)(implicit mv: MethodVisitor, ctx: Context): Unit

实现方法与 PA3 类似,这里不再赘述。 需要留意的是,在 JVM 这种栈式虚拟机下,指令生成与 TAC 相比其实更加简单。 但是,这里的一个难点在于,你的脑海中始终要有一个操作数栈的概念,因为这些指令的输入输出都跟栈紧密相关。 这有助于理清楚每条指令的具体含义。

PreviousJVM 字节码简介NextPA4:中间代码优化

Last updated 5 years ago

Was this helpful?