mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-08-02 21:00:56 +00:00
ffi: move type conversion to FfiType
This commit is contained in:
parent
437ab758be
commit
fdac4fb96a
@ -1,7 +1,27 @@
|
|||||||
package gay.pizza.pork.ffi
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
|
import com.kenai.jffi.MemoryIO
|
||||||
|
|
||||||
data class FfiAddress(val location: Long) {
|
data class FfiAddress(val location: Long) {
|
||||||
companion object {
|
companion object {
|
||||||
val Null = FfiAddress(0L)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class FfiNativeProvider : NativeProvider {
|
|||||||
val invoker = Invoker.getInstance()
|
val invoker = Invoker.getInstance()
|
||||||
return CallableFunction { functionArguments, _ ->
|
return CallableFunction { functionArguments, _ ->
|
||||||
val buffer = HeapInvocationBuffer(context)
|
val buffer = HeapInvocationBuffer(context)
|
||||||
val freeStringList = mutableListOf<FfiStringWrapper>()
|
val freeStringList = mutableListOf<FfiString>()
|
||||||
for ((index, spec) in arguments.withIndex()) {
|
for ((index, spec) in arguments.withIndex()) {
|
||||||
val ffiType = ffiTypeRegistry.lookup(functionDefinition.parameters[index]) ?:
|
val ffiType = ffiTypeRegistry.lookup(functionDefinition.parameters[index]) ?:
|
||||||
throw RuntimeException("Unknown ffi type: ${functionDefinition.parameters[index]}")
|
throw RuntimeException("Unknown ffi type: ${functionDefinition.parameters[index]}")
|
||||||
@ -42,18 +42,19 @@ class FfiNativeProvider : NativeProvider {
|
|||||||
variableArguments.forEach {
|
variableArguments.forEach {
|
||||||
var value = it
|
var value = it
|
||||||
if (value is String) {
|
if (value is String) {
|
||||||
value = FfiStringWrapper(value)
|
value = FfiString.allocate(value)
|
||||||
freeStringList.add(value)
|
freeStringList.add(value)
|
||||||
}
|
}
|
||||||
put(buffer, value)
|
FfiPrimitiveType.push(buffer, value)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
val converted = convert(ffiType, functionArguments[index])
|
var argumentValue = functionArguments[index]
|
||||||
if (converted is FfiStringWrapper) {
|
if (argumentValue is String) {
|
||||||
freeStringList.add(converted)
|
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")
|
?: 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) {
|
private fun invoke(invoker: Invoker, function: Function, buffer: HeapInvocationBuffer, type: FfiType): Any = when (type) {
|
||||||
FfiPrimitiveType.Pointer -> invoker.invokeAddress(function, buffer)
|
FfiPrimitiveType.Pointer -> invoker.invokeAddress(function, buffer)
|
||||||
FfiPrimitiveType.UnsignedInt, FfiPrimitiveType.Int -> invoker.invokeInt(function, buffer)
|
FfiPrimitiveType.UnsignedInt, FfiPrimitiveType.Int -> invoker.invokeInt(function, buffer)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package gay.pizza.pork.ffi
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
|
import com.kenai.jffi.InvocationBuffer
|
||||||
import gay.pizza.pork.evaluator.None
|
import gay.pizza.pork.evaluator.None
|
||||||
|
|
||||||
enum class FfiPrimitiveType(
|
enum class FfiPrimitiveType(
|
||||||
@ -19,14 +20,84 @@ enum class FfiPrimitiveType(
|
|||||||
Long("long", 8, numberConvert = { toLong() }),
|
Long("long", 8, numberConvert = { toLong() }),
|
||||||
UnsignedLong("unsigned long", 8, numberConvert = { toLong() }),
|
UnsignedLong("unsigned long", 8, numberConvert = { toLong() }),
|
||||||
Double("double", 8, numberConvert = { toDouble() }),
|
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 = {
|
Pointer("void*", 8, nullableConversion = {
|
||||||
when (this) {
|
when (this) {
|
||||||
is FfiAddress -> this
|
is FfiAddress -> this
|
||||||
|
is FfiString -> this.address
|
||||||
is None -> FfiAddress.Null
|
is None -> FfiAddress.Null
|
||||||
is Number -> FfiAddress(this.toLong())
|
is Number -> FfiAddress(this.toLong())
|
||||||
else -> FfiAddress.Null
|
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
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiString.kt
Normal file
20
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiString.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,43 @@
|
|||||||
package gay.pizza.pork.ffi
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
|
import com.kenai.jffi.InvocationBuffer
|
||||||
|
import gay.pizza.pork.evaluator.None
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class FfiStruct : FfiType {
|
class FfiStruct : FfiType {
|
||||||
private val fields = mutableListOf<FfiStructField>()
|
private val fields = TreeMap<String, FfiStructField>()
|
||||||
|
|
||||||
data class FfiStructField(val name: String, val type: FfiType)
|
data class FfiStructField(val name: String, val type: FfiType)
|
||||||
|
|
||||||
fun add(field: String, type: FfiType) {
|
fun add(field: String, type: FfiType) {
|
||||||
fields.add(FfiStructField(field, type))
|
fields[field] = FfiStructField(field, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val size: Long
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
package gay.pizza.pork.ffi
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
|
import com.kenai.jffi.InvocationBuffer
|
||||||
|
|
||||||
interface FfiType {
|
interface FfiType {
|
||||||
val size: Long
|
val size: Long
|
||||||
|
|
||||||
|
fun put(buffer: InvocationBuffer, value: Any?)
|
||||||
|
fun value(ffi: Any?): Any
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user