Implement varargs in interop for Kotlin N (#393)
* Implement CValues.equals and .hashCode * Add trivial test for interop varargs * Implement varargs in interop for Kotlin N * Compile and link runtime with libffi * Fix few places.
This commit is contained in:
committed by
Nikolay Igotti
parent
334d2f0ee6
commit
c4abb8f706
@@ -23,9 +23,9 @@ model {
|
||||
}
|
||||
binaries.all {
|
||||
cCompiler.args compilerArgsForJniIncludes
|
||||
cCompiler.args "-I$libffiDir/include"
|
||||
cCompiler.args "-I$hostLibffiDir/include"
|
||||
|
||||
linker.args "$libffiDir/lib/libffi.a"
|
||||
linker.args "$hostLibffiDir/lib/libffi.a"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,37 @@ abstract class CValues<T : CVariable> : CValuesRef<T>() {
|
||||
* Copies the values to [placement] and returns the pointer to the copy.
|
||||
*/
|
||||
override abstract fun getPointer(placement: NativePlacement): CPointer<T>
|
||||
|
||||
// TODO: optimize
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is CValues<*>) return false
|
||||
|
||||
val thisBytes = this.getBytes()
|
||||
val otherBytes = other.getBytes()
|
||||
|
||||
if (thisBytes.size != otherBytes.size) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (index in 0 .. thisBytes.size - 1) {
|
||||
if (thisBytes[index] != otherBytes[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = 0
|
||||
for (byte in this.getBytes()) {
|
||||
result = result * 31 + byte
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
abstract val size: Int
|
||||
}
|
||||
|
||||
fun <T : CVariable> CValues<T>.placeTo(placement: NativePlacement) = this.getPointer(placement)
|
||||
|
||||
@@ -158,6 +158,8 @@ fun <T : CVariable> zeroValue(size: Int, align: Int): CValue<T> = object : CValu
|
||||
nativeMemUtils.zeroMemory(result, size)
|
||||
return interpretCPointer(result.rawPtr)!!
|
||||
}
|
||||
|
||||
override val size get() = size
|
||||
}
|
||||
|
||||
inline fun <reified T : CVariable> zeroValue(): CValue<T> =
|
||||
@@ -175,6 +177,7 @@ fun <T : CVariable> CPointed.readValues(size: Int, align: Int): CValues<T> {
|
||||
|
||||
return object : CValues<T>() {
|
||||
override fun getPointer(placement: NativePlacement): CPointer<T> = placement.placeBytes(bytes, align)
|
||||
override val size get() = bytes.size
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +189,7 @@ fun <T : CVariable> CPointed.readValue(size: Int, align: Int): CValue<T> {
|
||||
nativeMemUtils.getByteArray(this, bytes, size)
|
||||
return object : CValue<T>() {
|
||||
override fun getPointer(placement: NativePlacement): CPointer<T> = placement.placeBytes(bytes, align)
|
||||
override val size get() = bytes.size
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +197,19 @@ fun <T : CVariable> CPointed.readValue(size: Int, align: Int): CValue<T> {
|
||||
// TODO: find better name.
|
||||
inline fun <reified T : CStructVar> T.readValue(): CValue<T> = this.readValue(sizeOf<T>().toInt(), alignOf<T>())
|
||||
|
||||
// TODO: optimize
|
||||
fun <T : CVariable> CValues<T>.getBytes(): ByteArray = memScoped {
|
||||
val result = ByteArray(size)
|
||||
|
||||
nativeMemUtils.getByteArray(
|
||||
source = this@getBytes.placeTo(memScope).reinterpret<CInt8Var>().pointed,
|
||||
dest = result,
|
||||
length = result.size
|
||||
)
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the [block] with temporary copy if this value as receiver.
|
||||
*/
|
||||
@@ -215,6 +232,7 @@ inline fun <reified T : CVariable> createValues(count: Int, initializer: T.(inde
|
||||
|
||||
fun cValuesOf(vararg elements: Byte): CValues<CInt8Var> = object : CValues<CInt8Var>() {
|
||||
override fun getPointer(placement: NativePlacement) = placement.allocArrayOf(elements)[0].ptr
|
||||
override val size get() = 1 * elements.size
|
||||
}
|
||||
|
||||
// TODO: optimize other [cValuesOf] methods:
|
||||
@@ -230,6 +248,7 @@ fun cValuesOf(vararg elements: Long): CValues<CInt64Var> =
|
||||
|
||||
fun cValuesOf(vararg elements: Float): CValues<CFloat32Var> = object : CValues<CFloat32Var>() {
|
||||
override fun getPointer(placement: NativePlacement) = placement.allocArrayOf(*elements)[0].ptr
|
||||
override val size get() = 4 * elements.size
|
||||
}
|
||||
|
||||
fun cValuesOf(vararg elements: Double): CValues<CFloat64Var> =
|
||||
@@ -258,6 +277,8 @@ val String.cstr: CValues<CInt8Var>
|
||||
val bytes = encodeToUtf8(this)
|
||||
|
||||
return object : CValues<CInt8Var>() {
|
||||
override val size get() = bytes.size + 1
|
||||
|
||||
override fun getPointer(placement: NativePlacement): CPointer<CInt8Var> {
|
||||
val result = placement.allocArray<CInt8Var>(bytes.size + 1)
|
||||
nativeMemUtils.putByteArray(bytes, result, bytes.size)
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package kotlinx.cinterop
|
||||
|
||||
private const val MAX_ARGUMENT_SIZE = 8
|
||||
|
||||
|
||||
typealias FfiTypeKind = Int
|
||||
// Also declared in Interop.cpp
|
||||
const val FFI_TYPE_KIND_VOID: FfiTypeKind = 0
|
||||
const val FFI_TYPE_KIND_SINT8: FfiTypeKind = 1
|
||||
const val FFI_TYPE_KIND_SINT16: FfiTypeKind = 2
|
||||
const val FFI_TYPE_KIND_SINT32: FfiTypeKind = 3
|
||||
const val FFI_TYPE_KIND_SINT64: FfiTypeKind = 4
|
||||
const val FFI_TYPE_KIND_FLOAT: FfiTypeKind = 5
|
||||
const val FFI_TYPE_KIND_DOUBLE: FfiTypeKind = 6
|
||||
const val FFI_TYPE_KIND_POINTER: FfiTypeKind = 7
|
||||
|
||||
private tailrec fun convertArgument(
|
||||
argument: Any?, isVariadic: Boolean, location: NativePointed,
|
||||
additionalPlacement: NativePlacement
|
||||
): FfiTypeKind = when (argument) {
|
||||
is CValuesRef<*>? -> {
|
||||
location.reinterpret<CPointerVar<*>>().value = argument?.getPointer(additionalPlacement)
|
||||
FFI_TYPE_KIND_POINTER
|
||||
}
|
||||
|
||||
is String -> {
|
||||
location.reinterpret<CPointerVar<*>>().value = argument.cstr.getPointer(additionalPlacement)
|
||||
FFI_TYPE_KIND_POINTER
|
||||
}
|
||||
|
||||
is Int -> {
|
||||
location.reinterpret<CInt32Var>().value = argument
|
||||
FFI_TYPE_KIND_SINT32
|
||||
}
|
||||
|
||||
is Long -> {
|
||||
location.reinterpret<CInt64Var>().value = argument
|
||||
FFI_TYPE_KIND_SINT64
|
||||
}
|
||||
|
||||
is Byte -> if (isVariadic) {
|
||||
convertArgument(argument.toInt(), isVariadic, location, additionalPlacement)
|
||||
} else {
|
||||
location.reinterpret<CInt8Var>().value = argument
|
||||
FFI_TYPE_KIND_SINT8
|
||||
}
|
||||
|
||||
is Short -> if (isVariadic) {
|
||||
convertArgument(argument.toInt(), isVariadic, location, additionalPlacement)
|
||||
} else {
|
||||
location.reinterpret<CInt16Var>().value = argument
|
||||
FFI_TYPE_KIND_SINT16
|
||||
}
|
||||
|
||||
is Double -> {
|
||||
location.reinterpret<CFloat64Var>().value = argument
|
||||
FFI_TYPE_KIND_DOUBLE
|
||||
}
|
||||
|
||||
is Float -> if (isVariadic) {
|
||||
convertArgument(argument.toDouble(), isVariadic, location, additionalPlacement)
|
||||
} else {
|
||||
location.reinterpret<CFloat32Var>().value = argument
|
||||
FFI_TYPE_KIND_FLOAT
|
||||
}
|
||||
|
||||
else -> throw Error("unsupported argument: $argument")
|
||||
}
|
||||
|
||||
fun callWithVarargs(codePtr: NativePtr, returnValuePtr: NativePtr, returnTypeKind: FfiTypeKind,
|
||||
fixedArguments: Array<out Any?>, variadicArguments: Array<out Any?>,
|
||||
argumentsPlacement: NativePlacement) {
|
||||
|
||||
val totalArgumentsNumber = fixedArguments.size + variadicArguments.size
|
||||
|
||||
// All supported arguments take at most 8 bytes each:
|
||||
val argumentsStorage = argumentsPlacement.allocArray<CInt64Var>(totalArgumentsNumber)
|
||||
val arguments = argumentsPlacement.allocArray<CPointerVar<*>>(totalArgumentsNumber)
|
||||
val types = argumentsPlacement.allocArray<CPointerVar<*>>(totalArgumentsNumber)
|
||||
|
||||
var index = 0
|
||||
|
||||
inline fun addArgument(argument: Any?, isVariadic: Boolean) {
|
||||
val storage = argumentsStorage[index]
|
||||
val typeKind = convertArgument(argument, isVariadic = isVariadic,
|
||||
location = storage, additionalPlacement = argumentsPlacement)
|
||||
|
||||
types[index].value = interpretCPointer<COpaque>(nativeNullPtr + typeKind.toLong())
|
||||
arguments[index].value = storage.ptr
|
||||
|
||||
++index
|
||||
}
|
||||
|
||||
for (argument in fixedArguments) {
|
||||
addArgument(argument, isVariadic = false)
|
||||
}
|
||||
|
||||
for (argument in variadicArguments) {
|
||||
addArgument(argument, isVariadic = true)
|
||||
}
|
||||
|
||||
assert (index == totalArgumentsNumber)
|
||||
|
||||
callWithVarargs(codePtr, returnValuePtr, returnTypeKind, arguments.rawPtr, types.rawPtr,
|
||||
fixedArguments.size, totalArgumentsNumber)
|
||||
}
|
||||
|
||||
@SymbolName("callWithVarargs")
|
||||
private external fun callWithVarargs(codePtr: NativePtr, returnValuePtr: NativePtr, returnTypeKind: FfiTypeKind,
|
||||
arguments: NativePtr, argumentTypeKinds: NativePtr,
|
||||
fixedArgumentsNumber: Int, totalArgumentsNumber: Int)
|
||||
Reference in New Issue
Block a user