diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index a0461f9977e..67ebc05209e 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -39,5 +39,8 @@ internal class KonanLower(val context: Context) { phaser.phase(KonanPhase.AUTOBOX) { Autoboxing(context).lower(irFile) } + phaser.phase(KonanPhase.LOWER_INLINE) { + FunctionInlining(context).inline(irFile) + } } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index 017b5547e25..2443f321cb6 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -16,6 +16,7 @@ enum class KonanPhase(val description: String, /* ... ... */ LOWER_SHARED_VARIABLES("Shared Variable Lowering"), /* ... ... */ LOWER_LOCAL_FUNCTIONS("Local Function Lowering"), /* ... ... */ LOWER_CALLABLES("Callable references Lowering"), + /* ... ... */ LOWER_INLINE("Functions inlining"), /* ... ... */ AUTOBOX("Autoboxing of primitive types"), /* ... */ BITCODE("LLVM BitCode Generation"), /* ... ... */ RTTI("RTTI Generation"), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/KonanIr.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/KonanIr.kt new file mode 100644 index 00000000000..f711be1b9e2 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/KonanIr.kt @@ -0,0 +1,37 @@ +package org.jetbrains.kotlin.backend.konan.ir + +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.expressions.IrBlock +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin +import org.jetbrains.kotlin.ir.expressions.impl.IrContainerExpressionBase +import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBase +import org.jetbrains.kotlin.ir.visitors.IrElementTransformer +import org.jetbrains.kotlin.ir.visitors.IrElementVisitor +import org.jetbrains.kotlin.types.KotlinType + +//-----------------------------------------------------------------------------// + +class IrInlineFunctionBody(startOffset: Int, endOffset: Int, type: KotlinType, origin: IrStatementOrigin? = null): + IrContainerExpressionBase(startOffset, endOffset, type, origin), IrBlock { + constructor(startOffset: Int, endOffset: Int, type: KotlinType, origin: IrStatementOrigin?, statements: List) : + this(startOffset, endOffset, type, origin) { + this.statements.addAll(statements) + } + + override fun accept(visitor: IrElementVisitor, data: D): R = + visitor.visitBlock(this, data) + + override fun acceptChildren(visitor: IrElementVisitor, data: D) { + statements.forEach { it.accept(visitor, data) } + } + + override fun transformChildren(transformer: IrElementTransformer, data: D) { + statements.forEachIndexed { i, irStatement -> + statements[i] = irStatement.transform(transformer, data) + } + } +} + +//-----------------------------------------------------------------------------// + diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/ModuleIndex.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/ModuleIndex.kt index 4cd7f713cd1..21fb20af825 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/ModuleIndex.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/ModuleIndex.kt @@ -1,7 +1,9 @@ package org.jetbrains.kotlin.backend.konan.ir +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid @@ -16,6 +18,11 @@ class ModuleIndex(val module: IrModuleFragment) { */ val classes: Map + /** + * Contains all functions declared in [module] + */ + val functions = mutableMapOf() + init { val map = mutableMapOf() @@ -33,6 +40,13 @@ class ModuleIndex(val module: IrModuleFragment) { } } + override fun visitFunction(declaration: IrFunction) { + super.visitFunction(declaration) + + val functionDescriptor = declaration.descriptor + functions[functionDescriptor] = declaration + } + }) classes = map diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 2e7f3275c1c..bd8d8d19a45 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -629,6 +629,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid is IrThrow -> return evaluateThrow (value) is IrTry -> return evaluateTry (value) is IrStringConcatenation -> return evaluateStringConcatenation(value) + is IrInlineFunctionBody -> return evaluateInlineFunction (value) is IrContainerExpression -> return evaluateContainerExpression(value) is IrWhileLoop -> return evaluateWhileLoop (value) is IrDoWhileLoop -> return evaluateDoWhileLoop (value) @@ -741,7 +742,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid */ private fun continuationBlock(type: KotlinType, code: (ContinuationBlock) -> Unit = {}): ContinuationBlock { - val entry = codegen.basicBlock() + val entry = codegen.basicBlock("continuation_block") codegen.appendingTo(entry) { val valuePhi = if (type.isUnit()) { @@ -1469,6 +1470,65 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// + private inner class InlinedFunctionScope(val inlineBody: IrInlineFunctionBody) : InnerScopeImpl() { + + var bbExit : LLVMBasicBlockRef? = null + var resultPhi : LLVMValueRef? = null + + fun getExit(): LLVMBasicBlockRef? { + if (bbExit == null) bbExit = codegen.basicBlock("inline_body_exit") + return bbExit + } + + fun getResult(): LLVMValueRef? { + if (resultPhi == null) { + val bbCurrent = codegen.currentBlock + codegen.positionAtEnd(getExit()!!) + resultPhi = codegen.phi(codegen.getLLVMType(inlineBody.type)) + codegen.positionAtEnd(bbCurrent) + } + return resultPhi + } + + override fun genReturn(target: CallableDescriptor, value: LLVMValueRef?) { + if (KotlinBuiltIns.isUnit(inlineBody.type) == false) { + codegen.assignPhis(getResult()!! to value!!) + } + codegen.br(getExit()!!) + } + } + + //-------------------------------------------------------------------------// + + private fun evaluateInlineFunction(value: IrInlineFunctionBody): LLVMValueRef { + context.log("evaluateInlineFunction : ${value.statements.forEach { ir2string(it) }}") + + val inlinedFunctionScope = InlinedFunctionScope(value) + using(inlinedFunctionScope) { + value.statements.forEach { + generateStatement(it) + } + } + + if (!codegen.isAfterTerminator()) { // TODO should we solve this problem once and for all + if (inlinedFunctionScope.resultPhi != null) { + codegen.unreachable() + } + } + + if (inlinedFunctionScope.bbExit != null) { + codegen.positionAtEnd(inlinedFunctionScope.bbExit!!) + } + + if (inlinedFunctionScope.resultPhi != null) { + return inlinedFunctionScope.resultPhi!! + } else { + return codegen.theUnitInstanceRef.llvm + } + } + + //-------------------------------------------------------------------------// + private fun evaluateContainerExpression(value: IrContainerExpression): LLVMValueRef { context.log("evaluateContainerExpression : ${value.statements.forEach { ir2string(it) }}") diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionInlining.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionInlining.kt new file mode 100644 index 00000000000..4e0d9f7433e --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/FunctionInlining.kt @@ -0,0 +1,66 @@ +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.ir.IrInlineFunctionBody +import org.jetbrains.kotlin.backend.konan.ir.getArguments +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ParameterDescriptor +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.util.DeepCopyIrTree +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid + +//-----------------------------------------------------------------------------// + +internal class FunctionInlining(val context: Context): IrElementTransformerVoid() { + + fun inline(irFile: IrFile) = irFile.accept(this, null) + + //-------------------------------------------------------------------------// + + override fun visitCall(expression: IrCall): IrExpression { + val functionDescriptor = expression.descriptor as FunctionDescriptor + if (!functionDescriptor.isInline) return super.visitCall(expression) // Function is not to be inlined - do nothing. + + val functionDeclaration = context.ir.moduleIndex.functions[functionDescriptor] // Get FunctionDeclaration by FunctionDescriptor. + if (functionDeclaration == null) return super.visitCall(expression) // Function is declared in another module. + val copyFuncDeclaration = functionDeclaration.accept(DeepCopyIrTree(), null) as IrFunction // Create copy of the function. + + val body = copyFuncDeclaration.body!! as IrBlockBody + val startOffset = copyFuncDeclaration.startOffset + val endOffset = copyFuncDeclaration.endOffset + val returnType = copyFuncDeclaration.descriptor.returnType!! + val statements = body.statements + val irBlock = IrInlineFunctionBody(startOffset, endOffset, returnType, null, statements) + + val parameterToExpression = expression.getArguments() // Build map parameter -> expression. + val parametersTransformer = ParametersTransformer(parameterToExpression) + irBlock.accept(parametersTransformer, null) // Replace parameters with expression. + + return irBlock // Return newly created IrBlock instead of IrCall. + } + + //-------------------------------------------------------------------------// + + override fun visitElement(element: IrElement) = element.accept(this, null) +} + +//-----------------------------------------------------------------------------// + +internal class ParametersTransformer(val parameterToExpression: List >): IrElementTransformerVoid() { + + override fun visitElement(element: IrElement) = element.accept(this, null) + + //-------------------------------------------------------------------------// + + override fun visitGetValue(expression: IrGetValue): IrExpression { + val descriptor = expression.descriptor + if (descriptor !is ParameterDescriptor) { // TODO do we need this check? + return super.visitGetValue(expression) + } + val parExp = parameterToExpression.find { it.first == descriptor } // Find expression to replace this parameter. + return parExp?.let { parExp.second } ?: super.visitGetValue(expression) // TODO should we proceed with IR iteration here? + } +} diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 8a017254d61..55147a82d03 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -884,3 +884,23 @@ task unit4(type: RunKonanTest) { goldValue = "Done\n" source = "codegen/basics/unit4.kt" } + +task inline0(type: RunKonanTest) { + goldValue = "84\n" + source = "codegen/inline/inline0.kt" +} + +task inline1(type: RunKonanTest) { + goldValue = "Hello world\n" + source = "codegen/inline/inline1.kt" +} + +task inline2(type: RunKonanTest) { + goldValue = "hello 1 8\n" + source = "codegen/inline/inline2.kt" +} + +task inline3(type: RunKonanTest) { + goldValue = "5\n" + source = "codegen/inline/inline3.kt" +} diff --git a/backend.native/tests/codegen/inline/inline0.kt b/backend.native/tests/codegen/inline/inline0.kt new file mode 100644 index 00000000000..c1284b1a0d4 --- /dev/null +++ b/backend.native/tests/codegen/inline/inline0.kt @@ -0,0 +1,13 @@ +@Suppress("NOTHING_TO_INLINE") +inline fun foo(i1: Int, j1: Int): Int { + return i1 + j1 +} + +fun bar(i: Int, j: Int): Int { + return i + foo(i, j) +} + +fun main(args: Array) { + println(bar(41, 2).toString()) +} + diff --git a/backend.native/tests/codegen/inline/inline1.kt b/backend.native/tests/codegen/inline/inline1.kt new file mode 100644 index 00000000000..36b23aee79d --- /dev/null +++ b/backend.native/tests/codegen/inline/inline1.kt @@ -0,0 +1,14 @@ +@Suppress("NOTHING_TO_INLINE") +inline fun foo(s4: String, s5: String): String { + return s4 + s5 +} + +fun bar(s1: String, s2: String, s3: String): String { + return s1 + foo(s2, s3) +} + +fun main(args: Array) { + println(bar("Hello ", "wor", "ld")) +} + + diff --git a/backend.native/tests/codegen/inline/inline2.kt b/backend.native/tests/codegen/inline/inline2.kt new file mode 100644 index 00000000000..8ab7e4218fd --- /dev/null +++ b/backend.native/tests/codegen/inline/inline2.kt @@ -0,0 +1,12 @@ +@Suppress("NOTHING_TO_INLINE") +inline fun foo(i4: Int, i5: Int) { + println("hello $i4 $i5") +} + +fun bar(i1: Int, i2: Int) { + foo(i1, i2) +} + +fun main(args: Array) { + bar(1, 8) +} diff --git a/backend.native/tests/codegen/inline/inline3.kt b/backend.native/tests/codegen/inline/inline3.kt new file mode 100644 index 00000000000..8b0edf296c0 --- /dev/null +++ b/backend.native/tests/codegen/inline/inline3.kt @@ -0,0 +1,16 @@ +@Suppress("NOTHING_TO_INLINE") +inline fun foo(i4: Int, i5: Int): Int { + try { + return i4 / i5 + } catch (e: Throwable) { + return i4 + } +} + +fun bar(i1: Int, i2: Int, i3: Int): Int { + return i1 + foo(i2, i3) +} + +fun main(args: Array) { + println(bar(1, 8, 2).toString()) +} diff --git a/backend.native/tests/codegen/inline/inline4.kt b/backend.native/tests/codegen/inline/inline4.kt new file mode 100644 index 00000000000..04f6a475291 --- /dev/null +++ b/backend.native/tests/codegen/inline/inline4.kt @@ -0,0 +1,13 @@ +@Suppress("NOTHING_TO_INLINE") +inline fun foo(i4: Int, i5: Int): Int { + if (i4 > 0) return i4 + return i5 +} + +fun bar(i1: Int, i2: Int): Int { + return foo(i1, i2) +} + +fun main(args: Array) { + println(bar(1, 8).toString()) +}