diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt index 8f14317939e..22f3d3761cb 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -116,6 +116,17 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F } } + private fun createBridgeBody(source: IrSimpleFunction, target: IrSimpleFunction) { + source.body = context.createIrBuilder(source.symbol, source.startOffset, source.endOffset).run { + irExprBody(irCall(target).apply { + passTypeArgumentsFrom(source) + for ((parameter, newParameter) in source.explicitParameters.zip(target.explicitParameters)) { + putArgument(newParameter, irGet(parameter)) + } + }) + } + } + private fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List { replacement.valueParameters.forEach { it.transformChildrenVoid() } replacement.body = function.body?.transform(this, null)?.patchDeclarationParents(replacement) @@ -125,16 +136,15 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F if (function.overriddenSymbols.isEmpty() || replacement.dispatchReceiverParameter != null) return listOf(replacement) - // Replace the function body with a wrapper - context.createIrBuilder(function.symbol, function.startOffset, function.endOffset).run { - val call = irCall(replacement).apply { - passTypeArgumentsFrom(function) - for ((parameter, newParameter) in function.explicitParameters.zip(replacement.explicitParameters)) { - putArgument(newParameter, irGet(parameter)) - } - } + // Update the overridden symbols to point to their inline class replacements + function.overriddenSymbols = replacement.overriddenSymbols - function.body = irExprBody(call) + // Replace the function body with a wrapper + if (!function.isFakeOverride || !function.parentAsClass.isInline) { + createBridgeBody(function, replacement) + } else { + // Fake overrides redirect from the replacement to the original function, which is in turn replaced during interfacePhase. + createBridgeBody(replacement, function) } return listOf(replacement, function) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/InlineClassAbi.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/InlineClassAbi.kt index 8a5df0da386..b9b9310de60 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/InlineClassAbi.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/InlineClassAbi.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -101,7 +101,7 @@ private val IrFunction.fullValueParameterList: List get() = listOfNotNull(extensionReceiverParameter) + valueParameters internal val IrFunction.hasMangledParameters: Boolean - get() = dispatchReceiverParameter?.type?.getClass()?.isInline == true || + get() = dispatchReceiverParameter != null && parentAsClass.isInline || fullValueParameterList.any { it.type.requiresMangling } || (this is IrConstructor && constructedClass.isInline) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/MemoizedInlineClassReplacements.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/MemoizedInlineClassReplacements.kt index 28c60f7dfcb..ffa1156d589 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/MemoizedInlineClassReplacements.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/inlineclasses/MemoizedInlineClassReplacements.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -39,27 +39,26 @@ class MemoizedInlineClassReplacements { val getReplacementFunction: (IrFunction) -> IrSimpleFunction? = storageManager.createMemoizedFunctionWithNullableValues { when { - !it.hasMangledParameters || - it.isSyntheticInlineClassMember || - it.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA || + // Don't mangle anonymous or synthetic functions + it.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA || it.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR || - it.origin.isSynthetic -> null - it.hasMethodReplacement -> createMethodReplacement(it) - it.hasStaticReplacement -> createStaticReplacement(it) - else -> null + it.origin == JvmLoweredDeclarationOrigin.STATIC_INLINE_CLASS_REPLACEMENT || + it.origin.isSynthetic || + it.isInlineClassFieldGetter -> null + + // Mangle all functions in the body of an inline class + it.parent.safeAs()?.isInline == true -> + createStaticReplacement(it) + + // Otherwise, mangle functions with mangled parameters, while ignoring constructors + it is IrSimpleFunction && it.hasMangledParameters -> + if (it.dispatchReceiverParameter != null) createMethodReplacement(it) else createStaticReplacement(it) + + else -> + null } } - private val IrFunction.hasStaticReplacement: Boolean - get() = origin != IrDeclarationOrigin.FAKE_OVERRIDE && - (this is IrSimpleFunction || this is IrConstructor && constructedClass.isInline) - - private val IrFunction.hasMethodReplacement: Boolean - get() = dispatchReceiverParameter != null && this is IrSimpleFunction && (parent as? IrClass)?.isInline != true - - private val IrFunction.isSyntheticInlineClassMember: Boolean - get() = origin == JvmLoweredDeclarationOrigin.SYNTHETIC_INLINE_CLASS_MEMBER || isInlineClassFieldGetter - /** * Get the box function for an inline class. Concretely, this is a synthetic * static function named "box-impl" which takes an unboxed value and returns @@ -150,20 +149,24 @@ class MemoizedInlineClassReplacements { buildReplacement(function, JvmLoweredDeclarationOrigin.STATIC_INLINE_CLASS_REPLACEMENT) { val newValueParameters = ArrayList() for ((index, parameter) in function.explicitParameters.withIndex()) { - val name = when (parameter) { - function.dispatchReceiverParameter -> Name.identifier("arg$index") - function.extensionReceiverParameter -> Name.identifier("\$this\$${function.name}") - else -> parameter.name + newValueParameters += when (parameter) { + // FAKE_OVERRIDEs have broken dispatch receivers + function.dispatchReceiverParameter -> + function.parentAsClass.thisReceiver!!.copyTo( + this, index = index, name = Name.identifier("arg$index"), + type = function.parentAsClass.defaultType, origin = IrDeclarationOrigin.MOVED_DISPATCH_RECEIVER + ) + function.extensionReceiverParameter -> + parameter.copyTo( + this, index = index, name = Name.identifier("\$this\$${function.name}"), + origin = IrDeclarationOrigin.MOVED_EXTENSION_RECEIVER + ) + else -> + parameter.copyTo(this, index = index, defaultValue = null).also { + // See comment next to a similar line above. + it.defaultValue = parameter.defaultValue?.patchDeclarationParents(this) + } } - val parameterOrigin = when (parameter) { - function.dispatchReceiverParameter -> IrDeclarationOrigin.MOVED_DISPATCH_RECEIVER - function.extensionReceiverParameter -> IrDeclarationOrigin.MOVED_EXTENSION_RECEIVER - else -> parameter.origin - } - val newParameter = parameter.copyTo(this, index = index, name = name, defaultValue = null, origin = parameterOrigin) - newValueParameters += newParameter - // See comment next to a similar line above. - newParameter.defaultValue = parameter.defaultValue?.patchDeclarationParents(this) } valueParameters = newValueParameters } diff --git a/compiler/testData/codegen/bytecodeText/inlineClasses/interfaceDefaultImplStubs.kt b/compiler/testData/codegen/bytecodeText/inlineClasses/interfaceDefaultImplStubs.kt new file mode 100644 index 00000000000..9af9e905a0f --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inlineClasses/interfaceDefaultImplStubs.kt @@ -0,0 +1,29 @@ +// !LANGUAGE: +InlineClasses +// FILE: test.kt + +fun box(): String { + val b = B(0) + return b.f() + b.g() +} + +interface A { + fun f() = "O" + fun g() = "K" +} + +inline class B(val x: Int) : A + +// 1 public static f-impl\(I\)Ljava/lang/String; +// 1 public f\(\)Ljava/lang/String; + +// 1 public static g-impl\(I\)Ljava/lang/String; +// 1 public g\(\)Ljava/lang/String; + +// JVM_TEMPLATES: +// The JVM backend calls f-impl, g-impl from f, g, respectively, in addition to the call from box. +// 2 INVOKESTATIC B.g-impl \(I\)Ljava/lang/String; +// 2 INVOKESTATIC B.f-impl \(I\)Ljava/lang/String; + +// JVM_IR_TEMPLATES: +// 1 INVOKESTATIC B.g-impl \(I\)Ljava/lang/String; +// 1 INVOKESTATIC B.f-impl \(I\)Ljava/lang/String; diff --git a/compiler/testData/codegen/bytecodeText/inlineClasses/interfaceJvmDefaultImplStubs.kt b/compiler/testData/codegen/bytecodeText/inlineClasses/interfaceJvmDefaultImplStubs.kt new file mode 100644 index 00000000000..d2bb7535524 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inlineClasses/interfaceJvmDefaultImplStubs.kt @@ -0,0 +1,36 @@ +// !LANGUAGE: +InlineClasses + +// IGNORE_BACKEND: JVM +// The JVM backend does not generate the g-impl method, but ends up calling it from box. + +// !JVM_DEFAULT_MODE: enable +// JVM_TARGET: 1.8 +// FILE: test.kt + + +fun box(): String { + val b = B(0) + return b.f() + b.g() +} + +interface A { + fun f() = "O" + @JvmDefault + fun g() = "K" +} + +inline class B(val x: Int) : A + +// 1 public static f-impl\(I\)Ljava/lang/String; +// 1 public f\(\)Ljava/lang/String; + +// 1 public static g-impl\(I\)Ljava/lang/String; +// 0 public g\(\)Ljava/lang/String; + +// 1 INVOKESTATIC B.g-impl \(I\)Ljava/lang/String; + +// JVM_TEMPLATES: +// 2 INVOKESTATIC B.f-impl \(I\)Ljava/lang/String; + +// JVM_IR_TEMPLATES: +// 1 INVOKESTATIC B.f-impl \(I\)Ljava/lang/String; diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 31821b15e89..10fbffa4697 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -2775,6 +2775,16 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassesUnboxingAfterAssertionOperator.kt"); } + @TestMetadata("interfaceDefaultImplStubs.kt") + public void testInterfaceDefaultImplStubs() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/inlineClasses/interfaceDefaultImplStubs.kt"); + } + + @TestMetadata("interfaceJvmDefaultImplStubs.kt") + public void testInterfaceJvmDefaultImplStubs() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/inlineClasses/interfaceJvmDefaultImplStubs.kt"); + } + @TestMetadata("isCheckForInlineClass.kt") public void testIsCheckForInlineClass() throws Exception { runTest("compiler/testData/codegen/bytecodeText/inlineClasses/isCheckForInlineClass.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java index 41c5b1da197..4c4ef65800e 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -2820,6 +2820,16 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassesUnboxingAfterAssertionOperator.kt"); } + @TestMetadata("interfaceDefaultImplStubs.kt") + public void testInterfaceDefaultImplStubs() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/inlineClasses/interfaceDefaultImplStubs.kt"); + } + + @TestMetadata("interfaceJvmDefaultImplStubs.kt") + public void testInterfaceJvmDefaultImplStubs() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/inlineClasses/interfaceJvmDefaultImplStubs.kt"); + } + @TestMetadata("isCheckForInlineClass.kt") public void testIsCheckForInlineClass() throws Exception { runTest("compiler/testData/codegen/bytecodeText/inlineClasses/isCheckForInlineClass.kt");