Add tests of kmath-memory and make all the memory operations on all the platforms use little-endian byte order #163

Closed
CommanderTvis wants to merge 4 commits from unify-endianness into dev
5 changed files with 108 additions and 51 deletions

View File

@ -56,6 +56,8 @@ public interface MemoryReader {
/**
* Reads [Float] at certain [offset].
*
* **Warning: since JS can't handle Float properly, the size of segment read by this function is 8 bytes.**
*/
public fun readFloat(offset: Int): Float
@ -112,6 +114,8 @@ public interface MemoryWriter {
/**
* Writes [Float] at certain [offset].
*
* **Warning: since JS can't handle Float properly, the size of segment written by this function is 8 bytes.**
*/
public fun writeFloat(offset: Int, value: Float)
@ -153,9 +157,3 @@ public inline fun Memory.write(block: MemoryWriter.() -> Unit) {
* Allocates the most effective platform-specific memory.
*/
public expect fun Memory.Companion.allocate(length: Int): Memory
/**
* Wraps a [Memory] around existing [ByteArray]. This operation is unsafe since the array is not copied
* and could be mutated independently from the resulting [Memory].
*/
public expect fun Memory.Companion.wrap(array: ByteArray): Memory

View File

@ -0,0 +1,82 @@
package kscience.kmath.memory
import kotlin.test.Test
import kotlin.test.assertEquals
internal class MemoryTest {
@Test
fun allocateReturnsMemoryWithValidSize() {
val mem = Memory.allocate(64)
assertEquals(64, mem.size)
}
@Test
fun rwByte() {
val mem = Memory.allocate(64)
mem.write { writeByte(0, 42.toByte()) }
assertEquals(42.toByte(), mem.read { readByte(0) })
mem.write { writeByte(1, (-4).toByte()) }
assertEquals((-4).toByte(), mem.read { readByte(1) })
}
@Test
fun rwShort() {
val mem = Memory.allocate(64)
mem.write { writeShort(0, 44555.toShort()) }
assertEquals(44555.toShort(), mem.read { readShort(0) })
mem.write { writeShort(2, (-33333).toShort()) }
assertEquals((-33333).toShort(), mem.read { readShort(2) })
}
@Test
fun rwInt() {
val mem = Memory.allocate(64)
mem.write { writeInt(0, 1234444444) }
assertEquals(1234444444, mem.read { readInt(0) })
mem.write { writeInt(4, -5595959) }
assertEquals(-5595959, mem.read { readInt(4) })
}
@Test
fun rwLong() {
val mem = Memory.allocate(64)
mem.write { writeLong(0, 1234444444L) }
assertEquals(1234444444L, mem.read { readLong(0) })
mem.write { writeLong(4, -5595959L) }
assertEquals(-5595959L, mem.read { readLong(4) })
mem.write { writeLong(8, 1234444444444149L) }
assertEquals(1234444444444149L, mem.read { readLong(8) })
mem.write { writeLong(16, -50000333595959L) }
assertEquals(-50000333595959L, mem.read { readLong(16) })
}
@Test
fun rwFloat() {
val mem = Memory.allocate(64)
mem.write { writeFloat(0, 12.12345f) }
assertEquals(12.12345f, mem.read { readFloat(0) })
mem.write { writeFloat(8, -313.13f) }
assertEquals(-313.13f, mem.read { readFloat(8) })
mem.write { writeFloat(16, Float.NaN) }
assertEquals(Float.NaN, mem.read { readFloat(16) })
mem.write { writeFloat(24, Float.POSITIVE_INFINITY) }
assertEquals(Float.POSITIVE_INFINITY, mem.read { readFloat(24) })
mem.write { writeFloat(32, Float.NEGATIVE_INFINITY) }
assertEquals(Float.NEGATIVE_INFINITY, mem.read { readFloat(32) })
}
@Test
fun rwDouble() {
val mem = Memory.allocate(64)
mem.write { writeDouble(0, 12.12345) }
assertEquals(12.12345, mem.read { readDouble(0) })
mem.write { writeDouble(8, -313.13) }
assertEquals(-313.13, mem.read { readDouble(8) })
mem.write { writeDouble(16, Double.NaN) }
assertEquals(Double.NaN, mem.read { readDouble(16) })
mem.write { writeDouble(24, Double.POSITIVE_INFINITY) }
assertEquals(Double.POSITIVE_INFINITY, mem.read { readDouble(24) })
mem.write { writeDouble(32, Double.NEGATIVE_INFINITY) }
assertEquals(Double.NEGATIVE_INFINITY, mem.read { readDouble(32) })
}
}

View File

@ -7,7 +7,6 @@ package space.kscience.kmath.memory
import org.khronos.webgl.ArrayBuffer
import org.khronos.webgl.DataView
import org.khronos.webgl.Int8Array
private class DataViewMemory(val view: DataView) : Memory {
override val size: Int get() = view.byteLength
@ -28,18 +27,18 @@ private class DataViewMemory(val view: DataView) : Memory {
private val reader: MemoryReader = object : MemoryReader {
override val memory: Memory get() = this@DataViewMemory
override fun readDouble(offset: Int): Double = view.getFloat64(offset, false)
override fun readDouble(offset: Int): Double = view.getFloat64(offset, true)
override fun readFloat(offset: Int): Float = view.getFloat32(offset, false)
override fun readFloat(offset: Int): Float = view.getFloat64(offset, true).toFloat()
override fun readByte(offset: Int): Byte = view.getInt8(offset)
override fun readShort(offset: Int): Short = view.getInt16(offset, false)
override fun readShort(offset: Int): Short = view.getInt16(offset, true)
override fun readInt(offset: Int): Int = view.getInt32(offset, false)
override fun readInt(offset: Int): Int = view.getInt32(offset, true)
override fun readLong(offset: Int): Long =
view.getInt32(offset, false).toLong() shl 32 or view.getInt32(offset + 4, false).toLong()
view.getInt32(offset, true).toLong() shl 32 or view.getInt32(offset + 4, true).toLong()
override fun release() {
// does nothing on JS
@ -52,11 +51,11 @@ private class DataViewMemory(val view: DataView) : Memory {
override val memory: Memory get() = this@DataViewMemory
override fun writeDouble(offset: Int, value: Double) {
view.setFloat64(offset, value, false)
view.setFloat64(offset, value, true)
}
override fun writeFloat(offset: Int, value: Float) {
view.setFloat32(offset, value, false)
view.setFloat64(offset, value.toDouble(), true)
}
override fun writeByte(offset: Int, value: Byte) {
@ -64,16 +63,16 @@ private class DataViewMemory(val view: DataView) : Memory {
}
override fun writeShort(offset: Int, value: Short) {
view.setUint16(offset, value, false)
view.setUint16(offset, value, true)
}
override fun writeInt(offset: Int, value: Int) {
view.setInt32(offset, value, false)
view.setInt32(offset, value, true)
}
override fun writeLong(offset: Int, value: Long) {
view.setInt32(offset, (value shr 32).toInt(), littleEndian = false)
view.setInt32(offset + 4, (value and 0xffffffffL).toInt(), littleEndian = false)
view.setInt32(offset, (value shr 32).toInt(), littleEndian = true)
view.setInt32(offset + 4, (value and 0xffffffffL).toInt(), littleEndian = true)
}
override fun release() {
@ -82,7 +81,6 @@ private class DataViewMemory(val view: DataView) : Memory {
}
override fun writer(): MemoryWriter = writer
}
/**
@ -92,12 +90,3 @@ public actual fun Memory.Companion.allocate(length: Int): Memory {
val buffer = ArrayBuffer(length)
return DataViewMemory(DataView(buffer, 0, length))
}
/**
* Wraps a [Memory] around existing [ByteArray]. This operation is unsafe since the array is not copied
* and could be mutated independently from the resulting [Memory].
*/
public actual fun Memory.Companion.wrap(array: ByteArray): Memory {
@Suppress("CAST_NEVER_SUCCEEDS") val int8Array = array as Int8Array
return DataViewMemory(DataView(int8Array.buffer, int8Array.byteOffset, int8Array.length))
}

View File

@ -7,6 +7,7 @@ package space.kscience.kmath.memory
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.channels.FileChannel
import java.nio.file.Files
import java.nio.file.Path
@ -31,7 +32,7 @@ internal class ByteBufferMemory(
}
override fun copy(): Memory {
val copy = ByteBuffer.allocate(buffer.capacity())
val copy = ByteBuffer.allocate(buffer.capacity()).order(ByteOrder.LITTLE_ENDIAN)
buffer.rewind()
copy.put(buffer)
copy.flip()
@ -43,7 +44,7 @@ internal class ByteBufferMemory(
override fun readDouble(offset: Int) = buffer.getDouble(position(offset))
override fun readFloat(offset: Int) = buffer.getFloat(position(offset))
override fun readFloat(offset: Int) = buffer.getDouble(position(offset)).toFloat()
override fun readByte(offset: Int) = buffer.get(position(offset))
@ -68,7 +69,7 @@ internal class ByteBufferMemory(
}
override fun writeFloat(offset: Int, value: Float) {
buffer.putFloat(position(offset), value)
buffer.putDouble(position(offset), value.toDouble())
}
override fun writeByte(offset: Int, value: Byte) {
@ -99,14 +100,7 @@ internal class ByteBufferMemory(
* Allocates memory based on a [ByteBuffer].
*/
public actual fun Memory.Companion.allocate(length: Int): Memory =
ByteBufferMemory(checkNotNull(ByteBuffer.allocate(length)))
/**
* Wraps a [Memory] around existing [ByteArray]. This operation is unsafe since the array is not copied
* and could be mutated independently from the resulting [Memory].
*/
public actual fun Memory.Companion.wrap(array: ByteArray): Memory =
ByteBufferMemory(checkNotNull(ByteBuffer.wrap(array)))
ByteBufferMemory(checkNotNull(ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN)))
/**
* Wraps this [ByteBuffer] to [Memory] object.

View File

@ -31,7 +31,7 @@ internal class NativeMemory(
override fun readDouble(offset: Int) = array.getDoubleAt(position(offset))
override fun readFloat(offset: Int) = array.getFloatAt(position(offset))
override fun readFloat(offset: Int) = array.getDoubleAt(position(offset)).toFloat()
override fun readByte(offset: Int) = array[position(offset)]
@ -42,7 +42,7 @@ internal class NativeMemory(
override fun readLong(offset: Int) = array.getLongAt(position(offset))
override fun release() {
// does nothing on JVM
// does nothing on Native
}
}
@ -56,11 +56,11 @@ internal class NativeMemory(
}
override fun writeFloat(offset: Int, value: Float) {
array.setFloatAt(position(offset), value)
array.setDoubleAt(position(offset), value.toDouble())
}
override fun writeByte(offset: Int, value: Byte) {
array.set(position(offset), value)
array[position(offset)] = value
}
override fun writeShort(offset: Int, value: Short) {
@ -76,19 +76,13 @@ internal class NativeMemory(
}
override fun release() {
// does nothing on JVM
// does nothing on Native
}
}
override fun writer(): MemoryWriter = writer
}
/**
* Wraps a [Memory] around existing [ByteArray]. This operation is unsafe since the array is not copied
* and could be mutated independently from the resulting [Memory].
*/
public actual fun Memory.Companion.wrap(array: ByteArray): Memory = NativeMemory(array)
/**
* Allocates the most effective platform-specific memory.
*/