diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index 436863aeb78..8ccd7f46774 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -272,7 +272,9 @@ internal class NativeIndexImpl(val language: Language) : NativeIndex() { val definitionCursor = clang_getCursorDefinition(cursor) val isDefined = (clang_Cursor_isNull(definitionCursor) == 0) - functionByName[name] = FunctionDecl(name, args, returnType, binaryName, isDefined) + val isVararg = clang_Cursor_isVariadic(cursor) != 0 + + functionByName[name] = FunctionDecl(name, args, returnType, binaryName, isDefined, isVararg) } CXIdxEntity_Enum -> { diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index 851980bbe4f..ac043c26072 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -70,7 +70,7 @@ class Parameter(val name: String?, val type: Type) * C function declaration. */ class FunctionDecl(val name: String, val parameters: List, val returnType: Type, val binaryName: String, - val isDefined: Boolean) + val isDefined: Boolean, val isVararg: Boolean) /** * C typedef definition. diff --git a/Interop/Runtime/build.gradle b/Interop/Runtime/build.gradle index 4da364c92e3..439bdb19055 100644 --- a/Interop/Runtime/build.gradle +++ b/Interop/Runtime/build.gradle @@ -23,9 +23,9 @@ model { } binaries.all { cCompiler.args compilerArgsForJniIncludes - cCompiler.args "-I$libffiDir/include" + cCompiler.args "-I$hostLibffiDir/include" - linker.args "$libffiDir/lib/libffi.a" + linker.args "$hostLibffiDir/lib/libffi.a" } } } diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt index cb7d045661c..abc1626e1a6 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt @@ -60,6 +60,37 @@ abstract class CValues : CValuesRef() { * Copies the values to [placement] and returns the pointer to the copy. */ override abstract fun getPointer(placement: NativePlacement): CPointer + + // TODO: optimize + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is CValues<*>) return false + + val thisBytes = this.getBytes() + val otherBytes = other.getBytes() + + if (thisBytes.size != otherBytes.size) { + return false + } + + for (index in 0 .. thisBytes.size - 1) { + if (thisBytes[index] != otherBytes[index]) { + return false + } + } + + return true + } + + override fun hashCode(): Int { + var result = 0 + for (byte in this.getBytes()) { + result = result * 31 + byte + } + return result + } + + abstract val size: Int } fun CValues.placeTo(placement: NativePlacement) = this.getPointer(placement) diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt index 9a0483e9fe7..4127c83e156 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt @@ -158,6 +158,8 @@ fun zeroValue(size: Int, align: Int): CValue = object : CValu nativeMemUtils.zeroMemory(result, size) return interpretCPointer(result.rawPtr)!! } + + override val size get() = size } inline fun zeroValue(): CValue = @@ -175,6 +177,7 @@ fun CPointed.readValues(size: Int, align: Int): CValues { return object : CValues() { override fun getPointer(placement: NativePlacement): CPointer = placement.placeBytes(bytes, align) + override val size get() = bytes.size } } @@ -186,6 +189,7 @@ fun CPointed.readValue(size: Int, align: Int): CValue { nativeMemUtils.getByteArray(this, bytes, size) return object : CValue() { override fun getPointer(placement: NativePlacement): CPointer = placement.placeBytes(bytes, align) + override val size get() = bytes.size } } @@ -193,6 +197,19 @@ fun CPointed.readValue(size: Int, align: Int): CValue { // TODO: find better name. inline fun T.readValue(): CValue = this.readValue(sizeOf().toInt(), alignOf()) +// TODO: optimize +fun CValues.getBytes(): ByteArray = memScoped { + val result = ByteArray(size) + + nativeMemUtils.getByteArray( + source = this@getBytes.placeTo(memScope).reinterpret().pointed, + dest = result, + length = result.size + ) + + result +} + /** * Calls the [block] with temporary copy if this value as receiver. */ @@ -215,6 +232,7 @@ inline fun createValues(count: Int, initializer: T.(inde fun cValuesOf(vararg elements: Byte): CValues = object : CValues() { override fun getPointer(placement: NativePlacement) = placement.allocArrayOf(elements)[0].ptr + override val size get() = 1 * elements.size } // TODO: optimize other [cValuesOf] methods: @@ -230,6 +248,7 @@ fun cValuesOf(vararg elements: Long): CValues = fun cValuesOf(vararg elements: Float): CValues = object : CValues() { override fun getPointer(placement: NativePlacement) = placement.allocArrayOf(*elements)[0].ptr + override val size get() = 4 * elements.size } fun cValuesOf(vararg elements: Double): CValues = @@ -258,6 +277,8 @@ val String.cstr: CValues val bytes = encodeToUtf8(this) return object : CValues() { + override val size get() = bytes.size + 1 + override fun getPointer(placement: NativePlacement): CPointer { val result = placement.allocArray(bytes.size + 1) nativeMemUtils.putByteArray(bytes, result, bytes.size) diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt new file mode 100644 index 00000000000..e63c79732bf --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Varargs.kt @@ -0,0 +1,111 @@ +package kotlinx.cinterop + +private const val MAX_ARGUMENT_SIZE = 8 + + +typealias FfiTypeKind = Int +// Also declared in Interop.cpp +const val FFI_TYPE_KIND_VOID: FfiTypeKind = 0 +const val FFI_TYPE_KIND_SINT8: FfiTypeKind = 1 +const val FFI_TYPE_KIND_SINT16: FfiTypeKind = 2 +const val FFI_TYPE_KIND_SINT32: FfiTypeKind = 3 +const val FFI_TYPE_KIND_SINT64: FfiTypeKind = 4 +const val FFI_TYPE_KIND_FLOAT: FfiTypeKind = 5 +const val FFI_TYPE_KIND_DOUBLE: FfiTypeKind = 6 +const val FFI_TYPE_KIND_POINTER: FfiTypeKind = 7 + +private tailrec fun convertArgument( + argument: Any?, isVariadic: Boolean, location: NativePointed, + additionalPlacement: NativePlacement +): FfiTypeKind = when (argument) { + is CValuesRef<*>? -> { + location.reinterpret>().value = argument?.getPointer(additionalPlacement) + FFI_TYPE_KIND_POINTER + } + + is String -> { + location.reinterpret>().value = argument.cstr.getPointer(additionalPlacement) + FFI_TYPE_KIND_POINTER + } + + is Int -> { + location.reinterpret().value = argument + FFI_TYPE_KIND_SINT32 + } + + is Long -> { + location.reinterpret().value = argument + FFI_TYPE_KIND_SINT64 + } + + is Byte -> if (isVariadic) { + convertArgument(argument.toInt(), isVariadic, location, additionalPlacement) + } else { + location.reinterpret().value = argument + FFI_TYPE_KIND_SINT8 + } + + is Short -> if (isVariadic) { + convertArgument(argument.toInt(), isVariadic, location, additionalPlacement) + } else { + location.reinterpret().value = argument + FFI_TYPE_KIND_SINT16 + } + + is Double -> { + location.reinterpret().value = argument + FFI_TYPE_KIND_DOUBLE + } + + is Float -> if (isVariadic) { + convertArgument(argument.toDouble(), isVariadic, location, additionalPlacement) + } else { + location.reinterpret().value = argument + FFI_TYPE_KIND_FLOAT + } + + else -> throw Error("unsupported argument: $argument") +} + +fun callWithVarargs(codePtr: NativePtr, returnValuePtr: NativePtr, returnTypeKind: FfiTypeKind, + fixedArguments: Array, variadicArguments: Array, + argumentsPlacement: NativePlacement) { + + val totalArgumentsNumber = fixedArguments.size + variadicArguments.size + + // All supported arguments take at most 8 bytes each: + val argumentsStorage = argumentsPlacement.allocArray(totalArgumentsNumber) + val arguments = argumentsPlacement.allocArray>(totalArgumentsNumber) + val types = argumentsPlacement.allocArray>(totalArgumentsNumber) + + var index = 0 + + inline fun addArgument(argument: Any?, isVariadic: Boolean) { + val storage = argumentsStorage[index] + val typeKind = convertArgument(argument, isVariadic = isVariadic, + location = storage, additionalPlacement = argumentsPlacement) + + types[index].value = interpretCPointer(nativeNullPtr + typeKind.toLong()) + arguments[index].value = storage.ptr + + ++index + } + + for (argument in fixedArguments) { + addArgument(argument, isVariadic = false) + } + + for (argument in variadicArguments) { + addArgument(argument, isVariadic = true) + } + + assert (index == totalArgumentsNumber) + + callWithVarargs(codePtr, returnValuePtr, returnTypeKind, arguments.rawPtr, types.rawPtr, + fixedArguments.size, totalArgumentsNumber) +} + +@SymbolName("callWithVarargs") +private external fun callWithVarargs(codePtr: NativePtr, returnValuePtr: NativePtr, returnTypeKind: FfiTypeKind, + arguments: NativePtr, argumentTypeKinds: NativePtr, + fixedArgumentsNumber: Int, totalArgumentsNumber: Int) \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 9870971a508..870ce199dbe 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -694,6 +694,13 @@ class StubGenerator( } } + private fun FunctionDecl.generateAsFfiVarargs(): Boolean = (platform == KotlinPlatform.NATIVE && this.isVararg && + // Neither takes nor returns structs by value or enum-typed values: + (this.parameters.map { it.type } + this.returnType).all { + val type = it.unwrapTypedefs() + type !is RecordType && type !is EnumType + }) + /** * Constructs [InValueBinding] for return value of Kotlin binding for given C function. */ @@ -760,6 +767,30 @@ class StubGenerator( "${name.asSimpleName()}: " + paramBindings[i].kotlinType }.joinToString(", ") + if (func.generateAsFfiVarargs()) { + val returnTypeKind = getFfiTypeKind(func.returnType) + + val variadicParameter = "vararg variadicArguments: Any?" + val allParameters = if (args.isEmpty()) variadicParameter else "$args, $variadicParameter" + val header = "fun ${func.name.asSimpleName()}($allParameters): ${retValBinding.kotlinType} = memScoped" + val returnValueMirror = mirror(func.returnType) + block(header) { + val resultPtr = if (!func.returnsVoid()) { + out("val resultVar = alloc<${returnValueMirror.pointedTypeName}>()") + "resultVar.rawPtr" + } else { + "nativeNullPtr" + } + out("callWithVarargs(${func.kotlinExternalName}(), $resultPtr, $returnTypeKind, " + + "arrayOf(${paramNames.joinToString()}), variadicArguments, memScope)") + + if (!func.returnsVoid()) { + out("resultVar.value") + } + } + return + } + val header = "fun ${func.name.asSimpleName()}($args): ${retValBinding.kotlinType}" if (!func.requiresKotlinAdapter()) { @@ -825,6 +856,27 @@ class StubGenerator( } } + private fun getFfiTypeKind(type: Type): String { + val unwrappedType = type.unwrapTypedefs() + return when (unwrappedType) { + is VoidType -> "FFI_TYPE_KIND_VOID" + is PointerType -> "FFI_TYPE_KIND_POINTER" + is IntegerType -> when (unwrappedType.size) { + 1 -> "FFI_TYPE_KIND_SINT8" + 2 -> "FFI_TYPE_KIND_SINT16" + 4 -> "FFI_TYPE_KIND_SINT32" + 8 -> "FFI_TYPE_KIND_SINT64" + else -> TODO(unwrappedType.toString()) + } + is FloatingType -> when (unwrappedType.size) { + 4 -> "FFI_TYPE_KIND_FLOAT" + 8 -> "FFI_TYPE_KIND_DOUBLE" + else -> TODO(unwrappedType.toString()) + } + else -> TODO(unwrappedType.toString()) + } + } + /** * Returns the expression to be parsed by Kotlin as string literal with given contents, * i.e. transforms `foo$bar` to `"foo\$bar"`. @@ -901,7 +953,7 @@ class StubGenerator( return true } - return this.returnsRecord() || + return this.returnsRecord() || this.generateAsFfiVarargs() || this.parameters.map { it.type }.any { it.unwrapTypedefs() is RecordType || representCFunctionParameterAsString(it) || @@ -919,6 +971,7 @@ class StubGenerator( } return this.isDefined || + this.generateAsFfiVarargs() || this.returnsRecord() || this.parameters.map { it.type }.any { it.unwrapTypedefs() is RecordType } } @@ -1077,6 +1130,14 @@ class StubGenerator( * Produces to [out] the definition of Kotlin JNI function used in binding for given C function. */ private fun generateKotlinExternalMethod(func: FunctionDecl) { + if (func.generateAsFfiVarargs()) { + assert (platform == KotlinPlatform.NATIVE) + // The C stub simply returns pointer to the function: + out(func.symbolNameAnnotation) + out("private external fun ${func.kotlinExternalName}(): NativePtr") + return + } + val paramNames = paramNames(func) val paramBindings = paramBindings(func) val retValBinding = retValBinding(func) @@ -1266,6 +1327,13 @@ class StubGenerator( * Produces to [out] the implementation of JNI function used in Kotlin binding for given C function. */ private fun generateCJniFunction(func: FunctionDecl) { + if (func.generateAsFfiVarargs()) { + assert (platform == KotlinPlatform.NATIVE) + // The C stub simply returns pointer to the function: + out("void* ${func.cStubName}() { return ${func.name}; }") + return + } + val paramNames = paramNames(func) val paramBindings = paramBindings(func) val retValBinding = retValBinding(func) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Distribution.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Distribution.kt index c32043c6541..f0b5cc6497e 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Distribution.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Distribution.kt @@ -44,6 +44,8 @@ class Distribution(val config: CompilerConfiguration) { sysRoot } + val libffi = "$dependenciesDir/${properties.propertyString("libffiDir.$suffix")}/lib/libffi.a" + val llvmBin = "$llvmHome/bin" val llvmLib = "$llvmHome/lib" diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt index 880d958c361..e07029121d5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt @@ -63,7 +63,7 @@ internal class IPhoneOSfromMacOSPlatform(distribution: Distribution) override val arch = properties.propertyString("arch.osx-ios")!! override val osVersionMin = properties.propertyList("osVersionMin.osx-ios") override val llvmLlcFlags = properties.propertyList("llvmLlcFlags.osx-ios") - override val targetSysRoot = "${distribution.dependencies}/${properties.propertyString("targetSysRoot.osx-ios")!!}" + override val targetSysRoot = "${distribution.dependenciesDir}/${properties.propertyString("targetSysRoot.osx-ios")!!}" } internal class IPhoneSimulatorFromMacOSPlatform(distribution: Distribution) @@ -72,7 +72,7 @@ internal class IPhoneSimulatorFromMacOSPlatform(distribution: Distribution) override val arch = properties.propertyString("arch.osx-ios-sim")!! override val osVersionMin = properties.propertyList("osVersionMin.osx-ios-sim") override val llvmLlcFlags = properties.propertyList("llvmLlcFlags.osx-ios-sim") - override val targetSysRoot = "${distribution.dependencies}/${properties.propertyString("targetSysRoot.osx-ios-sim")!!}" + override val targetSysRoot = "${distribution.dependenciesDir}/${properties.propertyString("targetSysRoot.osx-ios-sim")!!}" } internal open class LinuxPlatform(distribution: Distribution) @@ -119,7 +119,7 @@ internal open class LinuxPlatform(distribution: Distribution) internal class RaspberryPiPlatform(distribution: Distribution) : LinuxPlatform(distribution) { - override val sysRoot = "${distribution.dependencies}/${properties.propertyString("targetSysRoot.linux-raspberrypi")!!}" + override val sysRoot = "${distribution.dependenciesDir}/${properties.propertyString("targetSysRoot.linux-raspberrypi")!!}" override fun linkCommand(objectFiles: List, executable: String, optimize: Boolean): List { // TODO: Can we extract more to the konan.properties? @@ -229,6 +229,7 @@ internal class LinkStage(val context: Context) { fun link(objectFiles: List): ExecutableFile { val executable = config.get(KonanConfigKeys.EXECUTABLE_FILE)!! val linkCommand = platform.linkCommand(objectFiles, executable, optimize) + + distribution.libffi + asLinkerArgs(config.getNotNull(KonanConfigKeys.LINKER_ARGS)) + entryPointSelector diff --git a/backend.native/konan.properties b/backend.native/konan.properties index 3bfc4d00e44..50901398f4c 100644 --- a/backend.native/konan.properties +++ b/backend.native/konan.properties @@ -6,6 +6,7 @@ dependenciesUrl = https://jetbrains.bintray.com/kotlin-native-dependencies # macbook arch.osx = x86_64 sysRoot.osx = target-sysroot-1-darwin-macos +libffiDir.osx = libffi-3.2.1-2-darwin-macos llvmHome.osx = clang+llvm-3.9.0-darwin-macos llvmLtoFlags.osx = -O3 -function-sections -exported-symbol=_Konan_main llvmLlcFlags.osx = -mtriple=x86_64-apple-macosx10.10.0 --disable-fp-elim @@ -13,12 +14,13 @@ linkerKonanFlags.osx = -lc++ linkerOptimizationFlags.osx = -dead_strip osVersionMin.osx = -macosx_version_min 10.10.0 entrySelector.osx = -alias _Konan_main _main -dependencies.osx = target-sysroot-1-darwin-macos clang+llvm-3.9.0-darwin-macos +dependencies.osx = target-sysroot-1-darwin-macos libffi-3.2.1-2-darwin-macos clang+llvm-3.9.0-darwin-macos # iphone arch.osx-ios = arm64 sysRoot.osx-ios = target-sysroot-1-darwin-macos targetSysRoot.osx-ios = target-sysroot-2-darwin-ios +libffiDir.osx-ios = libffi-3.2.1-2-darwin-ios llvmHome.osx-ios = clang+llvm-3.9.0-darwin-macos llvmLtoFlags.osx-ios = -O3 -function-sections -exported-symbol=_Konan_main llvmLlcFlags.osx-ios = -mtriple=arm64-apple-ios5.0.0 --disable-fp-elim @@ -27,6 +29,7 @@ linkerOptimizationFlags.osx-ios = -dead_strip osVersionMin.osx-ios = -iphoneos_version_min 5.0.0 entrySelector.osx-ios = -alias _Konan_main _main dependencies.osx-ios = target-sysroot-1-darwin-macos \ + libffi-3.2.1-2-darwin-ios \ clang+llvm-3.9.0-darwin-macos \ target-sysroot-2-darwin-ios @@ -34,6 +37,7 @@ dependencies.osx-ios = target-sysroot-1-darwin-macos \ arch.osx-ios-sim = x86_64 sysRoot.osx-ios-sim = target-sysroot-1-darwin-macos targetSysRoot.osx-ios-sim = target-sysroot-1-darwin-ios-sim +libffiDir.osx-ios-sim = libffi-3.2.1-2-darwin-ios-sim llvmHome.osx-ios-sim = clang+llvm-3.9.0-darwin-macos llvmLtoFlags.osx-ios-sim = -O3 -function-sections -exported-symbol=_Konan_main llvmLlcFlags.osx-ios-sim = -mtriple=x86_64-apple-ios5.0.0 --disable-fp-elim @@ -42,11 +46,13 @@ linkerOptimizationFlags.osx-ios-sim = -dead_strip osVersionMin.osx-ios-sim = -ios_simulator_version_min 5.0.0 entrySelector.osx-ios-sim = -alias _Konan_main _main dependencies.osx-ios-sim = target-sysroot-1-darwin-macos \ + libffi-3.2.1-2-darwin-ios-sim \ clang+llvm-3.9.0-darwin-macos \ target-sysroot-1-darwin-ios-sim # linux sysRoot.linux = target-gcc-toolchain-3-linux-x86-64/x86_64-unknown-linux-gnu/sysroot +libffiDir.linux = libffi-3.2.1-2-linux-x86-64 llvmHome.linux = clang+llvm-3.9.0-linux-x86-64 libGcc.linux = target-gcc-toolchain-3-linux-x86-64/lib/gcc/x86_64-unknown-linux-gnu/4.8.5 gccToolChain.linux = target-gcc-toolchain-3-linux-x86-64 @@ -56,12 +62,13 @@ linkerKonanFlags.linux = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lpthread linkerOptimizationFlags.linux = --gc-sections pluginOptimizationFlags.linux = -plugin-opt=mcpu=x86-64 -plugin-opt=O3 entrySelector.linux = --defsym main=Konan_main -dependencies.linux = clang+llvm-3.9.0-linux-x86-64 target-gcc-toolchain-3-linux-x86-64 +dependencies.linux = clang+llvm-3.9.0-linux-x86-64 target-gcc-toolchain-3-linux-x86-64 libffi-3.2.1-2-linux-x86-64 # Raspberry Pi arch.linux-raspberrypi = armv7 sysRoot.linux-raspberrypi = target-gcc-toolchain-3-linux-x86-64/x86_64-unknown-linux-gnu/sysroot targetSysRoot.linux-raspberrypi = target-sysroot-1-raspberrypi +libffiDir.linux-raspberrypi = libffi-3.2.1-2-raspberrypi llvmHome.linux-raspberrypi = clang+llvm-3.9.0-linux-x86-64 libGcc.linux-raspberrypi = target-sysroot-1-raspberrypi/lib/gcc/arm-linux-gnueabihf/4.8.3/ gccToolChain.linux-raspberrypi = target-gcc-toolchain-3-linux-x86-64 @@ -71,5 +78,5 @@ linkerKonanFlags.linux-raspberrypi = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lc -l entrySelector.linux-raspberrypi = --defsym main=Konan_main dependencies.raspberrypi = clang+llvm-3.9.0-linux-x86-64 \ target-gcc-toolchain-3-linux-x86-64 \ - target-sysroot-1-raspberrypi - + target-sysroot-1-raspberrypi \ + libffi-3.2.1-2-linux-x86-64 diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index bf42fe36815..a799852f027 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1617,6 +1617,12 @@ task interop3(type: RunInteropKonanTest) { interop = 'cstdlib' } +task interop4(type: RunInteropKonanTest) { + goldValue = "a b -1 2 3 9223372036854775807 0.1 0.2\n1 42\n" + source = "interop/basics/4.kt" + interop = 'cstdio' +} + task interop_echo_server(type: RunInteropKonanTest) { if (isLinux()) { disabled = true diff --git a/backend.native/tests/interop/basics/4.kt b/backend.native/tests/interop/basics/4.kt new file mode 100644 index 00000000000..d243487cf1f --- /dev/null +++ b/backend.native/tests/interop/basics/4.kt @@ -0,0 +1,14 @@ +import cstdio.* +import kotlinx.cinterop.* + +fun main(args: Array) { + printf("%s %s %d %d %d %lld %.1f %.1lf\n", + "a", "b".cstr, (-1).toByte(), 2.toShort(), 3, Long.MAX_VALUE, 0.1.toFloat(), 0.2) + + memScoped { + val aVar = alloc() + val bVar = alloc() + val sscanfResult = sscanf("42", "%d%d", aVar.ptr, bVar.ptr) + printf("%d %d\n", sscanfResult, aVar.value) + } +} \ No newline at end of file diff --git a/dependencies/build.gradle b/dependencies/build.gradle index e0d18c5afe3..475e7f7abbd 100644 --- a/dependencies/build.gradle +++ b/dependencies/build.gradle @@ -70,7 +70,7 @@ class TgzNativeDep extends NativeDep { } -task libffi(type: TgzNativeDep) { +task hostLibffi(type: TgzNativeDep) { baseName = "libffi-3.2.1-2-$host" } @@ -85,6 +85,9 @@ if (isLinux()) { task raspberryPiSysroot(type: TgzNativeDep) { baseName = "target-sysroot-1-raspberrypi" } + task raspberrypiLibffi(type: TgzNativeDep) { + baseName = "libffi-3.2.1-2-raspberrypi" + } } else { task hostSysroot(type: TgzNativeDep) { baseName = "target-sysroot-1-$host" @@ -92,6 +95,9 @@ if (isLinux()) { task iphoneSysroot(type: TgzNativeDep) { baseName = "target-sysroot-2-darwin-ios" } + task iphoneLibffi(type: TgzNativeDep) { + baseName = "libffi-3.2.1-2-darwin-ios" + } // TODO: re-enable when we known how to bring the simulator // sysroot to dependencies. // task iphoneSimSysroot(type: TgzNativeDep) { diff --git a/runtime/build.gradle b/runtime/build.gradle index 37647e082b3..9167c1b51ac 100644 --- a/runtime/build.gradle +++ b/runtime/build.gradle @@ -11,6 +11,7 @@ targetList.each { targetName -> target targetName compilerArgs targetArgs[targetName] compilerArgs '-I' + project.file('../common/src/hash/headers') + compilerArgs '-I' + project.file(rootProject.ext.get("${targetName}LibffiDir") + "/include") linkerArgs project.file("../common/build/$targetName/hash.bc").path } } diff --git a/runtime/src/main/cpp/Interop.cpp b/runtime/src/main/cpp/Interop.cpp new file mode 100644 index 00000000000..37652e4332b --- /dev/null +++ b/runtime/src/main/cpp/Interop.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +namespace { + +typedef int FfiTypeKind; +// Also declared in Varargs.kt +const FfiTypeKind FFI_TYPE_KIND_VOID = 0; +const FfiTypeKind FFI_TYPE_KIND_SINT8 = 1; +const FfiTypeKind FFI_TYPE_KIND_SINT16 = 2; +const FfiTypeKind FFI_TYPE_KIND_SINT32 = 3; +const FfiTypeKind FFI_TYPE_KIND_SINT64 = 4; +const FfiTypeKind FFI_TYPE_KIND_FLOAT = 5; +const FfiTypeKind FFI_TYPE_KIND_DOUBLE = 6; +const FfiTypeKind FFI_TYPE_KIND_POINTER = 7; + +ffi_type* convertFfiTypeKindToType(FfiTypeKind typeKind) { + switch (typeKind) { + case FFI_TYPE_KIND_VOID: return &ffi_type_void; + case FFI_TYPE_KIND_SINT8: return &ffi_type_sint8; + case FFI_TYPE_KIND_SINT16: return &ffi_type_sint16; + case FFI_TYPE_KIND_SINT32: return &ffi_type_sint32; + case FFI_TYPE_KIND_SINT64: return &ffi_type_sint64; + case FFI_TYPE_KIND_FLOAT: return &ffi_type_float; + case FFI_TYPE_KIND_DOUBLE: return &ffi_type_double; + case FFI_TYPE_KIND_POINTER: return &ffi_type_pointer; + + default: assert(false); + } +} + +} // namespace + +extern "C" { + +void callWithVarargs(void* codePtr, void* returnValuePtr, FfiTypeKind returnTypeKind, + void** arguments, intptr_t* argumentTypeKinds, + int fixedArgumentsNumber, int totalArgumentsNumber) { + + + ffi_type** argumentTypes = (ffi_type**)argumentTypeKinds; + // In-place convertion: + for (int i = 0; i < totalArgumentsNumber; ++i) { + argumentTypes[i] = convertFfiTypeKindToType((FfiTypeKind) argumentTypeKinds[i]); + } + + ffi_cif cif; + ffi_prep_cif_var(&cif, FFI_DEFAULT_ABI, + fixedArgumentsNumber, totalArgumentsNumber, + convertFfiTypeKindToType(returnTypeKind), argumentTypes); + + ffi_call(&cif, (void (*)())codePtr, returnValuePtr, arguments); +} + +}