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:
committed by
Alexander Udalov
parent
8c9ebc1bf9
commit
465e9f2d68
+20
-10
@@ -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)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
|
||||
+34
-31
@@ -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
|
||||
}
|
||||
|
||||
+29
@@ -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;
|
||||
+36
@@ -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");
|
||||
|
||||
+10
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user