From 49815f72e037f591c31469a6cf842f9bf8ecc709 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 31 Jan 2017 13:13:30 +0700 Subject: [PATCH] Interop/Runtime: add support for Kotlin Native Also add minor improvements, fix bugs and workaround missing features. --- .../kotlin/kotlinx/cinterop/JvmCallbacks.kt | 3 - .../kotlin/kotlinx/cinterop/JvmNativeMem.kt | 4 +- .../jvm/kotlin/kotlinx/cinterop/JvmTypes.kt | 6 +- .../jvm/kotlin/kotlinx/cinterop/JvmUtils.kt | 4 ++ .../src/main/kotlin/kotlinx/cinterop/Types.kt | 23 +++---- .../src/main/kotlin/kotlinx/cinterop/Utils.kt | 55 ++++++++-------- .../kotlin/kotlinx/cinterop/NativeMem.kt | 66 +++++++++++++++++++ .../kotlin/kotlinx/cinterop/NativeTypes.kt | 26 ++++++++ .../kotlin/kotlinx/cinterop/NativeUtils.kt | 16 +++++ runtime/src/main/kotlin/kotlin/Exceptions.kt | 9 +++ 10 files changed, 164 insertions(+), 48 deletions(-) create mode 100644 Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt index 598fa97700b..402e9a7c732 100644 --- a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt @@ -150,9 +150,6 @@ abstract class CAdaptedFunctionTypeImpl> private typealias UserData = (ret: COpaquePointer, args: CArray)->Unit -inline fun > CAdaptedFunctionTypeImpl.Companion.of(): T = - T::class.objectInstance!! - private fun loadCallbacksLibrary() { System.loadLibrary("callbacks") } diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt index ee89f72d0be..17e6ed49875 100644 --- a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt @@ -38,12 +38,12 @@ object nativeMemUtils { fun getDouble(mem: NativePointed) = unsafe.getDouble(mem.address) fun putDouble(mem: NativePointed, value: Double) = unsafe.putDouble(mem.address, value) - fun getPtr(mem: NativePointed): NativePtr = when (dataModel) { + fun getNativePtr(mem: NativePointed): NativePtr = when (dataModel) { DataModel._32BIT -> getInt(mem).toLong() DataModel._64BIT -> getLong(mem) } - fun putPtr(mem: NativePointed, value: NativePtr) = when (dataModel) { + fun putNativePtr(mem: NativePointed, value: NativePtr) = when (dataModel) { DataModel._32BIT -> putInt(mem, value.toInt()) DataModel._64BIT -> putLong(mem, value) } diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt index f4665fefe3f..46129773ea1 100644 --- a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt @@ -8,7 +8,7 @@ val nativeNullPtr: NativePtr = 0L // TODO: the functions below should eventually be intrinsified -inline fun CVariable.Type.Companion.of() = T::class.companionObjectInstance as CVariable.Type +inline fun typeOf() = T::class.companionObjectInstance as CVariable.Type /** * Returns interpretation of entity with given pointer. @@ -27,4 +27,6 @@ inline fun interpretPointed(ptr: NativePtr): T { } inline fun > CAdaptedFunctionType.Companion.getInstanceOf(): T = - T::class.objectInstance!! \ No newline at end of file + T::class.objectInstance!! + +internal fun CPointer<*>.cPointerToString() = "CPointer(raw=0x%x)".format(rawValue) \ No newline at end of file diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt new file mode 100644 index 00000000000..5c7bf64a12c --- /dev/null +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt @@ -0,0 +1,4 @@ +package kotlinx.cinterop + +internal fun decodeFromUtf8(bytes: ByteArray) = String(bytes) +internal fun encodeToUtf8(str: String) = str.toByteArray() \ No newline at end of file diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt index 5bcba74cc31..69d367014ad 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt @@ -83,9 +83,7 @@ class CPointer private constructor(val rawValue: NativePtr) { return rawValue.hashCode() } - override fun toString(): String { - return "CPointer(raw=0x%x)".format(rawValue) - } + override fun toString() = this.cPointerToString() } /** @@ -141,18 +139,15 @@ interface CVariable : CPointed { open class Type(val size: Long, val align: Int) { init { - assert (size % align == 0L) + require(size % align == 0L) } - companion object - } - - companion object { - inline fun sizeOf() = Type.of().size - inline fun alignOf() = Type.of().align } } +inline fun sizeOf() = typeOf().size +inline fun alignOf() = typeOf().align + /** * The C data which is composed from several members. */ @@ -177,7 +172,7 @@ abstract class CStructVar : CVariable, CAggregate { */ sealed class CPrimitiveVar : CVariable { // aligning by size is obviously enough - open class Type(size: Int, align: Int = size) : CVariable.Type(size.toLong(), align) + open class Type(size: Int) : CVariable.Type(size.toLong(), align = size) } abstract class CEnumVar : CPrimitiveVar() @@ -256,8 +251,8 @@ typealias CPointerVar = CPointerVarWithValueMappedTo> * The value of this variable. */ inline var > CPointerVarWithValueMappedTo

.value: P? - get() = CPointer.createNullable(nativeMemUtils.getPtr(this)) as P? - set(value) = nativeMemUtils.putPtr(this, value.rawValue) + get() = CPointer.createNullable(nativeMemUtils.getNativePtr(this)) as P? + set(value) = nativeMemUtils.putNativePtr(this, value.rawValue) /** * The code or data pointed by the value of this variable. @@ -275,7 +270,7 @@ class CArray(override val rawPtr: NativePtr) : CAggregate inline fun CArray.elementOffset(index: Long) = if (index == 0L) { 0L // optimization for JVM impl which uses reflection for now. } else { - index * CVariable.sizeOf() + index * sizeOf() } inline operator fun CArray.get(index: Long): T = memberAt(elementOffset(index)) diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt index a8e11cf810e..33d27eafcc5 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt @@ -20,22 +20,22 @@ object nativeHeap : NativeFreeablePlacement { // TODO: implement optimally class Arena(private val parent: NativeFreeablePlacement = nativeHeap) : NativePlacement { - private val allocatedChunks = mutableListOf() + private val allocatedChunks = ArrayList() override fun alloc(size: Long, align: Int): NativePointed { - val res = nativeHeap.alloc(size, align) + val res = parent.alloc(size, align) try { allocatedChunks.add(res) return res } catch (e: Throwable) { - nativeHeap.free(res) + parent.free(res) throw e } } fun clear() { allocatedChunks.forEach { - nativeHeap.free(it) + parent.free(it) } allocatedChunks.clear() @@ -51,7 +51,7 @@ fun NativePlacement.alloc(size: Int, align: Int) = alloc(size.toLong(), align) * @param T must not be abstract */ inline fun NativePlacement.alloc(): T = - alloc(CVariable.sizeOf(), CVariable.alignOf()).reinterpret() + alloc(sizeOf(), alignOf()).reinterpret() /** * Allocates C array of given elements type and length. @@ -59,7 +59,7 @@ inline fun NativePlacement.alloc(): T = * @param T must not be abstract */ inline fun NativePlacement.allocArray(length: Long): CArray = - alloc(CVariable.sizeOf() * length, CVariable.alignOf()).reinterpret() + alloc(sizeOf() * length, alignOf()).reinterpret() /** * Allocates C array of given elements type and length. @@ -78,7 +78,7 @@ inline fun NativePlacement.allocArray(length: Long, initializer: T.(index: Long)->Unit): CArray { val res = allocArray(length) - (0 until length).forEach { index -> + (0 .. length - 1).forEach { index -> res[index].initializer(index) } @@ -112,7 +112,7 @@ fun NativePlacement.allocArrayOfPointersTo(elements: List): C * Allocates C array of pointers to given elements. */ fun NativePlacement.allocArrayOfPointersTo(vararg elements: T?) = - allocArrayOfPointersTo(elements.toList()) + allocArrayOfPointersTo(listOf(*elements)) /** * Allocates C array of given values. @@ -120,7 +120,7 @@ fun NativePlacement.allocArrayOfPointersTo(vararg elements: T?) = inline fun > NativePlacement.allocArrayOf(vararg elements: T?): CArray> { - return allocArrayOf(elements.toList()) + return allocArrayOf(listOf(*elements)) } /** @@ -139,8 +139,9 @@ inline fun > fun NativePlacement.allocArrayOf(elements: ByteArray): CArray { val res = allocArray(elements.size) - elements.forEachIndexed { i, byte -> - res[i].value = byte + var index = 0 + for (byte in elements) { + res[index++].value = byte } return res } @@ -173,7 +174,7 @@ class CString private constructor(override val rawPtr: NativePtr) : CPointed { val bytes = ByteArray(len) nativeMemUtils.getByteArray(array[0], bytes, len) - return String(bytes) // TODO: encoding + return decodeFromUtf8(bytes) // TODO: encoding } fun asCharPtr() = reinterpret() @@ -184,7 +185,7 @@ fun CString.Companion.fromString(str: String?, placement: NativePlacement): CStr return null } - val bytes = str.toByteArray() // TODO: encoding + val bytes = encodeToUtf8(str) // TODO: encoding val len = bytes.size val nativeBytes = nativeHeap.allocArray(len + 1) @@ -197,20 +198,16 @@ fun CString.Companion.fromString(str: String?, placement: NativePlacement): CStr fun CPointer.asCString() = CString.fromArray(this.reinterpret>().pointed) fun String.toCString(placement: NativePlacement) = CString.fromString(this, placement) -class MemScope private constructor(private val arena: Arena) : NativePlacement by arena { +class MemScope : NativePlacement { + + private val arena = Arena() + + override fun alloc(size: Long, align: Int) = arena.alloc(size, align) + + fun clear() = arena.clear() + val memScope: NativePlacement get() = this - - companion object { - internal inline fun use(block: MemScope.()->R): R { - val memScope = MemScope(Arena()) - try { - return memScope.block() - } finally { - memScope.arena.clear() - } - } - } } /** @@ -218,6 +215,10 @@ class MemScope private constructor(private val arena: Arena) : NativePlacement b * which will be automatically disposed at the end of this scope. */ inline fun memScoped(block: MemScope.()->R): R { - @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") // TODO: it is a hack - return MemScope.use(block) + val memScope = MemScope() + try { + return memScope.block() + } finally { + memScope.clear() + } } diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt new file mode 100644 index 00000000000..488241774b9 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt @@ -0,0 +1,66 @@ +package kotlinx.cinterop + +import konan.internal.Intrinsic + +internal inline val pointerSize: Int + get() = getPointerSize() + +@Intrinsic external fun getPointerSize(): Int + +// TODO: do not use singleton because it leads to init-check on any access. +object nativeMemUtils { + @Intrinsic external fun getByte(mem: NativePointed): Byte + @Intrinsic external fun putByte(mem: NativePointed, value: Byte) + + @Intrinsic external fun getShort(mem: NativePointed): Short + @Intrinsic external fun putShort(mem: NativePointed, value: Short) + + @Intrinsic external fun getInt(mem: NativePointed): Int + @Intrinsic external fun putInt(mem: NativePointed, value: Int) + + @Intrinsic external fun getLong(mem: NativePointed): Long + @Intrinsic external fun putLong(mem: NativePointed, value: Long) + + @Intrinsic external fun getFloat(mem: NativePointed): Float + @Intrinsic external fun putFloat(mem: NativePointed, value: Float) + + @Intrinsic external fun getDouble(mem: NativePointed): Double + @Intrinsic external fun putDouble(mem: NativePointed, value: Double) + + @Intrinsic external fun getNativePtr(mem: NativePointed): NativePtr + @Intrinsic external fun putNativePtr(mem: NativePointed, value: NativePtr) + + fun getByteArray(source: NativePointed, dest: ByteArray, length: Int) { + val sourceArray: CArray = source.reinterpret() + for (index in 0 .. length - 1) { + dest[index] = sourceArray[index].value + } + } + + fun putByteArray(source: ByteArray, dest: NativePointed, length: Int) { + val destArray: CArray = dest.reinterpret() + for (index in 0 .. length - 1) { + destArray[index].value = source[index] + } + } + + private class NativeAllocated(override val rawPtr: NativePtr) : NativePointed + + fun alloc(size: Long, align: Int): NativePointed { + val ptr = malloc(size, align) + if (ptr == nativeNullPtr) { + throw OutOfMemoryError("unable to allocate native memory") + } + return NativeAllocated(ptr) + } + + fun free(mem: NativePointed) { + free(mem.rawPtr) + } +} + +@SymbolName("Kotlin_interop_malloc") +private external fun malloc(size: Long, align: Int): NativePtr + +@SymbolName("Kotlin_interop_free") +private external fun free(ptr: NativePtr) \ No newline at end of file diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt new file mode 100644 index 00000000000..dc3a545ed57 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt @@ -0,0 +1,26 @@ +package kotlinx.cinterop + +import konan.internal.Intrinsic + +class NativePtr private constructor() { + @Intrinsic external operator fun plus(offset: Long): NativePtr +} + +inline val nativeNullPtr: NativePtr + get() = getNativeNullPtr() + +@Intrinsic external fun getNativeNullPtr(): NativePtr + +fun typeOf(): CVariable.Type = throw Error("typeOf() is called with erased argument") + +/** + * Returns interpretation of entity with given pointer. + * + * @param T must not be abstract + */ +@Intrinsic external fun interpretPointed(ptr: NativePtr): T + +inline fun > CAdaptedFunctionType.Companion.getInstanceOf(): T = + TODO("CAdaptedFunctionType.getInstanceOf") + +internal fun CPointer<*>.cPointerToString() = "CPointer(raw=$rawValue)" \ No newline at end of file diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt new file mode 100644 index 00000000000..16dcce28e61 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt @@ -0,0 +1,16 @@ +package kotlinx.cinterop + +internal fun decodeFromUtf8(bytes: ByteArray): String = kotlin.text.fromUtf8Array(bytes, 0, bytes.size) + +fun encodeToUtf8(str: String): ByteArray { + val result = ByteArray(str.length) + + for (index in 0 .. str.length - 1) { + val char = str[index] + if (char.toInt() >= 128) { + TODO("non-ASCII char") + } + result[index] = char.toByte() + } + return result +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/Exceptions.kt b/runtime/src/main/kotlin/kotlin/Exceptions.kt index 79efffba2f3..50f49c24daf 100644 --- a/runtime/src/main/kotlin/kotlin/Exceptions.kt +++ b/runtime/src/main/kotlin/kotlin/Exceptions.kt @@ -154,3 +154,12 @@ public class NoWhenBranchMatchedException : RuntimeException { constructor(s: String) : super(s) { } } + +public class OutOfMemoryError : Error { + + constructor() : super() { + } + + constructor(s: String) : super(s) { + } +} \ No newline at end of file