From 5304754e882de7bbd3b487329174838a98d9b193 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Wed, 3 Oct 2018 15:53:52 +0300 Subject: [PATCH] Generate specialized 'toString' for inline classes when possible #KT-25613 --- .../org/jetbrains/kotlin/codegen/AsmUtil.java | 58 ++++++++++++++++++- .../kotlin/codegen/ExpressionCodegen.java | 4 +- .../FunctionsFromAnyGeneratorImpl.java | 2 +- .../inlineClassInStringTemplate.kt | 46 +++++++++++++++ .../inlineClassInGeneratedToString.kt | 17 ++++++ .../inlineClassInStringTemplate.kt | 23 ++++++++ .../inlineClasses/resultApiDoesntUseBox.kt | 2 +- .../codegen/BlackBoxCodegenTestGenerated.java | 5 ++ .../codegen/BytecodeTextTestGenerated.java | 10 ++++ .../LightAnalysisModeTestGenerated.java | 5 ++ .../ir/IrBlackBoxCodegenTestGenerated.java | 5 ++ .../IrJsCodegenBoxTestGenerated.java | 5 ++ .../semantics/JsCodegenBoxTestGenerated.java | 5 ++ 13 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt create mode 100644 compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt create mode 100644 compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInStringTemplate.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java index 44e20e70ab4..96cc1037c8d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.config.JvmTarget; import org.jetbrains.kotlin.config.LanguageFeature; import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.descriptors.*; +import org.jetbrains.kotlin.incremental.components.NoLookupLocation; import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.load.java.JavaVisibilities; import org.jetbrains.kotlin.load.java.JvmAnnotationNames; @@ -665,8 +666,23 @@ public class AsmUtil { } public static void genInvokeAppendMethod(@NotNull InstructionAdapter v, @NotNull Type type, @Nullable KotlinType kotlinType) { + genInvokeAppendMethod(v, type, kotlinType, null); + } + + public static void genInvokeAppendMethod( + @NotNull InstructionAdapter v, + @NotNull Type type, + @Nullable KotlinType kotlinType, + @Nullable KotlinTypeMapper typeMapper + ) { Type appendParameterType; - if (kotlinType != null && InlineClassesUtilsKt.isInlineClassType(kotlinType)) { + + CallableMethod specializedToString = getSpecializedToStringCallableMethodOrNull(kotlinType, typeMapper); + if (specializedToString != null) { + specializedToString.genInvokeInstruction(v); + appendParameterType = AsmTypes.JAVA_STRING_TYPE; + } + else if (kotlinType != null && InlineClassesUtilsKt.isInlineClassType(kotlinType)) { appendParameterType = OBJECT_TYPE; SimpleType nullableAnyType = kotlinType.getConstructor().getBuiltIns().getNullableAnyType(); StackValue.coerce(type, kotlinType, appendParameterType, nullableAnyType, v); @@ -681,9 +697,17 @@ public class AsmUtil { public static StackValue genToString( @NotNull StackValue receiver, @NotNull Type receiverType, - @Nullable KotlinType receiverKotlinType + @Nullable KotlinType receiverKotlinType, + @Nullable KotlinTypeMapper typeMapper ) { return StackValue.operation(JAVA_STRING_TYPE, v -> { + CallableMethod specializedToString = getSpecializedToStringCallableMethodOrNull(receiverKotlinType, typeMapper); + if (specializedToString != null) { + receiver.put(receiverType, receiverKotlinType, v); + specializedToString.genInvokeInstruction(v); + return null; + } + Type type; KotlinType kotlinType; if (receiverKotlinType != null && InlineClassesUtilsKt.isInlineClassType(receiverKotlinType)) { @@ -701,6 +725,36 @@ public class AsmUtil { }); } + @Nullable + private static CallableMethod getSpecializedToStringCallableMethodOrNull( + @Nullable KotlinType receiverKotlinType, + @Nullable KotlinTypeMapper typeMapper + ) { + if (typeMapper == null) return null; + + if (receiverKotlinType == null) return null; + if (!InlineClassesUtilsKt.isInlineClassType(receiverKotlinType)) return null; + if (receiverKotlinType.isMarkedNullable()) return null; + + DeclarationDescriptor receiverTypeDescriptor = receiverKotlinType.getConstructor().getDeclarationDescriptor(); + assert receiverTypeDescriptor instanceof ClassDescriptor && ((ClassDescriptor) receiverTypeDescriptor).isInline() : + "Inline class type expected: " + receiverKotlinType; + ClassDescriptor receiverClassDescriptor = (ClassDescriptor) receiverTypeDescriptor; + FunctionDescriptor toStringDescriptor = receiverClassDescriptor.getUnsubstitutedMemberScope() + .getContributedFunctions(Name.identifier("toString"), NoLookupLocation.FROM_BACKEND) + .stream() + .filter( + f -> f.getValueParameters().size() == 0 + && KotlinBuiltIns.isString(f.getReturnType()) + && f.getDispatchReceiverParameter() != null + && f.getExtensionReceiverParameter() == null + ) + .findFirst() + .orElseThrow(() -> new AssertionError("'toString' not found in member scope of " + receiverClassDescriptor)); + + return typeMapper.mapToCallableMethod(toStringDescriptor, false, OwnerKind.ERASED_INLINE_CLASS); + } + static void genHashCode(MethodVisitor mv, InstructionAdapter iv, Type type, JvmTarget jvmTarget) { if (type.getSort() == Type.ARRAY) { Type elementType = correctElementType(type); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 88d9eea5f34..789943b95e4 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -833,7 +833,7 @@ public class ExpressionCodegen extends KtVisitor impleme StringTemplateEntry entry = entries.get(0); if (entry instanceof StringTemplateEntry.Expression) { KtExpression expr = ((StringTemplateEntry.Expression) entry).expression; - return genToString(gen(expr), expressionType(expr), kotlinType(expr)); + return genToString(gen(expr), expressionType(expr), kotlinType(expr), typeMapper); } else { return StackValue.constant(((StringTemplateEntry.Constant) entry).value, type); @@ -3879,7 +3879,7 @@ public class ExpressionCodegen extends KtVisitor impleme gen(expr, exprType, exprKotlinType); } - genInvokeAppendMethod(v, exprType, exprKotlinType); + genInvokeAppendMethod(v, exprType, exprKotlinType, typeMapper); } @Nullable diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionsFromAnyGeneratorImpl.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionsFromAnyGeneratorImpl.java index 49dcae30b5f..de24729f79b 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionsFromAnyGeneratorImpl.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionsFromAnyGeneratorImpl.java @@ -122,7 +122,7 @@ public class FunctionsFromAnyGeneratorImpl extends FunctionsFromAnyGenerator { } } } - genInvokeAppendMethod(iv, asmType, type.getKotlinType()); + genInvokeAppendMethod(iv, asmType, type.getKotlinType(), typeMapper); } iv.aconst(")"); diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt b/compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt new file mode 100644 index 00000000000..e3a102d8529 --- /dev/null +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt @@ -0,0 +1,46 @@ +// !LANGUAGE: +InlineClasses +// IGNORE_BACKEND: JVM_IR +// WITH_RUNTIME + +import kotlin.test.* + +inline class Z(val value: Int) + +fun test1_1(z: Z) = "$z" +fun test1_2(z: Z) = "$z$z" +fun test1_many(z: Z) = "$z $z $z" +fun test1_concat1(z: Z) = "-" + z +fun test1_concat2(z: Z) = "$z" + z +fun test1_concat3(z: Z) = "-" + z + z + +fun test2_1(z: Z?) = "$z" +fun test2_2(z: Z?) = "$z$z" +fun test2_many(z: Z?) = "$z $z $z" +fun test2_concat1(z: Z?) = "-" + z +fun test2_concat2(z: Z?) = "$z" + z +fun test2_concat3(z: Z?) = "-" + z + z + +fun box(): String { + assertEquals("Z(value=42)", test1_1(Z(42))) + assertEquals("Z(value=42)Z(value=42)", test1_2(Z(42))) + assertEquals("Z(value=42) Z(value=42) Z(value=42)", test1_many(Z(42))) + assertEquals("-Z(value=42)", test1_concat1(Z(42))) + assertEquals("Z(value=42)Z(value=42)", test1_concat2(Z(42))) + assertEquals("-Z(value=42)Z(value=42)", test1_concat3(Z(42))) + + assertEquals("null", test2_1(null)) + assertEquals("nullnull", test2_2(null)) + assertEquals("null null null", test2_many(null)) + assertEquals("-null", test2_concat1(null)) + assertEquals("nullnull", test2_concat2(null)) + assertEquals("-nullnull", test2_concat3(null)) + + assertEquals("Z(value=42)", test2_1(Z(42))) + assertEquals("Z(value=42)Z(value=42)", test2_2(Z(42))) + assertEquals("Z(value=42) Z(value=42) Z(value=42)", test2_many(Z(42))) + assertEquals("-Z(value=42)", test2_concat1(Z(42))) + assertEquals("Z(value=42)Z(value=42)", test2_concat2(Z(42))) + assertEquals("-Z(value=42)Z(value=42)", test2_concat3(Z(42))) + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt b/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt new file mode 100644 index 00000000000..e1a7fab3496 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt @@ -0,0 +1,17 @@ +// !LANGUAGE: +InlineClasses + +// FILE: Z.kt +inline class Z(val value: Int) + +// FILE: test.kt +data class Data(val z1: Z, val z2: Z) + +inline class Inline(val z: Z) + +// @Data.class: +// 0 Z.box +// 0 Z.unbox + +// @Inline.class: +// 0 Z.box +// 0 Z.unbox diff --git a/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInStringTemplate.kt b/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInStringTemplate.kt new file mode 100644 index 00000000000..baf0e9511ca --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInStringTemplate.kt @@ -0,0 +1,23 @@ +// !LANGUAGE: +InlineClasses + +// FILE: Z.kt +inline class Z(val value: Int) + +// FILE: test.kt +fun test1_1(z: Z) = "$z" +fun test1_2(z: Z) = "$z$z" +fun test1_many(z: Z) = "$z $z $z" +fun test1_concat1(z: Z) = "-" + z +fun test1_concat2(z: Z) = "$z" + z +fun test1_concat3(z: Z) = "-" + z + z + +fun test2_1(z: Z?) = "$z" +fun test2_2(z: Z?) = "$z$z" +fun test2_many(z: Z?) = "$z $z $z" +fun test2_concat1(z: Z?) = "-" + z +fun test2_concat2(z: Z?) = "$z" + z +fun test2_concat3(z: Z?) = "-" + z + z + +// @TestKt.class: +// 0 box +// 0 unbox diff --git a/compiler/testData/codegen/bytecodeText/inlineClasses/resultApiDoesntUseBox.kt b/compiler/testData/codegen/bytecodeText/inlineClasses/resultApiDoesntUseBox.kt index b1d3652d673..135cb450b96 100644 --- a/compiler/testData/codegen/bytecodeText/inlineClasses/resultApiDoesntUseBox.kt +++ b/compiler/testData/codegen/bytecodeText/inlineClasses/resultApiDoesntUseBox.kt @@ -34,4 +34,4 @@ fun test() { // 0 INVOKESTATIC Result.box-impl // 0 INVOKESTATIC Result.unbox-impl // 0 Result\$Failure -// 53 Result \ No newline at end of file +// 52 Result \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index dd83376c3dc..85d80a6d8ab 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -11704,6 +11704,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt"); } + @TestMetadata("inlineClassInStringTemplate.kt") + public void testInlineClassInStringTemplate() throws Exception { + runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt"); + } + @TestMetadata("inlineClassPropertyReferenceGetAndSet.kt") public void testInlineClassPropertyReferenceGetAndSet() throws Exception { runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index f33e7a94d5f..22f39499bf9 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -2136,6 +2136,16 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassBoxingUnboxingInsideInlinedLambda.kt"); } + @TestMetadata("inlineClassInGeneratedToString.kt") + public void testInlineClassInGeneratedToString() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInGeneratedToString.kt"); + } + + @TestMetadata("inlineClassInStringTemplate.kt") + public void testInlineClassInStringTemplate() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassInStringTemplate.kt"); + } + @TestMetadata("inlineClassesUnboxingAfterAssertionOperator.kt") public void testInlineClassesUnboxingAfterAssertionOperator() throws Exception { runTest("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassesUnboxingAfterAssertionOperator.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 7b5e86848e0..ca736f41bdb 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -11704,6 +11704,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt"); } + @TestMetadata("inlineClassInStringTemplate.kt") + public void testInlineClassInStringTemplate() throws Exception { + runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt"); + } + @TestMetadata("inlineClassPropertyReferenceGetAndSet.kt") public void testInlineClassPropertyReferenceGetAndSet() throws Exception { runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 541394cd405..ecf295241fc 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -11709,6 +11709,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/inlineClasses/inlineClassImplementsCollection.kt"); } + @TestMetadata("inlineClassInStringTemplate.kt") + public void testInlineClassInStringTemplate() throws Exception { + runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt"); + } + @TestMetadata("inlineClassPropertyReferenceGetAndSet.kt") public void testInlineClassPropertyReferenceGetAndSet() throws Exception { runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java index e7012181d5c..5aa77deb60d 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java @@ -10239,6 +10239,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvoke.kt"); } + @TestMetadata("inlineClassInStringTemplate.kt") + public void testInlineClassInStringTemplate() throws Exception { + runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt"); + } + @TestMetadata("inlineClassPropertyReferenceGetAndSet.kt") public void testInlineClassPropertyReferenceGetAndSet() throws Exception { runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index c494f932eb1..ba0d1158f21 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -11284,6 +11284,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/inlineClasses/inlineClassFunctionInvoke.kt"); } + @TestMetadata("inlineClassInStringTemplate.kt") + public void testInlineClassInStringTemplate() throws Exception { + runTest("compiler/testData/codegen/box/inlineClasses/inlineClassInStringTemplate.kt"); + } + @TestMetadata("inlineClassPropertyReferenceGetAndSet.kt") public void testInlineClassPropertyReferenceGetAndSet() throws Exception { runTest("compiler/testData/codegen/box/inlineClasses/inlineClassPropertyReferenceGetAndSet.kt");