556 lines
13 KiB
Java
556 lines
13 KiB
Java
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 <code>true</code>. */
|
|
public static final int TRUE = -1;
|
|
/** Representation of <code>false</code>. */
|
|
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();
|
|
}
|
|
}
|
|
}
|