diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiAddress.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiAddress.kt index 20eeda2..43fe4d7 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiAddress.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiAddress.kt @@ -1,7 +1,27 @@ package gay.pizza.pork.ffi +import com.kenai.jffi.MemoryIO + data class FfiAddress(val location: Long) { companion object { val Null = FfiAddress(0L) + + fun allocate(size: Long): FfiAddress = + FfiAddress(MemoryIO.getInstance().allocateMemory(size, true)) + } + + fun read(size: Int): ByteArray = read(0, size) + + fun read(offset: Int, size: Int): ByteArray { + val bytes = ByteArray(size) { 0 } + MemoryIO.getInstance().getByteArray(location, bytes, offset, size) + return bytes } + + fun readNullTerminated(): ByteArray = + MemoryIO.getInstance().getZeroTerminatedByteArray(location) + + fun readString(): String = String(readNullTerminated()) + + fun free(): Unit = MemoryIO.getInstance().freeMemory(location) } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt index b8b683a..ebabfca 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt @@ -32,7 +32,7 @@ class FfiNativeProvider : NativeProvider { val invoker = Invoker.getInstance() return CallableFunction { functionArguments, _ -> val buffer = HeapInvocationBuffer(context) - val freeStringList = mutableListOf() + val freeStringList = mutableListOf() for ((index, spec) in arguments.withIndex()) { val ffiType = ffiTypeRegistry.lookup(functionDefinition.parameters[index]) ?: throw RuntimeException("Unknown ffi type: ${functionDefinition.parameters[index]}") @@ -42,18 +42,19 @@ class FfiNativeProvider : NativeProvider { variableArguments.forEach { var value = it if (value is String) { - value = FfiStringWrapper(value) + value = FfiString.allocate(value) freeStringList.add(value) } - put(buffer, value) + FfiPrimitiveType.push(buffer, value) } break } else { - val converted = convert(ffiType, functionArguments[index]) - if (converted is FfiStringWrapper) { - freeStringList.add(converted) + var argumentValue = functionArguments[index] + if (argumentValue is String) { + argumentValue = FfiString.allocate(argumentValue) + freeStringList.add(argumentValue) } - put(buffer, converted) + ffiType.put(buffer, argumentValue) } } @@ -102,60 +103,6 @@ class FfiNativeProvider : NativeProvider { ?: throw RuntimeException("Unable to find library: $name") } - private fun convert(type: FfiType, value: Any?): Any { - if (type !is FfiPrimitiveType) { - return value ?: FfiAddress.Null - } - - if (type.numberConvert != null) { - return numberConvert(type.id, value, type.numberConvert) - } - - if (type.notNullConversion != null) { - return notNullConvert(type.id, value, type.notNullConversion) - } - - if (type.nullableConversion != null) { - return nullableConvert(value, type.nullableConversion) ?: FfiAddress.Null - } - return value ?: FfiAddress.Null - } - - private fun notNullConvert(type: String, value: Any?, into: Any.() -> T): T { - if (value == null) { - throw RuntimeException("Null values cannot be used for converting to type $type") - } - return into(value) - } - - private fun nullableConvert(value: Any?, into: Any.() -> T): T? { - if (value == null || value == None) { - return null - } - return into(value) - } - - private fun numberConvert(type: String, value: Any?, into: Number.() -> T): T { - if (value == null || value == None) { - throw RuntimeException("Null values cannot be used for converting to numeric type $type") - } - - if (value !is Number) { - throw RuntimeException("Cannot convert value '$value' into type $type") - } - return into(value) - } - - private fun put(buffer: InvocationBuffer, value: Any): Unit = when (value) { - is Byte -> buffer.putByte(value.toInt()) - is Short -> buffer.putShort(value.toInt()) - is Int -> buffer.putInt(value) - is Long -> buffer.putLong(value) - is FfiAddress -> buffer.putAddress(value.location) - is FfiStringWrapper -> buffer.putAddress(value.address) - else -> throw RuntimeException("Unknown buffer insertion: $value (${value.javaClass.name})") - } - private fun invoke(invoker: Invoker, function: Function, buffer: HeapInvocationBuffer, type: FfiType): Any = when (type) { FfiPrimitiveType.Pointer -> invoker.invokeAddress(function, buffer) FfiPrimitiveType.UnsignedInt, FfiPrimitiveType.Int -> invoker.invokeInt(function, buffer) diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPrimitiveType.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPrimitiveType.kt index 9a0f00d..627140c 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPrimitiveType.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPrimitiveType.kt @@ -1,5 +1,6 @@ package gay.pizza.pork.ffi +import com.kenai.jffi.InvocationBuffer import gay.pizza.pork.evaluator.None enum class FfiPrimitiveType( @@ -19,14 +20,84 @@ enum class FfiPrimitiveType( Long("long", 8, numberConvert = { toLong() }), UnsignedLong("unsigned long", 8, numberConvert = { toLong() }), Double("double", 8, numberConvert = { toDouble() }), - String("char*", 8, nullableConversion = { FfiStringWrapper(toString()) }), + String("char*", 8, nullableConversion = { + if (this is FfiString) { + this + } else FfiString.allocate(toString()) + }), Pointer("void*", 8, nullableConversion = { when (this) { is FfiAddress -> this + is FfiString -> this.address is None -> FfiAddress.Null is Number -> FfiAddress(this.toLong()) else -> FfiAddress.Null } }), - Void("void", 0) + Void("void", 0); + + override fun put(buffer: InvocationBuffer, value: Any?) { + if (numberConvert != null) { + push(buffer, numberConvert(id, value, numberConvert)) + } + + if (notNullConversion != null) { + push(buffer, notNullConvert(id, value, notNullConversion)) + } + + if (nullableConversion != null) { + val result = nullableConvert(value, nullableConversion) ?: FfiAddress.Null + push(buffer, result) + } + } + + private fun notNullConvert(type: kotlin.String, value: Any?, into: Any.() -> T): T { + if (value == null) { + throw RuntimeException("Null values cannot be used for converting to type $type") + } + return into(value) + } + + private fun nullableConvert(value: Any?, into: Any.() -> T): T? { + if (value == null || value == None) { + return null + } + return into(value) + } + + private fun numberConvert(type: kotlin.String, value: Any?, into: Number.() -> T): T { + if (value == null || value == None) { + throw RuntimeException("Null values cannot be used for converting to numeric type $type") + } + + if (value !is Number) { + throw RuntimeException("Cannot convert value '$value' into type $type") + } + return into(value) + } + + override fun value(ffi: Any?): Any { + if (ffi == null) { + return None + } + + if (ffi is FfiString) { + val content = ffi.read() + ffi.free() + return content + } + return ffi + } + + companion object { + fun push(buffer: InvocationBuffer, value: Any): Unit = when (value) { + is kotlin.Byte -> buffer.putByte(value.toInt()) + is kotlin.Short -> buffer.putShort(value.toInt()) + is kotlin.Int -> buffer.putInt(value) + is kotlin.Long -> buffer.putLong(value) + is FfiAddress -> buffer.putAddress(value.location) + is FfiString -> buffer.putAddress(value.address.location) + else -> throw RuntimeException("Unknown buffer insertion: $value (${value.javaClass.name})") + } + } } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiString.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiString.kt new file mode 100644 index 0000000..48ebbb4 --- /dev/null +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiString.kt @@ -0,0 +1,20 @@ +package gay.pizza.pork.ffi + +import com.kenai.jffi.MemoryIO + +class FfiString(val address: FfiAddress) { + fun read(): String = address.readString() + + fun free() { + address.free() + } + + companion object { + fun allocate(input: String): FfiString { + val bytes = input.toByteArray() + val buffer = FfiAddress.allocate(bytes.size + 1L) + MemoryIO.getInstance().putZeroTerminatedByteArray(buffer.location, bytes, 0, bytes.size) + return FfiString(buffer) + } + } +} diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStringWrapper.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStringWrapper.kt deleted file mode 100644 index 729812a..0000000 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStringWrapper.kt +++ /dev/null @@ -1,17 +0,0 @@ -package gay.pizza.pork.ffi - -import com.kenai.jffi.MemoryIO - -class FfiStringWrapper(input: String) { - val address: Long - - init { - val bytes = input.toByteArray() - address = MemoryIO.getInstance().allocateMemory((bytes.size + 1).toLong(), true) - MemoryIO.getInstance().putZeroTerminatedByteArray(address, bytes, 0, bytes.size) - } - - fun free() { - MemoryIO.getInstance().freeMemory(address) - } -} diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStruct.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStruct.kt index e9cdf8b..ea9e74e 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStruct.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStruct.kt @@ -1,14 +1,43 @@ package gay.pizza.pork.ffi +import com.kenai.jffi.InvocationBuffer +import gay.pizza.pork.evaluator.None +import java.util.* + class FfiStruct : FfiType { - private val fields = mutableListOf() + private val fields = TreeMap() data class FfiStructField(val name: String, val type: FfiType) fun add(field: String, type: FfiType) { - fields.add(FfiStructField(field, type)) + fields[field] = FfiStructField(field, type) } override val size: Long - get() = fields.sumOf { it.type.size } + get() = fields.values.sumOf { it.type.size } + + override fun put(buffer: InvocationBuffer, value: Any?) { + when (value) { + is Map<*, *> -> { + for (field in fields.values) { + val item = value[field.name] ?: None + field.type.put(buffer, item) + } + } + + is List<*> -> { + for ((index, field) in fields.values.withIndex()) { + field.type.put(buffer, value[index]) + } + } + + else -> { + throw RuntimeException("Unknown value type: $value") + } + } + } + + override fun value(ffi: Any?): Any { + return None + } } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiType.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiType.kt index e61710a..28604f9 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiType.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiType.kt @@ -1,5 +1,10 @@ package gay.pizza.pork.ffi +import com.kenai.jffi.InvocationBuffer + interface FfiType { val size: Long + + fun put(buffer: InvocationBuffer, value: Any?) + fun value(ffi: Any?): Any }