From 70af207a7ebdf9d6239a97d9c9c8e50e58dc0143 Mon Sep 17 00:00:00 2001 From: CharlieTap Date: Sat, 7 Sep 2024 18:21:11 +0100 Subject: [PATCH] rework the memory reader api to require a buffer --- .../embedding/ext/HostFunctionContextExt.kt | 47 ++++++++--- .../chasm/embedding/memory/ReadBytes.kt | 23 +++-- .../chasm/embedding/memory/ReadBytesTest.kt | 20 +++-- .../memory/LinearMemoryInteractorImpl.kt | 6 +- .../memory/read/MemoryInstanceBytesReader.kt | 34 +++++++- .../read/MemoryInstanceBytesReaderImpl.kt | 31 ------- .../read/MemoryInstanceByteReaderTest.kt | 83 +++++++++++++++++++ 7 files changed, 182 insertions(+), 62 deletions(-) delete mode 100644 executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceBytesReaderImpl.kt create mode 100644 executor/memory/src/commonTest/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceByteReaderTest.kt diff --git a/chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/ext/HostFunctionContextExt.kt b/chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/ext/HostFunctionContextExt.kt index b978700c..1d5b6b21 100644 --- a/chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/ext/HostFunctionContextExt.kt +++ b/chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/ext/HostFunctionContextExt.kt @@ -11,20 +11,25 @@ import io.github.charlietap.chasm.embedding.shapes.Memory import io.github.charlietap.chasm.embedding.shapes.map inline fun HostFunctionContext.byte( - pointer: Int, memory: Memory, -): ChasmResult = readByte(this.store, memory, pointer) + memoryPointer: Int, +): ChasmResult = readByte(this.store, memory, memoryPointer) inline fun HostFunctionContext.bytes( - pointer: Int, - numberOfBytes: Int, memory: Memory, -): ChasmResult = readBytes(this.store, memory, pointer, numberOfBytes) + buffer: ByteArray, + memoryPointer: Int, + bytesToRead: Int, + bufferPointer: Int = 0, +): ChasmResult = readBytes(this.store, memory, buffer, memoryPointer, bytesToRead, bufferPointer) inline fun HostFunctionContext.int( - pointer: Int, memory: Memory, -): ChasmResult = readBytes(this.store, memory, pointer, Int.SIZE_BYTES).map { bytes -> + buffer: ByteArray, + memoryPointer: Int, + bufferPointer: Int = 0, +): ChasmResult = readBytes(this.store, memory, buffer, memoryPointer, Int.SIZE_BYTES, bufferPointer).map { + bytes -> var result: Int = 0 for (i in 0 until Int.SIZE_BYTES) { result = result or (bytes[i].toInt() shl Byte.SIZE_BITS * i) @@ -33,9 +38,12 @@ inline fun HostFunctionContext.int( } inline fun HostFunctionContext.uint( - pointer: Int, memory: Memory, -): ChasmResult = readBytes(this.store, memory, pointer, Int.SIZE_BYTES).map { bytes -> + buffer: ByteArray, + memoryPointer: Int, + bufferPointer: Int = 0, +): ChasmResult = readBytes(this.store, memory, buffer, memoryPointer, UInt.SIZE_BYTES, bufferPointer).map { + bytes -> var result: UInt = 0u for (i in 0 until Int.SIZE_BYTES) { result = result or (bytes[i].toUInt() shl (Byte.SIZE_BITS * i)) @@ -44,9 +52,12 @@ inline fun HostFunctionContext.uint( } inline fun HostFunctionContext.long( - pointer: Int, memory: Memory, -): ChasmResult = readBytes(this.store, memory, pointer, Long.SIZE_BYTES).map { bytes -> + buffer: ByteArray, + memoryPointer: Int, + bufferPointer: Int = 0, +): ChasmResult = readBytes(this.store, memory, buffer, memoryPointer, Long.SIZE_BYTES, bufferPointer).map { + bytes -> var result: Long = 0 for (i in 0 until Long.SIZE_BYTES) { result = result or (bytes[i].toLong() shl Byte.SIZE_BITS * i) @@ -55,9 +66,19 @@ inline fun HostFunctionContext.long( } inline fun HostFunctionContext.ulong( - pointer: Int, memory: Memory, -): ChasmResult = readBytes(this.store, memory, pointer, Long.SIZE_BYTES).map { bytes -> + buffer: ByteArray, + memoryPointer: Int, + bufferPointer: Int = 0, +): ChasmResult = readBytes( + this.store, + memory, + buffer, + memoryPointer, + ULong.SIZE_BYTES, + bufferPointer, +).map { + bytes -> var result: ULong = 0uL for (i in 0 until Long.SIZE_BYTES) { result = result or (bytes[i].toULong() shl (Byte.SIZE_BITS * i)) diff --git a/chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/memory/ReadBytes.kt b/chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/memory/ReadBytes.kt index 11469ea2..0a99986e 100644 --- a/chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/memory/ReadBytes.kt +++ b/chasm/src/commonMain/kotlin/io/github/charlietap/chasm/embedding/memory/ReadBytes.kt @@ -11,22 +11,25 @@ import io.github.charlietap.chasm.embedding.shapes.ChasmResult.Success import io.github.charlietap.chasm.embedding.shapes.Memory import io.github.charlietap.chasm.embedding.shapes.Store import io.github.charlietap.chasm.executor.memory.read.MemoryInstanceBytesReader -import io.github.charlietap.chasm.executor.memory.read.MemoryInstanceBytesReaderImpl import io.github.charlietap.chasm.executor.runtime.error.ModuleTrapError import io.github.charlietap.chasm.executor.runtime.ext.memory fun readBytes( store: Store, memory: Memory, - pointer: Int, - numberOfBytes: Int, + buffer: ByteArray, + memoryPointer: Int, + bytesToRead: Int, + bufferPointer: Int = 0, ): ChasmResult = readBytes( store = store, memory = memory, - pointer = pointer, - numberOfBytes = numberOfBytes, - bytesReader = ::MemoryInstanceBytesReaderImpl, + buffer = buffer, + memoryPointer = memoryPointer, + bytesToRead = bytesToRead, + bufferPointer = bufferPointer, + bytesReader = ::MemoryInstanceBytesReader, ) .mapError(ChasmError::ExecutionError) .fold(::Success, ::Error) @@ -34,10 +37,12 @@ fun readBytes( internal fun readBytes( store: Store, memory: Memory, - pointer: Int, - numberOfBytes: Int, + buffer: ByteArray, + memoryPointer: Int, + bytesToRead: Int, + bufferPointer: Int, bytesReader: MemoryInstanceBytesReader, ): Result = binding { val instance = store.store.memory(memory.reference.address).bind() - bytesReader(instance, pointer, numberOfBytes).bind() + bytesReader(instance, buffer, memoryPointer, bytesToRead, bufferPointer).bind() } diff --git a/chasm/src/commonTest/kotlin/io/github/charlietap/chasm/embedding/memory/ReadBytesTest.kt b/chasm/src/commonTest/kotlin/io/github/charlietap/chasm/embedding/memory/ReadBytesTest.kt index 0bb8765a..f803f44b 100644 --- a/chasm/src/commonTest/kotlin/io/github/charlietap/chasm/embedding/memory/ReadBytesTest.kt +++ b/chasm/src/commonTest/kotlin/io/github/charlietap/chasm/embedding/memory/ReadBytesTest.kt @@ -22,14 +22,18 @@ class ReadBytesTest { val store = publicStore(store(memories = mutableListOf(instance))) val address = memoryAddress() val memory = publicMemory(memoryExternalValue(address)) - val pointer = 118 - val numberOfBytes = 2 + val memoryPointer = 118 + val bytesToRead = 2 + val buffer = byteArrayOf() + val bufferPointer = 117 val bytes: ByteArray = byteArrayOf(117, 118) - val bytesReader: MemoryInstanceBytesReader = { _instance, _pointer, _numberOfBytes -> + val bytesReader: MemoryInstanceBytesReader = { _instance, _buffer, _memoryPointer, _bytesToRead, _bufferPointer -> assertEquals(instance, _instance) - assertEquals(pointer, _pointer) - assertEquals(numberOfBytes, _numberOfBytes) + assertEquals(buffer, _buffer) + assertEquals(memoryPointer, _memoryPointer) + assertEquals(bytesToRead, _bytesToRead) + assertEquals(bufferPointer, _bufferPointer) Ok(bytes) } @@ -39,8 +43,10 @@ class ReadBytesTest { val actual = readBytes( store = store, memory = memory, - pointer = pointer, - numberOfBytes = numberOfBytes, + buffer = buffer, + memoryPointer = memoryPointer, + bytesToRead = bytesToRead, + bufferPointer = bufferPointer, bytesReader = bytesReader, ) diff --git a/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/LinearMemoryInteractorImpl.kt b/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/LinearMemoryInteractorImpl.kt index 212e62a7..6efff933 100644 --- a/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/LinearMemoryInteractorImpl.kt +++ b/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/LinearMemoryInteractorImpl.kt @@ -17,7 +17,11 @@ internal inline fun LinearMemoryInteractorImpl( val lastByte = offset + size return if (offset >= 0 && size >= 0 && lastByte > 0 && lastByte <= memory.size()) { - Ok(operation()) + try { + Ok(operation()) + } catch (_: IndexOutOfBoundsException) { + Err(InvocationError.MemoryOperationOutOfBounds) + } } else { Err(InvocationError.MemoryOperationOutOfBounds) } diff --git a/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceBytesReader.kt b/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceBytesReader.kt index ae20d70c..1ffb1ae6 100644 --- a/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceBytesReader.kt +++ b/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceBytesReader.kt @@ -1,7 +1,39 @@ +@file:Suppress("NOTHING_TO_INLINE") + package io.github.charlietap.chasm.executor.memory.read import com.github.michaelbull.result.Result +import io.github.charlietap.chasm.executor.memory.ByteArrayLinearMemory +import io.github.charlietap.chasm.executor.memory.LinearMemoryInteractor +import io.github.charlietap.chasm.executor.memory.LinearMemoryInteractorImpl import io.github.charlietap.chasm.executor.runtime.error.InvocationError import io.github.charlietap.chasm.executor.runtime.instance.MemoryInstance -typealias MemoryInstanceBytesReader = (MemoryInstance, Int, Int) -> Result +typealias MemoryInstanceBytesReader = (MemoryInstance, ByteArray, Int, Int, Int) -> Result + +fun MemoryInstanceBytesReader( + instance: MemoryInstance, + buffer: ByteArray, + memoryPointer: Int, + bytesToRead: Int, + bufferPointer: Int, +): Result = + MemoryInstanceBytesReader( + instance = instance, + buffer = buffer, + memoryPointer = memoryPointer, + bytesToRead = bytesToRead, + bufferPointer = bufferPointer, + linearMemoryInteractor = ::LinearMemoryInteractorImpl, + ) + +internal inline fun MemoryInstanceBytesReader( + instance: MemoryInstance, + buffer: ByteArray, + memoryPointer: Int, + bytesToRead: Int, + bufferPointer: Int, + linearMemoryInteractor: LinearMemoryInteractor, +): Result = linearMemoryInteractor(instance.data, memoryPointer, bytesToRead) { + (instance.data as ByteArrayLinearMemory).memory.copyInto(buffer, bufferPointer, memoryPointer, memoryPointer + bytesToRead) +} diff --git a/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceBytesReaderImpl.kt b/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceBytesReaderImpl.kt deleted file mode 100644 index b1e09407..00000000 --- a/executor/memory/src/commonMain/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceBytesReaderImpl.kt +++ /dev/null @@ -1,31 +0,0 @@ -@file:Suppress("NOTHING_TO_INLINE") - -package io.github.charlietap.chasm.executor.memory.read - -import com.github.michaelbull.result.Result -import io.github.charlietap.chasm.executor.memory.ByteArrayLinearMemory -import io.github.charlietap.chasm.executor.memory.LinearMemoryInteractor -import io.github.charlietap.chasm.executor.memory.LinearMemoryInteractorImpl -import io.github.charlietap.chasm.executor.runtime.error.InvocationError -import io.github.charlietap.chasm.executor.runtime.instance.MemoryInstance - -fun MemoryInstanceBytesReaderImpl( - instance: MemoryInstance, - pointer: Int, - numberOfBytes: Int, -): Result = - MemoryInstanceBytesReaderImpl( - instance = instance, - pointer = pointer, - numberOfBytes = numberOfBytes, - linearMemoryInteractor = ::LinearMemoryInteractorImpl, - ) - -internal inline fun MemoryInstanceBytesReaderImpl( - instance: MemoryInstance, - pointer: Int, - numberOfBytes: Int, - linearMemoryInteractor: LinearMemoryInteractor, -): Result = linearMemoryInteractor(instance.data, pointer, numberOfBytes) { - (instance.data as ByteArrayLinearMemory).memory.sliceArray(pointer until pointer + numberOfBytes) -} diff --git a/executor/memory/src/commonTest/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceByteReaderTest.kt b/executor/memory/src/commonTest/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceByteReaderTest.kt new file mode 100644 index 00000000..f9c331af --- /dev/null +++ b/executor/memory/src/commonTest/kotlin/io/github/charlietap/chasm/executor/memory/read/MemoryInstanceByteReaderTest.kt @@ -0,0 +1,83 @@ +package io.github.charlietap.chasm.executor.memory.read + +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok +import io.github.charlietap.chasm.executor.memory.ByteArrayLinearMemory +import io.github.charlietap.chasm.executor.memory.LinearMemoryInteractorImpl +import io.github.charlietap.chasm.executor.runtime.error.InvocationError +import io.github.charlietap.chasm.executor.runtime.memory.LinearMemory +import io.github.charlietap.chasm.fixture.instance.memoryInstance +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class MemoryInstanceByteReaderTest { + + @Test + fun `can read bytes from linear memory using a buffer `() { + + val memoryArray = byteArrayOf(0, 0, 117, 118, 0, 0) + val memory = ByteArrayLinearMemory(min = LinearMemory.Pages(1), max = LinearMemory.Pages(1), memory = memoryArray) + val instance = memoryInstance(data = memory) + + val memoryPointer = 2 + val buffer = byteArrayOf(0, 0, 0, 0) + val bufferPointer = 2 + + val actual = MemoryInstanceBytesReader( + instance = instance, + buffer = buffer, + memoryPointer = memoryPointer, + bytesToRead = 2, + bufferPointer = bufferPointer, + linearMemoryInteractor = ::LinearMemoryInteractorImpl, + ) + + assertEquals(Ok(buffer), actual) + assertContentEquals(byteArrayOf(0, 0, 117, 118), buffer) + } + + @Test + fun `error if returned if read is out of the memory's bounds `() { + + val memory = ByteArrayLinearMemory(min = LinearMemory.Pages(1)) + val instance = memoryInstance(data = memory) + + val memoryPointer = LinearMemory.PAGE_SIZE + val buffer = byteArrayOf(0) + val bufferPointer = 0 + + val actual = MemoryInstanceBytesReader( + instance = instance, + buffer = buffer, + memoryPointer = memoryPointer, + bytesToRead = 1, + bufferPointer = bufferPointer, + linearMemoryInteractor = ::LinearMemoryInteractorImpl, + ) + + assertEquals(Err(InvocationError.MemoryOperationOutOfBounds), actual) + } + + @Test + fun `error if returned if read is out of the buffer's bounds `() { + + val memory = ByteArrayLinearMemory(min = LinearMemory.Pages(1)) + val instance = memoryInstance(data = memory) + + val memoryPointer = 0 + val buffer = byteArrayOf(0) + val bufferPointer = 1 + + val actual = MemoryInstanceBytesReader( + instance = instance, + buffer = buffer, + memoryPointer = memoryPointer, + bytesToRead = 1, + bufferPointer = bufferPointer, + linearMemoryInteractor = ::LinearMemoryInteractorImpl, + ) + + assertEquals(Err(InvocationError.MemoryOperationOutOfBounds), actual) + } +}