diff --git a/Java/maven-import.txt b/Java/maven-import.txt index 555b856..9e03813 100644 --- a/Java/maven-import.txt +++ b/Java/maven-import.txt @@ -5,7 +5,7 @@ # your project has local repository in MY-PROJECT/lib/ # CIYAM AT JAR pathname is in ${CIYAM_AT_JAR} -CIYAM_AT_VERSION=1.3.7 +CIYAM_AT_VERSION=1.3.8 CIYAM_AT_JAR=../CIYAM-AT/Java/target/AT-${CIYAM_AT_VERSION}.jar cd MY-PROJECT mvn install:install-file -DlocalRepositoryPath=lib/ -Dfile=${CIYAM_AT_JAR} -DgroupId=org.ciyam -DartifactId=AT -Dpackaging=jar -Dversion=${CIYAM_AT_VERSION} diff --git a/Java/pom.xml b/Java/pom.xml index b772e6e..f668173 100644 --- a/Java/pom.xml +++ b/Java/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.ciyam AT - 1.3.7 + 1.3.8 jar true diff --git a/Java/src/main/java/org/ciyam/at/FunctionCode.java b/Java/src/main/java/org/ciyam/at/FunctionCode.java index 33a17e1..3c6c17a 100644 --- a/Java/src/main/java/org/ciyam/at/FunctionCode.java +++ b/Java/src/main/java/org/ciyam/at/FunctionCode.java @@ -1091,7 +1091,7 @@ public enum FunctionCode { byte[] message = new byte[dataLength]; - ByteBuffer messageByteBuffer = state.dataByteBuffer.slice(); + ByteBuffer messageByteBuffer = state.dataByteBuffer.asReadOnlyBuffer(); messageByteBuffer.position(dataStart * MachineState.VALUE_SIZE); messageByteBuffer.limit(dataStart * MachineState.VALUE_SIZE + dataLength); diff --git a/Java/src/main/java/org/ciyam/at/MachineState.java b/Java/src/main/java/org/ciyam/at/MachineState.java index 2d65d15..dc1fa03 100644 --- a/Java/src/main/java/org/ciyam/at/MachineState.java +++ b/Java/src/main/java/org/ciyam/at/MachineState.java @@ -172,18 +172,6 @@ public class MachineState { // Header OK } - private void setupSegmentsAndStacks() { - this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * this.constants.CODE_PAGE_SIZE); - this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE); - - // Set up stacks - this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * this.constants.CALL_STACK_PAGE_SIZE); - this.callStackByteBuffer.position(this.callStackByteBuffer.limit()); // Downward-growing stack, so start at the end - - this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * this.constants.USER_STACK_PAGE_SIZE); - this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end - } - /** For creating a new machine state */ public MachineState(API api, AtLoggerFactory loggerFactory, byte[] creationBytes) { this(ByteBuffer.wrap(creationBytes)); @@ -192,13 +180,17 @@ public class MachineState { if (creationBytes.length != expectedLength) throw new IllegalArgumentException("Creation bytes length does not match header values"); - setupSegmentsAndStacks(); - - System.arraycopy(creationBytes, HEADER_LENGTH, this.codeByteBuffer.array(), 0, this.numCodePages * this.constants.CODE_PAGE_SIZE); + int codeBytesLength = this.numCodePages * this.constants.CODE_PAGE_SIZE; + this.codeByteBuffer = ByteBuffer.allocate(codeBytesLength); + System.arraycopy(creationBytes, HEADER_LENGTH, this.codeByteBuffer.array(), 0, codeBytesLength); - System.arraycopy(creationBytes, HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE, this.dataByteBuffer.array(), 0, + // Copy initial data segment from creation bytes so that we don't modify creationBytes on execution + this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE); + System.arraycopy(creationBytes, HEADER_LENGTH + codeBytesLength, this.dataByteBuffer.array(), 0, this.numDataPages * this.constants.DATA_PAGE_SIZE); + constructStacks(); + commonFinalConstruction(api, loggerFactory); } @@ -212,15 +204,26 @@ public class MachineState { if (dataBytes.length > this.numDataPages * this.constants.DATA_PAGE_SIZE) throw new IllegalArgumentException("Number of data pages too small to hold data bytes"); - setupSegmentsAndStacks(); - - System.arraycopy(codeBytes, 0, this.codeByteBuffer.array(), 0, codeBytes.length); + this.codeByteBuffer = ByteBuffer.wrap(codeBytes).asReadOnlyBuffer(); + // Copy dataBytes so that we don't modify original during execution + this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE); System.arraycopy(dataBytes, 0, this.dataByteBuffer.array(), 0, dataBytes.length); + constructStacks(); + commonFinalConstruction(api, loggerFactory); } + private void constructStacks() { + // Set up stacks + this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * this.constants.CALL_STACK_PAGE_SIZE); + this.callStackByteBuffer.position(this.callStackByteBuffer.limit()); // Downward-growing stack, so start at the end + + this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * this.constants.USER_STACK_PAGE_SIZE); + this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end + } + private void commonFinalConstruction(API api, AtLoggerFactory loggerFactory) { this.api = api; this.loggerFactory = loggerFactory; @@ -442,7 +445,10 @@ public class MachineState { /** Returns code bytes only as these are read-only so no need to be duplicated in every serialized state */ public byte[] getCodeBytes() { - return this.codeByteBuffer.array(); + // We create a copy because codeByteBuffer is a read-only sub-slice of another ByteBuffer + byte[] codeBytes = new byte[this.codeByteBuffer.limit()]; + this.codeByteBuffer.position(0).get(codeBytes); + return codeBytes; } private static class NumericByteArrayOutputStream extends ByteArrayOutputStream { @@ -588,7 +594,6 @@ public class MachineState { if (codeBytes.length != state.numCodePages * state.constants.CODE_PAGE_SIZE) throw new IllegalStateException("Passed codeBytes does not match length in header"); - state.api = api; state.loggerFactory = loggerFactory; state.logger = loggerFactory.create(MachineState.class); @@ -597,15 +602,31 @@ public class MachineState { state.previousBalance = 0; state.steps = 0; - state.setupSegmentsAndStacks(); + // Ring-fence code bytes + state.codeByteBuffer = ByteBuffer.wrap(codeBytes).asReadOnlyBuffer(); + + reuse(state, api, byteBuffer); + + return state; + } - // Pull in code bytes - System.arraycopy(codeBytes, 0, state.codeByteBuffer.array(), 0, codeBytes.length); + /** For restoring a previously serialized machine state, reusing existing instance as much as possible. */ + public void reuseFromBytes(API api, byte[] stateBytes) { + ByteBuffer byteBuffer = ByteBuffer.wrap(stateBytes); + reuse(this, api, byteBuffer); + } + + private static void reuse(MachineState state, API api, ByteBuffer byteBuffer) { + byte[] stateBytes = byteBuffer.array(); + state.api = api; // Pull in data bytes - int dataBytesLength = state.dataByteBuffer.capacity(); - System.arraycopy(stateBytes, byteBuffer.position(), state.dataByteBuffer.array(), 0, dataBytesLength); - byteBuffer.position(byteBuffer.position() + dataBytesLength); + int dataBytesLength = state.numDataPages * state.constants.DATA_PAGE_SIZE; + state.dataByteBuffer = ByteBuffer.allocate(dataBytesLength); + System.arraycopy(stateBytes, HEADER_LENGTH, state.dataByteBuffer.array(), 0, dataBytesLength); + byteBuffer.position(HEADER_LENGTH + dataBytesLength); + + state.constructStacks(); // Pull in call stack int callStackLength = byteBuffer.getInt(); @@ -624,8 +645,6 @@ public class MachineState { byteBuffer.position(byteBuffer.position() + userStackLength); extractMisc(byteBuffer, state); - - return state; } /** For restoring only flags from a previously serialized machine state */ @@ -635,7 +654,7 @@ public class MachineState { MachineState state = new MachineState(byteBuffer); // Skip data segment - byteBuffer.position(byteBuffer.position() + state.numDataPages * state.constants.DATA_PAGE_SIZE); + byteBuffer.position(HEADER_LENGTH + state.numDataPages * state.constants.DATA_PAGE_SIZE); // Skip call stack int callStackLength = byteBuffer.getInt(); diff --git a/Java/src/test/java/org/ciyam/at/SerializationTests.java b/Java/src/test/java/org/ciyam/at/SerializationTests.java index 49f60e6..7aef33c 100644 --- a/Java/src/test/java/org/ciyam/at/SerializationTests.java +++ b/Java/src/test/java/org/ciyam/at/SerializationTests.java @@ -148,6 +148,14 @@ public class SerializationTests extends ExecutableTest { assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes)); assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(codeBytes, restoredCodeBytes)); + // Check reuse works too + byte[] dupStateBytes = new byte[stateBytes.length]; + System.arraycopy(stateBytes, 0, dupStateBytes, 0, stateBytes.length); + + restoredState.reuseFromBytes(api, dupStateBytes); + byte[] restoredReusedStateBytes = restoredState.toBytes(); + assertTrue("Serialization->Deserialization->Reserialization reuse error", Arrays.equals(stateBytes, restoredReusedStateBytes)); + return stateBytes; }