experiments/Interop: add first prototype

This commit is contained in:
Svyatoslav Scherbina
2016-09-15 13:42:36 +03:00
parent 4def3f79f5
commit c766a530aa
18 changed files with 1126 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>
@@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime">
<CLASSES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-runtime.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-runtime-sources.jar!/" />
</SOURCES>
</library>
</component>
@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="com.google.protobuf:protobuf-java:2.6.1" type="repository">
<properties maven-id="com.google.protobuf:protobuf-java:2.6.1" />
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/protobuf-java-2.6.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/Example/Example.iml" filepath="$PROJECT_DIR$/Example/Example.iml" />
<module fileurl="file://$PROJECT_DIR$/Interop.iml" filepath="$PROJECT_DIR$/Interop.iml" />
<module fileurl="file://$PROJECT_DIR$/Runtime/Runtime.iml" filepath="$PROJECT_DIR$/Runtime/Runtime.iml" />
<module fileurl="file://$PROJECT_DIR$/StubGenerator/StubGenerator.iml" filepath="$PROJECT_DIR$/StubGenerator/StubGenerator.iml" />
<module fileurl="file://$PROJECT_DIR$/../kni/indexer/indexer.iml" filepath="$PROJECT_DIR$/../kni/indexer/indexer.iml" />
</modules>
</component>
</project>
+14
View File
@@ -0,0 +1,14 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Example" type="JetRunConfigurationType" factoryName="Kotlin">
<option name="MAIN_CLASS_NAME" value="MainKt" />
<option name="VM_PARAMETERS" value="-Djava.library.path=$MODULE_DIR$/nativelib" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="Example" />
<envs />
<method />
</configuration>
</component>
@@ -0,0 +1,14 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="StubGenerator" type="JetRunConfigurationType" factoryName="Kotlin">
<option name="MAIN_CLASS_NAME" value="org.jetbrains.kotlin.native.interop.gen.jvm.MainKt" />
<option name="VM_PARAMETERS" value="-Djava.library.path=$PROJECT_DIR$/../kni/dist/build/indexer/native" />
<option name="PROGRAM_PARAMETERS" value="$PROJECT_DIR$/Example/src $PROJECT_DIR$/Example/nativelib" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="StubGenerator" />
<envs />
<method />
</configuration>
</component>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="Runtime" />
</component>
</module>
@@ -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
+48
View File
@@ -0,0 +1,48 @@
import kotlin_native.interop.*
import llvm.*
fun main(args: Array<String>) {
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)
}
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>
@@ -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
}
}
@@ -0,0 +1,141 @@
package kotlin_native.interop
// TODO: what about equals/hashCode?
open class NativeRef(val ptr: NativePtr) {
open class Type<T : NativeRef>(val byPtr: (NativePtr) -> T)
open class TypeWithSize<T : NativeRef>(val size: Int, byPtr: (NativePtr) -> T) : NativeRef.Type<T>(byPtr)
}
fun <T : NativeRef> NativePtr?.asRef(type: NativeRef.Type<T>) = this?.let { type.byPtr(this) }
fun NativeRef?.getNativePtr() = this?.ptr
class Int8Box(ptr: NativePtr) : NativeRef(ptr) {
companion object : NativeRef.TypeWithSize<Int8Box>(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<Int16Box>(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<Int32Box>(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<NativePtrBox>(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<Int64Box>(8, ::Int64Box)
var value: Long
get() = bridge.getInt64(ptr)
set(value) = bridge.putInt64(ptr, value)
}
class RefBox<T : NativeRef>(ptr: NativePtr, val referentType: NativeRef.Type<T>) : NativeRef(ptr) {
companion object {
infix fun <T : NativeRef> of(type: NativeRef.Type<T>) = NativeRef.TypeWithSize<RefBox<T>>(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 <T : NativeRef> NativeRef.Type<T>.ref: NativeRef.TypeWithSize<RefBox<T>>
get() = Ref to this
object Ref {
infix fun <T : NativeRef> to(type: NativeRef.Type<T>) = 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<T : NativeStruct>(size: Int, byPtr: (NativePtr) -> T) : NativeRef.TypeWithSize<T>(size, byPtr)
companion object {
class FieldAt<T : NativeRef>(val type: NativeRef.Type<T>, val offset: Int) {
operator fun getValue(thisRef: NativeStruct, property: kotlin.reflect.KProperty<*>): T {
return type.byPtr(thisRef.ptr.displacedBy(offset))
}
}
infix fun <T : NativeRef> NativeRef.Type<T>.at(offset: Int) = NativeStruct.Companion.FieldAt(this, offset)
}
}
class NativeArray<T : NativeRef>(ptr: NativePtr, val elemType: NativeRef.TypeWithSize<T>) : NativeRef(ptr) {
operator fun get(index: Int): T {
return elemType.byPtr(ptr.displacedBy(index * elemType.size))
}
companion object {
class Type<T : NativeRef> (val elemType: NativeRef.TypeWithSize<T>) :
NativeRef.Type<NativeArray<T>>({ ptr -> NativeArray(ptr, elemType) }) {
infix fun length(length: Int) =
NativeRef.TypeWithSize(elemType.size * length, { ptr -> NativeArray(ptr, elemType) })
}
infix fun <T : NativeRef> of(elemType: NativeRef.TypeWithSize<T>) = NativeArray.Companion.Type(elemType)
fun <T : NativeRef> byRefToFirstElem(ref: T, refType: NativeRef.TypeWithSize<T>) = NativeArray(ref.ptr, refType)
}
}
operator fun <T : NativeRef> NativeRef.TypeWithSize<T>.get(length: Int) = NativeArray.Companion of this length length
object array {
// array(type)
operator fun <T : NativeRef> invoke(type: NativeRef.TypeWithSize<T>) = NativeArray.Companion of type
// array[length](type)
operator fun get(length: Int) = array.ArrayWithLength(length)
class ArrayWithLength(val length: Int) {
infix fun <T : NativeRef> of(type: NativeRef.TypeWithSize<T>) = NativeArray.Companion of type length length
operator fun <T : NativeRef> invoke(type: NativeRef.TypeWithSize<T>) = this of type
}
}
class CString private constructor(internal val array: NativeArray<Int8Box>) : NativeRef(array.ptr) {
companion object {
fun fromArray(array: NativeArray<Int8Box>) = 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
}
}
@@ -0,0 +1,45 @@
package kotlin_native.interop
fun <T : NativeRef> malloc(type: NativeRef.TypeWithSize<T>) = type.byPtr(bridge.malloc(type.size))
fun <T : NativeRef, R> malloc(type: NativeRef.TypeWithSize<T>, 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 <T : NativeRef> mallocNativeArrayOf(elemType: NativeRef.Type<T>, vararg elements: T?): NativeArray<RefBox<T>> {
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)
}
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="indexer" />
</component>
</module>
@@ -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<CType>, 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<CType>()
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")
}
}
@@ -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<String>) {
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 <R> withOutput(output: (String) -> Unit, action: () -> R): R {
val oldOut = out
out = output
try {
return action()
} finally {
out = oldOut
}
}
private fun <R> transaction(action: () -> R): R {
val lines = mutableListOf<String>()
val res = withOutput({ lines.add(it) }, action)
// if action is completed successfully:
lines.forEach(out)
return res
}
private fun <R> 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 <T> Iterable<T>.uniqueBy(key: (T) -> Any): List<T> {
val found = mutableSetOf<Any>()
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<OutValueBinding> {
val paramBindings = func.parameterList.map { param ->
getOutValueBinding(parseType(param.type))
}.toTypedArray()
return paramBindings
}
private fun paramNames(func: NativeIndex.Function): Array<String> {
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<String>()
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<String>, translationUnit: NativeIndex.TranslationUnit) {
out("#include <stdint.h>")
out("#include <jni.h>")
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}")
}
}
}
}
@@ -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<String>) {
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<String>, compilerOpts: List<String>): 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)
}