# 第7章 字节码生成:让 AST 变成 JVM 指令
# 一、前言
本章把 AST 翻译为 JVM 字节码(.class),并通过反汇编对照验证结果。我们用“栈机器”的直觉理解指令序列:压栈、运算、跳转、打印。
# 二、目标
- 理解 JVM 栈模型与常见指令映射(IADD/IFEQ/INVOKEVIRTUAL 等)
- 能通过
--emit-class + javap -v
验证生成结果 - 掌握 println 重载选择、比较归一化为 0/1 的实现套路
# 三、设计
术语说明:
- 描述符(Descriptor):如
(I)V
、([Ljava/lang/String;)V
- Label:跳转锚点,用于 if/else、循环
核心流程图:
架构交互图:
# 四、实现
目录树(关注项):
src/main/java/com/lxg/codegen/CodeEmitter.java
src/main/java/com/lxg/codegen/ClassGenerator.java
命令:
java -jar target/my-language-0.1.0-SNAPSHOT.jar examples/arithmetic.lxg --emit-class=out/Program.class
javap -v out/Program.class | sed -n '1,200p'
代码对照:println 重载选择
private void emitPrint(PrintStmt ps) {
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
ValueType type = emitExpression(ps.expression);
if (type == ValueType.INT) {
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
} else if (type == ValueType.STRING) {
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
} else if (type == ValueType.BOOLEAN) {
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false);
} else {
throw new IllegalStateException("Unsupported type in print: " + type);
}
}
代码对照:一元逻辑非 !
的生成
private ValueType emitUnary(UnaryExpr ue) { ... }
代码对照:相等/比较生成 0/1 的布尔值
case EQ: ... case GE: { ... }
代码对照:常量入栈的“最短指令”选择
private void pushInt(int v) { ... }
代码对照:生成 main 与默认构造
public byte[] generate(CompilationUnit unit) { ... }
代码对照:分支模板(完整 emitIf)
private void emitIf(IfStmt ifs) {
ValueType condType = emitExpression(ifs.condition);
if (condType != ValueType.BOOLEAN) {
throw new IllegalStateException("if condition must be boolean");
}
Label elseLabel = new Label();
Label endLabel = new Label();
mv.visitJumpInsn(IFEQ, elseLabel);
for (Statement s : ifs.thenBlock.statements) emitStatement(s);
mv.visitJumpInsn(GOTO, endLabel);
mv.visitLabel(elseLabel);
if (ifs.elseBlock != null) {
for (Statement s : ifs.elseBlock.statements) emitStatement(s);
}
mv.visitLabel(endLabel);
}
# 五、测试
- 手动:对
examples/arithmetic.lxg
反汇编,核对println
、算术与比较序列 - 建议新增:带
true/false
、字符串与整型混合打印的 E2E 用例
# 六、总结
- 生成套路:压栈→运算/跳转→归一化→打印;配合
javap -v
精准定位生成问题 COMPUTE_FRAMES|COMPUTE_MAXS
简化了栈深与帧的管理