/*
This file is part of JOP, the Java Optimized Processor
see <http://www.jopdesign.com/>
Copyright (C) 2009, Benedikt Huber (benedikt.huber@gmail.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.timing.jop;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
import java.util.Map.Entry;
import com.jopdesign.tools.Instruction;
import com.jopdesign.tools.JopInstr;
import com.jopdesign.tools.Jopa;
import com.jopdesign.tools.Jopa.Line;
/**
* Parse microcode file, compute timings
* @author Benedikt Huber <benedikt.huber@gmail.com>
*/
public class MicrocodeAnalysis {
public static int JOPSYS_NOIM = 254;
public static class MicrocodeVerificationException extends Exception {
private static final long serialVersionUID = 1L;
public MicrocodeVerificationException(String msg) { super(msg); }
}
private static void ifail(String msg) throws AssertionError {
throw new AssertionError("Interpreter failed: "+msg);
}
/** Limit for the length of a microcode path during simulation */
public final int PATH_SIZE_LIMIT = 1000;
/** Number of branch delay slots */
public final int BRANCH_DELAY_SLOTS = 2;
/** Jumps to this label are assumed to happen on a null pointer exception and
* are treated in a special way
*/
public static final String NULL_PTR_CHECK_LABEL = "null_pointer";
/** Jumps to this label are assumed to happen on an array index out of bounds
* exception and are treated in a special way.
*/
public static final String ARRAY_BOUND_CHECK_LABEL = "array_bound";
public static final String VP_SAVE_LABEL = "invoke_vpsave";
public static String[] INFEASIBLE_BRANCHES =
{ NULL_PTR_CHECK_LABEL, ARRAY_BOUND_CHECK_LABEL, "! "+VP_SAVE_LABEL };
/** Symbolic values */
private static abstract class MachineValue {
public abstract boolean isConcrete();
public abstract Integer evaluate();
public static MachineValue number(int num) { return new NumberValue(num); }
public static MachineValue symbol(String sym) { return new SymbolicValue(sym); }
public static MachineValue alu(int opcode, MachineValue e1, MachineValue e2) {
if(e1.isConcrete() && e2.isConcrete()) {
int v1 = e1.evaluate();
int v2 = e2.evaluate();
int c = -1;
switch(opcode) {
case MicrocodeConstants.AND: c = v1 & v2;break;
case MicrocodeConstants.OR: c = v1 | v2;break;
case MicrocodeConstants.XOR: c = v1 ^ v2;break;
case MicrocodeConstants.ADD: c = v1 + v2;break;
case MicrocodeConstants.SUB: c = v1 - v2;break;
case MicrocodeConstants.USHR: c = v2 >>> v1; break;
case MicrocodeConstants.SHL: c = v2 << v1; break;
case MicrocodeConstants.SHR: c = v2 >> v1; break;
default: ifail("case statement [arithmethic]: default");
}
return number(c);
} else {
return expr(opcode,e1,e2);
}
}
public static MachineValue expr(int opcode, Vector<MachineValue> args) {
return new Expression(opcode,args);
}
public static MachineValue expr(int opcode, MachineValue e1, MachineValue e2) {
Vector<MachineValue> args = new Vector<MachineValue>();
args.add(e1);args.add(e2);
return expr(opcode,args);
}
}
private static class NumberValue extends MachineValue {
int val;
public NumberValue(int num) { this.val = num; }
@Override public Integer evaluate() { return val; }
@Override public boolean isConcrete() { return true; }
@Override public String toString() { return ""+val; }
}
private static class SymbolicValue extends MachineValue {
String symbol;
public SymbolicValue(String sym) { this.symbol = sym; }
@Override public Integer evaluate() { return null; }
@Override public boolean isConcrete() { return false; }
@Override public String toString() { return symbol; }
}
private static class Expression extends MachineValue {
int funSym;
List<MachineValue> args;
public Expression(int sym, List<MachineValue> args) {
this.funSym = sym;
this.args = args;
}
@Override public Integer evaluate() { return null; }
@Override public boolean isConcrete() { return false; }
@Override public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(Instruction.get(funSym));
sb.append('(');
sb.append(MicropathTiming.concat(", ", args));
sb.append(')');
return sb.toString();
}
}
/** machine state for microcode interpretation */
private class MachineState {
private int ic = -1; /* instruction counter */
private int addr; /* microcode address */
private int jmpAddr = -1; /* jmp target */
private int branchDelay = -1; /* number of pending delay slots */
private int mulStart = -1; /* time multiplier started */
private MachineValue mulResult = null; /* multiplication result */
private int stackInputCount; /* number of stack items consumed from the initial stack */
private int symbolGenCount = 0; /* number of generated symbols */
private String genSymbol(String prefix) { return prefix+"_"+(++symbolGenCount);}
private Stack<MachineValue> stack = new Stack<MachineValue>();
private HashMap<String,MachineValue> localVars = new HashMap<String,MachineValue>();
private HashMap<MachineValue,Boolean> constraints = new HashMap<MachineValue, Boolean>();
public MachineValue getLocalVar(String name) {
if(! localVars.containsKey(name)) {
localVars.put(name, MachineValue.symbol("local_"+name));
}
return localVars.get(name);
}
public MachineState(int addr) {
this.addr = addr;
this.stackInputCount = 0;
stack.push(MachineValue.symbol("ts_0"));
}
@SuppressWarnings("unchecked")
public MachineState clone() {
MachineState cloned = new MachineState(addr);
cloned.ic = ic;
cloned.jmpAddr = jmpAddr;
cloned.branchDelay = branchDelay;
cloned.mulStart = mulStart;
cloned.mulResult = mulResult;
cloned.stackInputCount = stackInputCount;
cloned.symbolGenCount = symbolGenCount;
cloned.stack = (Stack<MachineValue>) stack.clone();
cloned.localVars = new HashMap<String,MachineValue>(localVars);
cloned.constraints = new HashMap<MachineValue, Boolean>(constraints);
return cloned;
}
public int stackUsage() {
return stack.size() - stackInputCount;
}
public MachineValue stackPop() {
MachineValue v = stack.pop();
if(stack.empty()) {
stackInputCount++;
stack.push(MachineValue.symbol("ts_"+stackInputCount));
}
return v;
}
public void stackPush(MachineValue v) {
stack.push(v);
}
public void stackPushNewSymbol(String prefix) {
stack.push(MachineValue.symbol(genSymbol(prefix)));
}
public void arithmeticOp(int opcode) {
MachineValue v1 = stackPop();
MachineValue v2 = stackPop();
stackPush(MachineValue.alu(opcode,v1,v2));
}
public void initBranch(int targetPC) {
if(branchDelay >= 0) { /* branch initialized; fail */
ifail("initBranch: branching already initialized");
}
this.branchDelay = BRANCH_DELAY_SLOTS;
this.jmpAddr = targetPC;
}
public Line fetchNext() {
if(branchDelay == 0) {
addr = jmpAddr;
jmpAddr = branchDelay = -1;
} else if(branchDelay > 0) {
branchDelay--;
}
ic++;
return instrs.get(addr++);
}
public void initMul() {
MachineValue a = stackPop();
MachineValue b = stackPop();
stackPush(b);
mulStart = getIC();
if(a.isConcrete() && b.isConcrete()) {
mulResult = MachineValue.number(a.evaluate() * b.evaluate());
} else {
mulResult = MachineValue.expr(MicrocodeConstants.STMUL, a, b);
}
}
public int readMul() {
if(mulStart < 0) ifail("multiplier: load before store");
stackPush(mulResult);
int delay = getIC() - mulStart;
mulStart = -1; mulResult = null;
return delay;
}
public int getIC() {
return ic;
}
public void addConstraint(MachineValue val, boolean isEqualZero) throws MicrocodeVerificationException {
if(this.constraints.containsKey(val)) {
boolean wasEqualZero = constraints.get(val);
if(wasEqualZero != isEqualZero) {
throw new MicrocodeVerificationException("inconsistent path constraints on "+val);
}
}
this.constraints.put(val, isEqualZero);
}
public String dumpConstraints() {
Vector<String> cts = new Vector<String>();
for(Entry<MachineValue, Boolean> x : this.constraints.entrySet()) {
boolean isEqualZero = x.getValue();
if(isEqualZero) {
cts.add(""+x.getKey()+" = 0");
} else {
cts.add(""+x.getKey()+" /= 0");
}
}
return MicropathTiming.concat(", ", cts).toString();
}
}
/**
* mini-interpreter to compute microcode paths
*/
public class MicroInterpreter {
private class Continuation {
MicrocodePath p;
MachineState st;
Continuation(MicrocodePath p, MachineState st) {
this.p = p;
this.st = st;
}
public Continuation clone() {
return new Continuation(p.clone(), st.clone());
}
}
private Continuation current;
private Stack<Continuation> continuations = new Stack<Continuation>();
private void pushBranchFalse(boolean branchOnZero, MachineValue val) throws MicrocodeVerificationException {
Continuation stateCopy = current.clone();
stateCopy.st.addConstraint(val, ! branchOnZero);
continuations.push(stateCopy);
}
public MicroInterpreter() {
}
public Vector<MicrocodePath> interpret(String name,int startAddress) throws MicrocodeVerificationException {
Vector<MicrocodePath> thePaths = new Vector<MicrocodePath>();
current = new Continuation(new MicrocodePath(name),new MachineState(startAddress));
do {
try {
while(interpret()) { }
} catch(AssertionError ex) {
throw new MicrocodeVerificationException(ex.getMessage());
}
// System.out.println(" Final state: ");
// System.out.println(" Path: "+current.p);
// System.out.println(" Stack: "+current.st.stack);
// System.out.println(" Constraints: "+current.st.dumpConstraints());
thePaths.add(current.p);
current = null;
if(! continuations.empty()) current = continuations.pop();
} while(current != null);
return thePaths;
}
private boolean interpret() throws MicrocodeVerificationException {
Line microLine = current.st.fetchNext();
Instruction microInstr = microLine.getInstruction();
/* eliminate the error-prone getIntVal/getSymVal/getSpecial stuff */
int constant ; // constant arguments
String label = microLine.getSymVal(); // labels for jumps
String localVar = microLine.getSymVal(); // local variable names
if(microLine.getSymVal() != null) {
constant = (Integer) jopa.getSymMap().get(microLine.getSymVal());
} else {
constant = microLine.getIntVal();
}
current.p.addInstr(microLine, current.st.stack.peek().evaluate());
if(current.p.getPath().size() > PATH_SIZE_LIMIT) {
ifail("<loop>: path exceeded maximal size of "+PATH_SIZE_LIMIT);
}
switch(microInstr.opcode) {
case MicrocodeConstants.POP:
current.st.stackPop();break;
case MicrocodeConstants.AND:
case MicrocodeConstants.OR:
case MicrocodeConstants.XOR:
case MicrocodeConstants.ADD:
case MicrocodeConstants.SUB:
current.st.arithmeticOp(microInstr.opcode);
break;
// extension 'address' selects function 4 bits
// multiplication
case MicrocodeConstants.STMUL: /* init multiplication */
current.st.initMul();
break;
// memory read/write
case MicrocodeConstants.STMWA:
case MicrocodeConstants.STMRA:
case MicrocodeConstants.STMWD:
case MicrocodeConstants.STMRAC:
case MicrocodeConstants.STMRAF:
case MicrocodeConstants.STMWDF:
current.st.stackPop();break;
// array instructions
case MicrocodeConstants.STALD:
case MicrocodeConstants.STAST:
current.st.stackPop();break;
// getfield/putfield
case MicrocodeConstants.STGF:
case MicrocodeConstants.STPF:
current.st.stackPop();break;
// magic copying
case MicrocodeConstants.STCP:
current.st.stackPop();break;
// bytecode read
case MicrocodeConstants.STBCRD:
current.st.stackPop();break;
// TODO: why isn't the pop characteristic from
// instruction info used?
case MicrocodeConstants.STIDX:
case MicrocodeConstants.STPS:
current.st.stackPop();break;
// st (vp) 3 bits
case MicrocodeConstants.ST0:
case MicrocodeConstants.ST1:
case MicrocodeConstants.ST2:
case MicrocodeConstants.ST3:
case MicrocodeConstants.ST:
case MicrocodeConstants.STMI:
current.st.stackPop();break;
case MicrocodeConstants.STVP:
case MicrocodeConstants.STJPC:
case MicrocodeConstants.STAR:
case MicrocodeConstants.STSP:
current.st.stackPop();break;
//shift
case MicrocodeConstants.USHR:
case MicrocodeConstants.SHL:
case MicrocodeConstants.SHR:
current.st.arithmeticOp(microInstr.opcode);
break;
//5 bits
case MicrocodeConstants.STM:
MachineValue local = current.st.stackPop();
current.st.localVars.put(localVar,local);
break;
/* microcode branches */
case MicrocodeConstants.BNZ:
case MicrocodeConstants.BZ:
mcBranch(microInstr, label);
break;
// -----------------------------------------------------------------------------------
// 'no sp change' instructions
// -----------------------------------------------------------------------------------
/* microcode jump */
case MicrocodeConstants.JMP:
jump(label);
break;
case MicrocodeConstants.NOP:
break;
case MicrocodeConstants.WAIT:
current.p.setHasWait();
break;
// bytecode branch
case MicrocodeConstants.JBR:
break;
// start getstatic
case MicrocodeConstants.STGS:
break;
// -----------------------------------------------------------------------------------
// 'push' instructions
// -----------------------------------------------------------------------------------
//5 bits
// stack.push(local[n])
case MicrocodeConstants.LDM:
current.st.stackPush(current.st.getLocalVar(localVar));
break;
case MicrocodeConstants.LDI:
current.st.stackPush(MachineValue.number(constant));
break;
// extension 'address' selects function 4 bits
case MicrocodeConstants.LDMRD:
current.st.stackPushNewSymbol("ldmrd");break;
/* multiplier */
case MicrocodeConstants.LDMUL:
int delay = current.st.readMul();
current.p.setNeedsMultiplier(delay);
break;
case MicrocodeConstants.LDBCSTART:
current.st.stackPushNewSymbol("ldbcstart");
break;
//ld (vp) 3 bits
case MicrocodeConstants.LD0:
case MicrocodeConstants.LD1:
case MicrocodeConstants.LD2:
case MicrocodeConstants.LD3:
case MicrocodeConstants.LD:
case MicrocodeConstants.LDMI:
current.st.stackPushNewSymbol("LDx");break;
//2 bits
case MicrocodeConstants.LDSP:
case MicrocodeConstants.LDVP:
case MicrocodeConstants.LDJPC:
current.st.stackPushNewSymbol("LDsp");break;
//ld opd 2 bits
case MicrocodeConstants.LD_OPD_8U:
case MicrocodeConstants.LD_OPD_8S:
case MicrocodeConstants.LD_OPD_16U:
case MicrocodeConstants.LD_OPD_16S:
current.st.stackPushNewSymbol("LDopd");break;
case MicrocodeConstants.DUP:
MachineValue v = current.st.stackPop();
current.st.stackPush(v);current.st.stackPush(v);
break;
}
return ! (microLine.hasNxtFlag());
}
/* jump */
private void jump(String symVal) {
Integer targetPC = (Integer) symMap.get(symVal);
if(targetPC == null) ifail("Label not defined: "+symVal);
current.st.initBranch(targetPC);
}
/* non-deterministic jump */
private void jumpMaybe(Instruction branchInstr, MachineValue val, String symVal) throws MicrocodeVerificationException {
boolean branchOnZero = branchInstr.opcode == MicrocodeConstants.BZ;
pushBranchFalse(branchOnZero, val);
current.st.addConstraint(val, branchOnZero);
jump(symVal);
}
private void mcBranch(Instruction microInstr, String symVal) throws MicrocodeVerificationException {
/* branch when zero/not zero.
* Attention: the TOS from the last cycle isn't
* yet available. To avoid accidental errors, will require that
* the last instruction was a NOP */
MachineValue val = current.st.stackPop();
if(symVal.equals(NULL_PTR_CHECK_LABEL)) {
current.p.setNullPtrCheck();
} else if(symVal.equals(ARRAY_BOUND_CHECK_LABEL)) {
current.p.setArrayBoundCheck();
} else if(symVal.equals(VP_SAVE_LABEL)) {
jump(symVal); // ALWAYS taken
} else {
current.p.checkStableTOS();
if(! val.isConcrete()) {
jumpMaybe(microInstr, val, symVal); // Non-deterministic branch
} else {
boolean doBranch = (microInstr.opcode == MicrocodeConstants.BZ) ?
(val.evaluate() == 0) :
(val.evaluate() != 0);
if(doBranch) jump(symVal);
}
}
}
}
private File asmFile;
private Jopa jopa;
//private Vector<Line> lines;
private Map<String, Integer> symMap;
private Map<Integer,Integer> jInstrs;
private List<Line> instrs;
public static final File DEFAULT_ASM_FILE = new File("asm", new File("generated","jvmgen.asm").getPath());
public MicrocodeAnalysis(String jvmAsm) throws IOException {
asmFile = new File(jvmAsm);
parse(false);
}
private File preprocess() throws IOException {
/* create temporary file for preprocessing */
File asmfilePath = new File(asmFile.getParent());
File tmpFile = File.createTempFile("jvm", ".asm");
tmpFile.deleteOnExit();
String fn = asmFile.getName();
String[] cmd = {"gcc","-o",tmpFile.getPath(),"-x","c","-E","-C","-P",fn};
Process p = Runtime.getRuntime().exec(cmd,null,asmfilePath);
int exitCode;
try {
exitCode = p.waitFor();
} catch (InterruptedException e) {
throw new IOException("Preprocess failed: "+e);
}
if(exitCode != 0) {
System.err.println(Arrays.toString(cmd));
BufferedReader eis = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String l = null;
while((l = eis.readLine()) != null) {
System.err.println(l);
}
throw new IOException("Preprocessor failed with exit code "+exitCode);
}
return tmpFile;
}
private void parse(boolean preprocess) throws IOException {
if(! asmFile.exists()) {
throw new IOException("Assembler file "+asmFile+" not found. You may need to run e.g.: 'make gen_mem -e ASM_SRC=jvm JVM_TYPE=USB'");
}
File inFile;
if(preprocess) {
inFile = preprocess();
} else {
inFile = asmFile;
}
jopa = new Jopa(inFile.getName(),inFile.getParent(),inFile.getParent());
jopa.pass1();
this.symMap = jopa.getSymMap();
this.jInstrs = jopa.getJavaInstructions();
this.instrs = jopa.getInstructions();
}
public Integer getStartAddress(int opcode) {
if(opcode == JOPSYS_NOIM) return this.jInstrs.get(JOPSYS_NOIM); // sys no-im
String name = JopInstr.name(opcode);
int jopinstr = JopInstr.get(name);
return this.jInstrs.get(jopinstr);
}
Vector<MicrocodePath> getMicrocodePaths(String opName, int addr)
throws MicrocodeVerificationException{
MicroInterpreter microMachine = new MicroInterpreter();
return microMachine.interpret(opName,addr);
}
public static void main(String[] argv) {
// MicrocodeAnalysis mt = null;
// try {
// mt = new MicrocodeAnalysis(asmFile);
// } catch (Exception e) {
// e.printStackTrace();
// System.exit(1);
// }
// Detailed analysis ... (TODO)
}
}