项目的完整代码在 C2j-Compiler

前言

在上一篇解释完了一些基础的Java字节码指令后,就可以正式进入真正的代码生成部分了。但是这部分先说的是代码生成依靠的几个类,也就是用来生成指令的操作。

这一篇用到的文件都在codegen下:

  • Directive.java
  • Instruction.java
  • CodeGenerator.java
  • ProgramGenerator.java

Directive.java

这个是枚举类,用来生成一些比较特殊的指令

都生成像声明一个类或者一个方法的范围的指令,比较简单。

public enum Directive {CLASS_PUBLIC(".class public"),END_CLASS(".end class"),SUPER(".super"),FIELD_PRIVATE_STATIC(".field private static"),METHOD_STATIC(".method static"),METHOD_PUBLIC(".method public"),FIELD_PUBLIC(".field public"),METHOD_PUBBLIC_STATIC(".method public static"),END_METHOD(".end method"),LIMIT_LOCALS(".limit locals"),LIMIT_STACK(".limit stack"),VAR(".var"),LINE(".line");private String text;Directive(String text) {this.text = text;}public String toString() {return text;}
}

Instruction.java

这也是一个枚举类,用来生成一些基本的指令

public enum Instruction {LDC("ldc"),GETSTATIC("getstatic"),SIPUSH("sipush"),IADD("iadd"),IMUL("imul"),ISUB("isub"),IDIV("idiv"),INVOKEVIRTUAL("invokevirtual"),INVOKESTATIC("invokestatic"),INVOKESPECIAL("invokespecial"),RETURN("return"),IRETURN("ireturn"),ILOAD("iload"),ISTORE("istore"),NEWARRAY("newarray"),NEW("new"),DUP("dup"),ASTORE("astore"),IASTORE("iastore"),ALOAD("aload"),PUTFIELD("putfield"),GETFIELD("getfield"),ANEWARRAY("anewarray"),AASTORE("aastore"),AALOAD("aaload"),IF_ICMPEG("if_icmpeq"),  IF_ICMPNE("if_icmpne"),IF_ICMPLT("if_icmplt"),IF_ICMPGE("if_icmpge"),IF_ICMPGT("if_icmpgt"),IF_ICMPLE("if_icmple"),GOTO("goto"),IALOAD("iaload");private String text;Instruction(String s) {this.text = s;}public String toString() {return text;}
}

CodeGenerator.java

重点来了,生成的逻辑主要都在CodeGenerator和ProgramGenerator里,CodeGenerator是ProgramGenerator的父类

CodeGenerator的构造函数new了一个输出流,用来输出字节码到xxx.j里

public CodeGenerator() {String assemblyFileName = programName + ".j";try {bytecodeFile = new PrintWriter(new PrintStream(newFile(assemblyFileName)));} catch (FileNotFoundException e) {e.printStackTrace();}}

emit、emitString、emitDirective、emitBlankLine都属于输出基本指令的方法,都有多个重载方法来应对不一样操作和操作数。需要注意的是,有的指令可能需要先缓存起来,在最后的时候一起提交,比如buffered、classDefine就是用来判断是不是应该先缓存的布尔值

public void emitString(String s) {if (buffered) {bufferedContent += s + "\n";return;}if (classDefine) {classDefinition += s + "\n";return;}bytecodeFile.print(s);bytecodeFile.flush();}public void emit(Instruction opcode) {if (buffered) {bufferedContent += "\t" + opcode.toString() + "\n";return;}if (classDefine) {classDefinition += "\t" + opcode.toString() + "\n";return;}bytecodeFile.println("\t" + opcode.toString());bytecodeFile.flush();++instructionCount;
}public void emitDirective(Directive directive, String operand1, String operand2, String operand3) {if (buffered) {bufferedContent += directive.toString() + " " + operand1 + " " + operand2 + " " + operand3 + "\n";return;}if (classDefine) {classDefinition += directive.toString() + " " + operand1 + " " + operand2 + " " + operand3 + "\n";return;}bytecodeFile.println(directive.toString() + " " + operand1 + " " + operand2 + " " + operand3);++instructionCount;
}public void emitBlankLine() {if (buffered) {bufferedContent += "\n";return;}if (classDefine) {classDefinition += "\n";return;}bytecodeFile.println();bytecodeFile.flush();
}

ProgramGenerator.java

ProgramGenerator继承了CodeGenerator,也就是继承了一些基本的操作,在上一篇像结构体、数组的指令输出都在这个类里

处理嵌套

先看四个属性,这四个属性主要是就来处理嵌套的分支和循环。

private int branch_count = 0;
private int branch_out = 0;
private String embedded = "";
private int loopCount = 0;
  • 当没嵌套一个ifelse语句时候 embedded属性就会加上一个字符‘i’,而当退出一个分支的时候,就把这个‘i’切割掉

  • branch_count和branch_out都用来标志相同作用域的分支跳转

  • 也就是说如果有嵌套就用embedded来处理,如果是用一个作用域的分支就用branch_count和branch_out来做标志

