From 39e31d7086f6ddf64555d283d130f65437a17270 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 18 Apr 2019 09:55:46 +0300 Subject: [PATCH] Support *arrayOf(...) for variadic C/Objective-C functions To be used with named arguments, which may be required to resolve ambiguity. --- .../kotlin/backend/konan/cgen/CBridgeGen.kt | 100 +++++++++++++++--- .../backend/konan/lower/InteropLowering.kt | 4 + backend.native/tests/interop/objc/smoke.kt | 16 +++ 3 files changed, 108 insertions(+), 12 deletions(-) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt index 1a9f0c5c1ac..7145b6e5ec1 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt @@ -53,6 +53,7 @@ internal interface KotlinStubs { fun getUniqueKotlinFunctionReferenceClassName(prefix: String): String fun reportError(location: IrElement, message: String): Nothing + fun throwCompilerError(element: IrElement?, message: String): Nothing } private class KotlinToCCallBuilder( @@ -173,18 +174,7 @@ private fun KotlinToCCallBuilder.addArguments(arguments: List, ca val parameter = callee.valueParameters[index] if (parameter.isVararg) { require(index == arguments.lastIndex) - require(argument is IrVararg?) - argument?.elements.orEmpty().forEach { - when (it) { - is IrExpression -> addArgument(it, it.type, variadic = true, parameter = null) - - is IrSpreadElement -> - stubs.reportError(it, "spread operator is not supported for variadic " + - if (isObjCMethod) "Objective-C methods" else "C functions") - - else -> error(it) - } - } + addVariadicArguments(argument) cFunctionBuilder.variadic = true } else { addArgument(argument!!, parameter.type, variadic = false, parameter = parameter) @@ -192,6 +182,92 @@ private fun KotlinToCCallBuilder.addArguments(arguments: List, ca } } +private fun KotlinToCCallBuilder.addVariadicArguments( + argumentForVarargParameter: IrExpression? +) = handleArgumentForVarargParameter(argumentForVarargParameter) { variable, elements -> + if (variable == null) { + unwrapVariadicArguments(elements).forEach { + addArgument(it, it.type, variadic = true, parameter = null) + } + } else { + // See comment in [handleArgumentForVarargParameter]. + // Array for this vararg parameter is already computed before the call, + // so query statically known typed arguments from this array. + + with(irBuilder) { + val argumentTypes = unwrapVariadicArguments(elements).map { it.type } + argumentTypes.forEachIndexed { index, type -> + val untypedArgument = irCall(symbols.arrayGet[symbols.array]!!.owner).apply { + dispatchReceiver = irGet(variable) + putValueArgument(0, irInt(index)) + } + val argument = irAs(untypedArgument, type) // Note: this cast always succeeds. + addArgument(argument, type, variadic = true, parameter = null) + } + } + } +} + +private fun KotlinToCCallBuilder.unwrapVariadicArguments( + elements: List +): List = elements.flatMap { + when (it) { + is IrExpression -> listOf(it) + is IrSpreadElement -> { + val expression = it.expression + if (expression is IrCall && expression.symbol == symbols.arrayOf) { + handleArgumentForVarargParameter(expression.getValueArgument(0)) { _, elements -> + unwrapVariadicArguments(elements) + } + } else { + stubs.reportError(it, "When calling variadic " + + if (isObjCMethod) "Objective-C methods" else "C functions " + + "spread operator is supported only for *arrayOf(...)") + } + } + else -> stubs.throwCompilerError(it, "unexpected IrVarargElement") + } +} + +private fun KotlinToCCallBuilder.handleArgumentForVarargParameter( + argument: IrExpression?, + block: (variable: IrVariable?, elements: List) -> R +): R = when (argument) { + + null -> block(null, emptyList()) + + is IrVararg -> block(null, argument.elements) + + is IrGetValue -> { + /* This is possible when using named arguments with reordering, i.e. + * + * foo(second = *arrayOf(...), first = ...) + * + * psi2ir generates as + * + * val secondTmp = *arrayOf(...) + * val firstTmp = ... + * foo(firstTmp, secondTmp) + * + * + **/ + + val variable = argument.symbol.owner + if (variable is IrVariable && variable.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE && !variable.isVar) { + val initializer = variable.initializer + if (initializer is IrVararg) { + block(variable, initializer.elements) + } else { + stubs.throwCompilerError(initializer, "unexpected initializer") + } + } else { + stubs.throwCompilerError(variable, "unexpected value declaration") + } + } + + else -> stubs.throwCompilerError(argument, "unexpected vararg") +} + private fun KotlinToCCallBuilder.emitCBridge() { val cLines = mutableListOf() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index 8c3b2873823..26bd4d43e1b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -85,6 +85,10 @@ internal abstract class BaseInteropIrTransformer(private val context: Context) : override fun reportError(location: IrElement, message: String): Nothing = context.reportCompilationError(message, irFile, location) + + override fun throwCompilerError(element: IrElement?, message: String): Nothing { + error(irFile, element, message) + } } } diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt index 14689136e6a..f6b3f5db303 100644 --- a/backend.native/tests/interop/objc/smoke.kt +++ b/backend.native/tests/interop/objc/smoke.kt @@ -281,6 +281,22 @@ fun testVarargs() { 0.1.toFloat(), 0.2, true, false ).formatted ) + + assertEquals( + "1 2 3", + TestVarargs( + format = "%d %d %d", + args = *arrayOf(1, 2, 3) + ).formatted + ) + + assertEquals( + "4 5 6", + TestVarargs( + args = *arrayOf(4, *arrayOf(5, 6)), + format = "%d %d %d" + ).formatted + ) } private class MyException : Throwable()