package elw.dp.mips; import base.pattern.Result; import elw.dp.mips.asm.MipsAssembler; import gnu.trove.TIntIntHashMap; import org.akraievoy.base.ObjArrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class MipsValidator { private static final Logger log = LoggerFactory.getLogger(MipsValidator.class); private final MipsAssembler assembler = new MipsAssembler(); private final DataPath dataPath = new DataPath(); private final HashMap<String, Integer> labelIndex = new HashMap<String, Integer>(); private Instruction[] instructions = null; private TIntIntHashMap[] regs = null; private TIntIntHashMap[] data = null; // static setup, may be spring-injected at some time private int runSteps = 16384; public DataPath getDataPath() { return dataPath; } public int getRunSteps() { return runSteps; } public void setRunSteps(int runSteps) { this.runSteps = runSteps; } public Instruction[] assemble(Result[] resRef, List<String> sourceLines) { final Instruction[] newInstructions = assembler.loadInstructions(sourceLines, resRef, labelIndex); if (resRef[0].isSuccess()) { if (newInstructions == null) { throw new IllegalStateException("newInstructions == null"); }; instructions = newInstructions; return newInstructions; } else { return instructions = null; } } public void loadTest(Result[] resRef, String test) { final TaskBean.Test testBean = TaskBean.parseTest(test); final Map<Integer, List<String>> lineToErrors = testBean.parseErrors.getLineToErrors(); if (!lineToErrors.isEmpty()) { Result.failure(log, resRef, testBean.errors(lineToErrors)); return; } final TIntIntHashMap[] newRegs = assembler.loadRegs(testBean.regs, resRef); if (newRegs != null && resRef[0].isSuccess()) { final TIntIntHashMap[] newData = assembler.loadData(testBean.mem, resRef); if (newData != null && resRef[0].isSuccess()) { this.regs = newRegs; this.data = newData; } else { this.regs = this.data = null; } } else { this.regs = this.data = null; } } public void reset(Result[] resRef) { if (instructions != null && data != null && regs != null) { dataPath.getInstructions().setInstructions(Arrays.asList(instructions), labelIndex); dataPath.getMemory().setData(data[0]); dataPath.getRegisters().load(regs[0]); dataPath.getRegisters().setReg(Reg.pc, dataPath.getInstructions().getCodeBase()); dataPath.getRegisters().setReg(Reg.ra, dataPath.getInstructions().getCodeBase() - 4); dataPath.getRegisters().setReg(Reg.sp, dataPath.getInstructions().getStackBase()); regs[1].put(Reg.ra.ordinal(), dataPath.getInstructions().getCodeBase() - 4); regs[1].put(Reg.sp.ordinal(), dataPath.getInstructions().getStackBase()); Result.success(log, resRef, "Instructions, Data and Regs loaded"); } else { Result.failure(log, resRef, "Instructions, Data or Regs NOT loaded!"); } } public boolean step(Result[] resRef, int steps) { for (int step = 0; step < steps; step++) { final Instruction instruction = dataPath.execute(); if (instruction != null) { if (step == 0) { Result.success(log, resRef, "Executed " + instruction.getOpName()); } } else { verifyRegs(resRef); if (resRef[0].isSuccess()) { verifyMem(resRef); } if (resRef[0].isSuccess()) { Result.success(log, resRef, "Test Passed"); } return true; } } return false; } private void verifyMem(Result[] resRef) { final Memory memory = dataPath.getMemory(); final TIntIntHashMap expectedMemMap = data[1]; final int[] expectedAddrs = expectedMemMap.keys(); for (int expectedMem : expectedAddrs) { final String expectedMemHex = Integer.toString(expectedMem, 16); if (!memory.hasWord(expectedMem)) { Result.failure(log, resRef, "Test Failed: expecting data at 0x" + expectedMemHex + ", but word never set"); return; } final int value = memory.getWordInternal(expectedMem); final int expectedValue = expectedMemMap.get(expectedMem); if (expectedValue != value) { Result.failure(log, resRef, "Test Failed: expecting " + expectedValue + " at 0x" + expectedMemHex + ", but found " + value); return; } } final Instructions instructions = dataPath.getInstructions(); final int memSetBytes = memory.getSize(); for (int byteIndex = 0; byteIndex < memSetBytes; byteIndex++) { int byteAddr = memory.getAddressAt(byteIndex); if (instructions.getStackBase() > byteAddr && instructions.getMinStackBase() <= byteAddr) { continue; } final int byteAddrAligned = byteAddr - byteAddr % 4; if (expectedMemMap.contains(byteAddrAligned)) { continue; } final String byteAddrHex = Integer.toString(byteAddr, 16); Result.failure(log, resRef, "Test Failed: expecting clean byte at 0x" + byteAddrHex + ", but memory corrupted"); return; } Result.success(log, resRef, "Test Passed Memory Spec"); } private void verifyRegs(Result[] resRef) { final Reg[] setupRegs = dataPath.getRegisters().getSetupRegs(); final TIntIntHashMap expectedRegMap = regs[1]; final Reg[] expectedRegs = Reg.values(expectedRegMap.keys()); for (Reg expectedReg : expectedRegs) { if (!ObjArrays.contains(setupRegs, expectedReg)) { Result.failure(log, resRef, "Test Failed: expecting $" + expectedReg.toString() + ", but register never set"); return; } final int value = dataPath.getRegisters().getRegInternal(expectedReg); final int expectedValue = expectedRegMap.get(expectedReg.ordinal()); if (expectedValue != value) { Result.failure(log, resRef, "Test Failed: expecting $" + expectedReg.toString() + "=" + expectedValue + ", but $" + expectedReg.toString() + "=" + value); return; } } for (Reg setupReg : setupRegs) { if (Reg.tempRegs.contains(setupReg)) { continue; } if (ObjArrays.contains(expectedRegs, setupReg)) { continue; } Result.failure(log, resRef, "Test Failed: expecting clean $" + setupReg.toString() + ", but register corrupted"); return; } Result.success(log, resRef, "Test Passed Register Spec"); } public void run(Result[] resRef, final String test, final List<String> code) { assemble(resRef, code); if (resRef[0].isSuccess()) { loadTest(resRef, test); } if (resRef[0].isSuccess()) { reset(resRef); } if (resRef[0].isSuccess()) { if (!step(resRef, runSteps)) { Result.failure(log, resRef, "Execution timed out"); } } } public void batch(Result[] resRef, final TaskBean task, final List<String> code, final int[] passFailCounts) { int failCount = 0; List<String> tests = task.getTests(); for (int i = 0, testsLength = tests.size(); i < testsLength; i++) { String test = tests.get(i); final Result[] localResRef = {new Result("test status unknown", false)}; try { run(localResRef, test, code); if (!localResRef[0].isSuccess()) { failCount++; if (passFailCounts != null) { passFailCounts[1]++; } } else { if (passFailCounts != null) { passFailCounts[0]++; } } } catch (Throwable t) { failCount++; Result.failure(log, resRef, "Failed: " + t.getClass() + t.getMessage()); log.trace("trace", t); } } if (failCount > 0) { Result.failure(log, resRef, failCount + " of " + tests.size() + " tests failed"); } else { Result.success(log, resRef, tests.size() + " tests passed"); } } public void clearTest() { data = regs = null; } }