package pp.iloc; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.util.Scanner; import pp.iloc.eval.Machine; import pp.iloc.model.Label; import pp.iloc.model.Num; import pp.iloc.model.Op; import pp.iloc.model.OpClaz; import pp.iloc.model.OpCode; import pp.iloc.model.Operand.Type; import pp.iloc.model.Program; import pp.iloc.parse.FormatException; /** * ILOC program simulator * @author Arend Rensink */ public class Simulator { /** Representation of true. */ public static final int TRUE = -1; /** Representation of false. */ public static final int FALSE = 0; /** Flag controlling debug mode. */ public static boolean DEBUG = false; static public void main(String[] args) { if (args.length == 0) { System.err.println("Usage: filename.iloc"); return; } try { Program prog = Assembler.instance().assemble(new File(args[0])); new Simulator(prog).run(); } catch (FormatException | IOException exc) { exc.printStackTrace(); } } /** The simulated program. */ private final Program prg; /** The virtual machine on which the program is run. */ private final Machine vm; /** Flag signifying that the input is from stdin. */ private boolean stdIn; /** The reader used for the in-operations. */ private Scanner in; /** The print writer used for the out-operations. */ private PrintStream out; /** Constructs a simulator for a given program and VM. */ public Simulator(Program program, Machine vm) { assert program != null; assert vm != null; this.prg = program; this.vm = vm; this.out = System.out; this.in = new Scanner(System.in); this.stdIn = true; } /** Constructs a simulator for a given program and a fresh VM. */ public Simulator(Program program) { this(program, new Machine()); } /** Returns the program wrapped in this simulator. */ public Program getProgram() { return this.prg; } /** Returns the Virtual Machine wrapped in this simulator. */ public Machine getVM() { return this.vm; } /** Changes the input stream for the {@link OpCode#in}-operations. */ public void setIn(InputStream in) { this.in = new Scanner(in); this.stdIn = false; } /** Changes the output stream for the {@link OpCode#out}-operations. */ public void setOut(OutputStream out) { this.out = new PrintStream(out); } /** Runs the program. */ public void run() { while (this.vm.getPC() < this.prg.size() && this.vm.getInterrupt() == 0) { step(); } } /** Executes one operation of the program. */ public void step() { Op o = this.prg.getOpAt(this.vm.getPC()); OPContext c = new OPContext(o); Machine vm = this.vm; String pre = c.toPreString(); switch (o.getOpCode()) { case nop: // do nothing break; case add: c.setReg(2, c.reg(0) + c.reg(1)); break; case sub: c.setReg(2, c.reg(0) - c.reg(1)); break; case mult: c.setReg(2, c.reg(0) * c.reg(1)); break; case div: c.setReg(2, c.reg(0) / c.reg(1)); break; case addI: c.setReg(2, c.reg(0) + c.num(1)); break; case subI: c.setReg(2, c.reg(0) - c.num(1)); break; case rsubI: c.setReg(2, c.num(1) - c.reg(0)); break; case multI: c.setReg(2, c.reg(0) * c.num(1)); break; case divI: c.setReg(2, c.reg(0) / c.num(1)); break; case rdivI: c.setReg(2, c.num(1) / c.reg(0)); break; case lshift: c.setReg(2, c.num(0) << c.reg(1)); break; case lshiftI: c.setReg(2, c.num(0) << c.num(1)); break; case rshift: c.setReg(2, c.num(0) >>> c.reg(1)); break; case rshiftI: c.setReg(2, c.num(0) >>> c.num(1)); break; case and: c.setReg(2, -1 * c.reg(0) * c.reg(1)); break; case andI: c.setReg(2, -1 * c.reg(0) * c.num(1)); break; case or: c.setReg(2, Math.max(-1, c.reg(0) + c.reg(1))); break; case orI: c.setReg(2, Math.max(-1, c.reg(0) + c.num(1))); break; case xor: c.setReg(2, Math.max(-1, c.reg(0) ^ c.reg(1))); break; case xorI: c.setReg(2, Math.max(-1, c.reg(0) ^ c.num(1))); break; case load: c.setReg(1, vm.load(c.reg(0))); break; case loadI: c.setReg(1, c.num(0)); break; case loadAI: c.setReg(2, vm.load(c.reg(0) + c.num(1))); break; case loadAO: c.setReg(2, vm.load(c.reg(0) + c.reg(1))); break; case store: vm.store(c.reg(0), c.reg(1)); break; case storeAI: vm.store(c.reg(0), c.reg(1) + c.num(2)); break; case storeAO: vm.store(c.reg(0), c.reg(1) + c.reg(2)); break; case cload: c.setReg(1, vm.loadC(c.reg(0))); break; case cloadAI: c.setReg(2, vm.loadC(c.reg(0) + c.num(1))); break; case cloadAO: c.setReg(2, vm.loadC(c.reg(0) + c.reg(1))); break; case cstore: vm.storeC(c.reg(0), c.reg(1)); break; case cstoreAI: vm.storeC(c.reg(0), c.reg(1) + c.num(2)); break; case cstoreAO: vm.storeC(c.reg(0), c.reg(1) + c.reg(2)); break; case i2i: c.setReg(1, c.reg(0)); break; case i2c: c.setReg(1, (byte) c.reg(0)); break; case c2i: c.setReg(1, (byte) c.reg(0)); break; case c2c: c.setReg(1, (byte) c.reg(0)); break; case cmp_LT: c.setReg(2, c.reg(0) < c.reg(1)); break; case cmp_LE: c.setReg(2, c.reg(0) <= c.reg(1)); break; case cmp_EQ: c.setReg(2, c.reg(0) == c.reg(1)); break; case cmp_GE: c.setReg(2, c.reg(0) >= c.reg(1)); break; case cmp_GT: c.setReg(2, c.reg(0) > c.reg(1)); break; case cmp_NE: c.setReg(2, c.reg(0) != c.reg(1)); break; case cbr: if (c.reg(0) == 0) { this.vm.setPC(c.label(2)); } else { this.vm.setPC(c.label(1)); } break; case jumpI: this.vm.setPC(c.label(0)); break; case jump: this.vm.setPC(c.reg(0)); break; case tbl: // do nothing break; case push: push(c.reg(0)); break; case pop: c.setReg(0, pop()); break; case cpush: pushC(c.reg(0)); break; case cpop: c.setReg(0, popC()); break; case in: String message = o.str(0).getText(); if (this.stdIn) { this.out.print(message); } String in = this.in.nextLine(); int val = Integer.MAX_VALUE; do { try { val = Integer.parseInt(in); } catch (NumberFormatException e) { // try again if (this.stdIn) { this.out.printf("Input '%s' should be a number%n", in); this.out.print(message); in = this.in.nextLine(); } else { throw new IllegalArgumentException(String.format( "Input '%s' should be a number%n", in)); } } } while (val == Integer.MAX_VALUE); c.setReg(1, val); break; case out: this.out.print(o.str(0).getText()); this.out.println(c.reg(1)); break; case cin: message = o.str(0).getText(); if (this.stdIn) { this.out.print(message); } in = this.in.nextLine(); pushString(in); break; case cout: this.out.print(o.str(0).getText()); this.out.println(popString()); break; case comment: // do nothing break; case halt: this.vm.setInterrupt(c.reg(0)); if (DEBUG) System.err.printf("Program halted with code %d at line %d.", c.reg(0), o.getLine()); break; case haltI: this.vm.setInterrupt(c.num(0)); if (DEBUG) System.err.printf("Program halted with code %d at line %d.", c.num(0), o.getLine()); break; default: System.err.printf( "Error at line %d: Operation %s not yet implemented%n", o.getLine(), o.getOpCode()); this.vm.incPC(); return; } if (DEBUG) { System.out.printf("Op %d: %s%n", vm.getPC(), o); System.out.printf("Op %d: %s%n", vm.getPC(), pre+c.toPostString()); System.out.println(vm); } if (o.getClaz() != OpClaz.CONTROL) { this.vm.incPC(); } } /** Pushes a 4-byte integer onto the stack. */ private void push(int val) { int sp = this.vm.getReg(Machine.SP) - 4; if (sp < this.vm.getReg(Machine.BRK)) throw new RuntimeException("Stack overflow"); this.vm.setReg(Machine.SP, sp); this.vm.store(val, sp); } /** Pushes a character onto the stack. */ private void pushC(int val) { int sp = this.vm.getReg(Machine.SP) - this.vm.getCharSize(); if (sp < this.vm.getReg(Machine.BRK)) throw new RuntimeException("Stack overflow"); this.vm.setReg(Machine.SP, sp); this.vm.storeC(val, sp); } /** Pushes a string onto the stack. * The string is represented by an integer length followed * by the chars of the string, with first char on top . */ private void pushString(String text) { for (int i = text.length() - 1; i >= 0; i--) { pushC(text.charAt(i)); } push(text.length()); } /** Pops a 4-byte integer from the stack. */ private int pop() { int sp = this.vm.getReg(Machine.SP); int result = this.vm.load(sp); if (DEBUG) this.vm.store(0, sp); this.vm.setReg(Machine.SP, sp + 4); return result; } /** Pops a character from the stack. */ private int popC() { int sp = this.vm.getReg(Machine.SP); int result = this.vm.loadC(sp); if (DEBUG) this.vm.storeC(0, sp); this.vm.setReg(Machine.SP, sp + this.vm.getCharSize()); return result; } /** Pops a string from the stack. * The string should be represented by an integer length followed * by the chars of the string, with first char on top. * This method coerces all values to (Java) chars, * so if {@link Machine#setCharSize(int)} has been set to 4 then * all more significant bytes are lost */ private String popString() { StringBuilder result = new StringBuilder(); int len = pop(); for (int i = 0; i < len; i++) { result.append((char) popC()); } return result.toString(); } /** Operation context. * This is a helper class for easy access to the underlying VM, * given a particular operation. */ private class OPContext { private final Op op; OPContext(Op o) { this.op = o; } /** Sets a register of the processor to a given boolean value. * The boolean is converted to {@link #TRUE} of {@link #FALSE}. * @param regIx operand index of the register to be set * @param val the boolean value */ public void setReg(int regIx, boolean val) { getVM().setReg(this.op.reg(regIx), val ? TRUE : FALSE); } /** Sets a register of the VM to a given integer value. * @param regIx operand index of the register to be set * @param val the value */ public void setReg(int regIx, int val) { getVM().setReg(this.op.reg(regIx), val); } /** Returns the value of a register of the VM. * @param regIx operand index of the register to be returned */ public int reg(int regIx) { return getVM().getReg(this.op.reg(regIx)); } /** Returns the value assigned to a numeric constant. * @param numIx operand index of the num to be returned */ public int num(int numIx) { Num num = this.op.num(numIx); switch (num.getKind()) { case LAB: Label label = num.getLabel(); int line = getProgram().getLine(label); if (line < 0) { System.err.println("Label '" + label + "' does not occur in program"); } return line; case LIT: return num.getValue(); case SYMB: Integer result = getVM().getNum(num); if (result == null) { result = getProgram().getSymb(num); if (result == null) { System.err.println("Symbolic constant '" + num.getName() + "' not initialised in VM or program"); } } return result; default: assert false; return 0; } } /** Returns the instruction number associated with a given label. */ public int label(int ix) { return getProgram().getLine(this.op.label(ix)); } protected String toPreString() { OpCode opcode = op.getOpCode(); StringBuilder sb = new StringBuilder(); sb.append(opcode.toString()); sb.append(" "); for (int i = 0; i < opcode.getSigSize(); i++) { if (i == opcode.getSourceCount()) sb.append(" -> "); else if (i > 0) sb.append(", "); if (i >= opcode.getSourceCount()) break; try { switch (opcode.getSig().get(i)) { case LABEL: sb.append(String.format("%d", label(i))); break; case NUM: sb.append(String.format("%8x", num(i)).replace(" ", ".")); break; case REG: sb.append(String.format("%8x", reg(i)).replace(" ", ".")); break; case STR: sb.append(""); break; } } catch (IllegalArgumentException e) { sb.append("undefine"); } } return sb.toString(); } protected String toPostString() { OpCode opcode = op.getOpCode(); StringBuilder sb = new StringBuilder(); for (int i = opcode.getSourceCount(); i < opcode.getSigSize(); i++) { if (i > opcode.getSourceCount()) sb.append(", "); try { switch (opcode.getSig().get(i)) { case LABEL: sb.append(String.format("%d", label(i))); break; case NUM: sb.append(String.format("%8x", num(i)).replace(" ", ".")); break; case REG: sb.append(String.format("%8x", reg(i)).replace(" ", ".")); break; case STR: sb.append(""); break; } } catch (IllegalArgumentException e) { sb.append("undefine"); } } return sb.toString(); } } }