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