Skip to content

Commit

Permalink
ffi: move type conversion to FfiType
Browse files Browse the repository at this point in the history
  • Loading branch information
azenla committed Oct 7, 2023
1 parent 437ab75 commit fdac4fb
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 83 deletions.
20 changes: 20 additions & 0 deletions ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiAddress.kt
Original file line number Diff line number Diff line change
@@ -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)
}
69 changes: 8 additions & 61 deletions ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class FfiNativeProvider : NativeProvider {
val invoker = Invoker.getInstance()
return CallableFunction { functionArguments, _ ->
val buffer = HeapInvocationBuffer(context)
val freeStringList = mutableListOf<FfiStringWrapper>()
val freeStringList = mutableListOf<FfiString>()
for ((index, spec) in arguments.withIndex()) {
val ffiType = ffiTypeRegistry.lookup(functionDefinition.parameters[index]) ?:
throw RuntimeException("Unknown ffi type: ${functionDefinition.parameters[index]}")
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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 <T> 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 <T> nullableConvert(value: Any?, into: Any.() -> T): T? {
if (value == null || value == None) {
return null
}
return into(value)
}

private fun <T> 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)
Expand Down
75 changes: 73 additions & 2 deletions ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPrimitiveType.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gay.pizza.pork.ffi

import com.kenai.jffi.InvocationBuffer
import gay.pizza.pork.evaluator.None

enum class FfiPrimitiveType(
Expand All @@ -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 <T> 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 <T> nullableConvert(value: Any?, into: Any.() -> T): T? {
if (value == null || value == None) {
return null
}
return into(value)
}

private fun <T> 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})")
}
}
}
20 changes: 20 additions & 0 deletions ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiString.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
17 changes: 0 additions & 17 deletions ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStringWrapper.kt

This file was deleted.

35 changes: 32 additions & 3 deletions ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStruct.kt
Original file line number Diff line number Diff line change
@@ -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<FfiStructField>()
private val fields = TreeMap<String, FfiStructField>()

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
}
}
5 changes: 5 additions & 0 deletions ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiType.kt
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit fdac4fb

Please sign in to comment.