public void incraseIfElseEmbed() {embedded += "i";
}public void decraseIfElseEmbed() {embedded = embedded.substring(1);
}public void emitBranchOut() {String s = "\n" + embedded + "branch_out" + branch_out + ":\n";this.emitString(s);branch_out++;
}

loopCount则是对嵌套循环的处理

public void emitLoopBranch() {String s = "\n" + "loop" + loopCount + ":" + "\n";emitString(s);
}public String getLoopBranch() {return "loop" + loopCount;
}public void increaseLoopCount() {loopCount++;
}

处理结构体

putStructToClassDeclaration是定义结构体的,也就是new一个类。declareStructAsClass则是处理结构体里的变量,也就是相当于处理类的属性

  • 结构体如果已经类的定义的话,就会加入structNameList,不要进行重复的定义
  • symbol.getValueSetter()如果不是空的话就表明是一个结构体数组,这样就直接从数组加载这个实例,不用在堆栈上创建
  • declareStructAsClass则是依照上一篇说的Java字节码有关类的指令来创建一个类
public void putStructToClassDeclaration(Symbol symbol) {Specifier sp = symbol.getSpecifierByType(Specifier.STRUCTURE);if (sp == null) {return;}StructDefine struct = sp.getStruct();if (structNameList.contains(struct.getTag())) {return;} else {structNameList.add(struct.getTag());}if (symbol.getValueSetter() == null) {this.emit(Instruction.NEW, struct.getTag());this.emit(Instruction.DUP);this.emit(Instruction.INVOKESPECIAL, struct.getTag() + "/" + "<init>()V");int idx = this.getLocalVariableIndex(symbol);this.emit(Instruction.ASTORE, "" + idx);}declareStructAsClass(struct);
}private void declareStructAsClass(StructDefine struct) {this.setClassDefinition(true);this.emitDirective(Directive.CLASS_PUBLIC, struct.getTag());this.emitDirective(Directive.SUPER, "java/lang/Object");Symbol fields = struct.getFields();do {String fieldName = fields.getName() + " ";if (fields.getDeclarator(Declarator.ARRAY) != null) {fieldName += "[";}if (fields.hasType(Specifier.INT)) {fieldName += "I";} else if (fields.hasType(Specifier.CHAR)) {fieldName += "C";} else if (fields.hasType(Specifier.CHAR) && fields.getDeclarator(Declarator.POINTER) != null) {fieldName += "Ljava/lang/String;";}this.emitDirective(Directive.FIELD_PUBLIC, fieldName);fields = fields.getNextSymbol();} while (fields != null);this.emitDirective(Directive.METHOD_PUBLIC, "<init>()V");this.emit(Instruction.ALOAD, "0");String superInit = "java/lang/Object/<init>()V";this.emit(Instruction.INVOKESPECIAL, superInit);fields = struct.getFields();do {this.emit(Instruction.ALOAD, "0");String fieldName = struct.getTag() + "/" + fields.getName();String fieldType = "";if (fields.hasType(Specifier.INT)) {fieldType = "I";this.emit(Instruction.SIPUSH, "0");} else if (fields.hasType(Specifier.CHAR)) {fieldType = "C";this.emit(Instruction.SIPUSH, "0");} else if (fields.hasType(Specifier.CHAR) && fields.getDeclarator(Declarator.POINTER) != null) {fieldType = "Ljava/lang/String;";this.emit(Instruction.LDC, " ");}String classField = fieldName + " " + fieldType;this.emit(Instruction.PUTFIELD, classField);fields = fields.getNextSymbol();} while (fields != null);this.emit(Instruction.RETURN);this.emitDirective(Directive.END_METHOD);this.emitDirective(Directive.END_CLASS);this.setClassDefinition(false);
}

获取堆栈信息

其它有关Java字节码其实都是根据上一篇来完成的,逻辑不复杂,现在来看一个方法:getLocalVariableIndex,这个方法是获取变量当前在队列里的位置的

  • 先拿到当前执行的函数,然后拿到函数的对应参数,再反转(这和参数压栈的顺序有关)
  • 然后把当前符号对应作用域的符号都添加到列表里
  • 之后遍历这个列表就可以算出这个符号对应在队列里的位置
public int getLocalVariableIndex(Symbol symbol) {TypeSystem typeSys = TypeSystem.getInstance();String funcName = nameStack.peek();Symbol funcSym = typeSys.getSymbolByText(funcName, 0, "main");ArrayList<Symbol> localVariables = new ArrayList<>();Symbol s = funcSym.getArgList();while (s != null) {localVariables.add(s);s = s.getNextSymbol();}Collections.reverse(localVariables);ArrayList<Symbol> list = typeSys.getSymbolsByScope(symbol.getScope());for (int i = 0; i < list.size(); i++) {if (!localVariables.contains(list.get(i))) {localVariables.add(list.get(i));}}for (int i = 0; i < localVariables.size(); i++) {if (localVariables.get(i) == symbol) {return i;}}return -1;
}

