Interrupts WIP #230

This commit is contained in:
simon987 2020-07-30 21:26:12 -04:00
parent 51c0d4c3c7
commit b1d7121b22
10 changed files with 194 additions and 57 deletions

View File

@ -45,6 +45,11 @@ public class CPU implements MongoSerializable {
*/
private int codeSectionOffset;
private int interruptVectorTableOffset;
private final int graceInstructionCount;
private int graceInstructionLeft;
private boolean isGracePeriod;
/**
* Instruction pointer, always points to the next instruction
*/
@ -57,14 +62,10 @@ public class CPU implements MongoSerializable {
private int registerSetSize;
private static final char EXECUTION_COST_ADDR = 0x0050;
private static final char EXECUTED_INS_ADDR = 0x0051;
public CPU() {
instructionSet = new DefaultInstructionSet();
registerSet = new DefaultRegisterSet();
codeSectionOffset = GameServer.INSTANCE.getConfig().getInt("org_offset");
private static final char EXECUTION_COST_ADDR = 0x0150;
private static final char EXECUTED_INS_ADDR = 0x0151;
private void addInstructions() {
instructionSet.add(new JmpInstruction(this));
instructionSet.add(new JnzInstruction(this));
instructionSet.add(new JzInstruction(this));
@ -91,7 +92,20 @@ public class CPU implements MongoSerializable {
instructionSet.add(new PopfInstruction(this));
instructionSet.add(new JnaInstruction(this));
instructionSet.add(new JaInstruction(this));
instructionSet.add(new IntInstruction(this));
instructionSet.add(new IretInstruction(this));
}
public CPU() {
instructionSet = new DefaultInstructionSet();
registerSet = new DefaultRegisterSet();
IServerConfiguration config = GameServer.INSTANCE.getConfig();
codeSectionOffset = config.getInt("org_offset");
graceInstructionCount = config.getInt("grace_instruction_count");
interruptVectorTableOffset = config.getInt("ivt_offset");
addInstructions();
status = new Status();
}
@ -102,41 +116,32 @@ public class CPU implements MongoSerializable {
instructionSet = new DefaultInstructionSet();
registerSet = new DefaultRegisterSet();
codeSectionOffset = config.getInt("org_offset");
graceInstructionCount = config.getInt("grace_instruction_count");
instructionSet.add(new JmpInstruction(this));
instructionSet.add(new JnzInstruction(this));
instructionSet.add(new JzInstruction(this));
instructionSet.add(new JgInstruction(this));
instructionSet.add(new JgeInstruction(this));
instructionSet.add(new JleInstruction(this));
instructionSet.add(new JlInstruction(this));
instructionSet.add(new PushInstruction(this));
instructionSet.add(new PopInstruction(this));
instructionSet.add(new CallInstruction(this));
instructionSet.add(new RetInstruction(this));
instructionSet.add(new MulInstruction(this));
instructionSet.add(new DivInstruction(this));
instructionSet.add(new JnsInstruction(this));
instructionSet.add(new JsInstruction(this));
instructionSet.add(new HwiInstruction(this));
instructionSet.add(new HwqInstruction(this));
instructionSet.add(new XchgInstruction(this));
instructionSet.add(new JcInstruction(this));
instructionSet.add(new JncInstruction(this));
instructionSet.add(new JnoInstruction(this));
instructionSet.add(new JoInstruction(this));
instructionSet.add(new PushfInstruction(this));
instructionSet.add(new PopfInstruction(this));
instructionSet.add(new JnaInstruction(this));
instructionSet.add(new JaInstruction(this));
interruptVectorTableOffset = config.getInt("ivt_offset");
addInstructions();
status = new Status();
memory = new Memory(config.getInt("memory_size"));
}
/**
* Sets the IP to IVT + number and pushes flags then the old IP
*/
public void interrupt(int number) {
Instruction push = instructionSet.get(PushInstruction.OPCODE);
push.execute(status.toWord(), status);
push.execute(ip, status);
this.setIp((char) memory.get(interruptVectorTableOffset + number));
}
public void reset() {
status.clear();
ip = codeSectionOffset;
graceInstructionLeft = graceInstructionCount;
isGracePeriod = false;
}
public int execute(int timeout) {
@ -151,17 +156,16 @@ public class CPU implements MongoSerializable {
while (!status.isBreakFlag()) {
counter++;
if (counter % 10000 == 0) {
if (System.currentTimeMillis() > (startTime + timeout)) {
LogManager.LOGGER.fine("CPU Timeout " + this + " after " + counter + "instructions (" + timeout + "ms): " + (double) counter / ((double) timeout / 1000) / 1000000 + "MHz");
//Write execution cost and instruction count to memory
memory.set(EXECUTION_COST_ADDR, timeout);
memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter));
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
if (isGracePeriod) {
if (graceInstructionLeft-- == 0) {
writeExecutionStats(timeout, counter);
return timeout;
}
} else if (counter % 10000 == 0) {
if (System.currentTimeMillis() > (startTime + timeout)) {
interrupt(32);
isGracePeriod = true;
}
}
//fetch instruction
@ -184,15 +188,17 @@ public class CPU implements MongoSerializable {
// LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz");
//Write execution cost and instruction count to memory
memory.set(EXECUTION_COST_ADDR, elapsed);
memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter));
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
writeExecutionStats(elapsed, counter);
return elapsed;
}
private void writeExecutionStats(int timeout, int counter) {
memory.set(EXECUTION_COST_ADDR, timeout);
memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter));
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
}
public void executeInstruction(Instruction instruction, int source, int destination) {
//Execute the instruction

View File

@ -59,7 +59,6 @@ public abstract class Instruction {
*/
public Status execute(Target dst, int dstIndex, int src, Status status) {
return status;
}

View File

@ -133,7 +133,7 @@ public class Status implements Cloneable {
this.errorFlag = errorFlag;
}
public char toByte() {
public char toWord() {
char stat = 0;
stat = (char) (stat | ((signFlag ? 1 : 0) << 3));
stat = (char) (stat | ((zeroFlag ? 1 : 0) << 2));
@ -142,7 +142,7 @@ public class Status implements Cloneable {
return stat;
}
public void fromByte(char stat) {
public void fromWord(char stat) {
setSignFlag((stat & (1 << 3)) != 0);
setZeroFlag((stat & (1 << 2)) != 0);
setCarryFlag((stat & (1 << 1)) != 0);

View File

@ -0,0 +1,35 @@
package net.simon987.mar.server.assembly.instruction;
import net.simon987.mar.server.assembly.*;
/**
* Software Interrupt
* Pushes the flags register and the IP to the stack then
* Sets the IP to the CPU [ivt_offset + src].
*/
public class IntInstruction extends Instruction {
public static final int OPCODE = 48;
private final CPU cpu;
public IntInstruction(CPU cpu) {
super("int", OPCODE);
this.cpu = cpu;
}
@Override
public Status execute(int src, Status status) {
cpu.interrupt(src);
return status;
}
@Override
public boolean operandsValid(Operand o1, Operand o2) {
return false;
}
@Override
public boolean operandValid(Operand o1) {
return o1.getType() == OperandType.IMMEDIATE16 && o1.getData() < 256;
}
}

View File

@ -0,0 +1,43 @@
package net.simon987.mar.server.assembly.instruction;
import net.simon987.mar.server.assembly.*;
/**
* Interrupt Return
*
* Pops the IP and status flag from the stack.
*/
public class IretInstruction extends Instruction {
public static final int OPCODE = 49;
private final CPU cpu;
public IretInstruction(CPU cpu) {
super("iret", OPCODE);
this.cpu = cpu;
}
public Status execute(Status status) {
RegisterSet reg = cpu.getRegisterSet();
cpu.setIp((char) cpu.getMemory().get(reg.get(7))); //IP = (SP + 0)
status.fromWord((char) cpu.getMemory().get(reg.get(7) + 1)); //Status = (SP + 1)
reg.set(7, reg.get(7) + 2); //Increment SP (stack grows towards smaller)
return status;
}
@Override
public boolean noOperandsValid() {
return true;
}
@Override
public boolean operandsValid(Operand o1, Operand o2) {
return false;
}
@Override
public boolean operandValid(Operand o1) {
return false;
}
}

View File

@ -29,7 +29,7 @@ public class PopfInstruction extends Instruction {
char flags = (char) cpu.getMemory().get(sp.getValue());
// Overwrite the CPU flags
status.fromByte(flags);
status.fromWord(flags);
// Increment SP
sp.setValue(sp.getValue() + 1);

View File

@ -28,7 +28,7 @@ public class PushfInstruction extends Instruction {
sp.setValue(sp.getValue() - 1);
// Push the current flags
cpu.getMemory().set(sp.getValue(), status.toByte());
cpu.getMemory().set(sp.getValue(), status.toWord());
return status;
}

View File

@ -44,7 +44,9 @@ wg_createNewOnRequest=0
#CPU
tick_length=1000
org_offset=512
org_offset=4096
ivt_offset=0
grace_instruction_count=10000
stack_bottom=65536
memory_size=65536
user_timeout=100

View File

@ -8,8 +8,6 @@ import net.simon987.mar.server.assembly.exception.CancelledException;
public class TestHelper {
private static final int TIMEOUT = 100;
public static Assembler getTestAsm() {
IServerConfiguration config = getTestConfig();
CPU cpu = getTestCpu();
@ -20,7 +18,9 @@ public class TestHelper {
IServerConfiguration configuration = new FakeConfiguration();
configuration.setInt("memory_size", 65536);
configuration.setInt("org_offset", 400);
configuration.setInt("org_offset", 4096);
configuration.setInt("ivt_offset", 0);
configuration.setInt("grace_instruction_count", 10000);
return configuration;
}
@ -36,6 +36,10 @@ public class TestHelper {
}
public static TestExecutionResult executeCode(String code) {
return executeCode(code, 100);
}
public static TestExecutionResult executeCode(String code, int timeout) {
AssemblyResult ar = getTestAsm().parse(code);
CPU cpu = TestHelper.getTestCpu();
@ -50,7 +54,7 @@ public class TestHelper {
cpu.setCodeSectionOffset(ar.getCodeSectionOffset());
cpu.reset();
cpu.execute(TIMEOUT);
cpu.execute(timeout);
return new TestExecutionResult(cpu.getState(), host.callHistory, ar);
}

View File

@ -0,0 +1,48 @@
package net.simon987.mar.server.assembly.instruction;
import net.simon987.mar.server.TestExecutionResult;
import net.simon987.mar.server.assembly.TestHelper;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class IntInstructionTest {
@Test
public void intSimple1() {
String code = "" +
"isr: \n" +
" MOV X, 0x1234 \n" +
" IRET \n" +
".text \n" +
"MOV [32], isr \n" +
"INT 32 \n" +
"MOV Y, 0x4567 \n" +
"brk \n";
TestExecutionResult res = TestHelper.executeCode(code);
assertEquals(0x1234, res.regValue("X"));
assertEquals(0x4567, res.regValue("Y"));
}
@Test
public void intExecutionLimit() {
String code = "" +
"isr: \n" +
" MOV X, 0x1234 \n" +
" BRK \n" +
".text \n" +
"MOV [32], isr \n" +
"loop: \n" +
"ADD A, 1 \n" +
"JMP loop \n" +
"MOV Y, 0x4567 \n" +
"brk \n";
TestExecutionResult res = TestHelper.executeCode(code, 10);
assertEquals(0x1234, res.regValue("X"));
assertEquals(0, res.regValue("Y"));
}
}