From b1d7121b22bd783b6bfa40f7db88f93d4f96e69f Mon Sep 17 00:00:00 2001 From: simon987 Date: Thu, 30 Jul 2020 21:26:12 -0400 Subject: [PATCH] Interrupts WIP #230 --- .../net/simon987/mar/server/assembly/CPU.java | 100 ++++++++++-------- .../mar/server/assembly/Instruction.java | 1 - .../simon987/mar/server/assembly/Status.java | 4 +- .../assembly/instruction/IntInstruction.java | 35 ++++++ .../assembly/instruction/IretInstruction.java | 43 ++++++++ .../assembly/instruction/PopfInstruction.java | 2 +- .../instruction/PushfInstruction.java | 2 +- src/main/resources/config.properties | 4 +- .../mar/server/assembly/TestHelper.java | 12 ++- .../instruction/IntInstructionTest.java | 48 +++++++++ 10 files changed, 194 insertions(+), 57 deletions(-) create mode 100644 src/main/java/net/simon987/mar/server/assembly/instruction/IntInstruction.java create mode 100644 src/main/java/net/simon987/mar/server/assembly/instruction/IretInstruction.java create mode 100644 src/test/java/net/simon987/mar/server/assembly/instruction/IntInstructionTest.java diff --git a/src/main/java/net/simon987/mar/server/assembly/CPU.java b/src/main/java/net/simon987/mar/server/assembly/CPU.java index b8e3597..8db31f9 100755 --- a/src/main/java/net/simon987/mar/server/assembly/CPU.java +++ b/src/main/java/net/simon987/mar/server/assembly/CPU.java @@ -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 diff --git a/src/main/java/net/simon987/mar/server/assembly/Instruction.java b/src/main/java/net/simon987/mar/server/assembly/Instruction.java index 2989cf7..04cb8fd 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Instruction.java +++ b/src/main/java/net/simon987/mar/server/assembly/Instruction.java @@ -59,7 +59,6 @@ public abstract class Instruction { */ public Status execute(Target dst, int dstIndex, int src, Status status) { - return status; } diff --git a/src/main/java/net/simon987/mar/server/assembly/Status.java b/src/main/java/net/simon987/mar/server/assembly/Status.java index 21c0e82..6b4400b 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Status.java +++ b/src/main/java/net/simon987/mar/server/assembly/Status.java @@ -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); diff --git a/src/main/java/net/simon987/mar/server/assembly/instruction/IntInstruction.java b/src/main/java/net/simon987/mar/server/assembly/instruction/IntInstruction.java new file mode 100644 index 0000000..1fb9303 --- /dev/null +++ b/src/main/java/net/simon987/mar/server/assembly/instruction/IntInstruction.java @@ -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; + } +} diff --git a/src/main/java/net/simon987/mar/server/assembly/instruction/IretInstruction.java b/src/main/java/net/simon987/mar/server/assembly/instruction/IretInstruction.java new file mode 100644 index 0000000..dd89bd3 --- /dev/null +++ b/src/main/java/net/simon987/mar/server/assembly/instruction/IretInstruction.java @@ -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; + } +} diff --git a/src/main/java/net/simon987/mar/server/assembly/instruction/PopfInstruction.java b/src/main/java/net/simon987/mar/server/assembly/instruction/PopfInstruction.java index 909bc9a..65e2e4c 100644 --- a/src/main/java/net/simon987/mar/server/assembly/instruction/PopfInstruction.java +++ b/src/main/java/net/simon987/mar/server/assembly/instruction/PopfInstruction.java @@ -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); diff --git a/src/main/java/net/simon987/mar/server/assembly/instruction/PushfInstruction.java b/src/main/java/net/simon987/mar/server/assembly/instruction/PushfInstruction.java index 0f2e06c..f880794 100644 --- a/src/main/java/net/simon987/mar/server/assembly/instruction/PushfInstruction.java +++ b/src/main/java/net/simon987/mar/server/assembly/instruction/PushfInstruction.java @@ -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; } diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index 9762e90..19404a9 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -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 diff --git a/src/test/java/net/simon987/mar/server/assembly/TestHelper.java b/src/test/java/net/simon987/mar/server/assembly/TestHelper.java index bb5ab28..c77e7c7 100644 --- a/src/test/java/net/simon987/mar/server/assembly/TestHelper.java +++ b/src/test/java/net/simon987/mar/server/assembly/TestHelper.java @@ -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); } diff --git a/src/test/java/net/simon987/mar/server/assembly/instruction/IntInstructionTest.java b/src/test/java/net/simon987/mar/server/assembly/instruction/IntInstructionTest.java new file mode 100644 index 0000000..f093fd6 --- /dev/null +++ b/src/test/java/net/simon987/mar/server/assembly/instruction/IntInstructionTest.java @@ -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")); + } +}