小结

这一篇主要是根据上一篇的JVM字节码来对不同的操作提供不同的方法来去输出这些指令

欢迎Star!

转载于:https://www.cnblogs.com/secoding/p/11388347.html

从零写一个编译器(十二):代码生成之生成逻辑相关推荐

  1. 从零写一个编译器(二):语法分析之前置知识

    项目的完整代码在 C2j-Compiler 前言 在之前完成了词法分析之后,得到了Token流,那么接下来就是实现语法分析器来输入Token流得到抽象语法树 (Abstract Syntax Tree ...

  2. 从零写一个编译器(完结):总结和系列索引

    前言 这个系列算作我自己的学习笔记,到现在已经有十三篇了,加上这篇一共十四篇.一步一步的从词法分析到语法分析.语义分析,再到代码生成,准备在这一篇做一个总结收尾和一个这个系列以前文章的索引. (另外, ...

  3. 从零写一个编译器(一):输入系统和词法分析

    项目的完整代码在 C2j-Compiler 前言 从半抄半改的完成一个把C语言编译到Java字节码到现在也有些时间,一直想写一个系列来回顾整理一下写一个编译器的过程,也算是学习笔记吧.就从今天开始动笔 ...

  4. 从零写一个编译器(三):语法分析之几个基础数据结构

    项目的完整代码在 C2j-Compiler 写在前面 这个系列算作为我自己在学习写一个编译器的过程的一些记录,算法之类的都没有记录原理性的东西,想知道原理的在龙书里都写得非常清楚,但是我自己一开始是不 ...

  5. 从零写一个编译器(十三):代码生成之遍历AST

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成对JVM指令的生成,下面就可以真正进入代码生成部分了.通常现代编译器都是先把生成IR,再经过代码优化等等,最后才编译成目标平台代码.但是时 ...

  6. 从零写一个编译器(十):编译前传之直接解释执行

    项目的完整代码在 C2j-Compiler 前言 这一篇不看也不会影响后面代码生成部分 现在经过词法分析语法分析语义分析,终于可以进入最核心的部分了.前面那部分可以称作编译器的前端,代码生成代码优化都 ...

  7. 从零写一个编译器(十一):代码生成之Java字节码基础

    项目的完整代码在 C2j-Compiler 前言 第十一篇,终于要进入代码生成部分了,但是但是在此之前,因为我们要做的是C语言到字节码的编译,所以自然要了解一些字节码,但是由于C语言比较简单,所以只需 ...

  8. 从零写一个编译器(九):语义分析之构造抽象语法树(AST)

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST) 抽象语法树(abstract syntax ...

  9. 从零写一个编译器(八):语义分析之构造符号表

    项目的完整代码在 C2j-Compiler 前言 在之前完成了描述符号表的数据结构,现在就可以正式构造符号表了.符号表的创建自然是要根据语法分析过程中走的,所以符号表的创建就在LRStateTable ...

最新文章

  1. C++11中头文件chrono的使用
  2. 如何使用RecyclerView构建Horizo​​ntal ListView?
  3. 【NLP】如何清理文本数据?
  4. 树形结构:迭代方式遍历树,宽度优先,先序遍历,中序遍历,后序遍历
  5. 深度学习将灰度图着色_通过深度学习为视频着色
  6. Java 12:开关表达式
  7. 图象关于y轴对称是什么意思_数学概念丨“图象”与“图像”是有区别的 ,你知道吗?...
  8. MFC消息响应机制及映射机制理解
  9. vsftp 虚拟用户测试
  10. ReentrantLock可重入锁
  11. linux内存一直占满问题
  12. 风格迁移篇--AdaAttN:重新审视任意神经风格转移中的注意机制
  13. Matlab加矩形窗程序,基于MATLAB结合矩形窗设计FIR滤波器
  14. 【无标题】python类报错:takes no arguments
  15. [DEFCON全球黑客大会] CTF(Capture The Flag)
  16. java大马后门_【猥琐流】制作一个隐藏在黑页下的大马并且添加后门
  17. GPU深度发掘 -- GPGPU数学基础教程
  18. ZCMU 1600: 卡斯丁狗要吃糖葫芦
  19. matlab 设置perl解释器,Windows环境下静态编译Perl语言解释器(perl.exe)
  20. nonebot2——表情包生成插件升级版

热门文章

  1. Linux单用户模式、救援模式、克隆虚拟机与Linux机器互相登录
  2. iPhone走马灯控件实现
  3. django避免写models.py办法
  4. ImportError: numpy.core.multiarray failed to import
  5. ubuntu16.04/20.04 xfce4下面使用护眼软件redshift
  6. 2.12 矩阵及乘法重要总结
  7. 【机器学习】集成学习之stacking
  8. delphi 调用dll 整形返回值_VS2015 编写C++ DLL库及C++、 C#、python 调用
  9. vue 中watch函数名_VUE中watch用法
  10. types是什么意思中文翻译成_types 和 @types 是什么?