From 8ff61a6081b54ff19331f95b492bb6c9f208902b Mon Sep 17 00:00:00 2001 From: catbref Date: Thu, 18 Jun 2020 16:32:12 +0100 Subject: [PATCH] Add ADD/SUB/MUL/DIV_VAL opcodes + tests --- Java/src/main/java/org/ciyam/at/OpCode.java | 69 ++++++++ .../java/org/ciyam/at/DataOpCodeTests.java | 24 --- .../java/org/ciyam/at/ValueOpCodeTests.java | 149 ++++++++++++++++++ 3 files changed, 218 insertions(+), 24 deletions(-) create mode 100644 Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java diff --git a/Java/src/main/java/org/ciyam/at/OpCode.java b/Java/src/main/java/org/ciyam/at/OpCode.java index df3861a..95fb649 100644 --- a/Java/src/main/java/org/ciyam/at/OpCode.java +++ b/Java/src/main/java/org/ciyam/at/OpCode.java @@ -876,6 +876,55 @@ public enum OpCode { state.dataByteBuffer.putLong(address1, functionData.returnValue); } + }, + /** + * ADD VALue
+ * 0x46 addr1 value
+ * @addr1 += value + */ + ADD_VAL(0x46, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { + @Override + protected void executeWithParams(MachineState state, Object... args) throws ExecutionException { + executeValueOperation(state, (a, b) -> a + b, args); + } + }, + /** + * SUBtract VALue
+ * 0x07 addr1 value
+ * @addr1 -= value + */ + SUB_VAL(0x47, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { + @Override + protected void executeWithParams(MachineState state, Object... args) throws ExecutionException { + executeValueOperation(state, (a, b) -> a - b, args); + } + }, + /** + * MULtiply VALue
+ * 0x08 addr1 value
+ * @addr1 *= value + */ + MUL_VAL(0x48, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { + @Override + protected void executeWithParams(MachineState state, Object... args) throws ExecutionException { + executeValueOperation(state, (a, b) -> a * b, args); + } + }, + /** + * DIVide VALue
+ * 0x09 addr1 value
+ * @addr1 /= value + * Can also throw IllegealOperationException if divide-by-zero attempted. + */ + DIV_VAL(0x49, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { + @Override + protected void executeWithParams(MachineState state, Object... args) throws ExecutionException { + try { + executeValueOperation(state, (a, b) -> a / b, args); + } catch (ArithmeticException e) { + throw new IllegalOperationException("Divide by zero", e); + } + } }; public final byte value; @@ -1001,6 +1050,26 @@ public enum OpCode { state.dataByteBuffer.putLong(address1, newValue); } + /** + * Common code for ADD_VAL/SUB_VAL/MUL_VAL/DIV_VAL/MOD_VAL/SHL_VAL/SHR_VAL + * + * @param codeByteBuffer + * @param dataByteBuffer + * @param operator + * - typically a lambda operating on two long params, e.g. (a, b) -> a + b + * @throws ExecutionException + */ + protected void executeValueOperation(MachineState state, TwoValueOperator operator, Object... args) throws ExecutionException { + int address1 = (int) args[0]; + + long value1 = state.dataByteBuffer.getLong(address1); + long value2 = (long) args[1]; + + long newValue = operator.apply(value1, value2); + + state.dataByteBuffer.putLong(address1, newValue); + } + /** * Common code for BGT/BLT/BGE/BLE/BEQ/BNE * diff --git a/Java/src/test/java/org/ciyam/at/DataOpCodeTests.java b/Java/src/test/java/org/ciyam/at/DataOpCodeTests.java index 6faf1e0..be0d8e8 100644 --- a/Java/src/test/java/org/ciyam/at/DataOpCodeTests.java +++ b/Java/src/test/java/org/ciyam/at/DataOpCodeTests.java @@ -9,30 +9,6 @@ import org.junit.Test; public class DataOpCodeTests extends ExecutableTest { - @Test - public void testSET_VAL() throws ExecutionException { - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); - codeByteBuffer.put(OpCode.FIN_IMD.value); - - execute(true); - - assertTrue(state.isFinished()); - assertFalse(state.hadFatalError()); - assertEquals("Data does not match", 2222L, getData(2)); - } - - /** Check that trying to use an address outside data segment throws a fatal error. */ - @Test - public void testSET_VALunbounded() throws ExecutionException { - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(9999).putLong(2222L); - codeByteBuffer.put(OpCode.FIN_IMD.value); - - execute(true); - - assertTrue(state.isFinished()); - assertTrue(state.hadFatalError()); - } - @Test public void testSET_DAT() throws ExecutionException { codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); diff --git a/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java b/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java new file mode 100644 index 0000000..fc36a95 --- /dev/null +++ b/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java @@ -0,0 +1,149 @@ +package org.ciyam.at; + +import static org.junit.Assert.*; + +import org.ciyam.at.test.ExecutableTest; +import org.junit.Test; + +public class ValueOpCodeTests extends ExecutableTest { + + @Test + public void testSET_VAL() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Data does not match", 2222L, getData(2)); + } + + /** Check that trying to use an address outside data segment throws a fatal error. */ + @Test + public void testSET_VALunbounded() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(9999).putLong(2222L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertTrue(state.hadFatalError()); + } + + @Test + public void testADD_VAL() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); + codeByteBuffer.put(OpCode.ADD_VAL.value).putInt(2).putLong(3333L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Data does not match", 2222L + 3333L, getData(2)); + } + + /** Check that trying to use an address outside data segment throws a fatal error. */ + @Test + public void testADD_VALunbounded() throws ExecutionException { + codeByteBuffer.put(OpCode.ADD_VAL.value).putInt(9999).putLong(3333L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertTrue(state.hadFatalError()); + } + + /** Check that adding to an unsigned long value overflows correctly. */ + @Test + public void testADD_VALoverflow() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0x7fffffffffffffffL); + codeByteBuffer.put(OpCode.ADD_VAL.value).putInt(2).putLong(0x8000000000000099L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Data does not match", 0x0000000000000098L, getData(2)); + } + + @Test + public void testSUB_VAL() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); + codeByteBuffer.put(OpCode.SUB_VAL.value).putInt(3).putLong(2222L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Data does not match", 3333L - 2222L, getData(3)); + } + + @Test + public void testMUL_VAL() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(3333L); + codeByteBuffer.put(OpCode.MUL_VAL.value).putInt(2).putLong(2222L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Data does not match", (3333L * 2222L), getData(2)); + } + + @Test + public void testDIV_VAL() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(3333L); + codeByteBuffer.put(OpCode.DIV_VAL.value).putInt(2).putLong(2222L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Data does not match", (3333L / 2222L), getData(2)); + } + + /** Check divide-by-zero throws fatal error because error handler not set. */ + @Test + public void testDIV_VALzero() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); + codeByteBuffer.put(OpCode.DIV_VAL.value).putInt(3).putLong(0); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertTrue(state.hadFatalError()); + } + + /** Check divide-by-zero is non-fatal because error handler is set. */ + @Test + public void testDIV_DATzeroWithOnError() throws ExecutionException { + int errorAddr = 0x20; // adjust this manually + + codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr); + + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L); + codeByteBuffer.put(OpCode.DIV_VAL.value).putInt(3).putLong(0); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + // errorAddr: + assertEquals(errorAddr, codeByteBuffer.position()); + // Set 1 at address 1 to indicate we handled error OK + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Error flag not set", 1L, getData(1)); + } + +}