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:
SvyatoslavScherbina
2017-03-27 16:44:24 +03:00
committed by Nikolay Igotti
parent 334d2f0ee6
commit c4abb8f706
15 changed files with 340 additions and 13 deletions
+2 -2
View File
@@ -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)