用Java写一个翻译器,将Java的字节码翻译成汇编语言
目录
程序控制流开发
基本思路
在project7的基础上将带有控制流的vm字节码翻译成asm汇编文件,既然是翻译,那就是字符串替换问题,在第一部分的程序控制流实现中,我们要做的就是用asm汇编语言实现goto、if-goto和label。
首先是label,这个非常简单,就直接换成(label)就完了。
然后是goto label,这个也简单,拿到label的值直接跳转就行。
最后是if-goto,这个我们首先要搞懂它是什么意思?从课件上可以知道,if-goto的效果是当栈顶元素不为0时发生跳转,并且弹栈。
因此我们首先将栈顶元素取出来,然后栈指针自减,当栈顶元素不为0时跳转。
核心代码
首先是parser类的修改,在代码调试的时候发现这次的vm字节码文件出现了有连续空格的情况,之前的parser没有对这种情况的处理,这次更新一下,增加将连续空格变成单个空格的处理。
然后是parser判断vm指令类型的函数需要增加对label、goto和if-goto指令类型的判断。
然后最主要的就是codeWriter的功能增加,增加了对label、goto和if-goto指令字符串的替换。
然后主函数就是增加对label、goto和if-goto情况的判断即可。
实验结果,使用例子进行验证
首先是BasicLoop的测试,如下图所示,成功通过测试。
然后是斐波那契的测试,如下图所示,成功通过测试,可见控制流的实现通过了。
函数调用
基本思路
首先解决call,这个比较复杂,这个主要是保存在调用函数之前当前程序的状态,主要是LCL、ARG、THIS和THAT,还有函数调用者的返回地址。
接下来我们分别一部分一部分的讲解每一个应该怎么操作。
Push retAddrLabel比较简单,就是将调用者当前的地址保存下来,将调用返回地址压入栈中,当然这个地址需要处理一下,因为我们要区分开每一次调用的label。
对于push LCL/ARG/THIS/THAT的操作是一样的,都是类似于project7中push pointer的操作,即拿到对应的字段值,把它压进栈即可。
对于ARG=SP-5-nArgs,直接翻译,让ARG的值为SP-5-nArgs。
LCL=SP这个也好翻译,而goto functionName就直接跳过去就行了,但是要记得把调用返回地址标号写在后面,因为调用完函数之后要回来。
对于函数定义function functionName nVars,根据课件可知我们需要进行nVars次push constant 0的操作。
因此只需要写上function名字的label后调用nVars次我们之前在project7写的push constant 0的翻译就行了。
对于return而言,基本上就是做的call的反操作,把call时期保存的函数调用者的状态给还原,其中主要的就是拿到函数返回地址以及把之前压入栈的字段值恢复。
因为这个return涉及到的操作很多,我们还是需要一个部分一个部分的讲解。
首先需要一个局部变量拿到我们函数调用之前写入LCL的栈指针的值。
然后根据这个调用前的栈指针的值我们可以拿到之前压入栈的函数返回地址。
然后把函数返回值写入ARG,这里是project7的pop argument 0的操作。
然后是恢复函数调用者时期SP的值。
恢复THAT/THIS/ARG/LCL字段的值。
最后回到函数调用者的地址。
然后是最后两个测试程序需要调用它们的启动函数。
启动函数就是初始化栈指针的值为256,然后调用它们写好的Sys.init函数。
还有根据最后一个测试StaticTest的调试结果可以知道不同vm文件的static字段应该是不一样的,难怪需要setFileName函数的存在,因为我没有这个函数就一直跑不对。
核心代码
parser判断vm指令类型的函数需要增加对call、function和return指令类型的判断。
然后最主要的就是codeWriter的功能增加,增加了对call、function和return指令字符串的替换,其中call和return就一一写入相应的字符串,call还需要区分开每一次调用的地址,因此调用label需要加入序号。
而function的处理,则重复调用project7写的push constant 0进行翻译。
然后主函数就是增加对label、goto和if-goto情况的判断即可。
还有调用启动函数的编写。
因为这里出现了多个vm文件,因此我们需要在主函数里对输入文件路径做一些处理,我们首先创建一个文件容器,先判断这个文件路径是否是单个文件还是一个文件夹,如果是单个文件,把这个文件装进容器。如果是一个文件夹,那么把这个文件夹目录下所有vm文件装进容器。
然后对于容器里的每一个文件都生成一个parser解析器就行解析翻译。
实验结果,使用例子进行验证
SimpleFunction先注释掉我们启动的初始化函数,因为它已经包含了启动函数,测试结果如下图所示,测试成功。
然后是这个非常非常长的斐波那契测试,这里包括了两个vm文件,同时我们启用写好的调用启动函数代码,测试结果如下图所示,成功通过。
然后是最后的考验StaticTest,第一次测试其实是失败的,为什么吗,因为一开始我没有搞懂为什么有个setFileName函数要写,于是我就没写,然后就在这里调试的时候发现,对于不同的vm文件需要不同的static字段,后来重写了project7的pop和push函数,加上了区分不同vm文件的static函数。
然后就终于搞定了。
Parser
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Objects;
import java.util.Scanner;
public class Parser {
private String command = null;
private final Scanner scanner;
private String cmd0 = null;
private String cmd1 = null;
private int cmd2;
public Parser(File file) throws FileNotFoundException {
scanner = new Scanner(new FileReader(file));
}
public boolean hasMoreCommands() {
boolean hasMore = false;
while (scanner.hasNextLine()) {
command = scanner.nextLine();
command = command.replaceAll("\\s+", " "); //将连续的空格替换成单个空格
if (!Objects.equals(command, "") && command.charAt(0) != '/') { //去掉空白行和注释
String[] pure = command.split("/");
command = pure[0];
hasMore = true;
break;
}
}
return hasMore;
}
public void advance() {
String[] cmd = command.split(" ");
cmd0 = cmd[0];
if (cmd.length > 1) {
cmd1 = cmd[1];
if (cmd.length > 2) {
cmd2 = Integer.parseInt(cmd[2]);
}
}
}
public String commandType() {
if (Objects.equals(cmd0, "push")) {
return "C_PUSH";
} else if (Objects.equals(cmd0, "pop")) {
return "C_POP";
} else if (Objects.equals(cmd0, "label")) {
return "C_LABEL";
} else if (Objects.equals(cmd0, "goto")) {
return "C_GOTO";
} else if (Objects.equals(cmd0, "if-goto")) {
return "C_IF";
} else if (Objects.equals(cmd0, "call")) {
return "C_CALL";
} else if (Objects.equals(cmd0, "function")) {
return "C_FUNCTION";
} else if (Objects.equals(cmd0, "return")) {
return "C_RETURN";
} else {
cmd1 = cmd0;
return "C_ARITHMETIC";
}
}
public String arg1() {
return cmd1;
}
public int arg2() {
return cmd2;
}
public void close() {
scanner.close();
}
}
CodeWriter
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;
public class CodeWriter {
private final FileWriter asm;
private String asmCommand;
private String fileName="";
private final HashMap<String, String> vmToAsm = new HashMap<>();
private int jump = 0;
public CodeWriter(File file) throws IOException {
asm = new FileWriter(file);
String fetch = "@SP\nM=M-1\nA=M\nD=M\nA=A-1\n";
vmToAsm.put("add", fetch + "M=M+D\n");
vmToAsm.put("sub", fetch + "M=M-D\n");
vmToAsm.put("and", fetch + "M=M&D\n");
vmToAsm.put("or", fetch + "M=M|D\n");
vmToAsm.put("gt", fetch + "D=M-D\n@TRUE\nD;JGT\n@SP\nA=M-1\nM=0\n@END\n0;JMP\n(TRUE)\n@SP\nA=M-1\nM=-1\n(END)\n");
vmToAsm.put("eq", fetch + "D=M-D\n@TRUE\nD;JEQ\n@SP\nA=M-1\nM=0\n@END\n0;JMP\n(TRUE)\n@SP\nA=M-1\nM=-1\n(END)\n");
vmToAsm.put("lt", fetch + "D=M-D\n@TRUE\nD;JLT\n@SP\nA=M-1\nM=0\n@END\n0;JMP\n(TRUE)\n@SP\nA=M-1\nM=-1\n(END)\n");
vmToAsm.put("neg", "D=0\n@SP\nA=M-1\nM=D-M\n");
vmToAsm.put("not", "@SP\nA=M-1\nM=!M\n");
}
public void writeArithmetic(String vmCommand) throws IOException {
asmCommand = vmToAsm.get(vmCommand);
if (Objects.equals(vmCommand, "gt") || Objects.equals(vmCommand, "eq") || Objects.equals(vmCommand, "lt")) {
asmCommand = asmCommand.replaceAll("TRUE", "TRUE" + jump);
asmCommand = asmCommand.replaceAll("END", "END" + jump);
jump++;
}
asm.write(asmCommand);
}
public void writePushPop(String cmd, String segment, int index) throws IOException {
if (Objects.equals(cmd, "C_PUSH")) {
if (Objects.equals(segment, "constant")) {
asmCommand = "@" + index + "\nD=A\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
} else if (Objects.equals(segment, "local")) {
asmCommand = "@LCL\nD=M\n@" + index + "\nA=D+A\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
} else if (Objects.equals(segment, "argument")) {
asmCommand = "@ARG\nD=M\n@" + index + "\nA=D+A\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
} else if (Objects.equals(segment, "this")) {
asmCommand = "@THIS\nD=M\n@" + index + "\nA=D+A\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
} else if (Objects.equals(segment, "that")) {
asmCommand = "@THAT\nD=M\n@" + index + "\nA=D+A\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
} else if (Objects.equals(segment, "temp")) {
asmCommand = "@" + (5 + index) + "\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
} else if (Objects.equals(segment, "pointer")) {
if (index == 0) {
asmCommand = "@THIS\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
} else {
asmCommand = "@THAT\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
}
} else if (Objects.equals(segment, "static")) {
asmCommand = "@" + fileName+ index + "\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n";
}
} else {
if (Objects.equals(segment, "local")) {
asmCommand = "@LCL\nD=M\n@" + index + "\nD=D+A\n@255\nM=D\n@SP\nM=M-1\nA=M\nD=M\n@255\nA=M\nM=D\n";
} else if (Objects.equals(segment, "argument")) {
asmCommand = "@ARG\nD=M\n@" + index + "\nD=D+A\n@255\nM=D\n@SP\nM=M-1\nA=M\nD=M\n@255\nA=M\nM=D\n";
} else if (Objects.equals(segment, "this")) {
asmCommand = "@THIS\nD=M\n@" + index + "\nD=D+A\n@255\nM=D\n@SP\nM=M-1\nA=M\nD=M\n@255\nA=M\nM=D\n";
} else if (Objects.equals(segment, "that")) {
asmCommand = "@THAT\nD=M\n@" + index + "\nD=D+A\n@255\nM=D\n@SP\nM=M-1\nA=M\nD=M\n@255\nA=M\nM=D\n";
} else if (Objects.equals(segment, "temp")) {
asmCommand = "@SP\nM=M-1\nA=M\nD=M\n@" + (5 + index) + "\nM=D\n";
} else if (Objects.equals(segment, "pointer")) {
if (index == 0) {
asmCommand = "@SP\nM=M-1\nA=M\nD=M\n@THIS\nM=D\n";
} else {
asmCommand = "@SP\nM=M-1\nA=M\nD=M\n@THAT\nM=D\n";
}
} else if (Objects.equals(segment, "static")) {
asmCommand = "@SP\nM=M-1\nA=M\nD=M\n@" + fileName+ index + "\nM=D\n";
}
}
asm.write(asmCommand);
}
public void writeLabel(String label) throws IOException {
asm.write("(" + label + ")\n");
}
public void writeGoto(String label) throws IOException {
asm.write("@" + label + "\n0;JMP\n");
}
public void writeIf(String label) throws IOException {
asm.write("@SP\nM=M-1\nA=M\nD=M\n@" + label + "\nD;JNE\n");
}
public void writeCall(String functionName, int nArgs) throws IOException {
asm.write("@Caller" + jump + "\nD=A\n@SP\nA=M\nM=D\n@SP\nM=M+1\n");
asm.write("@LCL\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n");
asm.write("@ARG\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n");
asm.write("@THIS\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n");
asm.write("@THAT\nD=M\n@SP\nA=M\nM=D\n@SP\nM=M+1\n");
asm.write("@SP\nD=M\n@5\nD=D-A\n@" + nArgs + "\nD=D-A\n@ARG\nM=D\n");
asm.write("@SP\nD=M\n@LCL\nM=D\n");
asm.write("@" + functionName + "\n0;JMP\n(Caller" + jump + ")\n");
jump++;
}
public void writeReturn() throws IOException {
asm.write("@LCL\nD=M\n@FRAME\nM=D\n");
asm.write("@5\nA=D-A\nD=M\n@RET\nM=D\n");
asm.write("@ARG\nD=M\n@0\nD=D+A\n@255\nM=D\n@SP\nM=M-1\nA=M\nD=M\n@255\nA=M\nM=D\n");
asm.write("@ARG\nD=M\n@SP\nM=D+1\n");
asm.write("@FRAME\nD=M-1\nAM=D\nD=M\n@THAT\nM=D\n");
asm.write("@FRAME\nD=M-1\nAM=D\nD=M\n@THIS\nM=D\n");
asm.write("@FRAME\nD=M-1\nAM=D\nD=M\n@ARG\nM=D\n");
asm.write("@FRAME\nD=M-1\nAM=D\nD=M\n@LCL\nM=D\n");
asm.write("@RET\nA=M\n0;JMP\n");
}
public void writeFunction(String functionName, int nArgs) throws IOException {
asm.write("(" + functionName + ")\n");
for (int i = 0; i < nArgs; i++) {
writePushPop("C_PUSH", "constant", 0);
}
}
public void writeInit() throws IOException {
asm.write("@256\nD=A\n@SP\nM=D\n");
writeCall("Sys.init", 0);
}
public void close() throws IOException {
asm.close();
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
Main
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Objects;
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("C:\\Users\\Yezi\\Desktop\\Java程序设计\\nand2tetris\\projects\\08\\FunctionCalls\\StaticsTest");
ArrayList<File> vm = new ArrayList<>();
CodeWriter codeWriter = new CodeWriter(new File("C:\\Users\\Yezi\\Desktop\\Java程序设计\\nand2tetris\\projects\\08\\FunctionCalls\\StaticsTest\\StaticsTest.asm"));
codeWriter.writeInit();
if (file.isFile()) {
vm.add(file);
} else {
for (File one : Objects.requireNonNull(file.listFiles())) {
if (one.getName().endsWith(".vm")) {
vm.add(one);
}
}
}
for (File one : vm) {
Parser parser = new Parser(one);
codeWriter.setFileName(one.getName());
while (parser.hasMoreCommands()) {
parser.advance();
if (Objects.equals(parser.commandType(), "C_ARITHMETIC")) {
codeWriter.writeArithmetic(parser.arg1());
} else if (Objects.equals(parser.commandType(), "C_LABEL")) {
codeWriter.writeLabel(parser.arg1());
} else if (Objects.equals(parser.commandType(), "C_GOTO")) {
codeWriter.writeGoto(parser.arg1());
} else if (Objects.equals(parser.commandType(), "C_IF")) {
codeWriter.writeIf(parser.arg1());
} else if (Objects.equals(parser.commandType(), "C_FUNCTION")) {
codeWriter.writeFunction(parser.arg1(), parser.arg2());
} else if (Objects.equals(parser.commandType(), "C_RETURN")) {
codeWriter.writeReturn();
} else if (Objects.equals(parser.commandType(), "C_CALL")) {
codeWriter.writeCall(parser.arg1(), parser.arg2());
} else {
codeWriter.writePushPop(parser.commandType(), parser.arg1(), parser.arg2());
}
}
parser.close();
}
codeWriter.close();
}
}