From c766a530aa67e36e82d90f5fa887750a3a94438d Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 15 Sep 2016 13:42:36 +0300 Subject: [PATCH] experiments/Interop: add first prototype --- experiments/Interop/.idea/compiler.xml | 22 + .../.idea/libraries/KotlinJavaRuntime.xml | 12 + ...om_google_protobuf_protobuf_java_2_6_1.xml | 10 + experiments/Interop/.idea/modules.xml | 12 + .../.idea/runConfigurations/Example.xml | 14 + .../.idea/runConfigurations/StubGenerator.xml | 14 + experiments/Interop/Example/Example.iml | 13 + experiments/Interop/Example/src/llvm/LLVM.def | 60 +++ experiments/Interop/Example/src/main.kt | 48 ++ experiments/Interop/Interop.iml | 12 + experiments/Interop/Runtime/Runtime.iml | 12 + .../src/kotlin_native/interop/Bridge.kt | 46 ++ .../src/kotlin_native/interop/Types.kt | 141 ++++++ .../src/kotlin_native/interop/Utils.kt | 45 ++ .../Interop/StubGenerator/StubGenerator.iml | 13 + .../kotlin/native/interop/gen/Types.kt | 124 ++++++ .../native/interop/gen/jvm/StubGenerator.kt | 420 ++++++++++++++++++ .../kotlin/native/interop/gen/jvm/main.kt | 108 +++++ 18 files changed, 1126 insertions(+) create mode 100644 experiments/Interop/.idea/compiler.xml create mode 100644 experiments/Interop/.idea/libraries/KotlinJavaRuntime.xml create mode 100644 experiments/Interop/.idea/libraries/com_google_protobuf_protobuf_java_2_6_1.xml create mode 100644 experiments/Interop/.idea/modules.xml create mode 100644 experiments/Interop/.idea/runConfigurations/Example.xml create mode 100644 experiments/Interop/.idea/runConfigurations/StubGenerator.xml create mode 100644 experiments/Interop/Example/Example.iml create mode 100644 experiments/Interop/Example/src/llvm/LLVM.def create mode 100644 experiments/Interop/Example/src/main.kt create mode 100644 experiments/Interop/Interop.iml create mode 100644 experiments/Interop/Runtime/Runtime.iml create mode 100644 experiments/Interop/Runtime/src/kotlin_native/interop/Bridge.kt create mode 100644 experiments/Interop/Runtime/src/kotlin_native/interop/Types.kt create mode 100644 experiments/Interop/Runtime/src/kotlin_native/interop/Utils.kt create mode 100644 experiments/Interop/StubGenerator/StubGenerator.iml create mode 100644 experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/Types.kt create mode 100644 experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt create mode 100644 experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt diff --git a/experiments/Interop/.idea/compiler.xml b/experiments/Interop/.idea/compiler.xml new file mode 100644 index 00000000000..96cc43efa6a --- /dev/null +++ b/experiments/Interop/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/experiments/Interop/.idea/libraries/KotlinJavaRuntime.xml b/experiments/Interop/.idea/libraries/KotlinJavaRuntime.xml new file mode 100644 index 00000000000..c630c0b8796 --- /dev/null +++ b/experiments/Interop/.idea/libraries/KotlinJavaRuntime.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/experiments/Interop/.idea/libraries/com_google_protobuf_protobuf_java_2_6_1.xml b/experiments/Interop/.idea/libraries/com_google_protobuf_protobuf_java_2_6_1.xml new file mode 100644 index 00000000000..aee547201d3 --- /dev/null +++ b/experiments/Interop/.idea/libraries/com_google_protobuf_protobuf_java_2_6_1.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/experiments/Interop/.idea/modules.xml b/experiments/Interop/.idea/modules.xml new file mode 100644 index 00000000000..5285e4ee0cb --- /dev/null +++ b/experiments/Interop/.idea/modules.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/experiments/Interop/.idea/runConfigurations/Example.xml b/experiments/Interop/.idea/runConfigurations/Example.xml new file mode 100644 index 00000000000..c961bf6e578 --- /dev/null +++ b/experiments/Interop/.idea/runConfigurations/Example.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/experiments/Interop/.idea/runConfigurations/StubGenerator.xml b/experiments/Interop/.idea/runConfigurations/StubGenerator.xml new file mode 100644 index 00000000000..70238318dde --- /dev/null +++ b/experiments/Interop/.idea/runConfigurations/StubGenerator.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/experiments/Interop/Example/Example.iml b/experiments/Interop/Example/Example.iml new file mode 100644 index 00000000000..768a1f84fcb --- /dev/null +++ b/experiments/Interop/Example/Example.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/experiments/Interop/Example/src/llvm/LLVM.def b/experiments/Interop/Example/src/llvm/LLVM.def new file mode 100644 index 00000000000..306e554807b --- /dev/null +++ b/experiments/Interop/Example/src/llvm/LLVM.def @@ -0,0 +1,60 @@ +libName = llvmbridge + +headers = llvm-c/Core.h llvm-c/ExecutionEngine.h llvm-c/Target.h llvm-c/Analysis.h llvm-c/BitWriter.h + + +compiler = cc + +compilerOpts = -std=c99 -I/usr/local/Cellar/llvm/3.8.1/include -fPIC \ + -Wall -W -Wno-unused-parameter -Wwrite-strings -Wmissing-field-initializers \ + -pedantic -Wno-long-long -Wcovered-switch-default -Wdelete-non-virtual-dtor \ + -DNDEBUG -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS + + +linker = c++ + +linkerOpts = -stdlib=libc++ -fPIC -fvisibility-inlines-hidden \ + -Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers \ + -pedantic -Wno-long-long -Wcovered-switch-default -Wnon-virtual-dtor -Wdelete-non-virtual-dtor \ + -std=c++11 \ + -DNDEBUG -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS \ + -L/usr/local/Cellar/llvm/3.8.1/lib -Wl,-search_paths_first -Wl,-headerpad_max_install_names \ + -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMSelectionDAG -lLLVMAsmPrinter \ + -lLLVMX86Desc -lLLVMMCDisassembler -lLLVMX86Info -lLLVMX86AsmPrinter -lLLVMX86Utils -lLLVMInterpreter \ + -lLLVMCodeGen -lLLVMScalarOpts -lLLVMInstCombine -lLLVMInstrumentation -lLLVMProfileData -lLLVMTransformUtils \ + -lLLVMBitWriter -lLLVMExecutionEngine -lLLVMTarget -lLLVMAnalysis -lLLVMRuntimeDyld -lLLVMObject -lLLVMMCParser \ + -lLLVMBitReader -lLLVMMC -lLLVMCore -lLLVMSupport -lcurses -lpthread -lz -lm \ + -L/usr/local/Cellar/libffi/3.0.13/lib -lffi + +excludedFunctions = \ + LLVMInitializeAMDGPUAsmParser \ + LLVMInitializeAMDGPUAsmPrinter \ + LLVMInitializeAMDGPUTarget \ + LLVMInitializeAMDGPUTargetInfo \ + LLVMInitializeAMDGPUTargetMC \ + LLVMInitializeARMAsmParser \ + LLVMInitializeARMAsmPrinter \ + LLVMInitializeARMDisassembler \ + LLVMInitializeARMTarget \ + LLVMInitializeARMTargetInfo \ + LLVMInitializeARMTargetMC \ + LLVMInitializeNVPTXAsmPrinter \ + LLVMInitializeNVPTXTarget \ + LLVMInitializeNVPTXTargetInfo \ + LLVMInitializeNVPTXTargetMC \ + LLVMLinkInMCJIT \ + LLVMInitializeAllAsmParsers \ + LLVMInitializeAllAsmPrinters \ + LLVMInitializeAllTargets \ + LLVMInitializeAllTargetInfos \ + LLVMInitializeAllTargetMCs \ + LLVMInitializeAllAsmParsers \ + LLVMInitializeAllAsmPrinters \ + LLVMInitializeAllDisassemblers \ + LLVMInitializeAllTargets \ + LLVMInitializeAllTargetInfos \ + LLVMInitializeAllTargetMCs \ + LLVMInitializeAllAsmPrinters \ + LLVMInitializeAllTargets \ + LLVMInitializeAllTargetInfos \ + LLVMInitializeAllTargetMCs \ No newline at end of file diff --git a/experiments/Interop/Example/src/main.kt b/experiments/Interop/Example/src/main.kt new file mode 100644 index 00000000000..9a0c14a55e3 --- /dev/null +++ b/experiments/Interop/Example/src/main.kt @@ -0,0 +1,48 @@ +import kotlin_native.interop.* +import llvm.* + +fun main(args: Array) { + val module = LLVMModuleCreateWithName("module") + println("module=" + module.getNativePtr().asLong()) + + val paramTypes = mallocNativeArrayOf(LLVMOpaqueType, LLVMInt32Type(), LLVMInt32Type()) + val retType = LLVMFunctionType(LLVMInt32Type(), paramTypes[0], 2, 0) + free(paramTypes) + + val sum = LLVMAddFunction(module, "sum", retType) + val entry = LLVMAppendBasicBlock(sum, "entry") + val builder = LLVMCreateBuilder() + LLVMPositionBuilderAtEnd(builder, entry) + val tmp = LLVMBuildAdd(builder, LLVMGetParam(sum, 0), LLVMGetParam(sum, 1), "tmp") + LLVMBuildRet(builder, tmp) + val engineRef = malloc(Ref to LLVMOpaqueExecutionEngine) + val errorRef = malloc(Ref to Int8Box) + LLVMInitializeNativeTarget() + errorRef.value = null + if (LLVMCreateExecutionEngineForModule(engineRef, module, errorRef) != 0) { + println("failed to create execution engine") + return + } + val error = errorRef.value + if (error != null) { + println(CString.fromArray(NativeArray.byRefToFirstElem(error, Int8Box)).toString()) + return + } + + println(LLVMGetTypeKind(LLVMInt32Type())) + val x = 5L + val y = 6L + val args = malloc(array[2](Ref to LLVMOpaqueGenericValue)) + args[0].value = LLVMCreateGenericValueOfInt(LLVMInt32Type(), x, 0) + args[1].value = LLVMCreateGenericValueOfInt(LLVMInt32Type(), y, 0) + val runRes = LLVMRunFunction(engineRef.value, sum, 2, args[0]) + println(LLVMGenericValueToInt(runRes, 0)) + if (LLVMWriteBitcodeToFile(module, "/tmp/sum.bc") != 0) { + println("error writing bitcode to file, skipping") + } + LLVMDisposeBuilder(builder) + LLVMDisposeExecutionEngine(engineRef.value) + + + +} \ No newline at end of file diff --git a/experiments/Interop/Interop.iml b/experiments/Interop/Interop.iml new file mode 100644 index 00000000000..245d3429faa --- /dev/null +++ b/experiments/Interop/Interop.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/experiments/Interop/Runtime/Runtime.iml b/experiments/Interop/Runtime/Runtime.iml new file mode 100644 index 00000000000..245d3429faa --- /dev/null +++ b/experiments/Interop/Runtime/Runtime.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/experiments/Interop/Runtime/src/kotlin_native/interop/Bridge.kt b/experiments/Interop/Runtime/src/kotlin_native/interop/Bridge.kt new file mode 100644 index 00000000000..611202f6cdc --- /dev/null +++ b/experiments/Interop/Runtime/src/kotlin_native/interop/Bridge.kt @@ -0,0 +1,46 @@ +package kotlin_native.interop + +import sun.misc.Unsafe + +data class NativePtr private constructor(internal val value: Long) { + + fun displacedBy(offset: Int) = NativePtr(value + offset) + + companion object { + fun byValue(value: Long): NativePtr? { + return if (value == 0L) { + null + } else { + NativePtr(value) + } + } + } +} + +fun NativePtr?.asLong() = if (this == null) 0L else value + +internal object bridge { + + fun malloc(size: Int): NativePtr = NativePtr.byValue(unsafe.allocateMemory(size.toLong()))!! + fun free(ptr: NativePtr) = unsafe.freeMemory(ptr.asLong()) + + fun getInt64(ptr: NativePtr): Long = unsafe.getLong(ptr.asLong()) + fun putInt64(ptr: NativePtr, value: Long) = unsafe.putLong(ptr.asLong(), value) + + fun getPtr(ptr: NativePtr): NativePtr? = NativePtr.byValue(unsafe.getLong(ptr.asLong())) + fun putPtr(ptr: NativePtr, value: NativePtr?) = unsafe.putLong(ptr.asLong(), value.asLong()) + + fun getInt32(ptr: NativePtr): Int = unsafe.getInt(ptr.asLong()) + fun putInt32(ptr: NativePtr, value: Int) = unsafe.putInt(ptr.asLong(), value) + + fun getInt16(ptr: NativePtr): Short = unsafe.getShort(ptr.asLong()) + fun putInt16(ptr: NativePtr, value: Short) = unsafe.putShort(ptr.asLong(), value) + + fun getInt8(ptr: NativePtr): Byte = unsafe.getByte(ptr.asLong()) + fun putInt8(ptr: NativePtr, value: Byte) = unsafe.putByte(ptr.asLong(), value) + + private val unsafe = with(Unsafe::class.java.getDeclaredField("theUnsafe")) { + isAccessible = true + return@with this.get(null) as Unsafe + } +} \ No newline at end of file diff --git a/experiments/Interop/Runtime/src/kotlin_native/interop/Types.kt b/experiments/Interop/Runtime/src/kotlin_native/interop/Types.kt new file mode 100644 index 00000000000..c8629633839 --- /dev/null +++ b/experiments/Interop/Runtime/src/kotlin_native/interop/Types.kt @@ -0,0 +1,141 @@ +package kotlin_native.interop + +// TODO: what about equals/hashCode? + +open class NativeRef(val ptr: NativePtr) { + open class Type(val byPtr: (NativePtr) -> T) + open class TypeWithSize(val size: Int, byPtr: (NativePtr) -> T) : NativeRef.Type(byPtr) +} + +fun NativePtr?.asRef(type: NativeRef.Type) = this?.let { type.byPtr(this) } +fun NativeRef?.getNativePtr() = this?.ptr + +class Int8Box(ptr: NativePtr) : NativeRef(ptr) { + companion object : NativeRef.TypeWithSize(1, ::Int8Box) + var value: Byte + get() = bridge.getInt8(ptr) + set(value) = bridge.putInt8(ptr, value) +} + +class Int16Box(ptr: NativePtr) : NativeRef(ptr) { + companion object : NativeRef.TypeWithSize(2, ::Int16Box) + var value: Short + get() = bridge.getInt16(ptr) + set(value) = bridge.putInt16(ptr, value) +} + +class Int32Box(ptr: NativePtr) : NativeRef(ptr) { + companion object : NativeRef.TypeWithSize(4, ::Int32Box) + var value: Int + get() = bridge.getInt32(ptr) + set(value) = bridge.putInt32(ptr, value) +} + +class NativePtrBox(ptr: NativePtr) : NativeRef(ptr) { + companion object : NativeRef.TypeWithSize(8, ::NativePtrBox) // TODO: 64-bit specific + var value: NativePtr? + get() = bridge.getPtr(ptr) + set(value) = bridge.putPtr(ptr, value) +} + +class Int64Box(ptr: NativePtr) : NativeRef(ptr) { + companion object : NativeRef.TypeWithSize(8, ::Int64Box) + var value: Long + get() = bridge.getInt64(ptr) + set(value) = bridge.putInt64(ptr, value) +} + +class RefBox(ptr: NativePtr, val referentType: NativeRef.Type) : NativeRef(ptr) { + companion object { + infix fun of(type: NativeRef.Type) = NativeRef.TypeWithSize>(8, { RefBox(it, type) }) // TODO: 64-bit specific + } + + var value: T? + get() = bridge.getPtr(ptr).asRef(referentType) + set(value) = bridge.putPtr(ptr, value.getNativePtr()) +} + +val NativeRef.Type.ref: NativeRef.TypeWithSize> + get() = Ref to this + +object Ref { + infix fun to(type: NativeRef.Type) = RefBox.Companion of type + infix fun to(type: Byte.Companion) = Int8Box.Companion + infix fun to(type: Short.Companion) = Int16Box.Companion + infix fun to(type: Int.Companion) = Int32Box.Companion + infix fun to(type: Long.Companion) = Int64Box.Companion +} + +open class NativeStruct(ptr: NativePtr) : NativeRef(ptr) { + open class Type(size: Int, byPtr: (NativePtr) -> T) : NativeRef.TypeWithSize(size, byPtr) + + companion object { + class FieldAt(val type: NativeRef.Type, val offset: Int) { + operator fun getValue(thisRef: NativeStruct, property: kotlin.reflect.KProperty<*>): T { + return type.byPtr(thisRef.ptr.displacedBy(offset)) + } + } + + infix fun NativeRef.Type.at(offset: Int) = NativeStruct.Companion.FieldAt(this, offset) + } +} + +class NativeArray(ptr: NativePtr, val elemType: NativeRef.TypeWithSize) : NativeRef(ptr) { + operator fun get(index: Int): T { + return elemType.byPtr(ptr.displacedBy(index * elemType.size)) + } + + companion object { + + class Type (val elemType: NativeRef.TypeWithSize) : + NativeRef.Type>({ ptr -> NativeArray(ptr, elemType) }) { + + infix fun length(length: Int) = + NativeRef.TypeWithSize(elemType.size * length, { ptr -> NativeArray(ptr, elemType) }) + } + + infix fun of(elemType: NativeRef.TypeWithSize) = NativeArray.Companion.Type(elemType) + + fun byRefToFirstElem(ref: T, refType: NativeRef.TypeWithSize) = NativeArray(ref.ptr, refType) + } +} + +operator fun NativeRef.TypeWithSize.get(length: Int) = NativeArray.Companion of this length length +object array { + + // array(type) + operator fun invoke(type: NativeRef.TypeWithSize) = NativeArray.Companion of type + + // array[length](type) + operator fun get(length: Int) = array.ArrayWithLength(length) + + class ArrayWithLength(val length: Int) { + infix fun of(type: NativeRef.TypeWithSize) = NativeArray.Companion of type length length + + operator fun invoke(type: NativeRef.TypeWithSize) = this of type + } + +} + + +class CString private constructor(internal val array: NativeArray) : NativeRef(array.ptr) { + companion object { + fun fromArray(array: NativeArray) = CString(array) + } + + fun length(): Int { + var res = 0 + while (array[res].value != 0.toByte()) { + ++res + } + return res + } + + override fun toString(): String { + val bytes = ByteArray(this.length()) + bytes.forEachIndexed { i, byte -> + bytes[i] = this.array[i].value + } + return String(bytes) // TODO: encoding + } +} \ No newline at end of file diff --git a/experiments/Interop/Runtime/src/kotlin_native/interop/Utils.kt b/experiments/Interop/Runtime/src/kotlin_native/interop/Utils.kt new file mode 100644 index 00000000000..a8ce8a3b0a6 --- /dev/null +++ b/experiments/Interop/Runtime/src/kotlin_native/interop/Utils.kt @@ -0,0 +1,45 @@ +package kotlin_native.interop + +fun malloc(type: NativeRef.TypeWithSize) = type.byPtr(bridge.malloc(type.size)) + +fun malloc(type: NativeRef.TypeWithSize, action: (T) -> R): R { + val ref = malloc(type) + try { + return action(ref) + } finally { + bridge.free(ref.ptr) + } +} + +fun free(ptr: NativePtr?) { + if (ptr != null) { + bridge.free(ptr) + } +} + +fun free(ref: NativeRef?) = free(ref.getNativePtr()) + +fun mallocNativeArrayOf(elemType: NativeRef.Type, vararg elements: T?): NativeArray> { + val res = malloc(array[elements.size](elemType.ref)) + elements.forEachIndexed { i, element -> + res[i].value = element + } + return res +} + +fun CString.Companion.fromString(str: String?): CString? { + if (str == null) { + return null + } + + val bytes = str.toByteArray() // TODO: encoding + val len = bytes.size + val nativeBytes = malloc(NativeArray of Int8Box length (len + 1)) + + bytes.forEachIndexed { i, byte -> + nativeBytes[i].value = byte + } + nativeBytes[len].value = 0 + + return CString.fromArray(nativeBytes) +} \ No newline at end of file diff --git a/experiments/Interop/StubGenerator/StubGenerator.iml b/experiments/Interop/StubGenerator/StubGenerator.iml new file mode 100644 index 00000000000..0c344ac0aff --- /dev/null +++ b/experiments/Interop/StubGenerator/StubGenerator.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/Types.kt b/experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/Types.kt new file mode 100644 index 00000000000..8551e31768d --- /dev/null +++ b/experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/Types.kt @@ -0,0 +1,124 @@ +package org.jetbrains.kotlin.native.interop.gen + +open class CType +open class DirectlyMappedType(val kotlinType: String) : CType() +object VoidType : CType() +object Int8Type : DirectlyMappedType("Byte") +object UInt8Type : DirectlyMappedType("Byte") +object Int16Type : DirectlyMappedType("Short") +object UInt16Type : DirectlyMappedType("Short") +object Int32Type : DirectlyMappedType("Int") +object UInt32Type : DirectlyMappedType("Int") +object Int64Type : DirectlyMappedType("Long") +object UInt64Type : DirectlyMappedType("Long") +class RecordType(val name: String) : CType() +class EnumType(val name: String) : CType() +class PointerType(val pointeeType : CType) : CType() +class FunctionPointerType(val parameterTypes: List, val returnType: CType) : CType() +open class ArrayType(val elemType: CType) : CType() +class ConstArrayType(elemType: CType, val length: Int) : ArrayType(elemType) +class IncompleteArrayType(elemType: CType) : ArrayType(elemType) + + +fun parseType(type: String): CType = TypeParser(type).parse() + +class TypeParser(private val type: String) { + companion object { + private val primitiveType = mapOf( + "V" to VoidType, + "C" to Int8Type, + "UC" to UInt8Type, + "UB" to UInt8Type, + "S" to Int16Type, + "US" to UInt16Type, + "I" to Int32Type, + "UI" to UInt32Type, + "J" to Int64Type, + "UJ" to UInt64Type + ) + } + + private var at = 0 + + private fun at(s: String): Boolean = type.substring(at).startsWith(s) + + private fun expect(s: String) { + if (!advance(s)) error("Expecting <$s> (at=$at)") + } + + private fun advance(s: String): Boolean { + if (at(s)) { + at += s.length + return true + } + return false + } + + private fun error(s: String): Nothing = throw IllegalStateException(s + ": " + type) + + fun parse(): CType { + if (at == type.length) error("No type to parse") + + @Suppress("UNUSED_VARIABLE") + val isConst = advance("c") + + for ((string, type) in primitiveType.entries) { + if (advance(string)) return type + } + + if (advance("R")) { + val semicolon = type.indexOf(';', at) + if (semicolon < 0) error("L without a matching semicolon") + val recordName = type.substring(at, semicolon) + expect(recordName) + expect(";") + return RecordType(recordName) + } + + if (advance("E")) { // TODO: copy-pasted! + val semicolon = type.indexOf(';', at) + if (semicolon < 0) error("E without a matching semicolon") + val enumName = type.substring(at, semicolon) + expect(enumName) + expect(";") + return EnumType(enumName) + } + + if (advance("*(")) { + val paramTypes = mutableListOf() + while (!advance(")")) { + if (advance(".")) { + // TODO: support vararg + continue + } + paramTypes.add(parse()) + } + val returnType = parse() + expect(";") + return FunctionPointerType(paramTypes, returnType) + } + + if (advance("*")) { + val pointee = parse() + expect(";") + return PointerType(pointee) + } + + if (advance("[:")) { + val lengthEndIndex = type.indexOf(':', at) + val length = Integer.parseInt(type.substring(at, lengthEndIndex)) + at = lengthEndIndex + 1 + val elemType = parse() + expect(";") + return ConstArrayType(elemType, length) + } + + if (advance("[")) { + val elemType = parse() + expect(";") + return IncompleteArrayType(elemType) + } + + throw NotImplementedError("Unsupported type (at=$at): $type") + } +} \ No newline at end of file diff --git a/experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt new file mode 100644 index 00000000000..8d62df93a01 --- /dev/null +++ b/experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -0,0 +1,420 @@ +package org.jetbrains.kotlin.native.interop.gen.jvm + +import org.jetbrains.kni.indexer.NativeIndex +import org.jetbrains.kotlin.native.interop.gen.* + +class StubGenerator( + val translationUnit: NativeIndex.TranslationUnit, + val pkgName: String, + val libName: String, + val excludedFunctions: Set) { + + val forbiddenStructNames = run { + val functionNames = translationUnit.functionList.map { it.name } + val fieldNames = translationUnit.structList.flatMap { it.fieldList }.map { it.name } + (functionNames + fieldNames).toSet() + } + + val enums = translationUnit.enumList.map { it.name to it }.toMap() + + val functionsToBind = translationUnit.functionList.uniqueBy { it.name }.filter { it.name !in excludedFunctions } + + private fun mangleStructName(name: String) = if (name !in forbiddenStructNames) name else (name + "Struct") + + val NativeIndex.CStruct.mangledName: String + get() = mangleStructName(name) + + + val NativeIndex.CForwardStruct.mangledName: String + get() = mangleStructName(name) + + private var out: (String) -> Unit = { + throw IllegalStateException() + } + + fun withOutput(output: (String) -> Unit, action: () -> R): R { + val oldOut = out + out = output + try { + return action() + } finally { + out = oldOut + } + } + + private fun transaction(action: () -> R): R { + val lines = mutableListOf() + val res = withOutput({ lines.add(it) }, action) + // if action is completed successfully: + lines.forEach(out) + return res + } + + private fun indent(action: () -> R): R { + val oldOut = out + return withOutput({ oldOut(" $it") }, action) + } + + fun CType.getStringRepresentation(): String { + return when (this) { + is VoidType -> "void" + is Int8Type -> "char" + is UInt8Type -> "unsigned char" + is Int16Type -> "short" + is UInt16Type -> "unsigned short" + is Int32Type -> "int" + is UInt32Type -> "unsigned int" + is Int64Type -> "int64_t" + is UInt64Type -> "uint64_t" + is PointerType -> pointeeType.getStringRepresentation() + "*" + is RecordType -> "struct $name" + is FunctionPointerType -> this.returnType.getStringRepresentation() + " (*)(" + + this.parameterTypes.map { it.getStringRepresentation() }.joinToString(", ") + ")" + is ArrayType -> "void*" // TODO + is EnumType -> enums["${this.name}"]!!.spelling + else -> throw kotlin.NotImplementedError() + } + } + + class NativeRefType(val typeName: String, val typeExpr: String = typeName) + + fun getKotlinNativeRefType(type: CType): NativeRefType = when (type) { + is Int8Type, is UInt8Type -> NativeRefType("Int8Box") + is Int16Type, is UInt16Type -> NativeRefType("Int16Box") + is Int32Type, is UInt32Type -> NativeRefType("Int32Box") + is Int64Type, is UInt64Type -> NativeRefType("Int64Box") + is RecordType -> NativeRefType("${mangleStructName(type.name)}") + is PointerType -> { + if (type.pointeeType is VoidType) { + NativeRefType("NativePtrBox") + } else { + val pointeeRefType = getKotlinNativeRefType(type.pointeeType) + NativeRefType("RefBox<${pointeeRefType.typeName}>", "${pointeeRefType.typeExpr}.ref") + } + } + is FunctionPointerType -> getKotlinNativeRefType(PointerType(VoidType)) + is ConstArrayType -> { + val elemRefType = getKotlinNativeRefType(type.elemType) + NativeRefType("NativeArray<${elemRefType.typeName}>", "array[${type.length}](${elemRefType.typeExpr})") + } + is IncompleteArrayType -> { + val elemRefType = getKotlinNativeRefType(type.elemType) + NativeRefType("NativeArray<${elemRefType.typeName}>", "array(${elemRefType.typeExpr})") + } + else -> throw NotImplementedError() + } + + class OutValueBinding(val kotlinType: String, + val kotlinConv: ((String) -> String)? = null, + val convFree: ((String) -> String)? = null, + val kotlinJniBridgeType: String = kotlinType) + + class InValueBinding(val kotlinJniBridgeType: String, + val conv: ((String) -> String) = { it }, + val kotlinType: String = kotlinJniBridgeType) + + fun outValueRefBinding(refType: NativeRefType) = OutValueBinding( + kotlinType = refType.typeName + "?", + kotlinConv = { "$it.getNativePtr().asLong()" }, + kotlinJniBridgeType = "Long" + ) + + fun inValueRefBinding(refType: NativeRefType) = InValueBinding( + kotlinJniBridgeType = "Long", + conv = { "NativePtr.byValue($it).asRef(${refType.typeExpr})" }, + kotlinType = refType.typeName + "?" + ) + + fun getOutValueBinding(type: CType): OutValueBinding = when (type) { + is DirectlyMappedType -> OutValueBinding(type.kotlinType) + is PointerType -> { + if (type.pointeeType is VoidType) { + OutValueBinding( + kotlinType = "NativePtr?", + kotlinConv = { "$it.asLong()" }, + kotlinJniBridgeType = "Long" + ) + } else if (type.pointeeType is Int8Type) { + OutValueBinding( + kotlinType = "String?", + kotlinConv = { name -> "CString.fromString($name).getNativePtr().asLong()" }, + convFree = { name -> "free(NativePtr.byValue($name))" }, + kotlinJniBridgeType = "Long" + ) + } else { + outValueRefBinding(getKotlinNativeRefType(type.pointeeType)) + } + } + is EnumType -> OutValueBinding( + kotlinType = type.name, + kotlinConv = { "$it.value" }, + kotlinJniBridgeType = "Long" + ) + is ArrayType -> outValueRefBinding(getKotlinNativeRefType(type)) + is FunctionPointerType -> getOutValueBinding(PointerType(VoidType)) + else -> throw NotImplementedError() + } + + fun getInValueBinding(type: CType): InValueBinding = when (type) { + is VoidType -> InValueBinding("Unit") + is DirectlyMappedType -> InValueBinding(type.kotlinType) + is PointerType -> { + if (type.pointeeType is VoidType) { + InValueBinding( + kotlinJniBridgeType = "Long", + conv = { "NativePtr.byValue($it)" }, + kotlinType = "NativePtr?" + ) + } else { + inValueRefBinding(getKotlinNativeRefType(type.pointeeType)) + } + } + is EnumType -> InValueBinding( + kotlinJniBridgeType = "Long", + conv = { "${type.name}.byValue($it)" }, + kotlinType = type.name + ) + is ArrayType -> inValueRefBinding(getKotlinNativeRefType(type)) + else -> throw NotImplementedError() + } + + + private fun Iterable.uniqueBy(key: (T) -> Any): List { + val found = mutableSetOf() + return this.filter { found.add(key(it)) } + } + + + private fun generateKotlinStruct(s: NativeIndex.CStruct) { + val className = s.mangledName + out("class $className(ptr: NativePtr) : NativeStruct(ptr) {") + indent { + out("") + out("companion object : Type<$className>(${s.size}, ::$className)") + out("") + s.fieldList.forEach { field -> + try { + if (field.offset < 0) throw NotImplementedError(); + assert(field.offset % 8 == 0L) + val offset = field.offset / 8 + val fieldRefType = getKotlinNativeRefType(parseType(field.type)) + out("val ${field.name} by ${fieldRefType.typeExpr} at $offset") + } catch (e: Throwable) { + println("Warning: cannot generate definition for field $className.${field.name}") + } + } + } + out("}") + } + + private fun generateKotlinForwardStruct(s: NativeIndex.CForwardStruct) { + val className = s.mangledName + out("class $className(ptr: NativePtr) : NativeRef(ptr) {") + out(" companion object : Type<$className>(::$className)") + out("}") + } + + private fun generateKotlinEnum(e: NativeIndex.CEnum) { + out("enum class ${e.name}(val value: Long) {") + indent { + e.valueList.forEach { + out("${it.name}(${it.value}),") + } + out(";") + out("") + out("companion object {") + out(" fun byValue(value: Long) = ${e.name}.values().find { it.value == value }!!") + out("}") + } + out("}") + } + + private fun retValBinding(func: NativeIndex.Function) = getInValueBinding(parseType(func.returnType)) + + private fun paramBindings(func: NativeIndex.Function): Array { + val paramBindings = func.parameterList.map { param -> + getOutValueBinding(parseType(param.type)) + }.toTypedArray() + return paramBindings + } + + private fun paramNames(func: NativeIndex.Function): Array { + val paramNames = func.parameterList.mapIndexed { i: Int, parameter: NativeIndex.Function.Parameter -> + if (parameter.name != "") parameter.name else "arg$i" + }.toTypedArray() + return paramNames + } + + private fun generateKotlinBindingMethod(func: NativeIndex.Function) { + val paramNames = paramNames(func) + val paramBindings = paramBindings(func) + val retValBinding = retValBinding(func) + + val args = paramNames.mapIndexed { i: Int, name: String -> + "$name: " + paramBindings[i].kotlinType + }.joinToString(", ") + + out("fun ${func.name}($args): ${retValBinding.kotlinType} {") + indent { + val externalParamNames = paramNames.mapIndexed { i: Int, name: String -> + val binding = paramBindings[i] + val externalParamName: String + + if (binding.kotlinConv != null) { + externalParamName = "_$name" + out("val $externalParamName = " + binding.kotlinConv.invoke(name)) + } else { + externalParamName = name + } + + externalParamName + + } + out("val res = externals.${func.name}(" + externalParamNames.joinToString(", ") + ")") + paramNames.forEachIndexed { i, name -> + val binding = paramBindings[i] + if (binding.convFree != null) { + assert(binding.kotlinConv != null) + out(binding.convFree.invoke(externalParamNames[i])) + } + } + out("return " + retValBinding.conv("res")) + } + out("}") + } + + private fun generateKotlinExternalMethod(func: NativeIndex.Function) { + val paramNames = paramNames(func) + val paramBindings = paramBindings(func) + val retValBinding = retValBinding(func) + + val args = paramNames.mapIndexed { i: Int, name: String -> + "$name: " + paramBindings[i].kotlinJniBridgeType + }.joinToString(", ") + + out("external fun ${func.name}($args): ${retValBinding.kotlinJniBridgeType}") + } + + fun generateKotlinFile() { + if (pkgName != "") { + out("package $pkgName") + out("") + } + out("import kotlin_native.interop.*") + out("") + + functionsToBind.forEach { + try { + transaction { + generateKotlinBindingMethod(it) + out("") + } + } catch (e: Throwable) { + println("Warning: cannot generate binding definition for function ${it.name}") + } + } + + val generatedStructs = mutableSetOf() + + translationUnit.structList.uniqueBy { it.name }.forEach { s -> + try { + transaction { + generateKotlinStruct(s) + out("") + generatedStructs.add(s.name) + } + } catch (e: Throwable) { + println("Warning: cannot generate definition for struct ${s.name}") + } + } + + translationUnit.forwardStructList + .uniqueBy { it.name } + .filter { it.name !in generatedStructs } + .forEach { s -> + generateKotlinForwardStruct(s) + out("") + } + + translationUnit.enumList.uniqueBy { it.name }.forEach { e -> + generateKotlinEnum(e) + out("") + } + + out("object externals {") + indent { + out("init { System.loadLibrary(\"$libName\") }") + functionsToBind.forEach { + try { + transaction { + generateKotlinExternalMethod(it) + out("") + } + } catch (e: Throwable) { + println("Warning: cannot generate external definition for function ${it.name}") + } + } + } + out("}") + } + + fun getCJniBridgeType(kotlinJniBridgeType: String) = when (kotlinJniBridgeType) { + "Unit" -> "void" + "Byte" -> "jbyte" + "Short" -> "jshort" + "Int" -> "jint" + "Long" -> "jlong" + else -> throw NotImplementedError(kotlinJniBridgeType) + } + + + fun generateCFile(headerFiles: List, translationUnit: NativeIndex.TranslationUnit) { + out("#include ") + out("#include ") + headerFiles.forEach { + out("#include <$it>") + } + out("") + + functionsToBind.forEach { func -> + try { + val paramNames = paramNames(func) + val paramBindings = paramBindings(func) + val retValBinding = retValBinding(func) + + val args = + if (paramBindings.isEmpty()) + "" + else paramBindings + .map { getCJniBridgeType(it.kotlinJniBridgeType) } + .mapIndexed { i, type -> "$type ${paramNames[i]}" } + .joinToString(separator = ", ", prefix = ", ") + + val cReturnType = getCJniBridgeType(retValBinding.kotlinJniBridgeType) + + val params = func.parameterList.mapIndexed { i, parameter -> + val cType = parseType(parameter.type).getStringRepresentation() + "($cType)${paramNames[i]}" + }.joinToString(", ") + + val callExpr = "${func.name}($params)" + val funcFullName = if (pkgName.isEmpty()) { + "externals.${func.name}" + } else { + "$pkgName.externals.${func.name}" + } + val jniFuncName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024") + + out("JNIEXPORT $cReturnType JNICALL $jniFuncName (JNIEnv *env, jobject obj$args) {") + if (cReturnType == "void") { + out(" $callExpr;") + } else { + out(" return ($cReturnType) ($callExpr);") + } + out("}") + } catch (e: Throwable) { + System.err.println("Warning: cannot generate C JNI function definition ${func.name}") + } + } + } +} \ No newline at end of file diff --git a/experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt new file mode 100644 index 00000000000..dc942d324e9 --- /dev/null +++ b/experiments/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -0,0 +1,108 @@ +package org.jetbrains.kotlin.native.interop.gen.jvm + +import org.jetbrains.kni.indexer.IndexerOptions +import org.jetbrains.kni.indexer.Language +import org.jetbrains.kni.indexer.NativeIndex +import org.jetbrains.kni.indexer.buildNativeIndex +import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator +import java.io.File +import java.util.* +import kotlin.system.exitProcess + +fun main(args: Array) { + val ktSrcRoot = args[0] + val nativeLibsDir = args[1] + + val defFiles = File(ktSrcRoot).walk().filter { it.name.endsWith(".def") } + + defFiles.forEach { defFile -> + val config = Properties() + defFile.bufferedReader().use { reader -> + config.load(reader) + } + + val headerFiles = config.getProperty("headers").split(' ') + val compilerOpts = config.getProperty("compilerOpts").split(' ') + val compiler = config.getProperty("compiler") + val libName = config.getProperty("libName") + val linkerOpts = config.getProperty("linkerOpts").split(' ').toTypedArray() + val linker = config.getProperty("linker") + val excludedFunctions = config.getProperty("excludedFunctions")?.split(' ')?.toSet() ?: emptySet() + + + val outKtFile = defFile.absolutePath.substringBeforeLast(".def") + ".kt" + val outKtPkg = defFile.parentFile.relativeTo(File(ktSrcRoot)).path.replace(File.separatorChar, '.') + + + + val translationUnit = buildNativeIndex(headerFiles, compilerOpts) + + val gen = StubGenerator(translationUnit, outKtPkg, libName, excludedFunctions) + + File(outKtFile).bufferedWriter().use { out -> + gen.withOutput({ out.appendln(it) }) { + gen.generateKotlinFile() + } + } + + + val outCFile = createTempFile(suffix = ".c") + + outCFile.bufferedWriter().use { out -> + gen.withOutput({ out.appendln(it) }) { + gen.generateCFile(headerFiles, translationUnit) + } + } + + val outOFile = createTempFile(suffix = ".o") + + val compilerCmd = arrayOf(compiler, *compilerOpts.toTypedArray(), + "-I/System/Library/Frameworks/JavaVM.framework/Headers", // FIXME + "-c", outCFile.path, "-o", outOFile.path) + + println(compilerCmd.joinToString(" ")) + + val compilerRes = ProcessBuilder(*compilerCmd) + .inheritIO() + .start() + .waitFor() + + if (compilerRes != 0) { + exitProcess(compilerRes) + } + + File(nativeLibsDir).mkdirs() + + val outLib = nativeLibsDir + "/" + System.mapLibraryName(libName) + + val linkerCmd = arrayOf(linker, *linkerOpts, outOFile.path, "-shared", "-o", outLib) + println(linkerCmd.joinToString(" ")) + + val linkerRes = ProcessBuilder(*linkerCmd) + .inheritIO() + .start() + .waitFor() + + if (linkerRes != 0) { + exitProcess(linkerRes) + } + + outCFile.delete() + outOFile.delete() + + } + +} + +private fun buildNativeIndex(headerFiles: List, compilerOpts: List): NativeIndex.TranslationUnit { + val tempHeaderFile = createTempFile(suffix = ".h") + tempHeaderFile.deleteOnExit() + tempHeaderFile.writer().buffered().use { reader -> + headerFiles.forEach { + reader.appendln("#include <$it>") + } + } + + val indexerOptions = IndexerOptions(language = Language.CPP, args = compilerOpts) + return buildNativeIndex(tempHeaderFile, indexerOptions) +} \ No newline at end of file