mirror of
				https://github.com/GayPizzaSpecifications/pork.git
				synced 2025-11-03 17:39:38 +00:00 
			
		
		
		
	ffi: move type conversion to FfiType
This commit is contained in:
		@ -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
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user