From 0a166963528b4f43236139ec36ef6d6cdb82f22d Mon Sep 17 00:00:00 2001 From: catbref Date: Sat, 13 Nov 2021 15:32:10 +0000 Subject: [PATCH] New OpCodes: SLP_DAT addr: fixed to work as advertised SLP_VAL value: sleeps for blocks SHL_VAL addr value: left-shifts the contents of addr by value bits SHR_VAL addr value: right-shifts the contents of addr by value bits EXT_FUN_VAL func value: pass a value to a function instead of fetching via addr This allows new functions GET_A_DAT to take data segment address, instead of convoluted GET_A_IND which requires an address stored in the data segment (like a pointer). New FunctionCodes: GET_A_DAT, GET_B_DAT, SET_A_DAT, SET_B_DAT (see above) UNSIGNED_COMPARE_A_WITH_B, SIGNED_COMPARE_A_WITH_B Add branch offset bounds checking to OpCode.calcOffset method (throws if out of bounds). Added test to cover. Tidied up JavaDoc, which is now also generated by pom.xml! Added JavaDoc comments to OpCodeParam enum entries. Corrected some inaccurate descriptions. Converted obsolete tags to . Fixed some invalid HTML, converted raw characters like &, <, >, etc. to HTML entites like &, > and → Bumped version to 1.4.0 --- Java/pom.xml | 31 +- Java/src/main/java/org/ciyam/at/API.java | 10 +- .../main/java/org/ciyam/at/FunctionCode.java | 300 +++++++++----- Java/src/main/java/org/ciyam/at/OpCode.java | 368 +++++++++++------- .../main/java/org/ciyam/at/OpCodeParam.java | 143 ++++++- .../src/main/java/org/ciyam/at/Timestamp.java | 6 +- Java/src/main/java/org/ciyam/at/Utils.java | 16 +- .../test/java/org/ciyam/at/CompileTests.java | 27 ++ .../java/org/ciyam/at/FunctionCodeTests.java | 152 ++++++++ .../test/java/org/ciyam/at/OpCodeTests.java | 48 ++- .../java/org/ciyam/at/ValueOpCodeTests.java | 66 ++++ 11 files changed, 910 insertions(+), 257 deletions(-) diff --git a/Java/pom.xml b/Java/pom.xml index f668173..f49c4cd 100644 --- a/Java/pom.xml +++ b/Java/pom.xml @@ -4,10 +4,16 @@ 4.0.0 org.ciyam AT - 1.3.8 + 1.4.0 jar - true + UTF-8 + false + + 3.8.1 + 3.3.1 + 3.0.0-M4 + 1.64 @@ -15,20 +21,35 @@ src/test/java + org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + ${maven-compiler-plugin.version} - 11 + 11 + 11 org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M4 + ${maven-surefire-plugin.version} ${skipTests} + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadoc + + jar + + + + diff --git a/Java/src/main/java/org/ciyam/at/API.java b/Java/src/main/java/org/ciyam/at/API.java index 7244da6..4e46990 100644 --- a/Java/src/main/java/org/ciyam/at/API.java +++ b/Java/src/main/java/org/ciyam/at/API.java @@ -76,8 +76,8 @@ public abstract class API { *

* AT should sleep so it can use next block as source of entropy. *

- * Set state.isSleeping = true before exit on first call.
- * state.steps will be zero on second call after wake-up. + * Set state.isSleeping = true before exit on first call.
+ * state.steps will be zero on second call after wake-up. *

* Returns 0xffffffffffffffff if A not valid transaction. */ @@ -102,9 +102,9 @@ public abstract class API { public abstract void messageAToB(MachineState state); /** - * Returns minutes of blocks added to 'timestamp' + * Returns minutes of blocks added to 'timestamp' *

- * minutes is converted to rough number of blocks and added to 'timestamp' to create return value. + * minutes is converted to rough number of blocks and added to 'timestamp' to create return value. */ public abstract long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state); @@ -113,7 +113,7 @@ public abstract class API { * * @param amount * - final balance to be returned to creator - * @param state + * @param state AT machine state */ public abstract void onFinished(long amount, MachineState state); diff --git a/Java/src/main/java/org/ciyam/at/FunctionCode.java b/Java/src/main/java/org/ciyam/at/FunctionCode.java index 3c6c17a..f3fb5a9 100644 --- a/Java/src/main/java/org/ciyam/at/FunctionCode.java +++ b/Java/src/main/java/org/ciyam/at/FunctionCode.java @@ -12,19 +12,19 @@ import java.util.stream.Collectors; *

* Function codes are represented by a short. Functions can take 0 to 2 additional long values and optionally return a value too. *

- * FunctionCode instances can be obtained via the default FunctionCode.valueOf(String) or the additional FunctionCode.valueOf(int). + * FunctionCode instances can be obtained via the default FunctionCode.valueOf(String) or the additional FunctionCode.valueOf(int). *

- * Use the FunctionCode.execute method to perform the operation. + * Use the FunctionCode.execute method to perform the operation. *

* For more details, view the API Specification. * * @see FunctionCode#valueOf(int) - * @see FunctionCode#execute(FunctionData, MachineState) + * @see FunctionCode#execute(FunctionData, MachineState, short) */ public enum FunctionCode { /** * ECHO value to logger
- * 0x0001 value + * 0x0001 value */ ECHO(0x0001, 1, false) { @Override @@ -34,7 +34,7 @@ public enum FunctionCode { } }, /** - * 0x0100
+ * 0x0100
* Returns A1 value */ GET_A1(0x0100, 0, true) { @@ -44,7 +44,7 @@ public enum FunctionCode { } }, /** - * 0x0101
+ * 0x0101
* Returns A2 value */ GET_A2(0x0101, 0, true) { @@ -54,7 +54,7 @@ public enum FunctionCode { } }, /** - * 0x0102
+ * 0x0102
* Returns A3 value */ GET_A3(0x0102, 0, true) { @@ -64,7 +64,7 @@ public enum FunctionCode { } }, /** - * 0x0103
+ * 0x0103
* Returns A4 value */ GET_A4(0x0103, 0, true) { @@ -74,7 +74,7 @@ public enum FunctionCode { } }, /** - * 0x0104
+ * 0x0104
* Returns B1 value */ GET_B1(0x0104, 0, true) { @@ -84,7 +84,7 @@ public enum FunctionCode { } }, /** - * 0x0105
+ * 0x0105
* Returns B2 value */ GET_B2(0x0105, 0, true) { @@ -94,7 +94,7 @@ public enum FunctionCode { } }, /** - * 0x0106
+ * 0x0106
* Returns B3 value */ GET_B3(0x0106, 0, true) { @@ -104,7 +104,7 @@ public enum FunctionCode { } }, /** - * 0x0107
+ * 0x0107
* Returns B4 value */ GET_B4(0x0107, 0, true) { @@ -115,7 +115,7 @@ public enum FunctionCode { }, /** * Copies A into addr to addr+3
- * 0x0108 addr + * 0x0108 addr */ GET_A_IND(0x0108, 1, false) { @Override @@ -133,7 +133,7 @@ public enum FunctionCode { }, /** * Copies B into addr to addr+3
- * 0x0109 addr + * 0x0109 addr */ GET_B_IND(0x0109, 1, false) { @Override @@ -149,9 +149,47 @@ public enum FunctionCode { state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.b4); } }, + /** + * Copies A into addr to addr+3
+ * 0x010a addr
+ * To be used by calling EXT_FUN_VAL, passing destination address as value + */ + GET_A_DAT(0x010a, 1, false) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + // Validate data offset in arg1 + checkDataAddress(state, functionData.value1, 4); + + int dataIndex = (int) (functionData.value1 & Integer.MAX_VALUE); + + state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.a1); + state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.a2); + state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.a3); + state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.a4); + } + }, + /** + * Copies B into addr to addr+3
+ * 0x010b addr
+ * To be used by calling EXT_FUN_VAL, passing destination address as value + */ + GET_B_DAT(0x010b, 1, false) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + // Validate data offset in arg1 + checkDataAddress(state, functionData.value1, 4); + + int dataIndex = (int) (functionData.value1 & Integer.MAX_VALUE); + + state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.b1); + state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.b2); + state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.b3); + state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.b4); + } + }, /** * Set A1
- * 0x0110 value + * 0x0110 value */ SET_A1(0x0110, 1, false) { @Override @@ -161,7 +199,7 @@ public enum FunctionCode { }, /** * Set A2
- * 0x0111 value + * 0x0111 value */ SET_A2(0x0111, 1, false) { @Override @@ -171,7 +209,7 @@ public enum FunctionCode { }, /** * Set A3
- * 0x0112 value + * 0x0112 value */ SET_A3(0x0112, 1, false) { @Override @@ -181,7 +219,7 @@ public enum FunctionCode { }, /** * Set A4
- * 0x0113 value + * 0x0113 value */ SET_A4(0x0113, 1, false) { @Override @@ -191,7 +229,7 @@ public enum FunctionCode { }, /** * Set A1 and A2
- * 0x0114 value value + * 0x0114 value value */ SET_A1_A2(0x0114, 2, false) { @Override @@ -202,7 +240,7 @@ public enum FunctionCode { }, /** * Set A3 and A4
- * 0x0115 value value + * 0x0115 value value */ SET_A3_A4(0x0115, 2, false) { @Override @@ -213,7 +251,7 @@ public enum FunctionCode { }, /** * Set B1
- * 0x0116 value + * 0x0116 value */ SET_B1(0x0116, 1, false) { @Override @@ -223,7 +261,7 @@ public enum FunctionCode { }, /** * Set B2
- * 0x0117 value + * 0x0117 value */ SET_B2(0x0117, 1, false) { @Override @@ -233,7 +271,7 @@ public enum FunctionCode { }, /** * Set B3
- * 0x0118 value + * 0x0118 value */ SET_B3(0x0118, 1, false) { @Override @@ -243,7 +281,7 @@ public enum FunctionCode { }, /** * Set B4
- * 0x0119 value + * 0x0119 value */ SET_B4(0x0119, 1, false) { @Override @@ -253,7 +291,7 @@ public enum FunctionCode { }, /** * Set B1 and B2
- * 0x011a value value + * 0x011a value value */ SET_B1_B2(0x011a, 2, false) { @Override @@ -264,7 +302,7 @@ public enum FunctionCode { }, /** * Set B3 and B4
- * 0x011b value value + * 0x011b value value */ SET_B3_B4(0x011b, 2, false) { @Override @@ -275,7 +313,7 @@ public enum FunctionCode { }, /** * Copies addr to addr+3 into A
- * 0x011c addr + * 0x011c addr */ SET_A_IND(0x011c, 1, false) { @Override @@ -293,7 +331,7 @@ public enum FunctionCode { }, /** * Copies addr to addr+3 into B
- * 0x011d addr + * 0x011d addr */ SET_B_IND(0x011d, 1, false) { @Override @@ -309,9 +347,47 @@ public enum FunctionCode { state.b4 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE); } }, + /** + * Copies addr to addr+3 into A
+ * 0x011e addr
+ * To be used by calling EXT_FUN_VAL, passing source address as value + */ + SET_A_DAT(0x011e, 1, false) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + // Validate data offset in arg1 + checkDataAddress(state, functionData.value1, 4); + + int dataIndex = (int) (functionData.value1 & Integer.MAX_VALUE); + + state.a1 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE); + state.a2 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE); + state.a3 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE); + state.a4 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE); + } + }, + /** + * Copies addr to addr+3 into B
+ * 0x011f addr
+ * To be used by calling EXT_FUN_VAL, passing source address as value + */ + SET_B_DAT(0x011f, 1, false) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + // Validate data offset in arg1 + checkDataAddress(state, functionData.value1, 4); + + int dataIndex = (int) (functionData.value1 & Integer.MAX_VALUE); + + state.b1 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE); + state.b2 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE); + state.b3 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE); + state.b4 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE); + } + }, /** * Clear A
- * 0x0120 + * 0x0120 */ CLEAR_A(0x0120, 0, false) { @Override @@ -324,7 +400,7 @@ public enum FunctionCode { }, /** * Clear B
- * 0x0121 + * 0x0121 */ CLEAR_B(0x0121, 0, false) { @Override @@ -337,7 +413,7 @@ public enum FunctionCode { }, /** * Clear A and B
- * 0x0122 + * 0x0122 */ CLEAR_A_AND_B(0x0122, 0, false) { @Override @@ -354,7 +430,7 @@ public enum FunctionCode { }, /** * Copy A from B
- * 0x0123 + * 0x0123 */ COPY_A_FROM_B(0x0123, 0, false) { @Override @@ -367,7 +443,7 @@ public enum FunctionCode { }, /** * Copy B from A
- * 0x0124 + * 0x0124 */ COPY_B_FROM_A(0x0124, 0, false) { @Override @@ -380,7 +456,7 @@ public enum FunctionCode { }, /** * Check A is zero
- * 0x0125
+ * 0x0125
* Returns 1 if true, 0 if false */ CHECK_A_IS_ZERO(0x0125, 0, true) { @@ -394,7 +470,7 @@ public enum FunctionCode { }, /** * Check B is zero
- * 0x0126
+ * 0x0126
* Returns 1 if true, 0 if false */ CHECK_B_IS_ZERO(0x0126, 0, true) { @@ -408,7 +484,7 @@ public enum FunctionCode { }, /** * Check A equals B
- * 0x0127
+ * 0x0127
* Returns 1 if true, 0 if false */ CHECK_A_EQUALS_B(0x0127, 0, true) { @@ -422,7 +498,7 @@ public enum FunctionCode { }, /** * Swap A with B
- * 0x0128 + * 0x0128 */ SWAP_A_AND_B(0x0128, 0, false) { @Override @@ -445,7 +521,8 @@ public enum FunctionCode { }, /** * Bitwise-OR A with B
- * 0x0129 + * 0x0129
+ * A = A | B */ OR_A_WITH_B(0x0129, 0, false) { @Override @@ -458,20 +535,22 @@ public enum FunctionCode { }, /** * Bitwise-OR B with A
- * 0x012a + * 0x012a
+ * B = B | A */ OR_B_WITH_A(0x012a, 0, false) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - state.b1 = state.a1 | state.b1; - state.b2 = state.a2 | state.b2; - state.b3 = state.a3 | state.b3; - state.b4 = state.a4 | state.b4; + state.b1 = state.b1 | state.a1; + state.b2 = state.b2 | state.a2; + state.b3 = state.b3 | state.a3; + state.b4 = state.b4 | state.a4; } }, /** * Bitwise-AND A with B
- * 0x012b + * 0x012b
+ * A = A & B */ AND_A_WITH_B(0x012b, 0, false) { @Override @@ -484,20 +563,22 @@ public enum FunctionCode { }, /** * Bitwise-AND B with A
- * 0x012c + * 0x012c
+ * B = B & A */ AND_B_WITH_A(0x012c, 0, false) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - state.b1 = state.a1 & state.b1; - state.b2 = state.a2 & state.b2; - state.b3 = state.a3 & state.b3; - state.b4 = state.a4 & state.b4; + state.b1 = state.b1 & state.a1; + state.b2 = state.b2 & state.a2; + state.b3 = state.b3 & state.a3; + state.b4 = state.b4 & state.a4; } }, /** * Bitwise-XOR A with B
- * 0x012d + * 0x012d
+ * A = A ^ B */ XOR_A_WITH_B(0x012d, 0, false) { @Override @@ -510,20 +591,65 @@ public enum FunctionCode { }, /** * Bitwise-XOR B with A
- * 0x012e + * 0x012e
+ * B = B ^ A */ XOR_B_WITH_A(0x012e, 0, false) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - state.b1 = state.a1 ^ state.b1; - state.b2 = state.a2 ^ state.b2; - state.b3 = state.a3 ^ state.b3; - state.b4 = state.a4 ^ state.b4; + state.b1 = state.b1 ^ state.a1; + state.b2 = state.b2 ^ state.a2; + state.b3 = state.b3 ^ state.a3; + state.b4 = state.b4 ^ state.a4; + } + }, + /** + * Unsigned compare A with B
+ * 0x0130
+ * Returns -1, 0 or 1 depending on whether A is less than, equal or greater than B. + */ + UNSIGNED_COMPARE_A_WITH_B(0x0130, 0, true) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + int result = Long.compareUnsigned(state.a1, state.b1); + + if (result == 0) + result = Long.compareUnsigned(state.a2, state.b2); + + if (result == 0) + result = Long.compareUnsigned(state.a3, state.a3); + + if (result == 0) + result = Long.compareUnsigned(state.a4, state.a4); + + functionData.returnValue = Long.valueOf(result); + } + }, + /** + * Signed compare A with B
+ * 0x0131
+ * Returns -1, 0 or 1 depending on whether A is less than, equal or greater than B. + */ + SIGNED_COMPARE_A_WITH_B(0x0131, 0, true) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + int result = Long.compare(state.a1, state.b1); + + if (result == 0) + result = Long.compare(state.a2, state.b2); + + if (result == 0) + result = Long.compare(state.a3, state.a3); + + if (result == 0) + result = Long.compare(state.a4, state.a4); + + functionData.returnValue = Long.valueOf(result); } }, /** * MD5 data into B
- * 0x0200 start-addr byte-length
+ * 0x0200 start-addr byte-length
* MD5 hash stored in B1 and B2. B3 and B4 are zeroed. */ MD5_INTO_B(0x0200, 2, false) { @@ -548,7 +674,7 @@ public enum FunctionCode { }, /** * Check MD5 of data matches B
- * 0x0201 start-addr byte-length
+ * 0x0201 start-addr byte-length
* Other MD5 hash is in B1 and B2. B3 and B4 are ignored.
* Returns 1 if true, 0 if false */ @@ -579,7 +705,7 @@ public enum FunctionCode { }, /** * RIPE-MD160 data into B
- * 0x0202 start-addr byte-length
+ * 0x0202 start-addr byte-length
* RIPE-MD160 hash stored in LSB of B1 and all of B2 and B3. B4 is zeroed. */ RMD160_INTO_B(0x0202, 2, false) { @@ -604,7 +730,7 @@ public enum FunctionCode { }, /** * Check RIPE-MD160 of data matches B
- * 0x0203 start-addr byte-length
+ * 0x0203 start-addr byte-length
* Other RIPE-MD160 hash is in LSB of B1 and all of B2 and B3. B4 is ignored.
* Returns 1 if true, 0 if false */ @@ -637,7 +763,7 @@ public enum FunctionCode { }, /** * SHA256 data into B
- * 0x0204 start-addr byte-length
+ * 0x0204 start-addr byte-length
* SHA256 hash is stored in B1 through B4. */ SHA256_INTO_B(0x0204, 2, false) { @@ -662,7 +788,7 @@ public enum FunctionCode { }, /** * Check SHA256 of data matches B
- * 0x0205 start-addr byte-length
+ * 0x0205 start-addr byte-length
* Other SHA256 hash is in B1 through B4.
* Returns 1 if true, 0 if false */ @@ -695,7 +821,7 @@ public enum FunctionCode { }, /** * HASH160 data into B
- * 0x0206 start-addr byte-length
+ * 0x0206 start-addr byte-length
* Bitcoin's HASH160 hash is equivalent to RMD160(SHA256(data)).
* HASH160 hash stored in LSB of B1 and all of B2 and B3. B4 is zeroed. */ @@ -724,7 +850,7 @@ public enum FunctionCode { }, /** * Check HASH160 of data matches B
- * 0x0207 start-addr byte-length
+ * 0x0207 start-addr byte-length
* Other HASH160 hash is in B1, B2 and LSB of B3. B4 is ignored. * Returns 1 if true, 0 if false */ @@ -759,7 +885,7 @@ public enum FunctionCode { } }, /** - * 0x0300
+ * 0x0300
* Returns current block's "timestamp" */ GET_BLOCK_TIMESTAMP(0x0300, 0, true) { @@ -769,7 +895,7 @@ public enum FunctionCode { } }, /** - * 0x0301
+ * 0x0301
* Returns AT's creation block's "timestamp" */ GET_CREATION_TIMESTAMP(0x0301, 0, true) { @@ -779,7 +905,7 @@ public enum FunctionCode { } }, /** - * 0x0302
+ * 0x0302
* Returns previous block's "timestamp" */ GET_PREVIOUS_BLOCK_TIMESTAMP(0x0302, 0, true) { @@ -790,7 +916,7 @@ public enum FunctionCode { }, /** * Put previous block's hash in A
- * 0x0303 + * 0x0303 */ PUT_PREVIOUS_BLOCK_HASH_INTO_A(0x0303, 0, false) { @Override @@ -800,7 +926,7 @@ public enum FunctionCode { }, /** * Put transaction (to this AT) after timestamp in A, or zero if none
- * 0x0304 timestamp
+ * 0x0304 timestamp
* a-k-a "A_To_Tx_After_Timestamp" */ PUT_TX_AFTER_TIMESTAMP_INTO_A(0x0304, 1, false) { @@ -810,7 +936,7 @@ public enum FunctionCode { } }, /** - * 0x0305
+ * 0x0305
* Return transaction type from transaction in A
* Returns 0xffffffffffffffff in A not valid transaction */ @@ -821,7 +947,7 @@ public enum FunctionCode { } }, /** - * 0x0306
+ * 0x0306
* Return transaction amount from transaction in A
* Returns 0xffffffffffffffff in A not valid transaction */ @@ -832,7 +958,7 @@ public enum FunctionCode { } }, /** - * 0x0307
+ * 0x0307
* Return transaction timestamp from transaction in A
* Returns 0xffffffffffffffff in A not valid transaction */ @@ -844,7 +970,7 @@ public enum FunctionCode { }, /** * Generate random number using transaction in A
- * 0x0308
+ * 0x0308
* Returns 0xffffffffffffffff in A not valid transaction
* Can sleep to use next block as source of entropy */ @@ -866,7 +992,7 @@ public enum FunctionCode { }, /** * Put 'message' from transaction in A into B
- * 0x0309
+ * 0x0309
* If transaction has no 'message' then zero B
* Example 'message' could be 256-bit shared secret */ @@ -878,7 +1004,7 @@ public enum FunctionCode { }, /** * Put sender/creator address from transaction in A into B
- * 0x030a + * 0x030a */ PUT_ADDRESS_FROM_TX_IN_A_INTO_B(0x030a, 0, false) { @Override @@ -888,7 +1014,7 @@ public enum FunctionCode { }, /** * Put AT's creator's address into B
- * 0x030b + * 0x030b */ PUT_CREATOR_INTO_B(0x030b, 0, false) { @Override @@ -897,7 +1023,7 @@ public enum FunctionCode { } }, /** - * 0x0400
+ * 0x0400
* Returns AT's current balance */ GET_CURRENT_BALANCE(0x0400, 0, true) { @@ -907,7 +1033,7 @@ public enum FunctionCode { } }, /** - * 0x0401
+ * 0x0401
* Returns AT's previous balance at end of last execution round
* Does not include any amounts sent to AT since */ @@ -919,7 +1045,7 @@ public enum FunctionCode { }, /** * Pay fee-inclusive amount to account address in B
- * 0x0402 amount
+ * 0x0402 amount
* Reduces amount to current balance rather than failing due to insufficient funds */ PAY_TO_ADDRESS_IN_B(0x0402, 1, false) { @@ -941,7 +1067,7 @@ public enum FunctionCode { }, /** * Pay all remaining funds to account address in B
- * 0x0403 + * 0x0403 */ PAY_ALL_TO_ADDRESS_IN_B(0x0403, 0, false) { @Override @@ -955,7 +1081,7 @@ public enum FunctionCode { }, /** * Pay previous balance to account address in B
- * 0x0404
+ * 0x0404
* Reduces amount to current balance rather than failing due to insufficient funds */ PAY_PREVIOUS_TO_ADDRESS_IN_B(0x0404, 0, false) { @@ -977,7 +1103,7 @@ public enum FunctionCode { }, /** * Send A as a message to address in B
- * 0x0405 + * 0x0405 */ MESSAGE_A_TO_ADDRESS_IN_B(0x0405, 0, false) { @Override @@ -987,7 +1113,7 @@ public enum FunctionCode { }, /** * Add minutes to timestamp
- * 0x0406 timestamp minutes
+ * 0x0406 timestamp minutes
* Return 'timestamp' based on passed 'timestamp' plus minutes */ ADD_MINUTES_TO_TIMESTAMP(0x0406, 2, true) { @@ -997,7 +1123,7 @@ public enum FunctionCode { } }, /** - * 0x0500 - 0x06ff
+ * 0x0500 - 0x06ff
* Platform-specific functions.
* These are passed through to the API */ @@ -1050,12 +1176,10 @@ public enum FunctionCode { /** * Execute Function *

- * Can modify various fields of state, including programCounter. + * Can modify various fields of state, including programCounter. *

- * Throws a subclass of ExecutionException on error, e.g. InvalidAddressException. + * Throws a subclass of ExecutionException on error, e.g. InvalidAddressException. * - * @param functionData - * @param state * @throws ExecutionException */ public void execute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { @@ -1073,7 +1197,7 @@ public enum FunctionCode { postCheckExecute(functionData, state, rawFunctionCode); } - /** Actually execute function */ + /** Actually execute function after checks have completed. */ protected abstract void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException; // TODO: public abstract String disassemble(); diff --git a/Java/src/main/java/org/ciyam/at/OpCode.java b/Java/src/main/java/org/ciyam/at/OpCode.java index 95fb649..ae13ece 100644 --- a/Java/src/main/java/org/ciyam/at/OpCode.java +++ b/Java/src/main/java/org/ciyam/at/OpCode.java @@ -13,19 +13,19 @@ import java.util.stream.Collectors; *

* Op codes are represented by a single byte and maybe be followed by additional arguments like data addresses, offset, immediate values, etc. *

- * OpCode instances can be obtained via the default OpCode.valueOf(String) or the additional OpCode.valueOf(int). + * OpCode instances can be obtained via the default OpCode.valueOf(String) or the additional OpCode.valueOf(int). *

- * Use the OpCode.execute method to perform the operation. + * Use the OpCode.execute method to perform the operation. *

* In the documentation for each OpCode: *

- * @addr means "store at addr" + * @addr means "store at addr" *

- * $addr means "fetch from addr" + * $addr means "fetch from addr" *

- * @($addr) means "store at address fetched from addr", i.e. indirect + * @($addr) means "store at address fetched from addr", i.e. indirect *

- * $($addr1 + $addr2) means "fetch from address fetched from addr1 plus offset fetched from addr2", i.e. indirect indexed + * $($addr1 + $addr2) means "fetch from address fetched from addr1 plus offset fetched from addr2", i.e. indirect indexed * * @see OpCode#valueOf(int) * @see OpCode#executeWithParams(MachineState, Object...) @@ -34,7 +34,7 @@ public enum OpCode { /** * No OPeration
- * 0x7f
+ * 0x7f
* (Does nothing) */ NOP(0x7f) { @@ -45,8 +45,8 @@ public enum OpCode { }, /** * SET VALue
- * 0x01 addr value
- * @addr = value + * 0x01 addr value
+ * @addr = value */ SET_VAL(0x01, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { @Override @@ -59,8 +59,8 @@ public enum OpCode { }, /** * SET DATa
- * 0x02 addr1 addr2
- * @addr1 = $addr2 + * 0x02 addr1 addr2
+ * @addr1 = $addr2 */ SET_DAT(0x02, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -74,8 +74,8 @@ public enum OpCode { }, /** * CLeaR DATa
- * 0x03 addr
- * @addr = 0 + * 0x03 addr
+ * @addr = 0 */ CLR_DAT(0x03, OpCodeParam.DEST_ADDR) { @Override @@ -87,8 +87,8 @@ public enum OpCode { }, /** * INCrement DATa
- * 0x04 addr
- * @addr += 1 + * 0x04 addr
+ * @addr += 1 */ INC_DAT(0x04, OpCodeParam.DEST_ADDR) { @Override @@ -101,8 +101,8 @@ public enum OpCode { }, /** * DECrement DATa
- * 0x05 addr
- * @addr -= 1 + * 0x05 addr
+ * @addr -= 1 */ DEC_DAT(0x05, OpCodeParam.DEST_ADDR) { @Override @@ -115,8 +115,8 @@ public enum OpCode { }, /** * ADD DATa
- * 0x06 addr1 addr2
- * @addr1 += $addr2 + * 0x06 addr1 addr2
+ * @addr1 += $addr2 */ ADD_DAT(0x06, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -126,8 +126,8 @@ public enum OpCode { }, /** * SUBtract DATa
- * 0x07 addr1 addr2
- * @addr1 -= $addr2 + * 0x07 addr1 addr2
+ * @addr1 -= $addr2 */ SUB_DAT(0x07, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -137,8 +137,8 @@ public enum OpCode { }, /** * MULtiply DATa
- * 0x08 addr1 addr2
- * @addr1 *= $addr2 + * 0x08 addr1 addr2
+ * @addr1 *= $addr2 */ MUL_DAT(0x08, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -148,9 +148,9 @@ public enum OpCode { }, /** * DIVide DATa
- * 0x09 addr1 addr2
- * @addr1 /= $addr2
- * Can also throw IllegealOperationException if divide-by-zero attempted. + * 0x09 addr1 addr2
+ * @addr1 /= $addr2
+ * Can also throw IllegalOperationException if divide-by-zero attempted. */ DIV_DAT(0x09, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -164,8 +164,8 @@ public enum OpCode { }, /** * Binary-OR DATa
- * 0x0a addr1 addr2
- * @addr1 |= $addr2 + * 0x0a addr1 addr2
+ * @addr1 |= $addr2 */ BOR_DAT(0x0a, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -175,8 +175,8 @@ public enum OpCode { }, /** * Binary-AND DATa
- * 0x0b addr1 addr2
- * @addr1 &= $addr2 + * 0x0b addr1 addr2
+ * @addr1 &= $addr2 */ AND_DAT(0x0b, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -186,8 +186,8 @@ public enum OpCode { }, /** * EXclusive OR DATa
- * 0x0c addr1 addr2
- * @addr1 ^= $addr2 + * 0x0c addr1 addr2
+ * @addr1 ^= $addr2 */ XOR_DAT(0x0c, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -197,8 +197,8 @@ public enum OpCode { }, /** * Bitwise-NOT DATa
- * 0x0d addr
- * @addr = ~$addr + * 0x0d addr
+ * @addr = ~$addr */ NOT_DAT(0x0d, OpCodeParam.DEST_ADDR) { @Override @@ -211,8 +211,8 @@ public enum OpCode { }, /** * SET using INDirect data
- * 0x0e addr1 addr2
- * @addr1 = $($addr2) + * 0x0e addr1 addr2
+ * @addr1 = $($addr2) */ SET_IND(0x0e, OpCodeParam.DEST_ADDR, OpCodeParam.INDIRECT_SRC_ADDR) { @Override @@ -231,8 +231,8 @@ public enum OpCode { }, /** * SET using indirect InDeXed data
- * 0x0f addr1 addr2 addr3
- * @addr1 = $($addr2 + $addr3) + * 0x0f addr1 addr2 addr3
+ * @addr1 = $($addr2 + $addr3) */ SET_IDX(0x0f, OpCodeParam.DEST_ADDR, OpCodeParam.INDIRECT_SRC_ADDR_WITH_INDEX, OpCodeParam.INDEX) { @Override @@ -255,9 +255,9 @@ public enum OpCode { }, /** * PuSH DATa onto user stack
- * 0x10 addr
- * @--user_stack = $addr
- * Can also throw StackBoundsException if user stack exhausted. + * 0x10 addr
+ * @--user_stack = $addr
+ * Can also throw StackBoundsException if user stack exhausted. */ PSH_DAT(0x10, OpCodeParam.SRC_ADDR) { @Override @@ -278,9 +278,9 @@ public enum OpCode { }, /** * POP DATa from user stack
- * 0x11 addr
- * @addr = $user_stack++
- * Can also throw StackBoundsException if user stack empty. + * 0x11 addr
+ * @addr = $user_stack++
+ * Can also throw StackBoundsException if user stack empty. */ POP_DAT(0x11, OpCodeParam.DEST_ADDR) { @Override @@ -302,9 +302,10 @@ public enum OpCode { }, /** * JuMP into SUBroutine
- * 0x12 addr
- * @--call_stack = PC after opcode & args, PC = addr
- * Can also throw StackBoundsException if call stack exhausted. + * 0x12 addr
+ * @--call_stack = PC after opcode and args,
+ * PC = addr
+ * Can also throw StackBoundsException if call stack exhausted. */ JMP_SUB(0x12, OpCodeParam.CODE_ADDR) { @Override @@ -325,9 +326,9 @@ public enum OpCode { }, /** * RETurn from SUBroutine
- * 0x13
- * PC = $call_stack++
- * Can also throw StackBoundsException if call stack empty. + * 0x13
+ * PC = $call_stack++
+ * Can also throw StackBoundsException if call stack empty. */ RET_SUB(0x13) { @Override @@ -346,8 +347,8 @@ public enum OpCode { }, /** * Store INDirect DATa
- * 0x14 addr1 addr2
- * @($addr1) = $addr2 + * 0x14 addr1 addr2
+ * @($addr1) = $addr2 */ IND_DAT(0x14, OpCodeParam.INDIRECT_DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -366,8 +367,8 @@ public enum OpCode { }, /** * Store indirect InDeXed DATa
- * 0x15 addr1 addr2
- * @($addr1 + $addr2) = $addr3 + * 0x15 addr1 addr2
+ * @($addr1 + $addr2) = $addr3 */ IDX_DAT(0x15, OpCodeParam.INDIRECT_DEST_ADDR_WITH_INDEX, OpCodeParam.INDEX, OpCodeParam.SRC_ADDR) { @Override @@ -390,8 +391,8 @@ public enum OpCode { }, /** * MODulo DATa
- * 0x16 addr1 addr2
- * @addr1 %= $addr2 + * 0x16 addr1 addr2
+ * @addr1 %= $addr2 */ MOD_DAT(0x16, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -405,8 +406,8 @@ public enum OpCode { }, /** * SHift Left DATa
- * 0x17 addr1 addr2
- * @addr1 <<= $addr2 + * 0x17 addr1 addr2
+ * @addr1 <<= $addr2 */ SHL_DAT(0x17, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8L; @@ -419,8 +420,8 @@ public enum OpCode { }, /** * SHift Right DATa
- * 0x18 addr1 addr2
- * @addr1 >>= $addr2
+ * 0x18 addr1 addr2
+ * @addr1 >>= $addr2
* Note: new MSB bit will be zero */ SHR_DAT(0x18, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @@ -434,8 +435,8 @@ public enum OpCode { }, /** * JuMP to ADdRess
- * 0x1a addr
- * PC = addr + * 0x1a addr
+ * PC = addr */ JMP_ADR(0x1a, OpCodeParam.CODE_ADDR) { @Override @@ -447,9 +448,9 @@ public enum OpCode { }, /** * Branch if ZeRo
- * 0x1b addr offset
- * if ($addr == 0) PC += offset
- * Note: PC is considered to be immediately before opcode byte. + * 0x1b addr offset
+ * if ($addr == 0) PC += offset
+ * Note: PC is considered to be immediately before opcode byte. */ BZR_DAT(0x1b, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) { @Override @@ -467,9 +468,9 @@ public enum OpCode { }, /** * Branch if Not Zero
- * 0x1e addr offset
- * if ($addr != 0) PC += offset
- * Note: PC is considered to be immediately before opcode byte. + * 0x1e addr offset
+ * if ($addr != 0) PC += offset
+ * Note: PC is considered to be immediately before opcode byte. */ BNZ_DAT(0x1e, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) { @Override @@ -487,9 +488,9 @@ public enum OpCode { }, /** * Branch if Greater-Than DATa
- * 0x1f addr1 addr2 offset
- * if ($addr1 > $addr2) PC += offset
- * Note: PC is considered to be immediately before opcode byte. + * 0x1f addr1 addr2 offset
+ * if ($addr1 > $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte. */ BGT_DAT(0x1f, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) { @Override @@ -499,9 +500,9 @@ public enum OpCode { }, /** * Branch if Less-Than DATa
- * 0x20 addr1 addr2 offset
- * if ($addr1 < $addr2) PC += offset
- * Note: PC is considered to be immediately before opcode byte. + * 0x20 addr1 addr2 offset
+ * if ($addr1 < $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte. */ BLT_DAT(0x20, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) { @Override @@ -511,9 +512,9 @@ public enum OpCode { }, /** * Branch if Greater-or-Equal DATa
- * 0x21 addr1 addr2 offset
- * if ($addr1 >= $addr2) PC += offset
- * Note: PC is considered to be immediately before opcode byte. + * 0x21 addr1 addr2 offset
+ * if ($addr1 >= $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte. */ BGE_DAT(0x21, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) { @Override @@ -523,9 +524,9 @@ public enum OpCode { }, /** * Branch if Less-or-Equal DATa
- * 0x22 addr1 addr2 offset
- * if ($addr1 <= $addr2) PC += offset
- * Note: PC is considered to be immediately before opcode byte. + * 0x22 addr1 addr2 offset
+ * if ($addr1 <= $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte. */ BLE_DAT(0x22, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) { @Override @@ -535,9 +536,9 @@ public enum OpCode { }, /** * Branch if EQual DATa
- * 0x23 addr1 addr2 offset
- * if ($addr1 == $addr2) PC += offset
- * Note: PC is considered to be immediately before opcode byte. + * 0x23 addr1 addr2 offset
+ * if ($addr1 == $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte. */ BEQ_DAT(0x23, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) { @Override @@ -547,9 +548,9 @@ public enum OpCode { }, /** * Branch if Not-Equal DATa
- * 0x24 addr1 addr2 offset
- * if ($addr1 != $addr2) PC += offset
- * Note: PC is considered to be immediately before opcode byte. + * 0x24 addr1 addr2 offset
+ * if ($addr1 != $addr2) PC += offset
+ * Note: PC is considered to be immediately before opcode byte. */ BNE_DAT(0x24, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.OFFSET) { @Override @@ -559,16 +560,16 @@ public enum OpCode { }, /** * SLeeP until DATa
- * 0x25 addr
- * sleep until $addr, then carry on from current PC
- * Note: The value from $addr is considered to be a block height. + * 0x25 addr
+ * sleep until $addr, then carry on from current PC
+ * Note: The value from $addr is considered to be a block height. */ SLP_DAT(0x25, OpCodeParam.BLOCK_HEIGHT) { @Override protected void executeWithParams(MachineState state, Object... args) throws ExecutionException { int address = (int) args[0]; - long value = state.codeByteBuffer.getLong(address); + long value = state.dataByteBuffer.getLong(address); state.setSleepUntilHeight((int) value); state.setIsSleeping(true); @@ -576,8 +577,8 @@ public enum OpCode { }, /** * FInish if Zero DATa
- * 0x26 addr
- * if ($addr == 0) permanently stop + * 0x26 addr
+ * if ($addr == 0) permanently stop */ FIZ_DAT(0x26, OpCodeParam.SRC_ADDR) { @Override @@ -592,8 +593,8 @@ public enum OpCode { }, /** * STop if Zero DATa
- * 0x27 addr
- * if ($addr == 0) PC = PCS and stop + * 0x27 addr
+ * if ($addr == 0) PC = PCS and stop */ STZ_DAT(0x27, OpCodeParam.SRC_ADDR) { @Override @@ -610,8 +611,8 @@ public enum OpCode { }, /** * FINish IMmeDiately
- * 0x28
- * permanently stop + * 0x28
+ * permanently stop */ FIN_IMD(0x28) { @Override @@ -621,8 +622,8 @@ public enum OpCode { }, /** * SToP IMmeDiately
- * 0x29
- * stop + * 0x29
+ * stop */ STP_IMD(0x29) { @Override @@ -632,8 +633,8 @@ public enum OpCode { }, /** * SLeeP IMmeDiately
- * 0x2a
- * sleep until next block, then carry on from current PC + * 0x2a
+ * sleep until next block, then carry on from current PC */ SLP_IMD(0x2a) { @Override @@ -644,8 +645,8 @@ public enum OpCode { }, /** * Set ERRor ADdRess
- * 0x2b addr
- * PCE = addr + * 0x2b addr
+ * PCE = addr */ ERR_ADR(0x2b, OpCodeParam.CODE_ADDR) { @Override @@ -655,11 +656,26 @@ public enum OpCode { state.setOnErrorAddress(address); } }, + /** + * SLeeP for VALue blocks
+ * 0x2c value
+ * sleep until $addr, then carry on from current PC
+ * Note: The value from $addr is considered to be a block height. + */ + SLP_VAL(0x2c, OpCodeParam.VALUE) { + @Override + protected void executeWithParams(MachineState state, Object... args) throws ExecutionException { + long value = (long) args[0]; + + state.setSleepUntilHeight(state.getCurrentBlockHeight() + (int) value); + state.setIsSleeping(true); + } + }, /** * SET PCS (stop address)
- * 0x30
- * PCS = PC
- * Note: PC is considered to be immediately after this opcode byte. + * 0x30
+ * PCS = PC
+ * Note: PC is considered to be immediately after this opcode byte. */ SET_PCS(0x30) { @Override @@ -669,8 +685,8 @@ public enum OpCode { }, /** * Call EXTernal FUNction
- * 0x32 func
- * func() + * 0x32 func
+ * func() */ EXT_FUN(0x32, OpCodeParam.FUNC) { @Override @@ -699,8 +715,8 @@ public enum OpCode { }, /** * Call EXTernal FUNction with DATa
- * 0x33 func addr
- * func($addr) + * 0x33 func addr
+ * func($addr) */ EXT_FUN_DAT(0x33, OpCodeParam.FUNC, OpCodeParam.SRC_ADDR) { @Override @@ -731,8 +747,8 @@ public enum OpCode { }, /** * Call EXTernal FUNction with DATa x2
- * 0x34 func addr1 addr2
- * func($addr1, $addr2) + * 0x34 func addr1 addr2
+ * func($addr1, $addr2) */ EXT_FUN_DAT_2(0x34, OpCodeParam.FUNC, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -765,8 +781,8 @@ public enum OpCode { }, /** * Call EXTernal FUNction expecting RETurn value
- * 0x35 func addr
- * @addr = func() + * 0x35 func addr
+ * @addr = func() */ EXT_FUN_RET(0x35, OpCodeParam.FUNC, OpCodeParam.DEST_ADDR) { @Override @@ -801,8 +817,8 @@ public enum OpCode { }, /** * Call EXTernal FUNction expecting RETurn value with DATa
- * 0x36 func addr1 addr2
- * @addr1 = func($addr2) + * 0x36 func addr1 addr2
+ * @addr1 = func($addr2) */ EXT_FUN_RET_DAT(0x36, OpCodeParam.FUNC, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -839,8 +855,8 @@ public enum OpCode { }, /** * Call EXTernal FUNction expecting RETurn value with DATa x2
- * 0x37 func addr1 addr2 addr3
- * @addr1 = func($addr2, $addr3) + * 0x37 func addr1 addr2 addr3
+ * @addr1 = func($addr2, $addr3) */ EXT_FUN_RET_DAT_2(0x37, OpCodeParam.FUNC, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR, OpCodeParam.SRC_ADDR) { @Override @@ -877,10 +893,41 @@ public enum OpCode { state.dataByteBuffer.putLong(address1, functionData.returnValue); } }, + /** + * Call EXTernal FUNction with VALue
+ * 0x38 func value
+ * func(value) + */ + EXT_FUN_VAL(0x38, OpCodeParam.FUNC, OpCodeParam.VALUE) { + @Override + protected void preExecuteCheck(Object... args) throws ExecutionException { + short rawFunctionCode = (short) args[0]; + + FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode); + + if (functionCode == null) + throw new IllegalFunctionCodeException(String.format("Unknown function code 0x%04x encountered at %s", + rawFunctionCode, this.name())); + + functionCode.preExecuteCheck(1, false); + } + + @Override + protected void executeWithParams(MachineState state, Object... args) throws ExecutionException { + short rawFunctionCode = (short) args[0]; + long value = (long) args[1]; + + FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode); + + FunctionData functionData = new FunctionData(value, false); + + functionCode.execute(functionData, state, rawFunctionCode); + } + }, /** * ADD VALue
- * 0x46 addr1 value
- * @addr1 += value + * 0x46 addr1 value
+ * @addr1 += value */ ADD_VAL(0x46, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { @Override @@ -890,8 +937,8 @@ public enum OpCode { }, /** * SUBtract VALue
- * 0x07 addr1 value
- * @addr1 -= value + * 0x47 addr1 value
+ * @addr1 -= value */ SUB_VAL(0x47, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { @Override @@ -901,8 +948,8 @@ public enum OpCode { }, /** * MULtiply VALue
- * 0x08 addr1 value
- * @addr1 *= value + * 0x48 addr1 value
+ * @addr1 *= value */ MUL_VAL(0x48, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { @Override @@ -912,9 +959,9 @@ public enum OpCode { }, /** * DIVide VALue
- * 0x09 addr1 value
- * @addr1 /= value - * Can also throw IllegealOperationException if divide-by-zero attempted. + * 0x49 addr1 value
+ * @addr1 /= value + * Can also throw IllegalOperationException if divide-by-zero attempted. */ DIV_VAL(0x49, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { @Override @@ -925,6 +972,35 @@ public enum OpCode { throw new IllegalOperationException("Divide by zero", e); } } + }, + /** + * SHift Left VALue
+ * 0x4a addr1 value
+ * @addr1 <<= value + */ + SHL_VAL(0x4a, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { + private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8L; + + @Override + protected void executeWithParams(MachineState state, Object... args) throws ExecutionException { + // If 2nd arg is more than value size (in bits) then return 0 to simulate all bits being shifted out of existence + executeValueOperation(state, (a, b) -> b >= MAX_SHIFT ? 0 : a << b, args); + } + }, + /** + * SHift Right VALue
+ * 0x4b addr1 value
+ * @addr1 >>= value
+ * Note: new MSB bit will be zero + */ + SHR_VAL(0x4b, OpCodeParam.DEST_ADDR, OpCodeParam.VALUE) { + private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8L; + + @Override + protected void executeWithParams(MachineState state, Object... args) throws ExecutionException { + // If 2nd arg is more than value size (in bits) then return 0 to simulate all bits being shifted out of existence + executeValueOperation(state, (a, b) -> b >= MAX_SHIFT ? 0 : a >>> b, args); + } }; public final byte value; @@ -945,14 +1021,14 @@ public enum OpCode { /** * Execute OpCode with args fetched from code bytes *

- * Assumes codeByteBuffer.position() is already placed immediately after opcode and params.
- * state.getProgramCounter() is available to return position immediately before opcode and params. + * Assumes codeByteBuffer.position() is already placed immediately after opcode and params.
+ * state.getProgramCounter() is available to return position immediately before opcode and params. *

- * OpCode execution can modify codeByteBuffer.position() in cases like jumps, branches, etc. + * OpCode execution can modify codeByteBuffer.position() in cases like jumps, branches, etc. *

- * Can also modify userStackByteBuffer and various fields of state. + * Can also modify userStackByteBuffer and various fields of state. *

- * Throws a subclass of ExecutionException on error, e.g. InvalidAddressException. + * Throws a subclass of ExecutionException on error, e.g. InvalidAddressException. * * @param state * @param args @@ -978,11 +1054,18 @@ public enum OpCode { this.executeWithParams(state, argsArray); } - public static int calcOffset(ByteBuffer byteBuffer, Integer branchTarget) { + public static int calcOffset(ByteBuffer byteBuffer, Integer branchTarget) throws CompilationException { + // First-pass of compilation where we don't know branchTarget yet if (branchTarget == null) return 0; - return branchTarget - byteBuffer.position(); + int offset = branchTarget - byteBuffer.position(); + + // Bounds checking + if (offset < Byte.MIN_VALUE || offset > Byte.MAX_VALUE) + throw new CompilationException(String.format("Branch offset %02x (from PC %04x) is wider than a byte", offset, byteBuffer.position())); + + return offset; } public byte[] compile(Object... args) throws CompilationException { @@ -1032,10 +1115,10 @@ public enum OpCode { /** * Common code for ADD_DAT/SUB_DAT/MUL_DAT/DIV_DAT/MOD_DAT/SHL_DAT/SHR_DAT * - * @param codeByteBuffer - * @param dataByteBuffer + * @param state * @param operator - * - typically a lambda operating on two long params, e.g. (a, b) -> a + b + * - typically a lambda operating on two long params, e.g. (a, b) → a + b + * @param args * @throws ExecutionException */ protected void executeDataOperation(MachineState state, TwoValueOperator operator, Object... args) throws ExecutionException { @@ -1052,11 +1135,11 @@ public enum OpCode { /** * Common code for ADD_VAL/SUB_VAL/MUL_VAL/DIV_VAL/MOD_VAL/SHL_VAL/SHR_VAL - * - * @param codeByteBuffer - * @param dataByteBuffer + * + * @param state * @param operator - * - typically a lambda operating on two long params, e.g. (a, b) -> a + b + * - typically a lambda operating on two long params, e.g. (a, b) → a + b + * @param args * @throws ExecutionException */ protected void executeValueOperation(MachineState state, TwoValueOperator operator, Object... args) throws ExecutionException { @@ -1073,11 +1156,10 @@ public enum OpCode { /** * Common code for BGT/BLT/BGE/BLE/BEQ/BNE * - * @param codeByteBuffer - * @param dataByteBuffer * @param state * @param comparator - * - typically a lambda comparing two long params, e.g. (a, b) -> a == b + * - typically a lambda comparing two long params, e.g. (a, b) → a == b + * @param args * @throws ExecutionException */ protected void executeBranchConditional(MachineState state, TwoValueComparator comparator, Object... args) throws ExecutionException { diff --git a/Java/src/main/java/org/ciyam/at/OpCodeParam.java b/Java/src/main/java/org/ciyam/at/OpCodeParam.java index e86d1a8..4293037 100644 --- a/Java/src/main/java/org/ciyam/at/OpCodeParam.java +++ b/Java/src/main/java/org/ciyam/at/OpCodeParam.java @@ -4,6 +4,15 @@ import java.nio.ByteBuffer; enum OpCodeParam { + /** + * Literal 64-bit long value supplied from code segment. + *

+ *

+ * Example: SET_VAL DEST_ADDR VALUE
+ * Example: SET_VAL 3 12345
+ * Data segment address 3 will be set to the value 12345. + *

+ */ VALUE(OpCodeParam::compileLong) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -15,6 +24,15 @@ enum OpCodeParam { return String.format("#%016x", (Long) value); } }, + /** + * Destination address in data segment. + *

+ *

+ * Example: SET_VAL DEST_ADDR VALUE
+ * Example: SET_VAL 3 12345
+ * Data segment address 3 will be set to the value 12345. + *

+ */ DEST_ADDR(OpCodeParam::compileInt) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -26,6 +44,18 @@ enum OpCodeParam { return String.format("@%08x", ((Integer) value) / MachineState.VALUE_SIZE); } }, + /** + * Indirect destination address in data segment,
+ * using value extracted from supplied address, also in data segment. + *

+ *

+ * Example: IND_DAT INDIRECT_DEST_ADDR SRC_ADDR
+ * Example: IND_DAT 3 4
+ * If data segment address 3 contains the value 7,
+ * and data segment address 4 contains the value 12345,
+ * then data segment address 7 will be set to the value 12345. + *

+ */ INDIRECT_DEST_ADDR(OpCodeParam::compileInt) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -37,6 +67,21 @@ enum OpCodeParam { return String.format("@($%08x)", ((Integer) value) / MachineState.VALUE_SIZE); } }, + /** + * Indirect destination address in data segment,
+ * using value extracted from supplied address, also in data segment,
+ * and then offset by the value extracted from 2nd 'index' address, also in data segment. + *

+ *

+ * Example: IDX_DAT INDIRECT_DEST_ADDR_WITH_INDEX INDEX SRC_ADDR
+ * Example: IDX_DAT 3 4 20
+ * If data segment address 3 contains the value 7,
+ * and data segment address 4 contains the value 2,
+ * and data segment address 20 contains the value 12345,
+ * then data segment address 9 (7 + 2) will be set to the value 12345. + *

+ * @see OpCodeParam#INDEX + */ INDIRECT_DEST_ADDR_WITH_INDEX(OpCodeParam::compileInt) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -48,6 +93,16 @@ enum OpCodeParam { return String.format("@($%08x", ((Integer) value) / MachineState.VALUE_SIZE); } }, + /** + * Source address in data segment. + *

+ *

+ * Example: SET_DAT DEST_ADDR SRC_ADDR
+ * Example: SET_DAT 2 3
+ * If data segment address 3 contains the value 12345,
+ * then data segment address 2 will be set to the value 12345. + *

+ */ SRC_ADDR(OpCodeParam::compileInt) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -59,6 +114,18 @@ enum OpCodeParam { return String.format("$%08x", ((Integer) value) / MachineState.VALUE_SIZE); } }, + /** + * Indirect source address in data segment,
+ * using value extracted from supplied address, also in data segment. + *

+ *

+ * Example: SET_IND DEST_ADDR INDIRECT_SRC_ADDR
+ * Example: SET_IND 3 4
+ * If data segment address 4 contains the value 7,
+ * and data segment address 7 contains the value 12345,
+ * then data segment address 3 will be set to the value 12345. + *

+ */ INDIRECT_SRC_ADDR(OpCodeParam::compileInt) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -70,6 +137,20 @@ enum OpCodeParam { return String.format("$($%08x)", ((Integer) value) / MachineState.VALUE_SIZE); } }, + /** + * Indirect source address in data segment,
+ * using value extracted from supplied address, also in data segment,
+ * and then offset by the value extracted from 2nd 'index' address, also in data segment. + *

+ *

+ * Example: SET_IDX DEST_ADDR INDIRECT_SRC_ADDR INDEX
+ * Example: SET_IDX 3 4 5
+ * If data segment address 4 contains the value 7,
+ * and data segment address 5 contains the value 2,
+ * and data segment address 9 (7 + 2) contains the value 12345,
+ * then data segment address 3 will be set to the value 12345. + *

+ */ INDIRECT_SRC_ADDR_WITH_INDEX(OpCodeParam::compileInt) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -81,6 +162,21 @@ enum OpCodeParam { return String.format("$($%08x", ((Integer) value) / MachineState.VALUE_SIZE); } }, + /** + * Offset value extracted from address in data segment.
+ * Used with {@link OpCodeParam#INDIRECT_DEST_ADDR_WITH_INDEX} and {@link OpCodeParam#INDIRECT_DEST_ADDR_WITH_INDEX} + *

+ *

+ * Example: SET_IDX DEST_ADDR INDIRECT_SRC_ADDR INDEX
+ * Example: SET_IDX 3 4 5
+ * If data segment address 4 contains the value 7,
+ * and data segment address 5 contains the value 2,
+ * and data segment address 9 (7 + 2) contains the value 12345,
+ * then data segment address 3 will be set to the value 12345. + *

+ * @see OpCodeParam#INDIRECT_DEST_ADDR_WITH_INDEX + * @see OpCodeParam#INDIRECT_SRC_ADDR_WITH_INDEX + */ INDEX(OpCodeParam::compileInt) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -92,6 +188,15 @@ enum OpCodeParam { return String.format("+ $%08x)", ((Integer) value) / MachineState.VALUE_SIZE); } }, + /** + * Literal program address in code segment. + *

+ *

+ * Example: JMP_ADR CODE_ADDR
+ * Example: JMP_ADR 123
+ * Jump (set PC) to code address 123. + *

+ */ CODE_ADDR(OpCodeParam::compileInt) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -103,6 +208,21 @@ enum OpCodeParam { return String.format("[%04x]", (Integer) value); } }, + /** + * Byte offset from current program counter, in code segment. + *

+ *

+ * Example: BZR_DAT SRC_ADDR OFFSET
+ * Example: BZR_DAT 4 123
+ * If data segment address 4 contains the value 0,
+ * then add 123 to program counter (PC). + *

+ *

+ *

+ * Note: PC is considered to be immediately before opcode byte.
+ * Because this value is only a signed byte, maximum offsets are -128 and +127! + *

+ */ OFFSET(OpCodeParam::compileByte) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -114,6 +234,17 @@ enum OpCodeParam { return String.format("PC+%02x=[%04x]", (int) ((Byte) value), postOpcodeProgramCounter - 1 + (Byte) value); } }, + /** + * Literal 16-bit short function code supplied from code segment. + *

+ *

+ * Example: EXT_FUN FUNC
+ * Example: EXT_FUN 0x0001
+ * Calls function 0x0001 (ECHO). + *

+ * @see FunctionCode#ECHO + * @see FunctionCode + */ FUNC(OpCodeParam::compileFunc) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { @@ -135,10 +266,20 @@ enum OpCodeParam { return "\"" + functionCode.name() + "\"" + String.format("{%04x}", (Short) value); } }, + /** + * Block height extracted via address in data segment. + *

+ *

+ * Example: SLP_DAT BLOCK_HEIGHT
+ * Example: SLP_DAT 3
+ * If data segment address 3 contains the value 12345,
+ * then the AT will sleep until block height reaches 12345. + *

+ */ BLOCK_HEIGHT(OpCodeParam::compileInt) { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return Integer.valueOf(codeByteBuffer.getInt()); + return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); } @Override diff --git a/Java/src/main/java/org/ciyam/at/Timestamp.java b/Java/src/main/java/org/ciyam/at/Timestamp.java index 64cb407..c8c936d 100644 --- a/Java/src/main/java/org/ciyam/at/Timestamp.java +++ b/Java/src/main/java/org/ciyam/at/Timestamp.java @@ -4,16 +4,16 @@ package org.ciyam.at; * CIYAM-AT "Timestamp" *

* With CIYAM-ATs, "timestamp" does not mean a real timestamp but instead is an artificial timestamp that includes three parts: - *

+ *

*
    *
  • block height (32 bits)
  • *
  • blockchain ID (8 bits)
  • *
  • intra-block transaction sequence (24 bits)
  • *
* This allows up to 256 different blockchains and up to ~16million transactions per block. - *

+ *

* A blockchain ID of zero is assumed to be the 'native' blockchain. - *

+ *

* Timestamp values are not directly manipulated by AT OpCodes so endianness isn't important here. * * @see Timestamp#Timestamp(int, int, int) diff --git a/Java/src/main/java/org/ciyam/at/Utils.java b/Java/src/main/java/org/ciyam/at/Utils.java index 34e9ab6..af3f060 100644 --- a/Java/src/main/java/org/ciyam/at/Utils.java +++ b/Java/src/main/java/org/ciyam/at/Utils.java @@ -8,7 +8,7 @@ interface Utils { /** * Returns immediate function code enum from code bytes at current position. *

- * Initial position is codeByteBuffer.position() but on return is incremented by 2. + * Initial position is codeByteBuffer.position() but on return is incremented by 2. * * @param codeByteBuffer * @return FunctionCode @@ -33,9 +33,9 @@ interface Utils { /** * Returns code address from code bytes at current position. *

- * Initial position is codeByteBuffer.position() but on return is incremented by 4. + * Initial position is codeByteBuffer.position() but on return is incremented by 4. *

- * Note: address is not scaled by Constants.VALUE_SIZE unlike other methods in this class. + * Note: address is not scaled by Constants.VALUE_SIZE unlike other methods in this class. * * @param codeByteBuffer * @return int address into code segment @@ -58,9 +58,9 @@ interface Utils { /** * Returns data address from code bytes at current position. *

- * Initial position is codeByteBuffer.position() but on return is incremented by 4. + * Initial position is codeByteBuffer.position() but on return is incremented by 4. *

- * Note: address is returned scaled by Constants.VALUE_SIZE. + * Note: address is returned scaled by Constants.VALUE_SIZE. * * @param codeByteBuffer * @return int address into data segment @@ -83,9 +83,9 @@ interface Utils { /** * Returns byte offset from code bytes at current position. *

- * Initial position is codeByteBuffer.position() but on return is incremented by 1. + * Initial position is codeByteBuffer.position() but on return is incremented by 1. *

- * Note: offset is not scaled by Constants.VALUE_SIZE unlike other methods in this class. + * Note: offset is not scaled by Constants.VALUE_SIZE unlike other methods in this class. * * @param codeByteBuffer * @return byte offset @@ -103,7 +103,7 @@ interface Utils { /** * Returns long immediate value from code bytes at current position. *

- * Initial position is codeByteBuffer.position() but on return is incremented by 8. + * Initial position is codeByteBuffer.position() but on return is incremented by 8. * * @param codeByteBuffer * @return long value diff --git a/Java/src/test/java/org/ciyam/at/CompileTests.java b/Java/src/test/java/org/ciyam/at/CompileTests.java index 6a7a20c..8d994df 100644 --- a/Java/src/test/java/org/ciyam/at/CompileTests.java +++ b/Java/src/test/java/org/ciyam/at/CompileTests.java @@ -145,6 +145,33 @@ public class CompileTests { assertTrue(Arrays.equals(expectedBytes, actualBytes)); } + @Test + public void testTwoPassBranchCompileFailure() throws CompilationException { + int addrData = 0; + Integer actualTarget = null; + int expectedTarget = 0x06; + + // Old version + codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrData).put((byte) expectedTarget); + + // Two-pass version + ByteBuffer compileBuffer = ByteBuffer.allocate(512); + for (int pass = 0; pass < 2; ++pass) { + compileBuffer.clear(); + + try { + compileBuffer.put(OpCode.BZR_DAT.compile(addrData, calcOffset(compileBuffer, actualTarget))); + } catch (CompilationException e) { + // expected + return; + } + + actualTarget = compileBuffer.position() + 333; // wider than a signed byte + } + + fail("CompilationException should have been thrown"); + } + @SuppressWarnings("unused") @Test public void testComplexCompile() throws CompilationException { diff --git a/Java/src/test/java/org/ciyam/at/FunctionCodeTests.java b/Java/src/test/java/org/ciyam/at/FunctionCodeTests.java index 35894c7..94bc2b9 100644 --- a/Java/src/test/java/org/ciyam/at/FunctionCodeTests.java +++ b/Java/src/test/java/org/ciyam/at/FunctionCodeTests.java @@ -16,6 +16,40 @@ public class FunctionCodeTests extends ExecutableTest { int sourceAddress = 2; int destAddress = sourceAddress + MachineState.AB_REGISTER_SIZE / MachineState.VALUE_SIZE; + // Not used (compared to indirect method) + dataByteBuffer.putLong(12345L); + // Not used (compared to indirect method) + dataByteBuffer.putLong(54321L); + + // Data to load into A (or B) + assertEquals(sourceAddress * MachineState.VALUE_SIZE, dataByteBuffer.position()); + dataByteBuffer.put(TEST_BYTES); + + // Data saved from A (or B) + assertEquals(destAddress * MachineState.VALUE_SIZE, dataByteBuffer.position()); + + // Set A register to data segment starting at address passed by value + codeByteBuffer.put(OpCode.EXT_FUN_VAL.value).putShort(FunctionCode.SET_A_DAT.value).putLong(sourceAddress); + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value); + // Save B register to data segment starting at address passed by value + codeByteBuffer.put(OpCode.EXT_FUN_VAL.value).putShort(FunctionCode.GET_B_DAT.value).putLong(destAddress); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + byte[] dest = new byte[TEST_BYTES.length]; + getDataBytes(destAddress, dest); + assertTrue("Data wasn't copied correctly", Arrays.equals(TEST_BYTES, dest)); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + } + + @Test + public void testABGetSetIndirect() throws ExecutionException { + int sourceAddress = 2; + int destAddress = sourceAddress + MachineState.AB_REGISTER_SIZE / MachineState.VALUE_SIZE; + // Address of source bytes dataByteBuffer.putLong(sourceAddress); // Address where to save bytes @@ -111,4 +145,122 @@ public class FunctionCodeTests extends ExecutableTest { assertTrue(state.hadFatalError()); } + @Test + public void testUnsignedCompare() throws ExecutionException { + int compareABresultAddress = 0; + int compareBAresultAddress = 1; + int compareAAresultAddress = 2; + + int smallerAddress = 3; + int largerAddress = smallerAddress + MachineState.AB_REGISTER_SIZE / MachineState.VALUE_SIZE; + + // A-B Comparison result + dataByteBuffer.putLong(999L); + // B-A Comparison result + dataByteBuffer.putLong(999L); + // A-A Comparison result + dataByteBuffer.putLong(999L); + + // Smaller value to load into A (or B) + assertEquals(smallerAddress * MachineState.VALUE_SIZE, dataByteBuffer.position()); + dataByteBuffer.putLong(0x4444444444444444L); + dataByteBuffer.putLong(0x3333333333333333L); + dataByteBuffer.putLong(0xF222222222222222L); + dataByteBuffer.putLong(0xF111111111111111L); + + // Larger value to load into A (or B) + assertEquals(largerAddress * MachineState.VALUE_SIZE, dataByteBuffer.position()); + dataByteBuffer.putLong(0xCCCCCCCCCCCCCCCCL); // negative if signed, larger if unsigned + dataByteBuffer.putLong(0xDDDDDDDDDDDDDDDDL); + dataByteBuffer.putLong(0x2222222222222222L); + dataByteBuffer.putLong(0x1111111111111111L); + + // Set A register to data segment starting at address passed by value + codeByteBuffer.put(OpCode.EXT_FUN_VAL.value).putShort(FunctionCode.SET_A_DAT.value).putLong(smallerAddress); + // Set B register to data segment starting at address passed by value + codeByteBuffer.put(OpCode.EXT_FUN_VAL.value).putShort(FunctionCode.SET_B_DAT.value).putLong(largerAddress); + // Compare A and B, put result into compareABresultAddress + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.UNSIGNED_COMPARE_A_WITH_B.value).putInt(compareABresultAddress); + + // Swap A and B + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value); + // Compare A and B, put result into compareBAresultAddress + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.UNSIGNED_COMPARE_A_WITH_B.value).putInt(compareBAresultAddress); + + // Copy A to B + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.COPY_B_FROM_A.value); + // Compare A and B, put result into compareBAresultAddress + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.UNSIGNED_COMPARE_A_WITH_B.value).putInt(compareAAresultAddress); + + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + + assertEquals("AB compare failed", -1L, getData(compareABresultAddress)); + assertEquals("BA compare failed", +1L, getData(compareBAresultAddress)); + assertEquals("AA compare failed", 0L, getData(compareAAresultAddress)); + } + + @Test + public void testSignedCompare() throws ExecutionException { + int compareABresultAddress = 0; + int compareBAresultAddress = 1; + int compareAAresultAddress = 2; + + int smallerAddress = 3; + int largerAddress = smallerAddress + MachineState.AB_REGISTER_SIZE / MachineState.VALUE_SIZE; + + // A-B Comparison result + dataByteBuffer.putLong(999L); + // B-A Comparison result + dataByteBuffer.putLong(999L); + // A-A Comparison result + dataByteBuffer.putLong(999L); + + // Smaller value to load into A (or B) + assertEquals(smallerAddress * MachineState.VALUE_SIZE, dataByteBuffer.position()); + dataByteBuffer.putLong(0xCCCCCCCCCCCCCCCCL); // negative if signed, larger if unsigned + dataByteBuffer.putLong(0xDDDDDDDDDDDDDDDDL); + dataByteBuffer.putLong(0x2222222222222222L); + dataByteBuffer.putLong(0x1111111111111111L); + + // Larger value to load into A (or B) + assertEquals(largerAddress * MachineState.VALUE_SIZE, dataByteBuffer.position()); + dataByteBuffer.putLong(0x4444444444444444L); + dataByteBuffer.putLong(0x3333333333333333L); + dataByteBuffer.putLong(0xF222222222222222L); + dataByteBuffer.putLong(0xF111111111111111L); + + // Set A register to data segment starting at address passed by value + codeByteBuffer.put(OpCode.EXT_FUN_VAL.value).putShort(FunctionCode.SET_A_DAT.value).putLong(smallerAddress); + // Set B register to data segment starting at address passed by value + codeByteBuffer.put(OpCode.EXT_FUN_VAL.value).putShort(FunctionCode.SET_B_DAT.value).putLong(largerAddress); + // Compare A and B, put result into compareABresultAddress + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.SIGNED_COMPARE_A_WITH_B.value).putInt(compareABresultAddress); + + // Swap A and B + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value); + // Compare A and B, put result into compareBAresultAddress + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.SIGNED_COMPARE_A_WITH_B.value).putInt(compareBAresultAddress); + + // Copy A to B + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.COPY_B_FROM_A.value); + // Compare A and B, put result into compareBAresultAddress + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.SIGNED_COMPARE_A_WITH_B.value).putInt(compareAAresultAddress); + + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + + assertEquals("AB compare failed", -1L, getData(compareABresultAddress)); + assertEquals("BA compare failed", +1L, getData(compareBAresultAddress)); + assertEquals("AA compare failed", 0L, getData(compareAAresultAddress)); + } + } diff --git a/Java/src/test/java/org/ciyam/at/OpCodeTests.java b/Java/src/test/java/org/ciyam/at/OpCodeTests.java index b262484..2f6ee7d 100644 --- a/Java/src/test/java/org/ciyam/at/OpCodeTests.java +++ b/Java/src/test/java/org/ciyam/at/OpCodeTests.java @@ -44,18 +44,29 @@ public class OpCodeTests extends ExecutableTest { @Test public void testSLP_DAT() throws ExecutionException { - int blockHeight = 12345; + int targetBlockHeight = api.getCurrentBlockHeight() + 5; - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(blockHeight); - codeByteBuffer.put(OpCode.SLP_DAT.value).putInt(0); + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(targetBlockHeight); + codeByteBuffer.put(OpCode.SLP_DAT.value).putInt(1); codeByteBuffer.put(OpCode.FIN_IMD.value); + // one round only, to check AT has started sleeping execute(true); assertTrue(state.isSleeping()); assertFalse(state.isFinished()); assertFalse(state.hadFatalError()); - assertEquals("Sleep-until block height incorrect", blockHeight, getData(0)); + assertEquals("Sleep-until block height incorrect", targetBlockHeight, state.getSleepUntilHeight().intValue()); + + // continue sleeping until AT awakens + execute(false); + + assertEquals("Reawaken block height incorrect", targetBlockHeight, state.getCurrentBlockHeight()); + + assertFalse(state.isSleeping()); + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Sleep-until block height incorrect", null, state.getSleepUntilHeight()); } @Test @@ -189,6 +200,35 @@ public class OpCodeTests extends ExecutableTest { assertEquals("Error flag not set", 1L, getData(2)); } + @Test + public void testSLP_VAL() throws ExecutionException { + int blockCount = 5; + + int startingBlockHeight = api.getCurrentBlockHeight(); + int targetBlockHeight = startingBlockHeight + blockCount; + + codeByteBuffer.put(OpCode.SLP_VAL.value).putLong(blockCount); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + // one round only, to check AT has started sleeping + execute(true); + + assertTrue(state.isSleeping()); + assertFalse(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Sleep-until block height incorrect", targetBlockHeight, state.getSleepUntilHeight().intValue()); + + // continue sleeping until AT awakens + execute(false); + + assertEquals("Reawaken block height incorrect", targetBlockHeight, state.getCurrentBlockHeight()); + + assertFalse(state.isSleeping()); + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Sleep-until block height incorrect", null, state.getSleepUntilHeight()); + } + @Test public void testPCS() throws ExecutionException { codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000011111111")); diff --git a/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java b/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java index fc36a95..3ef84f4 100644 --- a/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java +++ b/Java/src/test/java/org/ciyam/at/ValueOpCodeTests.java @@ -146,4 +146,70 @@ public class ValueOpCodeTests extends ExecutableTest { assertEquals("Error flag not set", 1L, getData(1)); } + @Test + public void testSHL_VAL() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); + codeByteBuffer.put(OpCode.SHL_VAL.value).putInt(2).putLong(3L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Data does not match", 2222L << 3, getData(2)); + } + + @Test + public void testSHL_VALexcess() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); + codeByteBuffer.put(OpCode.SHL_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", 0L, getData(2)); + } + + @Test + public void testSHR_VAL() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); + codeByteBuffer.put(OpCode.SHR_VAL.value).putInt(2).putLong(3L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Data does not match", 2222L >>> 3, getData(2)); + } + + @Test + public void testSHR_VALexcess() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L); + codeByteBuffer.put(OpCode.SHR_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", 0L, getData(2)); + } + + @Test + public void testSHR_VALsign() throws ExecutionException { + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(-1L); + codeByteBuffer.put(OpCode.SHR_VAL.value).putInt(2).putLong(3L); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.isFinished()); + assertFalse(state.hadFatalError()); + assertEquals("Data does not match", -1L >>> 3, getData(2)); + assertTrue("Sign does not match", getData(2) >= 0); + } + }