diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt index fb5e9e38e55..3356a2c4d02 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt @@ -263,7 +263,8 @@ abstract class InlineCodegen( node, parameters, info, FieldRemapper(null, null, parameters), isSameModule, "Method inlining " + sourceCompiler.callElementText, createNestedSourceMapper(nodeAndSmap, defaultSourceMapper), info.callSiteInfo, - if (functionDescriptor.isInlineOnly()) InlineOnlySmapSkipper(codegen) else null + if (functionDescriptor.isInlineOnly()) InlineOnlySmapSkipper(codegen) else null, + !isInlinedToInlineFunInKotlinRuntime() ) //with captured val remapper = LocalVarRemapper(parameters, initialFrameSize) @@ -297,6 +298,14 @@ abstract class InlineCodegen( return result } + private fun isInlinedToInlineFunInKotlinRuntime(): Boolean { + val codegen = this.codegen as? ExpressionCodegen ?: return false + val caller = codegen.context.functionDescriptor + if (!caller.isInline) return false + val callerPackage = DescriptorUtils.getParentOfType(caller, PackageFragmentDescriptor::class.java) ?: return false + return callerPackage.fqName.asString().startsWith("kotlin.") + } + private fun generateClosuresBodies() { for (info in expressionMap.values) { info.generateLambdaBody(sourceCompiler, reifiedTypeInliner) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt index bb9d73c2219..cd08ab3a662 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.ClosureCodegen import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods +import org.jetbrains.kotlin.codegen.optimization.ApiVersionCallsPreprocessingMethodTransformer import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful @@ -46,7 +47,8 @@ class MethodInliner( private val errorPrefix: String, private val sourceMapper: SourceMapper, private val inlineCallSiteInfo: InlineCallSiteInfo, - private val inlineOnlySmapSkipper: InlineOnlySmapSkipper? //non null only for root + private val inlineOnlySmapSkipper: InlineOnlySmapSkipper?, //non null only for root + private val shouldPreprocessApiVersionCalls: Boolean = false ) { private val typeMapper = inliningContext.state.typeMapper private val invokeCalls = ArrayList() @@ -394,7 +396,7 @@ class MethodInliner( ): MethodNode { val processingNode = prepareNode(node, finallyDeepShift) - normalizeNodeBeforeInline(processingNode, labelOwner) + preprocessNodeBeforeInline(processingNode, labelOwner) val sources = analyzeMethodNodeBeforeInline(processingNode) @@ -508,7 +510,7 @@ class MethodInliner( return processingNode } - private fun normalizeNodeBeforeInline(node: MethodNode, labelOwner: LabelOwner) { + private fun preprocessNodeBeforeInline(node: MethodNode, labelOwner: LabelOwner) { try { FixStackWithLabelNormalizationMethodTransformer().transform("fake", node) } @@ -516,6 +518,11 @@ class MethodInliner( throw wrapException(e, node, "couldn't inline method call") } + if (shouldPreprocessApiVersionCalls) { + val targetApiVersion = inliningContext.state.languageVersionSettings.apiVersion + ApiVersionCallsPreprocessingMethodTransformer(targetApiVersion).transform("fake", node) + } + val frames = analyzeMethodNodeBeforeInline(node) val localReturnsNormalizer = LocalReturnsNormalizer() diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/ApiVersionCallsPreprocessingMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/ApiVersionCallsPreprocessingMethodTransformer.kt new file mode 100644 index 00000000000..35fda40a4ce --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/ApiVersionCallsPreprocessingMethodTransformer.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen.optimization + +import org.jetbrains.kotlin.codegen.optimization.boxing.isMethodInsnWith +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.config.ApiVersion +import org.jetbrains.kotlin.config.MavenComparableVersion +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.* + +class ApiVersionCallsPreprocessingMethodTransformer(private val targetApiVersion: ApiVersion) : MethodTransformer() { + private val constantConditionElimination = ConstantConditionEliminationMethodTransformer() + + override fun transform(internalClassName: String, methodNode: MethodNode) { + for (insn in methodNode.instructions.toArray()) { + if (!insn.isApiVersionIsAtLeastCall()) continue + + val prev3 = insn.previous ?: continue + val minor = prev3.getIntConstValue() ?: continue + + val prev2 = prev3.previous ?: continue + val major = prev2.getIntConstValue() ?: continue + + val prev1 = prev2.previous ?: continue + val epic = prev1.getIntConstValue() ?: continue + + val atLeastVersion = MavenComparableVersion("$epic.$major.$minor") + + val replacementInsn = + if (targetApiVersion.version >= atLeastVersion) + InsnNode(Opcodes.ICONST_1) + else + InsnNode(Opcodes.ICONST_0) + + methodNode.instructions.run { + remove(prev1) + remove(prev2) + remove(prev3) + set(insn, replacementInsn) + } + } + + constantConditionElimination.transform(internalClassName, methodNode) + } + + private fun AbstractInsnNode.isApiVersionIsAtLeastCall(): Boolean = + isMethodInsnWith(Opcodes.INVOKESTATIC) { + owner.startsWith("kotlin/internal") && + name == "apiVersionIsAtLeast" && + desc == "(III)Z" + } + + private fun AbstractInsnNode.getIntConstValue(): Int? = + when (this) { + is InsnNode -> + if (opcode in Opcodes.ICONST_M1..Opcodes.ICONST_5) + opcode - Opcodes.ICONST_0 + else + null + + is IntInsnNode -> + when (opcode) { + Opcodes.BIPUSH -> operand + Opcodes.SIPUSH -> operand + else -> null + } + + is LdcInsnNode -> cst as? Int + + else -> null + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt b/compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt new file mode 100644 index 00000000000..b5364190bb1 --- /dev/null +++ b/compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt @@ -0,0 +1,41 @@ +// TARGET_BACKEND: JVM +// FILE: 1.kt +package kotlin.internal + +fun apiVersionIsAtLeast(epic: Int, major: Int, minor: Int): Boolean { + return false +} + +var properFunctionWasClled = false + +fun doSomethingNew() { + properFunctionWasClled = true +} + +fun doSomethingOld() { + throw AssertionError("Should not be called") +} + +inline fun versionDependentInlineFun() { + if (apiVersionIsAtLeast(1, 1, 0)) { + doSomethingNew() + } + else { + doSomethingOld() + } +} + +fun test() { + versionDependentInlineFun() +} + + +// FILE: 2.kt +import kotlin.internal.* + +fun box(): String { + test() + if (!properFunctionWasClled) return "Fail 1" + + return "OK" +} \ No newline at end of file diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java index b510fa962ef..1d844273d18 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java @@ -642,6 +642,21 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli } } + @TestMetadata("compiler/testData/codegen/boxInline/bytecodePreprocessing") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class BytecodePreprocessing extends AbstractIrBlackBoxInlineCodegenTest { + public void testAllFilesPresentInBytecodePreprocessing() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/bytecodePreprocessing"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("apiVersionAtLeast1.kt") + public void testApiVersionAtLeast1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/callableReference") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java index bbdd4282ca0..b256426ddee 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java @@ -642,6 +642,21 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC } } + @TestMetadata("compiler/testData/codegen/boxInline/bytecodePreprocessing") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class BytecodePreprocessing extends AbstractIrCompileKotlinAgainstInlineKotlinTest { + public void testAllFilesPresentInBytecodePreprocessing() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/bytecodePreprocessing"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("apiVersionAtLeast1.kt") + public void testApiVersionAtLeast1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/callableReference") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java index c8da2d68c1c..02354bf5181 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java @@ -642,6 +642,21 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo } } + @TestMetadata("compiler/testData/codegen/boxInline/bytecodePreprocessing") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class BytecodePreprocessing extends AbstractBlackBoxInlineCodegenTest { + public void testAllFilesPresentInBytecodePreprocessing() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/bytecodePreprocessing"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("apiVersionAtLeast1.kt") + public void testApiVersionAtLeast1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/callableReference") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java index 936a348dbcd..84df6dcffc2 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -642,6 +642,21 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi } } + @TestMetadata("compiler/testData/codegen/boxInline/bytecodePreprocessing") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class BytecodePreprocessing extends AbstractCompileKotlinAgainstInlineKotlinTest { + public void testAllFilesPresentInBytecodePreprocessing() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/bytecodePreprocessing"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("apiVersionAtLeast1.kt") + public void testApiVersionAtLeast1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/callableReference") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)