diff --git a/docs/tester/Checkpoint1.java b/docs/tester/Checkpoint1.java deleted file mode 100644 index ffa95d6..0000000 --- a/docs/tester/Checkpoint1.java +++ /dev/null @@ -1,71 +0,0 @@ -package tester; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Scanner; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/* Automated regression tester for Checkpoint 1 tests - * Created by Max Beckman-Harned - * Put your tests in "tests/pa1_tests" folder in your Eclipse workspace directory - */ -public class Checkpoint1 { - - static ExecutorService threadPool = Executors.newCachedThreadPool(); - - public static void main(String[] args) throws IOException, InterruptedException { - File testDir = new File(System.getProperty("java.class.path") + "/../tests/pa1_tests"); - System.out.println(System.getProperty("java.class.path")); - int failures = 0; - for (File x : testDir.listFiles()) { - int returnCode = runTest(x); - if (x.getName().indexOf("pass") != -1) { - if (returnCode == 0) - System.out.println(x.getName() + " passed successfully!"); - else { - failures++; - System.err.println(x.getName() - + " failed but should have passed!"); - } - } else { - if (returnCode == 4) - System.out.println(x.getName() + " failed successfully!"); - else { - System.err.println(x.getName() + " did not fail properly!"); - failures++; - } - } - } - System.out.println(failures + " failures in all."); - } - - private static int runTest(File x) throws IOException, InterruptedException { - ProcessBuilder pb = new ProcessBuilder("java", "miniJava.Compiler", x.getPath()).directory(new File(System.getProperty("java.class.path"))); - Process p = pb.start(); - threadPool.execute(new ProcessOutputter(p.getInputStream(), false)); - p.waitFor(); - return p.exitValue(); - } - - static class ProcessOutputter implements Runnable { - private Scanner processOutput; - private boolean output; - - public ProcessOutputter(InputStream _processStream, boolean _output) { - processOutput = new Scanner(_processStream); - output = _output; - } - @Override - public void run() { - while(processOutput.hasNextLine()) { - String line = processOutput.nextLine(); - if (output) - System.out.println(line); - } - } - - - } -} diff --git a/src/mJAM/Assembler.java b/src/mJAM/Assembler.java new file mode 100644 index 0000000..6546944 --- /dev/null +++ b/src/mJAM/Assembler.java @@ -0,0 +1,5 @@ +package mJAM; + +public class Assembler { + // TBD +} diff --git a/src/mJAM/Disassembler.java b/src/mJAM/Disassembler.java new file mode 100644 index 0000000..7d9b452 --- /dev/null +++ b/src/mJAM/Disassembler.java @@ -0,0 +1,309 @@ +/** + * mJAM instruction format + * @author prins + * @version COMP 520 V2.2 + */ +package mJAM; + +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Disassemble the mJAM object code + * from input file xxx.mJAM + * into output file xxx.asm + * + * @author prins + * @version COMP 520 v2.2 + */ +public class Disassembler { + + private String objectFileName; + private String asmName; + private FileWriter asmOut; + private boolean error = false; + private Map addrToLabel; + + public Disassembler(String objectFileName) { + this.objectFileName = objectFileName; + } + + /** + * Writes the r-field of an instruction in the form "lregr", where + * l and r are the bracket characters to use. + * @param leftbracket the character to print before the register. + * @param r the number of the register. + * @param rightbracket the character to print after the register. + */ + private void writeR(char leftbracket, int r, char rightbracket) { + asmWrite(Character.toString(leftbracket)); + asmWrite(Machine.intToReg[r].toString()); + asmWrite(Character.toString(rightbracket)); + } + + /** + * Writes a void n-field of an instruction. + */ + private void blankN() { + asmWrite(" "); + } + + // Writes the n-field of an instruction. + /** + * Writes the n-field of an instruction in the form "(n)". + * @param n the integer to write. + */ + private void writeN(int n) { + asmWrite(String.format("%-6s","(" + n + ")")); + } + + /** + * Writes the d-field of an instruction. + * @param d the integer to write. + */ + private void writeD(int d) { + asmWrite(Integer.toString(d)); + } + + /** + * Writes the name of primitive routine with relative address d. + * @param d the displacment of the primitive routine. + */ + private void writePrimitive(int d) { + Machine.Prim prim = Machine.intToPrim[d]; + asmWrite(String.format("%-8s",prim.toString())); + } + + /** + * Writes the given instruction in assembly-code format. + * @param instr the instruction to display. + */ + private void writeInstruction(Instruction instr) { + + String targetLabel = "***"; + // get label of destination addr, if instr transfers control + if (instr.r == Machine.Reg.CB.ordinal()) + targetLabel = addrToLabel.get(instr.d); + + Machine.Op instruction = Machine.intToOp[instr.op]; + asmWrite(String.format("%-7s",instruction.toString())); + switch (instruction) { + case LOAD: + blankN(); + writeD(instr.d); + writeR('[', instr.r, ']'); + break; + + case LOADA: + blankN(); + writeD(instr.d); + writeR('[', instr.r, ']'); + break; + + case LOADI: + break; + + case LOADL: + blankN(); + writeD(instr.d); + break; + + case STORE: + blankN(); + writeD(instr.d); + writeR('[', instr.r, ']'); + break; + + case STOREI: + break; + + case CALL: + if (instr.r == Machine.Reg.PB.ordinal()) { + blankN(); + writePrimitive(instr.d); + } else { + blankN(); + asmWrite(targetLabel); + } + break; + + case CALLI: + blankN(); + asmWrite(targetLabel); + break; + + case RETURN: + writeN(instr.n); + writeD(instr.d); + break; + + case CALLD: + blankN(); + writeD(instr.d); + break; + + case PUSH: + blankN(); + writeD(instr.d); + break; + + case POP: + blankN(); + writeD(instr.d); + break; + + case JUMP: + blankN(); + asmWrite(targetLabel); + break; + + case JUMPI: + break; + + case JUMPIF: + writeN(instr.n); + asmWrite(targetLabel); + break; + + case HALT: + writeN(instr.n); + break; + + default: + asmWrite("???? "); + writeN(instr.n); + writeD(instr.d); + writeR('[', instr.r, ']'); + break; + } + } + + /** + * disassembles program held in code store + */ + void disassembleProgram(String asmFileName) { + + try { + asmOut = new FileWriter(asmFileName); + } catch (IOException e) { + System.out.println("Disassembler: can not create asm output file " + + asmName); + error = true; + return; + } + + // collect all addresses that may be the target of a jump instruction + SortedSet targets = new TreeSet(); + for (int addr = Machine.CB; addr < Machine.CT; addr++) { + Instruction inst = Machine.code[addr]; + Machine.Op op = Machine.intToOp[inst.op]; + switch (op) { + case CALL: + case CALLI: + // only consider calls (branches) within code memory (i.e. not primitives) + if (inst.r == Machine.Reg.CB.ordinal()) + targets.add(inst.d); + break; + case JUMP: + // address following an unconditional branch is an implicit target + targets.add(addr+1); + targets.add(inst.d); + break; + case JUMPIF: + // a jump of any sort creates a branch target + targets.add(inst.d); + break; + default: + break; + } + } + + // map branch target addresses to unique labels + addrToLabel = new HashMap(); + int labelCounter = 10; + for (Integer addr : targets) { + String label = "L" + labelCounter++ ; + addrToLabel.put(addr, label); + } + + // disassemble each instruction + for (int addr = Machine.CB; addr < Machine.CT; addr++) { + + // generate instruction address + asmWrite(String.format("%3d ", addr)); + + // if this addr is a branch target, output label + if (addrToLabel.containsKey(addr)) + asmWrite(String.format("%-7s", addrToLabel.get(addr) + ":")); + else + asmWrite(" "); + + // instruction + writeInstruction(Machine.code[addr]); + + // newline + asmWrite("\n"); + } + + // close output file + try { + asmOut.close(); + } catch (IOException e) { + error = true; + } + } + + private void asmWrite(String s) { + try { + asmOut.write(s); + } catch (IOException e) { + error = true; + } + } + + public static void main(String[] args) { + System.out.println("********** mJAM Disassembler (1.0) **********"); + String objectFileName = "obj.mJAM"; + if (args.length == 1) + objectFileName = args[0]; + Disassembler d = new Disassembler(objectFileName); + d.disassemble(); + } + + /** + * Disassemble object file + * @return true if error encountered else false + */ + public boolean disassemble() { + ObjectFile objectFile = new ObjectFile(objectFileName); + + // read object file into code store + if (objectFile.read()) { + System.out.println("Disassembler: unable to read object file" + + objectFileName); + return true; + } + + // assembler-code output file name + if (objectFileName.endsWith(".mJAM")) + asmName = objectFileName.substring(0, objectFileName.length() - 5) + + ".asm"; + else + asmName = objectFileName + ".asm"; + + // disassemble to file + disassembleProgram(asmName); + + if (error) { + System.out.println("Disassembler: unable to write asm file" + + asmName); + return true; + } + + return false; + } +} diff --git a/src/mJAM/Instruction.java b/src/mJAM/Instruction.java new file mode 100644 index 0000000..8e3ef84 --- /dev/null +++ b/src/mJAM/Instruction.java @@ -0,0 +1,36 @@ +/** + * mJAM instruction format + * @author prins + * @version COMP 520 V2.2 + */ +package mJAM; + +public class Instruction { + + public Instruction() { + op = 0; + r = 0; + n = 0; + d = 0; + } + + public Instruction(int op, int n, int r, int d) { + this.op = op; + this.n = n; + this.r = r; + this.d = d; + } + + // Java has no type synonyms, so the following representations are + // assumed: + // + // type + // OpCode = 0..15; {4 bits unsigned} + // Register = 0..15; (4 bits unsigned) + // Length = 0..255; {8 bits unsigned} + // Operand = -2147483648 .. +2147483647; (32 bits signed for use with LOADL) + public int op; // OpCode + public int r; // RegisterNumber + public int n; // Length + public int d; // Operand +} diff --git a/src/mJAM/Interpreter.java b/src/mJAM/Interpreter.java new file mode 100644 index 0000000..c2716f3 --- /dev/null +++ b/src/mJAM/Interpreter.java @@ -0,0 +1,888 @@ +/** + * Interprets mJAM programs + * @author prins + * @version COMP 520 V2.2 + */ +package mJAM; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Scanner; + +// import mJAM.Machine.Reg; + +public class Interpreter { + + // DATA STORE + static int[] data = new int[1024]; + + // DATA STORE REGISTERS AND OTHER REGISTERS + final static int CB = 0, SB = 0, HB = 1024; // = upper bound of data array + 1 + + + static int CT, CP, ST, HT, LB, OB, status, temp; + + // machine status values + final static int running = 0, halted = 1, failedDataStoreFull = 2, + failedInvalidCodeAddress = 3, failedInvalidInstruction = 4, + failedOverflow = 5, failedZeroDivide = 6, failedIOError = 7, + failedArrayIndex = 8, failedNullRef = 9, failedHeapRef =10, + failedMethodIndex = 11; + + static long accumulator; + + // Debugger state + enum DebuggerStatus { + PAUSED, RUNNING + } + + static DebuggerStatus debuggerStatus = DebuggerStatus.PAUSED; + static ArrayList breakpoints = new ArrayList(); + static ArrayList sourceLines; + + static int content(int r) { + // Returns the current content of register r, + Machine.Reg reg = Machine.intToReg[r]; + switch (reg) { + case CB: + return CB; + case CT: + return CT; + case PB: + return Machine.PB; + case PT: + return Machine.PT; + case SB: + return SB; + case ST: + return ST; + case HB: + return HB; + case HT: + return HT; + case LB: + return LB; + case OB: + return OB; + case CP: + return CP; + default: + return 0; + } + } + + // PROGRAM STATUS + + static void dump() { + // Writes a summary of the machine state. + int addr, dynamicLink; + System.out.println(""); + System.out.println("At instruction " + CP + + ", state of mJAM data store and registers is:"); + System.out.println(""); + if (HT == HB) + System.out.println(" |--------| (heap is empty)"); + else { + System.out.println(" HB--> "); + System.out.println(" |--------|"); + for (addr = HB - 1; addr >= HT; addr--) { + System.out.print(rightPad(6, addr + ":")); + if (addr == OB) + System.out.print("OB--> "); + else if (addr == HT) + System.out.print("HT--> "); + else + System.out.print(" "); + System.out.println("|" + leftPad(8, String.valueOf(data[addr])) + + "|"); + } + System.out.println(" |--------|"); + } + System.out.println(" |////////|"); + System.out.println(" |////////|"); + if (ST == SB) + System.out.println(" |--------| (stack is empty)"); + else { + dynamicLink = LB; + System.out.println(" ST--> |////////|"); + System.out.println(" |--------|"); + for (addr = ST - 1; addr >= SB; addr--) { + System.out.print(rightPad(6, addr + ": ")); + if (addr == SB) + System.out.print("SB--> "); + else if (addr == LB) + System.out.print("LB--> "); + else + System.out.print(" "); + if ((addr == dynamicLink) && (dynamicLink != SB)) + System.out.print("|OB=" + + leftPad(5, String.valueOf(data[addr])) + "|"); + else if ((addr == dynamicLink + 1) && (dynamicLink != SB)) + System.out.print("|DL=" + + leftPad(5, String.valueOf(data[addr])) + "|"); + else if ((addr == dynamicLink + 2) && (dynamicLink != SB)) + System.out.print("|RA=" + + leftPad(5, String.valueOf(data[addr])) + "|"); + else + System.out.print("|" + + leftPad(8, String.valueOf(data[addr])) + "|"); + System.out.println(""); + if (addr == dynamicLink) { + System.out.println(" |--------|"); + dynamicLink = data[addr + 1]; + } + } + } + System.out.println(""); + } + + private static String leftPad(int len, String s) { + int aLen = Math.max(len, s.length()); + StringBuffer buf = new StringBuffer(s); + String r = buf.insert(0, " ").toString(); + return r.substring(r.length() - aLen, r.length()); + } + + private static String rightPad(int len, String s) { + int aLen = Math.max(len, s.length()); + String r = s + " "; + return r.substring(0, aLen); + } + + static void showStatus() { + // Writes an indication of whether and why the program has terminated. + System.out.println(""); + System.out.print("*** "); + switch (status) { + case running: + System.out.println("Program is running."); + break; + case halted: + System.out.println("Program has halted normally."); + break; + case failedDataStoreFull: + System.out.println("Program has failed due to exhaustion of Data Store."); + break; + case failedInvalidCodeAddress: + System.out.println("Program has failed due to an invalid code address."); + break; + case failedInvalidInstruction: + System.out.println("Program has failed due to an invalid instruction."); + break; + case failedOverflow: + System.out.println("Program has failed due to overflow."); + break; + case failedZeroDivide: + System.out.println("Program has failed due to division by zero."); + break; + case failedIOError: + System.out.println("Program has failed due to an IO error."); + break; + case failedArrayIndex: + System.out.println("Program has failed due to an array index error."); + break; + case failedNullRef: + System.out.println("Program has failed due to a null pointer reference."); + break; + case failedHeapRef: + System.out.println("Program has failed due to an invalid Heap reference."); + break; + case failedMethodIndex: + System.out.println("Program has failed due to an improper method index in CALLD."); + break; + default: + System.out.println("Machine is in an unknown state."); + break; + } + if (status != halted) + dump(); + } + + // INTERPRETATION + + static void checkSpace(int spaceNeeded) { + // Signals failure if there is not enough space to expand the stack or + // heap by spaceNeeded. + if (HT - ST < spaceNeeded) + status = failedDataStoreFull; + } + + static boolean invalidHeapRef(int addr) { + // if addr is null ptr or outside of heap bounds, sets status to failure + if (addr == Machine.nullRep) + status = failedNullRef; + else if (addr < HT + 2 || addr >= HB) + status = failedHeapRef; + return (status != running); + } + + static boolean isTrue(int datum) { + // Tests whether the given datum represents true. + return (datum == Machine.trueRep); + } + + static int overflowChecked(long datum) { + // Signals failure if the datum is too large to fit into a single word, + // otherwise returns the datum as a single word. + if ((Machine.minintRep <= datum) && (datum <= Machine.maxintRep)) + return (int) datum; + else { + status = failedOverflow; + return 0; + } + } + + static int toInt(boolean b) { + return b ? Machine.trueRep : Machine.falseRep; + } + + static int currentChar; + + static int readInt() throws java.io.IOException { + int temp = 0; + int sign = 1; + + do { + currentChar = System.in.read(); + } while (Character.isWhitespace((char) currentChar)); + + if ((currentChar == '-') || (currentChar == '+')) + do { + sign = (currentChar == '-') ? -1 : 1; + currentChar = System.in.read(); + } while ((currentChar == '-') || currentChar == '+'); + + if (Character.isDigit((char) currentChar)) + do { + temp = temp * 10 + (currentChar - '0'); + currentChar = System.in.read(); + } while (Character.isDigit((char) currentChar)); + + return sign * temp; + } + + // Invoke primitive operation with argument(s) on the stack + // primitives are static and are not supplied an instance on the stack. + static void callPrimitive(int id) { + + int addr, size, index; + char ch; + + Machine.Prim prim = Machine.intToPrim[id]; + switch (prim) { + case id: + break; // nothing to be done + case not: + data[ST - 1] = toInt(!isTrue(data[ST - 1])); + break; + case and: + ST = ST - 1; + data[ST - 1] = toInt(isTrue(data[ST - 1]) & isTrue(data[ST])); + break; + case or: + ST = ST - 1; + data[ST - 1] = toInt(isTrue(data[ST - 1]) | isTrue(data[ST])); + break; + case succ: + data[ST - 1] = overflowChecked(data[ST - 1] + 1); + break; + case pred: + data[ST - 1] = overflowChecked(data[ST - 1] - 1); + break; + case neg: + data[ST - 1] = overflowChecked(-data[ST - 1]); + break; + case add: + ST = ST - 1; + accumulator = data[ST - 1]; + data[ST - 1] = overflowChecked(accumulator + data[ST]); + break; + case sub: + ST = ST - 1; + accumulator = data[ST - 1]; + data[ST - 1] = overflowChecked(accumulator - data[ST]); + break; + case mult: + ST = ST - 1; + accumulator = data[ST - 1]; + data[ST - 1] = overflowChecked(accumulator * data[ST]); + break; + case div: + ST = ST - 1; + accumulator = data[ST - 1]; + if (data[ST] != 0) + data[ST - 1] = (int) (accumulator / data[ST]); + else + status = failedZeroDivide; + break; + case mod: + ST = ST - 1; + accumulator = data[ST - 1]; + if (data[ST] != 0) + data[ST - 1] = (int) (accumulator % data[ST]); + else + status = failedZeroDivide; + break; + case lt: + ST = ST - 1; + data[ST - 1] = toInt(data[ST - 1] < data[ST]); + break; + case le: + ST = ST - 1; + data[ST - 1] = toInt(data[ST - 1] <= data[ST]); + break; + case ge: + ST = ST - 1; + data[ST - 1] = toInt(data[ST - 1] >= data[ST]); + break; + case gt: + ST = ST - 1; + data[ST - 1] = toInt(data[ST - 1] > data[ST]); + break; + case eq: + ST = ST - 1; + data[ST - 1] = toInt(data[ST - 1] == data[ST]); + break; + case ne: + ST = ST - 1; + data[ST - 1] = toInt(data[ST - 1] != data[ST]); + break; + case eol: + data[ST] = toInt(currentChar == '\n'); + ST = ST + 1; + break; + case eof: + data[ST] = toInt(currentChar == -1); + ST = ST + 1; + break; + case get: + ST = ST - 1; + addr = data[ST]; + try { + currentChar = System.in.read(); + } catch (java.io.IOException s) { + status = failedIOError; + } + data[addr] = (int) currentChar; + break; + case put: + ST = ST - 1; + ch = (char) data[ST]; + System.out.print(ch); + break; + case geteol: + try { + while ((currentChar = System.in.read()) != '\n') + ; + } catch (java.io.IOException s) { + status = failedIOError; + } + break; + case puteol: + System.out.println(""); + break; + case getint: + ST = ST - 1; + addr = data[ST]; + try { + accumulator = readInt(); + } catch (java.io.IOException s) { + status = failedIOError; + } + data[addr] = (int) accumulator; + break; + case putint: + ST = ST - 1; + accumulator = data[ST]; + System.out.print(accumulator); + break; + // output with prefix for tester + case putintnl: + ST = ST - 1; + accumulator = data[ST]; + System.out.print(">>> " + accumulator + "\n"); + break; + case alloc: + size = data[ST - 1]; + checkSpace(size); + HT = HT - size; + data[ST - 1] = HT; + break; + case dispose: + ST = ST - 1; // no action taken at present + break; + case newobj: + // ..., class obj addr, number of fields ==> ..., new obj addr + size = data[ST - 1] + 2; // number of fields + 2 word descriptor + checkSpace(size); + HT = HT - size; // reserve space + data[HT] = data[ST - 2]; // set class object addr + data[HT + 1] = size - 2; // set size of object + data[ST - 2] = HT + 2; // addr of new object instance, returned on stack + ST = ST - 1; // net effect of pop 2 args, push 1 result + for (int i = 2; i < size; i++) { + data[HT + i] = 0; // zero all fields of new object + } + break; + case newarr: + // ..., number of elements ==> ..., new int[] addr + size = data[ST - 1] + 2; // array + 2 word descriptor + checkSpace(size); + HT = HT - size; + data[HT] = -2; // tag for array + data[HT + 1] = size - 2; // size of array + data[ST - 1] = HT + 2; // addr of array instance, returned on stack + for (int i = 2; i < size; i++) { + data[HT + i] = 0; // zero all elements of new array + } + break; + case arrayref: + // ..., array addr a, element index i ==> ..., a[i] + addr = data[ST - 2]; + if (invalidHeapRef(addr)) + break; + index = data[ST - 1]; + if (data[addr - 2] != -2 || index < 0 || index >= data[addr - 1]) { + status = failedArrayIndex; + break; + } + data[ST - 2] = data[addr + index]; // result element, returned on stack + ST = ST - 1; // pop two args, return one result + break; + case arrayupd: + // ..., array addr a, element index i, new value v ==> ... + // and a[i] := v + addr = data[ST - 3]; + if (invalidHeapRef(addr)) + break; + index = data[ST - 2]; + if (data[addr - 2] != -2 || index < 0 || index >= data[addr - 1]) { + status = failedArrayIndex; + break; + } + data[addr + index] = data[ST - 1]; // update array element + ST = ST - 3; // pop 3 args, return no result + break; + case fieldref: + // ..., obj addr a, field index i ==> ..., value of ith field of a + addr = data[ST - 2]; + if (invalidHeapRef(addr)) + break; + index = data[ST - 1]; + if (index < 0 || index >= data[addr - 1]) { + status = failedArrayIndex; + break; + } + data[ST - 2] = data[addr + index]; // field to stack top + ST = ST - 1; // pop two args, return one result + break; + case fieldupd: + // ..., obj addr a, field index i, new value v ==> ... + // and a.i := v + addr = data[ST - 3]; + if (invalidHeapRef(addr)) + break; + index = data[ST - 2]; + if (index < 0 || index >= data[addr - 1]) { + status = failedArrayIndex; + break; + } + data[addr + index] = data[ST - 1]; // update field to new value + ST = ST - 3; // pop 3 args, return no result + break; + } + } + + static void interpretOneOperation() { + // Fetch instruction ... + Instruction currentInstr = Machine.code[CP]; + // Decode instruction ... + int op = currentInstr.op; + int r = currentInstr.r; + int n = currentInstr.n; + int d = currentInstr.d; + int addr; + // Execute instruction ... + + Machine.Op operation = Machine.intToOp[op]; + + switch (operation) { + case LOAD: + addr = d + content(r); + checkSpace(1); + data[ST] = data[addr]; + ST = ST + 1; + CP = CP + 1; + break; + case LOADA: + addr = d + content(r); + checkSpace(1); + data[ST] = addr; + ST = ST + 1; + CP = CP + 1; + break; + case LOADI: + ST = ST - 1; + addr = data[ST]; + checkSpace(1); + data[ST] = data[addr]; + ST = ST + 1; + CP = CP + 1; + break; + case LOADL: + checkSpace(1); + data[ST] = d; + ST = ST + 1; + CP = CP + 1; + break; + case STORE: + addr = d + content(r); + ST = ST - 1; + data[addr] = data[ST]; + CP = CP + 1; + break; + case STOREI: + ST = ST - 1; + addr = data[ST]; + ST = ST - 1; + data[addr] = data[ST]; + CP = CP + 1; + break; + + case CALL: + // call static method, including primitives + // arguments are on stack + addr = d + content(r); // effective address + if (addr >= Machine.PB) { + callPrimitive(addr - Machine.PB); + CP = CP + 1; + } else { + // static method in code segment, no instance addr on stack + checkSpace(3); + data[ST] = OB; // save caller OB in callee frame + data[ST + 1] = LB; // save caller LB in callee frame (dynamic link) + data[ST + 2] = CP + 1; // save caller return address in callee frame + OB = Machine.nullRep; // set callee OB (null since no instance) + LB = ST; // set LB = start of callee frame + ST = ST + 3; // set ST = end of callee frame + CP = addr; // execution resumes at addr specified in CALL inst + } + break; + + case CALLI: + // call instance method + // arguments on stack, followed by instance address + addr = d + content(r); // effective address + if (addr >= Machine.CT) { + // no instance methods outside of code segment + status = failedInvalidInstruction; + break; + } + // instance address is last arg on stack and is overwritten by frame + checkSpace(2); + temp = data[ST - 1]; // save instance address temporarily + data[ST - 1] = OB; // save caller OB in callee frame + data[ST] = LB; // save caller LB in callee frame (dynamic link) + data[ST + 1] = CP + 1; // save caller return address in callee frame + OB = temp; // set OB for callee + LB = ST - 1; // set LB = start of callee frame + ST = ST + 2; // set ST = end of callee frame + CP = addr; // execution resumes at addr specified in CALL inst + break; + + case RETURN: + // d = number of method args (does not include instance addr for CALLI) + // n = size of result (0 or 1) + if (n < 0 || n > 1) { + status = failedInvalidInstruction; + break; + } + addr = LB - d; // addr of caller args + OB = data[LB]; // restore caller OB, LB, CP + CP = data[LB + 2]; + LB = data[LB + 1]; + if (n == 1) + data[addr] = data[ST - 1]; // return value if any + ST = addr + n; // caller stack top + break; + + case CALLD: + // dynamic method dispatch of method with index d (origin 0) + // arguments on stack, followed by instance addr + { + addr = data[ST - 1]; // instance addr + if (invalidHeapRef(addr)) + break; + int classDescAddr = data[addr - 2]; + if (classDescAddr >= ST || classDescAddr <= SB || d >= data[classDescAddr + 1] || d < 0) { + status = failedMethodIndex; + break; + } + ST = ST - 1; + checkSpace(3); + data[ST] = OB; + data[ST + 1] = LB; + data[ST + 2] = CP + 1; + OB = addr; + LB = ST; + ST = ST + 3; + CP = data[classDescAddr + 2 + n]; + } + break; + case PUSH: // push d elements on stack + checkSpace(d); + ST = ST + d; + CP = CP + 1; + break; + case POP: // pop d elements off stack + ST = ST - d; + CP = CP + 1; + break; + case JUMP: + CP = d + content(r); + break; + case JUMPI: + ST = ST - 1; + CP = data[ST]; + break; + case JUMPIF: + ST = ST - 1; + if (data[ST] == n) + CP = d + content(r); + else + CP = CP + 1; + break; + case HALT: + if (n > 0) { + // halt n > 0 --> snapshot machine state and continue execution + dump(); + CP = CP + 1; + } else + status = halted; + break; + } + + if ((CP < CB) || (CP >= CT)) + status = failedInvalidCodeAddress; + + if (breakpoints.indexOf(CP) != -1) { + debuggerStatus = DebuggerStatus.PAUSED; + System.out.println("Breakpoint hit: " + sourceLines.get(CP)); + } + } + + static void initMachine() { + // Initialize registers ... + ST = SB; + HT = HB; + LB = SB; + CP = CB; + OB = -1; // invalid instance addr + CT = Machine.CT; + status = running; + } + + static void interpretProgram() { + // Runs the program in code store. + initMachine(); + do { + interpretOneOperation(); + } while (status == running); + } + + static void runProgramFromStart() { + initMachine(); + continueProgram(); + } + + static void continueProgram() { + debuggerStatus = DebuggerStatus.RUNNING; + do { + interpretOneOperation(); + } while (status == running && debuggerStatus == DebuggerStatus.RUNNING); + } + + static void printHelp() { + String[] help = { + "p or print:", + " print entire machine state", + "l or list [offset] [size]:", + " print the instructions around CP + offset, with size lines on either side", + " offset = 0 and size = 2 by default", + "b or break [address]:", + " set a breakpoint at address", + " address = CP by default", + "del:", + " delete one or more breakpoints", + "n or next:", + " execute one instruction", + "c or continue:", + " continue running the program from current position, until next breakpoint or completion", + "r or run:", + " run the program from start, until next breakpoint or completion", + "i or info:", " list the current breakpoints", + "q, quit or :", " quit the debugger", + "Simply press enter to repeat the last command", "? or help:", + " print this help" }; + + for (String line : help) { + System.out.println(" " + line); + } + } + + static void debugProgram() { + initMachine(); + + BufferedReader inputReader = new BufferedReader(new InputStreamReader( + System.in)); + + String lastCommand = ""; + + while (true) { + System.out.print("\n: "); + String inputLine; + + try { + inputLine = inputReader.readLine(); + } catch (IOException e) { + return; + } + + if (inputLine == null) + return; + + Scanner scanner = new Scanner(inputLine); + String command = (scanner.hasNext() ? scanner.next() : lastCommand); + lastCommand = command; + + if (command.equals("?") || command.equals("help")) { + printHelp(); + } else if (command.equalsIgnoreCase("p") + || command.equalsIgnoreCase("print")) { + dump(); + } else if (command.equalsIgnoreCase("l") + || command.equalsIgnoreCase("list")) { + int offset = 0, size = 2; + if (scanner.hasNextInt()) + offset = scanner.nextInt(); + if (scanner.hasNextInt()) + size = scanner.nextInt(); + + for (int i = CP + offset - size; i <= CP + offset + size; ++i) { + if (i >= 0 && i < sourceLines.size()) + System.out.println((i == CP ? " >" : " ") + + sourceLines.get(i)); + } + } else if (command.equalsIgnoreCase("b") + || command.equalsIgnoreCase("break")) { + int addr = scanner.hasNextInt() ? scanner.nextInt() : CP; + if (!breakpoints.contains(addr)) + breakpoints.add(addr); + System.out.println("Added breakpoint at " + + sourceLines.get(addr)); + } else if (command.equalsIgnoreCase("del")) { + while (scanner.hasNextInt()) { + int addr = scanner.nextInt(), idx = breakpoints + .indexOf(addr); + if (idx != -1) { + breakpoints.remove(idx); + } else { + System.out.println("No breakpoint at " + addr); + } + } + } else if (command.equalsIgnoreCase("n") + || command.equalsIgnoreCase("next")) { + if (status == running) { + interpretOneOperation(); + } else { + System.out.println("Program is not running"); + } + } else if (command.equalsIgnoreCase("c") + || command.equalsIgnoreCase("continue")) { + continueProgram(); + } else if (command.equalsIgnoreCase("r") + || command.equalsIgnoreCase("run")) { + runProgramFromStart(); + } else if (command.equalsIgnoreCase("i") + || command.equalsIgnoreCase("info")) { + System.out.println("Breakpoints:"); + for (int b : breakpoints) { + System.out.println("\t" + sourceLines.get(b)); + } + } else if (command.equalsIgnoreCase("q") + || command.equalsIgnoreCase("quit")) { + scanner.close(); + return; + } else { + System.out.println("Unknown command '" + command + + "'. Type 'help' for a list of commands"); + } + scanner.close(); + } + } + + // RUNNING + + public static void main(String[] args) { + System.out + .println("********** mJAM Interpreter (Version 1.2) **********"); + + String objectFileName; + if (args.length >= 1) + objectFileName = args[0]; + else + objectFileName = "obj.mJAM"; + + String sourceFileName; + if (args.length >= 2) { + sourceFileName = args[1]; + debug(objectFileName, sourceFileName); + } else { + interpret(objectFileName); + } + } + + public static void interpret(String objectFileName) { + + ObjectFile objectFile = new ObjectFile(objectFileName); + if (objectFile.read()) { + System.out.println("Unable to load object file " + objectFileName); + return; + } + interpretProgram(); + showStatus(); + } + + public static void debug(String objectFileName, String sourceFileName) { + ObjectFile objectFile = new ObjectFile(objectFileName); + if (objectFile.read()) { + System.out.println("Unable to load object file " + objectFileName); + return; + } + + sourceLines = new ArrayList(); + try { + BufferedReader reader = new BufferedReader(new FileReader(new File( + sourceFileName))); + String line = reader.readLine(); + while (line != null) { + sourceLines.add(line); + line = reader.readLine(); + } + reader.close(); + } catch (FileNotFoundException e) { + System.out.println("Unable to load source file " + sourceFileName); + return; + } catch (IOException ie) { + System.out.println("Unable to load source file " + sourceFileName); + return; + } + + debugProgram(); + } +} diff --git a/src/mJAM/Machine.java b/src/mJAM/Machine.java new file mode 100644 index 0000000..fb3b838 --- /dev/null +++ b/src/mJAM/Machine.java @@ -0,0 +1,232 @@ +package mJAM; + +/** + * Defines names and sizes of mJAM instructions and primitives + * @author prins + * @version COMP 520 V2.2 + */ +public final class Machine { + + /** + * mJAM instructions + */ + public enum Op { + LOAD, + LOADA, + LOADI, + LOADL, + STORE, + STOREI, + CALL, // direct call of instance method + CALLI, // indirect call of instance method + CALLD, // dynamic call of instance method + RETURN, + PUSH, + POP, + JUMP, + JUMPI, + JUMPIF, + HALT; + } + public static Op [] intToOp = Op.values(); + + + /** + * mJAM registers + */ + public enum Reg { + ZR, // zero, not used + CB, // code base + CT, // code top + CP, // code pointer + PB, // primitives base + PT, // primitives top + SB, // execution stack base + ST, // execution stack top + LB, // locals base + HB, // heap base + HT, // heap top + OB; // object base + } + public static Reg [] intToReg = Reg.values(); + + /** + * mJAM primitives + */ + public enum Prim { + id, + not, + and, + or, + succ, + pred, + neg, + add, + sub, + mult, + div, + mod, + lt, + le, + ge, + gt, + eq, + ne, + eol, + eof, + get, + put, + geteol, + puteol, + getint, + putint, + putintnl, + alloc, + dispose, + newobj, + newarr, + arrayref, + arrayupd, + fieldref, + fieldupd; + } + public static Prim [] intToPrim = Prim.values(); + + + +// range for int constants + public final static long + minintRep = -2147483648, + maxintRep = 2147483647; + + + // CODE STORE REGISTERS + public final static int CB = 0; // start of code space + public final static int PB = 1024; // size of code space reserved for instructions + public final static int PT = PB + Prim.values().length; // code space reserved for primitives + + // CODE STORE + public static Instruction[] code = new Instruction[PB]; + public static int CT = CB; + + public static void initCodeGen() { + CT = CB; + } + + /** + * Places an instruction, with the given fields, into the next position in the code store + * @param op - operation + * @param n - length + * @param r - register + * @param d - displacement + */ +public static void emit(Op op, int n, Reg r, Prim d) { + emit(op.ordinal(), n, r.ordinal(), d.ordinal()); + } + +/** + * emit operation with single literal argument d (n,r not used). These are + * operations like LOADL 44, PUSH 3, and CALLD 1 + */ +public static void emit(Op op, int d) { + emit(op.ordinal(), 0, 0, d); +} + + /** + * emit "call primitive operation" (operation built-in to mJAM). This + * generates CALL primitiveop[PB] + */ + public static void emit(Prim d) { + emit(Op.CALL.ordinal(), 0, Machine.Reg.PB.ordinal(), d.ordinal()); + } + + /** + * emit operations without arguments. These are operations like + * LOADI and STOREI + */ + public static void emit(Op op) { + emit(op, 0, 0, 0); + } + + /** + * emit operation with register r and integer displacement. These are + * operations like JUMP 25[CB] and LOAD 6[LB] + */ + public static void emit(Op op, Reg r, int d) { + emit(op.ordinal(), 0, r.ordinal(), d); + } + + /** + * emit operation with n field, and register r and integer displacement. These are + * operations like JUMPIF (1) 25[CB]. In the assembly code the value of n is shown + * in parens. + */ + public static void emit(Op op, int n, Reg r, int d) { + emit(op.ordinal(), n, r.ordinal(), d); + } + + /** + * emit operation with integer n, r, d. These are operations + * like RETURN (1) 3 and HALT (4) 0. For RETURN the value + * of d is the number of caller args to pop off the callers + * stack and n is the number of values to return at caller stack + * top. n must be 0 or 1. + */ + public static void emit(Op op, int n, int r, int d) { + emit(op.ordinal(), n, r, d); + } + + /** + * helper operation for emit using integer values + */ +private static void emit (int op, int n, int r, int d) { + if (n > 255) { + System.out.println("length of operand can't exceed 255 words"); + n = 255; // to allow code generation to continue + } + if (CT >= Machine.PB) + System.out.println("mJAM: code segment capacity exceeded"); + + Instruction nextInstr = new Instruction(op, n, r, d); + Machine.code[CT] = nextInstr; + CT = CT + 1; + } + +/** + * @return address (relative to CB) of next instruction to be generated + */ +public static int nextInstrAddr() { + return CT; +} + +/** + * Update the displacement component of the (JUMP or CALL) instruction at addr + * @param addr + * @param displacement + */ +public static void patch(int addr, int displacement) { + if (addr < 0 || addr >= CT) { + System.out.println("patch: address of instruction to be patched is out of range"); + return; + } + if (displacement < 0 || displacement > CT) { + System.out.println("patch: target address of patch is out of range"); + return; + } + Machine.code[addr].d = displacement; + return; +} + +// DATA REPRESENTATION + + public final static int + booleanSize = 1, + characterSize = 1, + integerSize = 1, + addressSize = 1, + linkDataSize = 3 * addressSize, // caller's OB, LB, CP + falseRep = 0, + trueRep = 1, + nullRep = 0; + +} diff --git a/src/mJAM/ObjectFile.java b/src/mJAM/ObjectFile.java new file mode 100644 index 0000000..a164b3c --- /dev/null +++ b/src/mJAM/ObjectFile.java @@ -0,0 +1,70 @@ +/** + * Reads and writes mJAM object files + * @author prins + * @version COMP 520 V2.2 + */ +package mJAM; + +import java.io.FileInputStream; +import java.io.DataInputStream; +import java.io.FileOutputStream; +import java.io.DataOutputStream; + +public class ObjectFile { + + String objectFileName; + + public ObjectFile(String objectFileName) { + super(); + this.objectFileName = objectFileName; + } + + /** + * Write code store as object file + * @param output object file + * @return true if write fails + */ + public boolean write(){ + boolean failed = false; + try { + FileOutputStream objectFile = new FileOutputStream(objectFileName); + DataOutputStream is = new DataOutputStream(objectFile); + for (int i = Machine.CB; i < Machine.CT; i++ ){ + Instruction inst = Machine.code[i]; + is.writeInt(inst.op); + is.writeInt(inst.n); + is.writeInt(inst.r); + is.writeInt(inst.d); + } + objectFile.close(); + } + catch (Exception e) {failed = true;} + return failed; + } + + /** + * Read object file into code store, setting CT + * @return true if object code read fails + */ + public boolean read() { + boolean failed = false; + try { + FileInputStream objectFile = new FileInputStream(objectFileName); + DataInputStream is = new DataInputStream(objectFile); + + Machine.CT = Machine.CB; + while (is.available() > 0 && Machine.CT < Machine.PB){ + Instruction inst = new Instruction(); + inst.op = is.readInt(); + inst.n = is.readInt(); + inst.r = is.readInt(); + inst.d = is.readInt(); + Machine.code[Machine.CT++] = inst; + } + objectFile.close(); + } catch (Exception e) { + failed = true; + } + return failed; + } +} diff --git a/src/mJAM/Test.java b/src/mJAM/Test.java new file mode 100644 index 0000000..b886ca9 --- /dev/null +++ b/src/mJAM/Test.java @@ -0,0 +1,158 @@ +/** + * Example illustrating components of mJAM package + * @author prins + * @version COMP 520 V2.2 + */ +package mJAM; +import mJAM.Machine.Op; +import mJAM.Machine.Reg; +import mJAM.Machine.Prim; + +// test class to construct and run an mJAM program +public class Test +{ + public static void main(String[] args){ + + Machine.initCodeGen(); + System.out.println("Generating test program object code"); + + /* class A { + * int x; + * int p(){return x;} + * } + */ + Machine.emit(Op.LOADL,11); // hello + Machine.emit(Prim.putintnl); + int patchme_coA = Machine.nextInstrAddr(); + Machine.emit(Op.JUMP,Reg.CB,0); // jump around methods of class A (branch to /*coA*/) + + // code for p() in A + int label_pA = Machine.nextInstrAddr(); +/*pA*/ Machine.emit(Op.LOAD,Reg.OB,0); // x at offset 0 in current instance of A + Machine.emit(Op.HALT,4,0,0); + Machine.emit(Op.RETURN,1,0,0); // return one value, pop zero args + + // build class object for A at 0[SB] + int label_coA = Machine.nextInstrAddr(); + Machine.patch(patchme_coA, label_coA); +/*coA*/ Machine.emit(Op.LOADL,-1); // no superclass object + Machine.emit(Op.LOADL,1); // number of methods + Machine.emit(Op.LOADA,Reg.CB,label_pA); // code addr of p_A + + + /* class B extends A { + * int y; + * int p(){return x + 22;} + * } + */ + int patchme_coB = Machine.nextInstrAddr(); + Machine.emit(Op.JUMP,Reg.CB,0); // branch around methods in class B + + // code for p() in B + int label_pB = Machine.nextInstrAddr(); +/*pB*/ Machine.emit(Op.LOAD,Reg.OB,0); // x at offset 0 in current instance + Machine.emit(Op.LOADL,22); + Machine.emit(Op.HALT,4,0,0); + Machine.emit(Prim.add); + Machine.emit(Op.RETURN,1,0,0); // return one value, pop zero args + + // build class object for B at 3[SB] + int label_coB = Machine.nextInstrAddr(); + Machine.patch(patchme_coB, label_coB); +/*coB*/ Machine.emit(Op.LOADA,Reg.SB,0); // addr of superclass object + Machine.emit(Op.LOADL,1); // number of methods + Machine.emit(Op.LOADA,Reg.CB,label_pB); // code addr of p_B + + + /* class C { + * public static void main(String [] args) { + * A a = new A(); + * a.x = 33; + * System.out.println(a.p()); + * ... + */ + int patchme_coC = Machine.nextInstrAddr(); + Machine.emit(Op.JUMP,Reg.CB,0); // branch around methods of class C + + // code for main() in C + int label_mainC = Machine.nextInstrAddr(); +/*mainC*/ Machine.emit(Op.HALT,4,0,0); + // local var "a" will be at 3[LB] after init + Machine.emit(Op.LOADA,Reg.SB,0); // class descriptor for A + Machine.emit(Op.LOADL,1); // size of A + Machine.emit(Prim.newobj); // result addr becomes value of "a" + Machine.emit(Op.LOAD,Reg.LB,3); // value of "a" (heap addr) + Machine.emit(Op.LOADL,0); // "x" is field 0 in A + Machine.emit(Op.LOADL,33); // new value 33 + Machine.emit(Op.HALT,4,0,0); + Machine.emit(Prim.fieldupd); // a.x = 33 + Machine.emit(Op.LOAD,Reg.LB,3); // addr of instance "a" on heap + Machine.emit(Op.CALLI,Reg.CB,label_pA); // call to known instance method p_A + Machine.emit(Prim.putintnl); // print result + + /* ... + * A b = new B(); + * b.x = 44; + * System.out.println(b.p()); + * } // end main + * } // end class C + */ + // local var "b" will be at 4[LB] after init + Machine.emit(Op.LOADA,Reg.SB,3); // class descriptor for B + Machine.emit(Op.LOADL,2); // size of B + Machine.emit(Prim.newobj); // result addr becomes value of "b" + Machine.emit(Op.LOAD,Reg.LB,4); // fetch b + Machine.emit(Op.LOADL,0); // field 0 + Machine.emit(Op.LOADL,44); // b.x = 44 + Machine.emit(Prim.fieldupd); + Machine.emit(Op.HALT,4,0,0); + Machine.emit(Op.LOAD,Reg.LB,4); // addr of instance "b" + Machine.emit(Op.CALLD,0); // dynamic call, method index 0 (= method p) + Machine.emit(Prim.putintnl); // print result + Machine.emit(Op.RETURN,0,0,1); // return no value (void), pop 1 arg (= String [] args) + + // build class descriptor for C at 6[SB] + int label_coC = Machine.nextInstrAddr(); + Machine.patch(patchme_coC, label_coC); +/*coC*/ Machine.emit(Op.LOADL,-1); // no superclass object + Machine.emit(Op.LOADL,0); // number of methods = 0 + + /* + * End of class declarations - call main + */ + Machine.emit(Op.LOADL,Machine.nullRep); // put null on stack as value of main's arg + Machine.emit(Op.CALL,Reg.CB,label_mainC); // call known static main() + Machine.emit(Op.LOADL,88); // goodbye + Machine.emit(Prim.putintnl); + Machine.emit(Machine.Op.HALT,0,0,0); // halt + + /* write code as an object file */ + String objectCodeFileName = "test.mJAM"; + ObjectFile objF = new ObjectFile(objectCodeFileName); + System.out.print("Writing object code file " + objectCodeFileName + " ... "); + if (objF.write()) { + System.out.println("FAILED!"); + return; + } + else + System.out.println("SUCCEEDED"); + + /* create asm file using disassembler */ + String asmCodeFileName = "test.asm"; + System.out.print("Writing assembly file ... "); + Disassembler d = new Disassembler(objectCodeFileName); + if (d.disassemble()) { + System.out.println("FAILED!"); + return; + } + else + System.out.println("SUCCEEDED"); + + /* run code */ + System.out.println("Running code ... "); + Interpreter.debug(objectCodeFileName, asmCodeFileName); + // Interpreter.interpret(objectCodeFileName); + + System.out.println("*** mJAM execution completed"); + } +} diff --git a/src/miniJava/AbstractSyntaxTrees/Declaration.java b/src/miniJava/AbstractSyntaxTrees/Declaration.java index 8859aa4..a412631 100644 --- a/src/miniJava/AbstractSyntaxTrees/Declaration.java +++ b/src/miniJava/AbstractSyntaxTrees/Declaration.java @@ -5,6 +5,7 @@ */ package miniJava.AbstractSyntaxTrees; +import miniJava.CodeGenerator.RuntimeEntity; import miniJava.SyntacticAnalyzer.SourcePosition; public abstract class Declaration extends AST { @@ -25,6 +26,7 @@ public abstract class Declaration extends AST { } } + public RuntimeEntity entity; public String name; public Type type; } diff --git a/src/miniJava/AbstractSyntaxTrees/Reference.java b/src/miniJava/AbstractSyntaxTrees/Reference.java index 7a6e351..7998896 100644 --- a/src/miniJava/AbstractSyntaxTrees/Reference.java +++ b/src/miniJava/AbstractSyntaxTrees/Reference.java @@ -5,6 +5,7 @@ */ package miniJava.AbstractSyntaxTrees; +import miniJava.CodeGenerator.RuntimeEntity; import miniJava.SyntacticAnalyzer.SourcePosition; public abstract class Reference extends AST { @@ -13,4 +14,5 @@ public abstract class Reference extends AST { } public Declaration decl; + public RuntimeEntity entity; } diff --git a/src/miniJava/CodeGenerator/Encoder.java b/src/miniJava/CodeGenerator/Encoder.java new file mode 100644 index 0000000..5773186 --- /dev/null +++ b/src/miniJava/CodeGenerator/Encoder.java @@ -0,0 +1,698 @@ +package miniJava.CodeGenerator; + +import mJAM.Machine; +import mJAM.Machine.*; +import miniJava.AbstractSyntaxTrees.*; +import miniJava.AbstractSyntaxTrees.Package; +import miniJava.ContextualAnalyzer.Analyzer; + +public class Encoder implements Visitor { + + private int mdLBOffset = 0; + + // ///////////////////////////////////////////////////////////////////////////// + // + // Convenience Functions + // + // ///////////////////////////////////////////////////////////////////////////// + + /** + * Get size of type for declaration purposes. + * @param t + * @return + */ + private int getSize(Type t) { + switch(t.typeKind) { + case ARRAY: + case CLASS: + return Machine.addressSize; + case INT: + return Machine.integerSize; + case BOOLEAN: + return Machine.booleanSize; + case VOID: + return 0; + default: + return -1; + } + } + + + // ///////////////////////////////////////////////////////////////////////////// + // + // PACKAGE + // + // ///////////////////////////////////////////////////////////////////////////// + + @Override + public Integer visitPackage(Package prog, Integer arg) { + + // Initialize static fields + for(ClassDecl cd : prog.classDeclList) { + for(int i = 0; i < cd.fieldDeclList.size(); i++) { + if(cd.fieldDeclList.get(i).isStatic) { + cd.fieldDeclList.get(i).visit(this, i); + } + } + } + + int patch = Machine.nextInstrAddr(); + Machine.emit(Op.JUMP, Reg.CB, 0); + + // Initialize all classes and methods + for(ClassDecl cd : prog.classDeclList) { + cd.visit(this, 1); + } + + // Link classes & build stack + Machine.patch(patch, Machine.nextInstrAddr()); + for(ClassDecl cd : prog.classDeclList) { + int cdPatch = Machine.nextInstrAddr(); + Machine.emit(Op.JUMP, Reg.CB, 0); + Machine.patch(cdPatch, cd.visit(this, 0)); + } + + // Build main function + mdLBOffset = Machine.linkDataSize; + int mainPatch = Machine.nextInstrAddr(); + Machine.emit(Op.JUMP, Reg.CB, 0); + Analyzer.mainMethod.visit(this, 0); + + + // Run main function + int mainLabel = Machine.nextInstrAddr(); + RuntimeEntity main = Analyzer.mainMethod.entity; + + Machine.emit(Op.LOADL, Machine.nullRep); + Machine.emit(Op.CALL, Reg.CB, main.addr); + Machine.emit(Machine.Op.HALT, 0 ,0, 0); + Machine.patch(mainPatch, mainLabel); + + return 0; + } + + + // ///////////////////////////////////////////////////////////////////////////// + // + // DECLARATIONS + // + // ///////////////////////////////////////////////////////////////////////////// + + @Override + public Integer visitClassDecl(ClassDecl cd, Integer init) { + + /* Give an address to each method and class, so it can be referred + * to directly by jumping. + */ + if(init != 0) { + + // Get size of class + int size = 0; + for(FieldDecl fd : cd.fieldDeclList) { + if(!fd.isStatic) { + fd.visit(this, size); + size += getSize(fd.type); + } + } + + // Build Methods + for(MethodDecl md : cd.methodDeclList) { + md.visit(this, init); + } + + // Runtime Entity + int addr = Machine.nextInstrAddr(); + cd.entity = new RuntimeEntity(size, addr, Reg.CB, addr); + Machine.emit(Op.JUMP, Reg.CB, 0); + + return addr; + } + + // Build Instance Methods + for(MethodDecl md : cd.methodDeclList) { + if(!md.isStatic) { + md.visit(this, 0); + } + } + + // Build class descriptor + int label = Machine.nextInstrAddr(); + Machine.patch(cd.entity.instr, label); + + Machine.emit(Op.LOADL, -1); // No superclasses allowed + Machine.emit(Op.LOADL, cd.entity.size); // Size of class + Machine.emit(Op.LOADA, Reg.CB, cd.entity.addr); // Address of class + + return label; + } + + @Override + public Integer visitFieldDecl(FieldDecl fd, Integer addr) { + + int size = getSize(fd.type); + + // Static fields are placed onto the stack + if(fd.isStatic) { + fd.entity = new RuntimeEntity(size, addr, Reg.SB); + Machine.emit(Op.PUSH, size); + } else { + fd.entity = new RuntimeEntity(size, addr, Reg.OB); + } + + return 0; + } + + + @Override + public Integer visitMethodDecl(MethodDecl md, Integer init) { + + mdLBOffset = Machine.linkDataSize; + + if(init != 0) { + int size = getSize(md.type); + int addr = Machine.nextInstrAddr(); + md.entity = new RuntimeEntity(size, addr, Reg.CB, addr); + Machine.emit(Op.JUMP, Reg.CB, 0); + } + + else { + // Setup parameters + int paramS = 0; + for(ParameterDecl pd : md.parameterDeclList) { + paramS += getSize(md.type); + pd.visit(this, -paramS); + } + + // Setup body + int addr = Machine.nextInstrAddr(); + Machine.patch(md.entity.instr, addr); + for(Statement s : md.statementList) { + s.visit(this, 0); + } + + // Setup return statement + if(md.returnExp != null) { + md.returnExp.visit(this, 0); + } + + // Return from function + // Machine.emit(Op.HALT, 4, 0, 0); (See snapshot) + Machine.emit(Op.RETURN, md.entity.size, 0, paramS); + } + + return 0; + } + + /** + * Note parameter addresses are negative to LB since they are + * placed onto the stack before emitting a CALL. + */ + @Override + public Integer visitParameterDecl(ParameterDecl pd, Integer addr) { + + // Builds runtime entity, offset at LB + int size = getSize(pd.type); + pd.entity = new RuntimeEntity(size, addr, Reg.LB); + + return 0; + } + + /** + * Variable declarations are never disposed (even in static closures). + */ + @Override + public Integer visitVarDecl(VarDecl decl, Integer addr) { + + // Builds runtime entity, offset at LB + int size = getSize(decl.type); + decl.entity = new RuntimeEntity(size, addr, Reg.LB); + + // Allocates space on stack + Machine.emit(Op.PUSH, size); + + return 0; + } + + + // ///////////////////////////////////////////////////////////////////////////// + // + // TYPES + // + // ///////////////////////////////////////////////////////////////////////////// + + @Override + public Integer visitBaseType(BaseType type, Integer arg) { + + return 0; + } + + @Override + public Integer visitClassType(ClassType type, Integer arg) { + + return 0; + } + + @Override + public Integer visitArrayType(ArrayType type, Integer arg) { + + return 0; + } + + + // ///////////////////////////////////////////////////////////////////////////// + // + // STATEMENTS + // + // ///////////////////////////////////////////////////////////////////////////// + + /** + * We note a block statement may contain a variable declaration, + * that should go out of scope at the end of the statement. + * If the passed argument is 1, we indicate we want this to + * happen. + */ + @Override + public Integer visitBlockStmt(BlockStmt stmt, Integer arg) { + + // Execute statements + int size = 0; + for(Statement s : stmt.sl) { + s.visit(this, 0); + + // Push variable declarations if necessary + if(arg == 1 && s instanceof VarDeclStmt) { + VarDeclStmt decl = (VarDeclStmt) s; + size += getSize(decl.varDecl.type); + } + } + + // Pop off variable declarations + Machine.emit(Op.POP, size); + + return 0; + } + + /** + * We declare the variable, place the RHS onto the stack, and + * replace the value of the variable with the top of the stack. + */ + @Override + public Integer visitVardeclStmt(VarDeclStmt stmt, Integer arg) { + stmt.varDecl.visit(this, mdLBOffset); + stmt.initExp.visit(this, 0); + + // Assign value + RuntimeEntity e = stmt.varDecl.entity; + Machine.emit(Op.STORE, e.size, e.register, e.addr); + + // Update position + mdLBOffset += getSize(stmt.varDecl.type); + + return 0; + } + + @Override + public Integer visitAssignStmt(AssignStmt stmt, Integer arg) { + + stmt.ref.visit(this, 0); + + // Setup + RuntimeEntity e = stmt.ref.entity; + + // Build code accordingly + if(stmt.ref instanceof QualifiedRef) { + e.parent.load(); + Machine.emit(Op.LOADL, e.addr); + stmt.val.visit(this, 0); + Machine.emit(Prim.fieldupd); + } + + else if(stmt.ref instanceof IndexedRef) { + IndexedRef ref = (IndexedRef) stmt.ref; + e.load(); + ref.indexExpr.visit(this, 0); + stmt.val.visit(this, 0); + Machine.emit(Prim.arrayupd); + } + + else if(stmt.ref instanceof IdRef) { + stmt.val.visit(this, 0); + Machine.emit(Op.STORE, e.size, e.register, e.addr); + } + + return 0; + } + + /** + * The method in question can either be an instance or static + * function (it must be a MethodDecl). + */ + @Override + public Integer visitCallStmt(CallStmt stmt, Integer arg) { + + MethodDecl md = (MethodDecl) stmt.methodRef.decl; + + // Request to print out + if(md == Analyzer.println) { + stmt.argList.get(0).visit(this, 0); + Machine.emit(Prim.putintnl); + return 0; + } + + // Push parameters on (must iterate in reverse order) + for(int i = stmt.argList.size() - 1; i >= 0; i--) { + stmt.argList.get(i).visit(this, 0); + } + + // Call Method + stmt.methodRef.visit(this, 0); + if(md.isStatic) { + Machine.emit(Op.CALL, Reg.CB, md.entity.addr); + } else { + stmt.methodRef.entity.call(); + Machine.emit(Op.CALLI, Reg.CB, md.entity.addr); + } + + return 0; + } + + @Override + public Integer visitIfStmt(IfStmt stmt, Integer arg) { + + stmt.cond.visit(this, 0); + + // Build Then Statement + int ifPatch = Machine.nextInstrAddr(); + Machine.emit(Op.JUMPIF, Machine.trueRep, Reg.CB, 0); + + int elsePatch = Machine.nextInstrAddr(); + Machine.emit(Op.JUMP, Reg.CB, 0); + + int thenLabel = Machine.nextInstrAddr(); + stmt.thenStmt.visit(this, 0); + + int thenPatch = Machine.nextInstrAddr(); + Machine.emit(Op.JUMP, Reg.CB, 0); + + // Connect labels/patches + int endLabel = Machine.nextInstrAddr(); + Machine.patch(elsePatch, endLabel); + + if(stmt.elseStmt != null) { + stmt.elseStmt.visit(this, 0); + endLabel = Machine.nextInstrAddr(); + } + + Machine.patch(ifPatch, thenLabel); + Machine.patch(thenPatch, endLabel); + + return 0; + } + + /** + * We note since the same declaration can be reached multiple times, we + * are forced to pop each declaration initialized. + */ + @Override + public Integer visitWhileStmt(WhileStmt stmt, Integer arg) { + + // Must check the condition each loop + int whileLabel = Machine.nextInstrAddr(); + stmt.cond.visit(this, 0); + + // Jump out once condition fails + int whileEndPatch = Machine.nextInstrAddr(); + Machine.emit(Op.JUMPIF, Machine.falseRep, Reg.CB, 0); + + // Execute + stmt.body.visit(this, 1); + Machine.emit(Op.JUMP, Reg.CB, whileLabel); + Machine.patch(whileEndPatch, Machine.nextInstrAddr()); + + return 0; + } + + + // ///////////////////////////////////////////////////////////////////////////// + // + // EXPRESSIONS + // + // ///////////////////////////////////////////////////////////////////////////// + + @Override + public Integer visitUnaryExpr(UnaryExpr expr, Integer arg) { + + expr.expr.visit(this, 0); + switch(expr.operator.spelling) { + case "!": + Machine.emit(Prim.not); + break; + case "-": + Machine.emit(Prim.neg); + break; + } + + return 0; + } + + @Override + public Integer visitBinaryExpr(BinaryExpr expr, Integer arg) { + expr.left.visit(this, 0); + expr.right.visit(this, 0); + expr.operator.visit(this, 0); + + return 0; + } + + @Override + public Integer visitRefExpr(RefExpr expr, Integer arg) { + + expr.ref.visit(this, 0); + + // Build code accordingly + if(expr.ref instanceof QualifiedRef) { + + // Must be requesting length of array + if(expr.ref.decl.type.typeKind == TypeKind.ARRAY) { + int size = Machine.integerSize; + int addr = expr.ref.entity.addr + size; + Machine.emit(Op.LOAD, size, Reg.LB, addr); + } else { + expr.ref.entity.load(); + } + } + + else if(expr.ref instanceof IndexedRef) { + IndexedRef ref = (IndexedRef) expr.ref; + ref.entity.load(); + ref.indexExpr.visit(this, 0); + Machine.emit(Prim.arrayref); + } + + else if(expr.ref instanceof ThisRef) { + RuntimeEntity e = expr.ref.entity; + Machine.emit(Op.LOADA, e.size, e.register, e.addr); + } + + else { + expr.ref.entity.load(); + } + + return 0; + } + + @Override + public Integer visitCallExpr(CallExpr expr, Integer arg) { + + // Push parameters on (must iterate in reverse order) + for(int i = expr.argList.size() - 1; i >= 0; i--) { + expr.argList.get(i).visit(this, 0); + } + + // Call method + MethodDecl md = (MethodDecl) expr.functionRef.decl; + expr.functionRef.visit(this, 0); + if(md.isStatic) { + Machine.emit(Op.CALL, Reg.CB, md.entity.addr); + } else { + expr.functionRef.entity.call(); + Machine.emit(Op.CALLI, Reg.CB, md.entity.addr); + } + + return 0; + } + + @Override + public Integer visitLiteralExpr(LiteralExpr expr, Integer arg) { + + expr.literal.visit(this, 0); + + return 0; + } + + @Override + public Integer visitNewObjectExpr(NewObjectExpr expr, Integer arg) { + + RuntimeEntity e = expr.classtype.className.decl.entity; + Machine.emit(Op.LOADA, e.register, e.addr); + Machine.emit(Op.LOADL, e.size); + Machine.emit(Prim.newobj); + + return 0; + } + + /** + * Returns the address where the new array's size is being + * stored (as it cannot be easily accessed otherwise). + */ + @Override + public Integer visitNewArrayExpr(NewArrayExpr expr, Integer arg) { + + // Setup + int addr = mdLBOffset; + int size = Machine.integerSize; + mdLBOffset += Machine.integerSize; + + // Add to stack + expr.sizeExpr.visit(this, 0); + + // Create new array + Machine.emit(Op.LOAD, size, Reg.LB, addr); + Machine.emit(Prim.newarr); + + return addr; + } + + + // ///////////////////////////////////////////////////////////////////////////// + // + // REFERENCES + // + // ///////////////////////////////////////////////////////////////////////////// + + @Override + public Integer visitQualifiedRef(QualifiedRef ref, Integer arg) { + + ref.ref.visit(this, 0); + + // Must be accessing length of an array + if(ref.ref.decl.type.typeKind == TypeKind.ARRAY) { + ref.decl = ref.ref.decl; + ref.entity = ref.ref.entity; + } + + // Access class member + else { + ref.entity = ref.id.decl.entity; + ref.entity.parent = ref.ref.entity; + } + + return 0; + } + + @Override + public Integer visitIndexedRef(IndexedRef ref, Integer arg) { + + ref.entity = ref.decl.entity; + + return 0; + } + + @Override + public Integer visitIdRef(IdRef ref, Integer arg) { + + ref.entity = ref.decl.entity; + + return 0; + } + + @Override + public Integer visitThisRef(ThisRef ref, Integer arg) { + + RuntimeEntity e = ref.decl.entity; + ref.entity = new RuntimeEntity(e.size, 0, Reg.OB); + ref.entity.indirect = true; + + return 0; + } + + + // ///////////////////////////////////////////////////////////////////////////// + // + // TERMINALS + // + // ///////////////////////////////////////////////////////////////////////////// + + @Override + public Integer visitIdentifier(Identifier id, Integer arg) { + + return 0; + } + + @Override + public Integer visitOperator(Operator op, Integer arg) { + + switch(op.token.spelling) { + case "+": + Machine.emit(Prim.add); + break; + case "-": + Machine.emit(Prim.sub); + break; + case "*": + Machine.emit(Prim.mult); + break; + case "/": + Machine.emit(Prim.div); + break; + case "<": + Machine.emit(Prim.lt); + break; + case ">": + Machine.emit(Prim.gt); + break; + case "<=": + Machine.emit(Prim.le); + break; + case ">=": + Machine.emit(Prim.ge); + break; + case "==": + Machine.emit(Prim.eq); + break; + case "!=": + Machine.emit(Prim.ne); + break; + case "&&": + Machine.emit(Prim.and); + break; + case "||": + Machine.emit(Prim.or); + break; + } + + return 0; + } + + @Override + public Integer visitIntLiteral(IntLiteral num, Integer arg) { + + Integer lit = Integer.parseInt(num.spelling); + Machine.emit(Op.LOADL, lit.intValue()); + + return 0; + } + + @Override + public Integer visitBooleanLiteral(BooleanLiteral bool, Integer arg) { + + if(bool.spelling.equals("true")) { + Machine.emit(Op.LOADL, Machine.trueRep); + } else { + Machine.emit(Op.LOADL, Machine.falseRep); + } + + return 0; + } + +} diff --git a/src/miniJava/CodeGenerator/RuntimeEntity.java b/src/miniJava/CodeGenerator/RuntimeEntity.java new file mode 100644 index 0000000..a05ebce --- /dev/null +++ b/src/miniJava/CodeGenerator/RuntimeEntity.java @@ -0,0 +1,52 @@ +package miniJava.CodeGenerator; + +import mJAM.Machine; +import mJAM.Machine.Op; +import mJAM.Machine.Prim; +import mJAM.Machine.Reg; + +public class RuntimeEntity { + + public int size = -1; // Size of type + public int addr = -1; // Position relative to register + public int instr = -1; // Instruction relative to CB + public Reg register = Reg.ZR; // Register to offset by + + // For use with nested elements + public boolean indirect = false; + public RuntimeEntity parent = null; + + public RuntimeEntity(int size, int addr, Reg register) { + this(size, addr, register, -1); + } + + public RuntimeEntity(int size, int addr, Reg register, int instr) { + this.size = size; + this.addr = addr; + this.register = register; + this.instr = instr; + } + + // Load entity into memory (if this, should call LOADA) + public void load() { + if(parent != null) { + parent.load(); + Machine.emit(Op.LOADL, addr); + Machine.emit(Prim.fieldref); + } else { + if(indirect) { + Machine.emit(Op.LOADA, size, register, addr); + } else { + Machine.emit(Op.LOAD, size, register, addr); + } + } + } + + public void call() { + if(parent != null) { + parent.load(); + } else { + Machine.emit(Op.LOADA, size, Reg.OB, 0); + } + } +} diff --git a/src/miniJava/Compiler.java b/src/miniJava/Compiler.java index c5e46ac..f137eb9 100644 --- a/src/miniJava/Compiler.java +++ b/src/miniJava/Compiler.java @@ -2,10 +2,11 @@ package miniJava; import java.io.*; +import mJAM.*; import miniJava.SyntacticAnalyzer.*; -// import miniJava.AbstractSyntaxTrees.ASTDisplay; import miniJava.AbstractSyntaxTrees.Package; import miniJava.ContextualAnalyzer.Analyzer; +import miniJava.CodeGenerator.Encoder; import miniJava.Exceptions.*; public class Compiler { @@ -25,15 +26,28 @@ public class Compiler { Scanner scanner = new Scanner(new BufferedReader(input)); Parser parser = new Parser(scanner); Package p = parser.parse(); - - // Display - // ASTDisplay display = new ASTDisplay(); - // display.showTree(p); // Identification/Type Checking Analyzer analyzer = new Analyzer(); analyzer.visitPackage(p, null); - System.exit(analyzer.validate()); + int analyzed = analyzer.validate(); + + // Begin Compilation to mJAM + if(analyzed == 0) { + + Encoder encoder = new Encoder(); + encoder.visitPackage(p, null); + + // Create object file + int pos = args[0].lastIndexOf(".java"); + String objectFileName = args[0].substring(0, pos) + ".mJAM"; + ObjectFile objF = new ObjectFile(objectFileName); + if(objF.write()) { + System.out.println("***Object File Failed."); + } + } + + System.exit(analyzed); } catch (FileNotFoundException e) { System.out.println("***" + e.getMessage()); diff --git a/src/miniJava/ContextualAnalyzer/Analyzer.java b/src/miniJava/ContextualAnalyzer/Analyzer.java index 36cf6f8..9a23e8a 100644 --- a/src/miniJava/ContextualAnalyzer/Analyzer.java +++ b/src/miniJava/ContextualAnalyzer/Analyzer.java @@ -11,10 +11,18 @@ import miniJava.AbstractSyntaxTrees.Package; public class Analyzer implements Visitor { - private MethodDecl mainMethod = null; + public static MethodDecl mainMethod = null; + public static MethodDecl println = null; + + // Notifies if handling predefined as opposed to user defined code + private boolean predefinedGen = true; + + // Keeps track of declarations + private VarDecl currentVarDecl = null; private ClassDecl currentClassDecl = null; private MethodDecl currentMethodDecl = null; - private VarDecl currentVarDecl = null; + + // Keeps track of identifcatoin private IdentificationTable table = new IdentificationTable(); // Keep track of all predefined names to handle @@ -38,6 +46,7 @@ public class Analyzer implements Visitor { Parser parser = new Parser(scanner); parser.parse().visit(this, table); } + predefinedGen = false; } /** @@ -134,6 +143,10 @@ public class Analyzer implements Visitor { } public Type visitMethodDecl(MethodDecl md, IdentificationTable arg) { + + if(predefinedGen && md.name.equals("println")) { + println = md; + } // Check if a valid entry point to program if (IdentificationTable.isMainMethod(md)) { @@ -420,8 +433,18 @@ public class Analyzer implements Visitor { Type refType = ref.ref.visit(this, arg); + // Array types only have the single 'length' field + if(refType.typeKind == TypeKind.ARRAY) { + if(!ref.id.spelling.equals("length")) { + Reporter.report(ErrorType.LENGTH, ref.id, null); + return refType; + } + + return new BaseType(TypeKind.INT, ref.id.posn); + } + // Note qualified ref's only make sense in the context of classes - if(refType.typeKind != TypeKind.CLASS) { + else if(refType.typeKind != TypeKind.CLASS) { if(refType.typeKind != TypeKind.ERROR) // Don't need to report multiple times Reporter.report(ErrorType.TYPE_MISMATCH, new BaseType(TypeKind.CLASS, null), refType); @@ -446,7 +469,7 @@ public class Analyzer implements Visitor { Reporter.report(ErrorType.UNDEFINED, ref.id, null); return new BaseType(TypeKind.ERROR, ref.id.posn); } - + // If the qualifed ref is a class declaration, members must be static (must check for 'this') else if(qualified instanceof ClassDecl && !(ref.ref instanceof ThisRef)) { if(!md.isStatic) { @@ -468,7 +491,7 @@ public class Analyzer implements Visitor { Reporter.report(ErrorType.VISIBILITY, md, ref.id); return new BaseType(TypeKind.ERROR, ref.id.posn); } - + ref.id.decl = md; ref.decl = md; return md.type; diff --git a/src/miniJava/ContextualAnalyzer/Reporter.java b/src/miniJava/ContextualAnalyzer/Reporter.java index d2baea2..66fd900 100644 --- a/src/miniJava/ContextualAnalyzer/Reporter.java +++ b/src/miniJava/ContextualAnalyzer/Reporter.java @@ -4,6 +4,7 @@ import miniJava.AbstractSyntaxTrees.*; enum ErrorType { THIS, + LENGTH, VOID_TYPE, CLASS_IDENTIFER, VARDECL_USED, @@ -72,9 +73,15 @@ public class Reporter { break; } + // Array types have the single field 'length' + case LENGTH: { + emit("Array types have only a single field 'length' (at " + a1.posn + ")."); + break; + } + // Can't use a class as an identifier solely case CLASS_IDENTIFER: { - emit("Cannot use class identifier by outside of a qualified reference at " + a1.posn); + emit("Cannot use class identifier outside of a qualified reference at " + a1.posn); break; }