diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/ChangeBoxingMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/ChangeBoxingMethodTransformer.kt new file mode 100644 index 00000000000..9da5da71a8e --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/ChangeBoxingMethodTransformer.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.codegen.coroutines + +import org.jetbrains.kotlin.codegen.optimization.boxing.isPrimitiveBoxing +import org.jetbrains.kotlin.codegen.optimization.common.asSequence +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.codegen.topLevelClassInternalName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType +import org.jetbrains.kotlin.utils.sure +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode +import org.jetbrains.org.objectweb.asm.tree.MethodNode + +private val BOXING_CLASS_INTERNAL_NAME = + RELEASE_COROUTINES_VERSION_SETTINGS.coroutinesJvmInternalPackageFqName().child(Name.identifier("Boxing")).topLevelClassInternalName() + +object ChangeBoxingMethodTransformer : MethodTransformer() { + private val wrapperToInternalBoxing: Map + + init { + val map = hashMapOf() + for (primitiveType in JvmPrimitiveType.values()) { + val name = primitiveType.wrapperFqName.topLevelClassInternalName() + map[name] = "box${primitiveType.javaKeywordName.capitalize()}" + } + wrapperToInternalBoxing = map + } + + override fun transform(internalClassName: String, methodNode: MethodNode) { + for (boxing in methodNode.instructions.asSequence().filter { it.isPrimitiveBoxing() }) { + assert(boxing.opcode == Opcodes.INVOKESTATIC) { + "boxing shall be INVOKESTATIC wrapper.valueOf" + } + boxing as MethodInsnNode + val methodName = wrapperToInternalBoxing[boxing.owner].sure { + "expected primitive wrapper, but got ${boxing.owner}" + } + methodNode.instructions.set( + boxing, + MethodInsnNode(boxing.opcode, BOXING_CLASS_INTERNAL_NAME, methodName, boxing.desc, false) + ) + } + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt index 319f12a1706..d7a5caae9da 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt @@ -83,6 +83,9 @@ class CoroutineTransformerMethodVisitor( FixStackMethodTransformer().transform(containingClassInternalName, methodNode) RedundantLocalsEliminationMethodTransformer(languageVersionSettings).transform(containingClassInternalName, methodNode) + if (languageVersionSettings.isReleaseCoroutines()) { + ChangeBoxingMethodTransformer.transform(containingClassInternalName, methodNode) + } updateMaxStack(methodNode) val suspensionPoints = collectSuspensionPoints(methodNode) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt index 71ce0ec3d64..67de127d838 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt @@ -56,7 +56,7 @@ const val DO_RESUME_METHOD_NAME = "doResume" const val INVOKE_SUSPEND_METHOD_NAME = "invokeSuspend" const val EXCEPTION_FIELD_NAME = "exception" -private val RELEASE_COROUTINES_VERSION_SETTINGS = LanguageVersionSettingsImpl(LanguageVersion.KOTLIN_1_3, ApiVersion.KOTLIN_1_3) +val RELEASE_COROUTINES_VERSION_SETTINGS = LanguageVersionSettingsImpl(LanguageVersion.KOTLIN_1_3, ApiVersion.KOTLIN_1_3) fun LanguageVersionSettings.isResumeImplMethodName(name: String) = if (isReleaseCoroutines()) diff --git a/compiler/testData/codegen/bytecodeText/boxing/crossinlineSuspend.kt b/compiler/testData/codegen/bytecodeText/boxing/crossinlineSuspend.kt new file mode 100644 index 00000000000..9e80d863a3f --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/boxing/crossinlineSuspend.kt @@ -0,0 +1,9 @@ +// !LANGUAGE: +ReleaseCoroutines + +inline fun inlineMe(crossinline c: suspend () -> Int): suspend () -> Int { + val i: suspend () -> Int = { c() + c() } + return i +} + +// 1 valueOf +// 0 boxInt diff --git a/compiler/testData/codegen/bytecodeText/boxing/inlineSuspend.kt b/compiler/testData/codegen/bytecodeText/boxing/inlineSuspend.kt new file mode 100644 index 00000000000..67750bc9323 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/boxing/inlineSuspend.kt @@ -0,0 +1,9 @@ +// !LANGUAGE: +ReleaseCoroutines + +inline suspend fun inlineMe() = 1000 + +// inlineMe$$forInline : valueOf +// inlineMe : boxInt + +// 1 valueOf +// 1 boxInt diff --git a/compiler/testData/codegen/bytecodeText/boxing/suspend.kt b/compiler/testData/codegen/bytecodeText/boxing/suspend.kt new file mode 100644 index 00000000000..6868f7da3da --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/boxing/suspend.kt @@ -0,0 +1,6 @@ +// !LANGUAGE: +ReleaseCoroutines + +suspend fun produce(): Int = 1000 + +// 0 valueOf +// 1 boxInt diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 3347b3690e8..996a5289270 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -397,6 +397,34 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { } } + @TestMetadata("compiler/testData/codegen/bytecodeText/boxing") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Boxing extends AbstractBytecodeTextTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInBoxing() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/boxing"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("crossinlineSuspend.kt") + public void testCrossinlineSuspend() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxing/crossinlineSuspend.kt"); + } + + @TestMetadata("inlineSuspend.kt") + public void testInlineSuspend() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxing/inlineSuspend.kt"); + } + + @TestMetadata("suspend.kt") + public void testSuspend() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/boxing/suspend.kt"); + } + } + @TestMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)