JVM IR: Always produce stubs for interface methods in inline classes

This matches the behavior of the JVM backend, with the exception of
@JvmDefault methods, which are currently broken on the JVM backend.
This commit is contained in:
Steven Schäfer
2020-02-17 17:44:05 +01:00
committed by Alexander Udalov
parent 8c9ebc1bf9
commit 465e9f2d68
7 changed files with 141 additions and 43 deletions
@@ -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<IrDeclaration> {
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)
@@ -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<IrValueParameter>
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)
@@ -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<IrClass>()?.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<IrValueParameter>()
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
}
@@ -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;
@@ -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;
@@ -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");
@@ -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");