diff --git a/src/main/java/net/simon987/mar/cubot/event/UserCreationListener.java b/src/main/java/net/simon987/mar/cubot/event/UserCreationListener.java index 414d7f1..3322a04 100644 --- a/src/main/java/net/simon987/mar/cubot/event/UserCreationListener.java +++ b/src/main/java/net/simon987/mar/cubot/event/UserCreationListener.java @@ -8,6 +8,7 @@ import net.simon987.mar.server.assembly.Assembler; import net.simon987.mar.server.assembly.AssemblyResult; import net.simon987.mar.server.assembly.CPU; import net.simon987.mar.server.assembly.exception.CancelledException; +import net.simon987.mar.server.event.CpuInitialisationEvent; import net.simon987.mar.server.event.GameEvent; import net.simon987.mar.server.event.GameEventListener; import net.simon987.mar.server.event.UserCreationEvent; @@ -55,13 +56,20 @@ public class UserCreationListener implements GameEventListener { //Create CPU try { - cubot.setCpu(new CPU(GameServer.INSTANCE.getConfig(), cubot)); + CPU cpu = new CPU(config); + cubot.setCpu(cpu); cubot.getCpu().setHardwareHost(cubot); user.setUserCode(config.getString("new_user_code")); + GameEvent initEvent = new CpuInitialisationEvent(cpu, cubot); + GameServer.INSTANCE.getEventDispatcher().dispatch(event); + if (initEvent.isCancelled()) { + throw new CancelledException(); + } + //Compile user code - AssemblyResult ar = new Assembler(cubot.getCpu().getInstructionSet(), cubot.getCpu().getRegisterSet(), - GameServer.INSTANCE.getConfig()).parse(user.getUserCode()); + AssemblyResult ar = new Assembler(cpu.getInstructionSet(), cpu.getRegisterSet(), + config).parse(user.getUserCode()); cubot.getCpu().getMemory().clear(); diff --git a/src/main/java/net/simon987/mar/server/assembly/AssemblyResult.java b/src/main/java/net/simon987/mar/server/assembly/AssemblyResult.java index 7f3a16c..5e6f5a2 100755 --- a/src/main/java/net/simon987/mar/server/assembly/AssemblyResult.java +++ b/src/main/java/net/simon987/mar/server/assembly/AssemblyResult.java @@ -22,7 +22,7 @@ public class AssemblyResult { /** * A list of labels */ - HashMap labels = new HashMap<>(20); + public HashMap labels = new HashMap<>(20); /** * List of exceptions encountered during the assembly attempt, * they will be displayed in the editor 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 5f6a16f..b8e3597 100755 --- a/src/main/java/net/simon987/mar/server/assembly/CPU.java +++ b/src/main/java/net/simon987/mar/server/assembly/CPU.java @@ -98,7 +98,7 @@ public class CPU implements MongoSerializable { /** * Creates a new CPU */ - public CPU(IServerConfiguration config, ControllableUnit unit) throws CancelledException { + public CPU(IServerConfiguration config) throws CancelledException { instructionSet = new DefaultInstructionSet(); registerSet = new DefaultRegisterSet(); codeSectionOffset = config.getInt("org_offset"); @@ -132,12 +132,6 @@ public class CPU implements MongoSerializable { status = new Status(); memory = new Memory(config.getInt("memory_size")); - - GameEvent event = new CpuInitialisationEvent(this, unit); - GameServer.INSTANCE.getEventDispatcher().dispatch(event); - if (event.isCancelled()) { - throw new CancelledException(); - } } public void reset() { @@ -398,15 +392,20 @@ public class CPU implements MongoSerializable { public static CPU deserialize(Document obj, ControllableUnit unit) throws CancelledException { - CPU cpu = new CPU(GameServer.INSTANCE.getConfig(), unit); + CPU cpu = new CPU(GameServer.INSTANCE.getConfig()); cpu.codeSectionOffset = obj.getInteger("codeSegmentOffset"); cpu.memory = new Memory((Document) obj.get("memory")); cpu.registerSet = RegisterSet.deserialize((Document) obj.get("registerSet")); - return cpu; + GameEvent event = new CpuInitialisationEvent(cpu, unit); + GameServer.INSTANCE.getEventDispatcher().dispatch(event); + if (event.isCancelled()) { + throw new CancelledException(); + } + return cpu; } public InstructionSet getInstructionSet() { @@ -466,4 +465,15 @@ public class CPU implements MongoSerializable { public void setHardwareHost(HardwareHost hardwareHost) { this.hardwareHost = hardwareHost; } + + /** + * For testing/debugging, this creates an copy (please be mindful of the memory usage) + */ + public CpuState getState() { + return new CpuState( + registerSet.clone(), + memory.clone(), + status.clone() + ); + } } diff --git a/src/main/java/net/simon987/mar/server/assembly/CpuState.java b/src/main/java/net/simon987/mar/server/assembly/CpuState.java new file mode 100644 index 0000000..60115ba --- /dev/null +++ b/src/main/java/net/simon987/mar/server/assembly/CpuState.java @@ -0,0 +1,16 @@ +package net.simon987.mar.server.assembly; + +public class CpuState { + + public RegisterSet registers; + + public Memory memory; + + public Status status; + + public CpuState(RegisterSet registers, Memory memory, Status status) { + this.registers = registers; + this.memory = memory; + this.status = status; + } +} diff --git a/src/main/java/net/simon987/mar/server/assembly/DefaultRegisterSet.java b/src/main/java/net/simon987/mar/server/assembly/DefaultRegisterSet.java index 8ae4cec..3fd3189 100755 --- a/src/main/java/net/simon987/mar/server/assembly/DefaultRegisterSet.java +++ b/src/main/java/net/simon987/mar/server/assembly/DefaultRegisterSet.java @@ -9,14 +9,13 @@ class DefaultRegisterSet extends RegisterSet { DefaultRegisterSet() { super(); - addRegister(1, new Register("A")); - addRegister(2, new Register("B")); - addRegister(3, new Register("C")); - addRegister(4, new Register("D")); - addRegister(5, new Register("X")); - addRegister(6, new Register("Y")); - addRegister(7, new Register("SP")); - addRegister(8, new Register("BP")); - + put(1, new Register("A")); + put(2, new Register("B")); + put(3, new Register("C")); + put(4, new Register("D")); + put(5, new Register("X")); + put(6, new Register("Y")); + put(7, new Register("SP")); + put(8, new Register("BP")); } } diff --git a/src/main/java/net/simon987/mar/server/assembly/Memory.java b/src/main/java/net/simon987/mar/server/assembly/Memory.java index 1d2a9d5..bc30408 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Memory.java +++ b/src/main/java/net/simon987/mar/server/assembly/Memory.java @@ -21,7 +21,7 @@ import java.util.zip.InflaterOutputStream; /** * Represents the available memory for a CPU in the game universe */ -public class Memory implements Target, MongoSerializable { +public class Memory implements Target, MongoSerializable, Cloneable { /** @@ -105,6 +105,7 @@ public class Memory implements Target, MongoSerializable { address = (char) address; if (address >= words.length) { + Thread.dumpStack(); LogManager.LOGGER.info("DEBUG: Trying to set memory out of bounds: " + address); return; } @@ -117,25 +118,25 @@ public class Memory implements Target, MongoSerializable { * * @param blockSize Block size (in words) in which to randomly flip one bit */ - public void corrupt(int blockSize) { - Random rand = new Random(); + public void corrupt(int blockSize) { + Random rand = new Random(); - // Increment offset by blockSize - for (int offset = 0; offset < words.length; offset += blockSize) { + // Increment offset by blockSize + for (int offset = 0; offset < words.length; offset += blockSize) { - // Calculate address to corrupt by adding a random value between 0 to (blocksize-1) to offset - int address = rand.nextInt(blockSize) + offset; + // Calculate address to corrupt by adding a random value between 0 to (blocksize-1) to offset + int address = rand.nextInt(blockSize) + offset; - // Checking here avoids having a protected area at the end of the address space - if(address < words.length) { + // Checking here avoids having a protected area at the end of the address space + if (address < words.length) { - // Calculate bitmask by left-shifting 1 by a random value between 0 and 15 - int bitmask = 1 << rand.nextInt(16); + // Calculate bitmask by left-shifting 1 by a random value between 0 and 15 + int bitmask = 1 << rand.nextInt(16); - // Flip the bit with XOR - words[address] ^= bitmask; - } - } + // Flip the bit with XOR + words[address] ^= bitmask; + } + } } /** @@ -186,4 +187,12 @@ public class Memory implements Target, MongoSerializable { public char[] getWords() { return words; } + + @Override + public Memory clone() { + Memory memory = new Memory(words.length); + memory.words = new char[words.length]; + System.arraycopy(memory.words, 0, words, 0, words.length); + return memory; + } } diff --git a/src/main/java/net/simon987/mar/server/assembly/Register.java b/src/main/java/net/simon987/mar/server/assembly/Register.java index 2171c5d..80cea2b 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Register.java +++ b/src/main/java/net/simon987/mar/server/assembly/Register.java @@ -3,7 +3,7 @@ package net.simon987.mar.server.assembly; /** * Represents a register in a cpu */ -public class Register { +public class Register implements Cloneable { /** * Name of the register @@ -47,4 +47,14 @@ public class Register { return name + "=" + value; } + + @Override + public Register clone() { + try { + return (Register) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return null; + } } diff --git a/src/main/java/net/simon987/mar/server/assembly/RegisterSet.java b/src/main/java/net/simon987/mar/server/assembly/RegisterSet.java index f0ca554..f0fcbe7 100755 --- a/src/main/java/net/simon987/mar/server/assembly/RegisterSet.java +++ b/src/main/java/net/simon987/mar/server/assembly/RegisterSet.java @@ -2,29 +2,23 @@ package net.simon987.mar.server.assembly; import net.simon987.mar.server.io.MongoSerializable; -import net.simon987.mar.server.logging.LogManager; import org.bson.Document; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; /** * A set of registers for a CPU */ -public class RegisterSet implements Target, MongoSerializable { +public class RegisterSet implements Target, MongoSerializable, Cloneable { - /** - * List of registers - */ - private final HashMap registers = new HashMap<>(8); + // TODO configurable number of registers + private static final int REG_COUNT = 8 + 1; + private final Register[] registers = new Register[REG_COUNT]; - /** - * Create an empty Register set - */ public RegisterSet() { } @@ -40,9 +34,8 @@ public class RegisterSet implements Target, MongoSerializable { name = name.toUpperCase(); - for (Integer i : registers.keySet()) { - if (registers.get(i).getName().equals(name)) { - + for (int i = 1; i < REG_COUNT; i++) { + if (registers[i].getName().equals(name)) { return i; } } @@ -56,7 +49,7 @@ public class RegisterSet implements Target, MongoSerializable { * @param index index of the register */ public Register getRegister(int index) { - return registers.get(index); + return registers[index]; } /** @@ -68,7 +61,8 @@ public class RegisterSet implements Target, MongoSerializable { name = name.toUpperCase(); - for (Register r : registers.values()) { + for (Register r : registers) { + if (r == null) continue; if (r.getName().equals(name)) { return r; } @@ -86,15 +80,11 @@ public class RegisterSet implements Target, MongoSerializable { */ @Override public int get(int address) { - - Register register = registers.get(address); - - if (register != null) { - return register.getValue(); - } else { + if (address <= 0 || address >= REG_COUNT) { return 0; } + return registers[address].getValue(); } /** @@ -105,23 +95,17 @@ public class RegisterSet implements Target, MongoSerializable { */ @Override public void set(int address, int value) { - - Register register = registers.get(address); - - if (register != null) { - register.setValue(value); - } else { - LogManager.LOGGER.info("DEBUG: trying to set unknown reg index : " + address); + if (address <= 0 || address >= REG_COUNT) { + return; } - } - public void put(int index, Register register) { - registers.put(index, register); + registers[address].setValue(value); } public void clear() { - for (Register register : registers.values()) { - register.setValue(0); + for (Register r : registers) { + if (r == null) continue; + r.setValue(0); } } @@ -134,24 +118,24 @@ public class RegisterSet implements Target, MongoSerializable { * @param index Index of the register * @param reg Register to add */ - void addRegister(int index, Register reg) { - registers.put(index, reg); + public void put(int index, Register reg) { + registers[index] = reg; } int size() { - return registers.size(); + return REG_COUNT - 1; } @Override public Document mongoSerialise() { List registers = new ArrayList<>(); - for (Integer index : this.registers.keySet()) { + for (int i = 1; i < REG_COUNT; i++) { Document register = new Document(); - register.put("index", index); - register.put("name", getRegister(index).getName()); - register.put("value", (int) getRegister(index).getValue()); + register.put("index", i); + register.put("name", getRegister(i).getName()); + register.put("value", (int) getRegister(i).getValue()); registers.add(register); } @@ -169,11 +153,12 @@ public class RegisterSet implements Target, MongoSerializable { List registers = (ArrayList) obj.get("registers"); for (Object sRegister : registers) { + if (sRegister == null) continue; Register register = new Register((String) ((Document) sRegister).get("name")); register.setValue(((Document) sRegister).getInteger("value")); - registerSet.registers.put(((Document) sRegister).getInteger("index"), register); + registerSet.put(((Document) sRegister).getInteger("index"), register); } @@ -191,7 +176,7 @@ public class RegisterSet implements Target, MongoSerializable { Register register = new Register((String) jsonRegister.get("name")); register.setValue((int) (long) jsonRegister.get("value")); - registerSet.registers.put((int) (long) jsonRegister.get("index"), register); + registerSet.put((int) (long) jsonRegister.get("index"), register); } @@ -202,10 +187,20 @@ public class RegisterSet implements Target, MongoSerializable { public String toString() { String str = ""; - for (Integer index : registers.keySet()) { - str += index + " " + registers.get(index).getName() + "=" + Util.toHex(registers.get(index).getValue()) + "\n"; + for (int i = 1; i < REG_COUNT; i++) { + str += i + " " + getRegister(i).getName() + "=" + Util.toHex(getRegister(i).getValue()) + "\n"; } return str; } + + @Override + public RegisterSet clone() { + RegisterSet rs = new RegisterSet(); + + for (int i = 1; i < REG_COUNT; i++) { + rs.put(i, getRegister(i).clone()); + } + return rs; + } } 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 6393282..21c0e82 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Status.java +++ b/src/main/java/net/simon987/mar/server/assembly/Status.java @@ -3,7 +3,7 @@ package net.simon987.mar.server.assembly; /** * Represents the state of the processor */ -public class Status { +public class Status implements Cloneable { /** * Set to true when the result of @@ -148,4 +148,14 @@ public class Status { setCarryFlag((stat & (1 << 1)) != 0); setOverflowFlag((stat & 1) != 0); } + + @Override + public Status clone() { + try { + return (Status) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return null; + } } diff --git a/src/main/java/net/simon987/mar/server/assembly/instruction/HwiInstruction.java b/src/main/java/net/simon987/mar/server/assembly/instruction/HwiInstruction.java index 8a057ac..fb059aa 100755 --- a/src/main/java/net/simon987/mar/server/assembly/instruction/HwiInstruction.java +++ b/src/main/java/net/simon987/mar/server/assembly/instruction/HwiInstruction.java @@ -23,17 +23,12 @@ public class HwiInstruction extends Instruction { @Override public Status execute(Target src, int srcIndex, Status status) { status.setErrorFlag(cpu.getHardwareHost().hardwareInterrupt(src.get(srcIndex), cpu.getStatus())); - return status; } @Override public Status execute(int src, Status status) { - status.setErrorFlag(cpu.getHardwareHost().hardwareInterrupt(src, cpu.getStatus())); - return status; } - - } diff --git a/src/main/java/net/simon987/mar/server/game/objects/HardwareHost.java b/src/main/java/net/simon987/mar/server/game/objects/HardwareHost.java index 01946d8..79df104 100644 --- a/src/main/java/net/simon987/mar/server/game/objects/HardwareHost.java +++ b/src/main/java/net/simon987/mar/server/game/objects/HardwareHost.java @@ -12,5 +12,4 @@ public interface HardwareHost { boolean hardwareInterrupt(int address, Status status); int hardwareQuery(int address); - } diff --git a/src/test/java/net/simon987/mar/server/FakeHardwareHost.java b/src/test/java/net/simon987/mar/server/FakeHardwareHost.java new file mode 100644 index 0000000..e456aa0 --- /dev/null +++ b/src/test/java/net/simon987/mar/server/FakeHardwareHost.java @@ -0,0 +1,53 @@ +package net.simon987.mar.server; + +import net.simon987.mar.server.assembly.CPU; +import net.simon987.mar.server.assembly.CpuState; +import net.simon987.mar.server.assembly.HardwareModule; +import net.simon987.mar.server.assembly.Status; +import net.simon987.mar.server.game.objects.HardwareHost; + +import java.util.ArrayList; +import java.util.List; + + +public class FakeHardwareHost implements HardwareHost { + + public final List callHistory = new ArrayList<>(); + private final CPU cpu; + + public FakeHardwareHost(CPU cpu) { + this.cpu = cpu; + } + + @Override + public void attachHardware(HardwareModule hardware, int address) { + // noop + } + + @Override + public void detachHardware(int address) { + // noop + } + + @Override + public boolean hardwareInterrupt(int address, Status status) { + callHistory.add(new HwiCall(address, cpu.getState())); + return true; + } + + @Override + public int hardwareQuery(int address) { + return -1; + } + + public static class HwiCall { + public HwiCall(int address, CpuState state) { + this.address = address; + this.state = state; + } + + public int address; + public CpuState state; + } +} + diff --git a/src/test/java/net/simon987/mar/server/TestExecutionResult.java b/src/test/java/net/simon987/mar/server/TestExecutionResult.java new file mode 100644 index 0000000..198f4e4 --- /dev/null +++ b/src/test/java/net/simon987/mar/server/TestExecutionResult.java @@ -0,0 +1,35 @@ +package net.simon987.mar.server; + +import net.simon987.mar.server.assembly.AssemblyResult; +import net.simon987.mar.server.assembly.CpuState; + +import java.util.List; + +public class TestExecutionResult { + + public CpuState state; + + public List hwiHistory; + + private AssemblyResult ar; + + public TestExecutionResult(CpuState state, List hwiHistory, AssemblyResult ar) { + this.state = state; + this.hwiHistory = hwiHistory; + this.ar = ar; + } + + public int regValue(String register) { + return state.registers.getRegister(register).getValue(); + } + + public int labelOffset(String label) { + return ar.labels.get(label); + } + + public int memValue(int offset) { + return state.memory.get(offset); + } + + // Add more utility methods here +} diff --git a/src/test/java/net/simon987/mar/server/assembly/RegisterSetTest.java b/src/test/java/net/simon987/mar/server/assembly/RegisterSetTest.java index d69cd1a..fc80665 100644 --- a/src/test/java/net/simon987/mar/server/assembly/RegisterSetTest.java +++ b/src/test/java/net/simon987/mar/server/assembly/RegisterSetTest.java @@ -74,5 +74,4 @@ public class RegisterSetTest { //Test unknown indexes registerSet.set(3, 10); } - } 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 570d590..bb5ab28 100644 --- a/src/test/java/net/simon987/mar/server/assembly/TestHelper.java +++ b/src/test/java/net/simon987/mar/server/assembly/TestHelper.java @@ -1,18 +1,57 @@ package net.simon987.mar.server.assembly; import net.simon987.mar.server.FakeConfiguration; +import net.simon987.mar.server.FakeHardwareHost; import net.simon987.mar.server.IServerConfiguration; +import net.simon987.mar.server.TestExecutionResult; +import net.simon987.mar.server.assembly.exception.CancelledException; -class TestHelper { +public class TestHelper { - static Assembler getTestAsm() { + private static final int TIMEOUT = 100; - IServerConfiguration configuration = new FakeConfiguration(); - - configuration.setInt("memory_size", 1000); - configuration.setInt("org_offset", 400); - - return new Assembler(new DefaultInstructionSet(), new DefaultRegisterSet(), configuration); + public static Assembler getTestAsm() { + IServerConfiguration config = getTestConfig(); + CPU cpu = getTestCpu(); + return new Assembler(cpu.getInstructionSet(), cpu.getRegisterSet(), config); } + public static IServerConfiguration getTestConfig() { + IServerConfiguration configuration = new FakeConfiguration(); + + configuration.setInt("memory_size", 65536); + configuration.setInt("org_offset", 400); + return configuration; + } + + public static CPU getTestCpu() { + + CPU cpu = null; + try { + cpu = new CPU(getTestConfig()); + } catch (CancelledException e) { + e.printStackTrace(); + } + return cpu; + } + + public static TestExecutionResult executeCode(String code) { + AssemblyResult ar = getTestAsm().parse(code); + CPU cpu = TestHelper.getTestCpu(); + + FakeHardwareHost host = new FakeHardwareHost(cpu); + cpu.setHardwareHost(host); + + cpu.getMemory().clear(); + + char[] assembledCode = ar.getWords(); + + cpu.getMemory().write((char) ar.origin, assembledCode, 0, assembledCode.length); + cpu.setCodeSectionOffset(ar.getCodeSectionOffset()); + + cpu.reset(); + cpu.execute(TIMEOUT); + + return new TestExecutionResult(cpu.getState(), host.callHistory, ar); + } } diff --git a/src/test/java/net/simon987/mar/server/assembly/instruction/CallInstructionTest.java b/src/test/java/net/simon987/mar/server/assembly/instruction/CallInstructionTest.java index 9fd19cf..e5f286e 100644 --- a/src/test/java/net/simon987/mar/server/assembly/instruction/CallInstructionTest.java +++ b/src/test/java/net/simon987/mar/server/assembly/instruction/CallInstructionTest.java @@ -1,6 +1,30 @@ package net.simon987.mar.server.assembly.instruction; +import net.simon987.mar.server.FakeConfiguration; +import net.simon987.mar.server.IServerConfiguration; +import net.simon987.mar.server.TestExecutionResult; +import net.simon987.mar.server.assembly.*; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + public class CallInstructionTest { + @Test + public void callSimple1() { + String code = "" + + "my_routine: \n" + + " MOV X, 0x1234 \n" + + " RET \n" + + ".text \n" + + "CALL my_routine \n" + + "MOV Y, 0x4567 \n" + + "brk \n"; + TestExecutionResult res = TestHelper.executeCode(code); + + assertEquals(0x1234, res.regValue("X")); + assertEquals(0x4567, res.regValue("Y")); + } } diff --git a/src/test/java/net/simon987/mar/server/assembly/instruction/HwiInstructionTest.java b/src/test/java/net/simon987/mar/server/assembly/instruction/HwiInstructionTest.java new file mode 100644 index 0000000..1ab7b22 --- /dev/null +++ b/src/test/java/net/simon987/mar/server/assembly/instruction/HwiInstructionTest.java @@ -0,0 +1,26 @@ +package net.simon987.mar.server.assembly.instruction; + +import net.simon987.mar.server.FakeHardwareHost; +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 HwiInstructionTest { + + @Test + public void hwiSimple1() { + String code = "" + + "MOV A, 0x123 \n" + + "HWI 0x4567 \n" + + "MOV A, 0x789 \n" + + "brk \n"; + + TestExecutionResult res = TestHelper.executeCode(code); + + FakeHardwareHost.HwiCall call = res.hwiHistory.get(0); + assertEquals(0x4567, call.address); + assertEquals(0x123, call.state.registers.getRegister("A").getValue()); + } +}