diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt index b39b2e1144a..3a57a65d590 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt @@ -210,6 +210,13 @@ internal val stringConcatenationPhase = makeKonanFileLoweringPhase( description = "String concatenation lowering" ) +internal val stringConcatenationTypeNarrowingPhase = makeKonanFileLoweringPhase( + ::StringConcatenationTypeNarrowing, + name = "StringConcatenationTypeNarrowing", + description = "String concatenation type narrowing", + prerequisite = setOf(stringConcatenationPhase) +) + internal val kotlinNothingValueExceptionPhase = makeKonanFileLoweringPhase( ::KotlinNothingValueExceptionLowering, name = "KotlinNothingValueException", diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index 7b7cf4d0715..f68ac6ecdef 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -279,6 +279,7 @@ internal val allLoweringsPhase = NamedCompilerPhase( foldConstantLoweringPhase, computeStringTrimPhase, stringConcatenationPhase, + stringConcatenationTypeNarrowingPhase, enumConstructorsPhase, initializersPhase, localFunctionsPhase, @@ -626,6 +627,7 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) { disableUnless(linkBitcodeDependenciesPhase, config.produce.involvesLinkStage) disableUnless(checkExternalCallsPhase, getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)) disableUnless(rewriteExternalCallsCheckerGlobals, getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)) + disableUnless(stringConcatenationTypeNarrowingPhase, config.optimizationsEnabled) disableUnless(optimizeTLSDataLoadsPhase, config.optimizationsEnabled) disableUnless(objectFilesPhase, config.produce.involvesLinkStage) disableUnless(linkerPhase, config.produce.involvesLinkStage) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/StringConcatenationTypeNarrowing.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/StringConcatenationTypeNarrowing.kt new file mode 100644 index 00000000000..3c41aa26f2b --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/StringConcatenationTypeNarrowing.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2010-2022 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. + */ + +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer +import org.jetbrains.kotlin.backend.common.lower.at +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrGetValue +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.functions +import org.jetbrains.kotlin.ir.util.shallowCopy +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.Name + +/** + * This pass replaces calls to: + * - StringBuilder.append(Any?) with StringBuilder.append(String?) + * - StringBuilder.append(Any) with StringBuilder.append(String) + * - String.plus(Any?) with String.plusImpl(String) + * - String?.plus(Any?) with String.plusImpl(String) + * For this, toString() is called for non-String arguments. This call can be later devirtualized, improving escape analysis + * For nullable arguments, the following snippet is used: + * - "if (arg==null) null else arg.toString()" to pass to StringBuilder.append(String?) + * - "if (arg==null) "null" else arg.toString()" to pass to other methods as non-nullable String + */ +internal class StringConcatenationTypeNarrowing(val context: Context) : FileLoweringPass, IrBuildingTransformer(context) { + + private val string = context.ir.symbols.string.owner + private val stringBuilder = context.ir.symbols.stringBuilder.owner + private val namePlusImpl = Name.identifier("plusImpl") + private val nameAppend = Name.identifier("append") + + private val appendStringFunction = stringBuilder.functions.single { // StringBuilder.append(String) + it.name == nameAppend && + it.valueParameters.singleOrNull()?.type?.isString() == true + } + private val appendNullableStringFunction = stringBuilder.functions.single { // StringBuilder.append(String?) + it.name == nameAppend && + it.valueParameters.singleOrNull()?.type?.isNullableString() == true + } + private val appendAnyFunction = stringBuilder.functions.single { // StringBuilder.append(Any?) + it.name == nameAppend && + it.valueParameters.singleOrNull()?.type?.isNullableAny() == true + } + + private val plusImplFunction = string.functions.single { // external fun String.plusImpl(String) + it.name == namePlusImpl && + it.valueParameters.size == 1 && + it.valueParameters.single().type.isString() + } + + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(this) + } + + override fun visitCall(expression: IrCall): IrExpression { + expression.transformChildrenVoid(this) + return with(expression) { + builder.at(this) + when (symbol) { + appendAnyFunction.symbol -> { // StringBuilder.append(Any?) + val argument = getValueArgument(0)!! + if (argument.type.isNullable()) { + // Transform `StringBuilder.append(Any?)` to `StringBuilder.append(ARG?.toString())`, using "StringBuilder.append(String?)" + buildConcatenationCall(appendNullableStringFunction, dispatchReceiver!!, buildNullableArgToNullableString(argument)) + } else { + // Transform `StringBuilder.append(Any)` to `StringBuilder.append(ARG.toString())`, using "StringBuilder.append(String)" + buildConcatenationCall(appendStringFunction, dispatchReceiver!!, buildNonNullableArgToString(argument)) + } + } + + context.irBuiltIns.memberStringPlus -> + buildConcatenationCall(plusImplFunction, dispatchReceiver!!, buildNullableArgToString(getValueArgument(0)!!)) + + context.irBuiltIns.extensionStringPlus -> + buildConcatenationCall(plusImplFunction, buildNullableArgToString(extensionReceiver!!), buildNullableArgToString(getValueArgument(0)!!)) + + else -> expression + } + } + } + + private fun IrCall.buildConcatenationCall(function: IrSimpleFunction, receiver: IrExpression, argument: IrExpression): IrExpression = + builder.irCall(function.symbol, function.returnType, valueArgumentsCount = 1, typeArgumentsCount = 0) + .apply { + putValueArgument(0, argument) + dispatchReceiver = receiver + } + + /** Builds snippet of type String + * - "if(argument==null) "null" else argument.toString()", if argument's type is nullable. Note: fortunately, all "null" string structures are unified + * - "argument.toString()", otherwise + */ + private fun buildNullableArgToString(argument: IrExpression): IrExpression = + if (argument.type.isNullable()) { + builder.irBlock { + nullableArgToStringType(argument, context.irBuiltIns.stringType, irString("null")) + } + } else buildNonNullableArgToString(argument) + + /** Builds snippet of type String? + * "if(argument==null) null else argument.toString()", that is similar to "argument?.toString()" + */ + private fun buildNullableArgToNullableString(argument: IrExpression): IrExpression = + builder.irBlock { + nullableArgToStringType(argument, context.irBuiltIns.stringType.makeNullable(), irNull()) + } + + private fun IrBlockBuilder.nullableArgToStringType(argument: IrExpression, stringType: IrType, ifNull: IrExpression) { + val (firstExpression, secondExpression) = twoExpressionsForSubsequentUsages(argument) + +irIfThenElse( + stringType, + condition = irEqeqeq(firstExpression, irNull()), + thenPart = ifNull, + elsePart = buildNonNullableArgToString(secondExpression), + origin = null + ) + } + + /** Builds snippet of type String + * - "argument", in case argument's type is String, since String.toString() is no-op + * - "argument", in case argument's type is String?, due to smart-cast and no-op + * - "argument.toString()", otherwise + */ + private fun buildNonNullableArgToString(argument: IrExpression): IrExpression { + return if (argument.type.isString() || argument.type.isNullableString()) + argument + else builder + .irCall(context.ir.symbols.memberToString, context.irBuiltIns.stringType, valueArgumentsCount = 1, typeArgumentsCount = 0) + .apply { dispatchReceiver = argument } + } + + /** + * This function returns two expressions based on the parameter: + * - , should its second usage be idempotent and have runtime cost not greater than local val read. + * This reduces excessive local variable usage without performance degradation. + * - , otherwise. + */ + private fun IrBlockBuilder.twoExpressionsForSubsequentUsages(argument: IrExpression): Pair = + if (argument is IrGetValue) + Pair(argument, argument.shallowCopy()) + else + createTmpVariable(argument).let { Pair(irGet(it), irGet(it)) } +} diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index fd4bb835134..7895419be1a 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -6222,6 +6222,66 @@ fileCheckTest("filecheck_intrinsics") { annotatedSource = project.file('filecheck/intrinsics.kt') } +task kt53119_side_effect(type: KonanLocalTest) { + useGoldenData = true + source = "codegen/stringConcatenationTypeNarrowing/kt53119_side_effect.kt" +} + +fileCheckTest("filecheck_kt53119_plus_generated_noescape") { + annotatedSource = project.file('codegen/stringConcatenationTypeNarrowing/kt53119_plus_generated_noescape.kt') + enabled = project.globalTestArgs.contains('-opt') + extraOpts = ["-tr"] +} + +task kt53119_plus_generated_noescape(type: KonanLocalTest) { + useGoldenData = true + source = "codegen/stringConcatenationTypeNarrowing/kt53119_plus_generated_noescape.kt" +} + +fileCheckTest("filecheck_kt53119_plus_member") { + annotatedSource = project.file('codegen/stringConcatenationTypeNarrowing/kt53119_plus_member.kt') + enabled = project.globalTestArgs.contains('-opt') + extraOpts = ["-tr"] +} + +task kt53119_plus_member(type: KonanLocalTest) { + useGoldenData = true + source = "codegen/stringConcatenationTypeNarrowing/kt53119_plus_member.kt" +} + +fileCheckTest("filecheck_kt53119_plus_extension") { + annotatedSource = project.file('codegen/stringConcatenationTypeNarrowing/kt53119_plus_extension.kt') + enabled = project.globalTestArgs.contains('-opt') + extraOpts = ["-tr"] +} + +task kt53119_plus_extension(type: KonanLocalTest) { + useGoldenData = true + source = "codegen/stringConcatenationTypeNarrowing/kt53119_plus_extension.kt" +} + +fileCheckTest("filecheck_kt53119_append_generated") { + annotatedSource = project.file('codegen/stringConcatenationTypeNarrowing/kt53119_append_generated.kt') + enabled = project.globalTestArgs.contains('-opt') + extraOpts = ["-tr"] +} + +task kt53119_append_generated(type: KonanLocalTest) { + useGoldenData = true + source = "codegen/stringConcatenationTypeNarrowing/kt53119_append_generated.kt" +} + +fileCheckTest("filecheck_kt53119_append_manual") { + annotatedSource = project.file('codegen/stringConcatenationTypeNarrowing/kt53119_append_manual.kt') + enabled = project.globalTestArgs.contains('-opt') + extraOpts = ["-tr"] +} + +task kt53119_append_manual(type: KonanLocalTest) { + useGoldenData = true + source = "codegen/stringConcatenationTypeNarrowing/kt53119_append_manual.kt" +} + fileCheckTest("filecheck_escape_analysis_enabled") { annotatedSource = project.file('filecheck/escape_analysis.kt') enabled = project.globalTestArgs.contains('-opt') diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_generated.kt b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_generated.kt new file mode 100644 index 00000000000..402f65c9136 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_generated.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2022 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. + */ + +package codegen.stringConcatenationTypeNarrowing.kt53119_append_generated +import kotlin.test.* + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_generated#maybeAnyMaybeAny +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String?) +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String) +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String?) +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append + +// CHECK: %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_generated.Foo#toString(){}kotlin.String" +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append + +// CHECK: %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_generated.Foo#toString(){}kotlin.String" +// CHECK-NOT: Foo#toString(){}kotlin.String" +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append + +// CHECK: ret %struct.ObjHeader* + +fun maybeAnyMaybeAny(maybeAny1: Any?, maybeAny2: Any?): String { + return "$maybeAny1,$maybeAny2" +} + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_generated#maybeAnyMaybeString +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String?) +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String) +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String?) +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append + +// CHECK: %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_generated.Foo#toString(){}kotlin.String" +// CHECK-NOT: Foo#toString(){}kotlin.String" + +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append +// CHECK: ret %struct.ObjHeader* + +fun maybeAnyMaybeString(maybeAny1: Any?, maybeString2: String?): String { + return "$maybeAny1,$maybeString2" +} + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_generated#maybeAnyString +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String?) +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String) +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String) +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append + +// CHECK: %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_generated.Foo#toString(){}kotlin.String" +// CHECK-NOT: Foo#toString(){}kotlin.String" + +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append +// CHECK: ret %struct.ObjHeader* + +fun maybeAnyString(maybeAny1: Any?, string: String): String { + return "$maybeAny1,$string" +} + +data class Foo(val bar: Int) + +@Test +fun runTest() { + val foo = Foo(42) + println(maybeAnyMaybeAny(foo, foo)) + println(maybeAnyMaybeAny(null, null)) + println(maybeAnyMaybeString(foo, "bar")) + println(maybeAnyMaybeString(null, null)) + println(maybeAnyString(foo, "bar")) + println(maybeAnyString(null, "bar")) +} diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_generated.out b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_generated.out new file mode 100644 index 00000000000..55ccd76a57a --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_generated.out @@ -0,0 +1,6 @@ +Foo(bar=42),Foo(bar=42) +null,null +Foo(bar=42),bar +null,null +Foo(bar=42),bar +null,bar diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_manual.kt b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_manual.kt new file mode 100644 index 00000000000..22c598577a9 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_manual.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2022 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. + */ + +package codegen.stringConcatenationTypeNarrowing.kt53119_append_manual +import kotlin.test.* + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_manual#appendMaybeAny(kotlin.Any?) +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String?) +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append + +// CHECK: %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_manual.Foo#toString(){}kotlin.String" +// CHECK-NOT: Foo#toString(){}kotlin.String" +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append + +// CHECK: ret %struct.ObjHeader* + +fun appendMaybeAny(maybeAny: Any?): String { + val sb = kotlin.text.StringBuilder() + sb.append(maybeAny) + return sb.toString() +} + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_manual#appendAny(kotlin.Any) +// CHECK: %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_manual.Foo#toString(){}kotlin.String" +// CHECK-NOT: Foo#toString(){}kotlin.String" +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String) + +// CHECK-NOT: Foo#toString(){}kotlin.String" +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append +// CHECK: ret %struct.ObjHeader* + +fun appendAny(any: Any): String { + val sb = kotlin.text.StringBuilder() + sb.append(any) + return sb.toString() +} + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_manual#appendMaybeString(kotlin.String?) +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String?) +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append + +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.String#toString(){}kotlin.String + +// CHECK: ret %struct.ObjHeader* + +fun appendMaybeString(maybeStr: String?): String { + val sb = kotlin.text.StringBuilder() + sb.append(maybeStr) + return sb.toString() +} + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_append_manual#appendString(kotlin.String) +// CHECK: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append(kotlin.String?) +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.text.StringBuilder#append + +// CHECK-NOT: %struct.ObjHeader* @"kfun:kotlin.String#toString(){}kotlin.String + +// CHECK: ret %struct.ObjHeader* + +fun appendString(str: String): String { + val sb = kotlin.text.StringBuilder() + sb.append(str) + return sb.toString() +} + +data class Foo(val bar: Int) + +@Test +fun runTest() { + val foo = Foo(42) + println(appendMaybeAny(foo)) + println(appendMaybeAny(null)) + println(appendAny(foo)) + println(appendMaybeString("foo")) + println(appendMaybeString(null)) + println(appendString("foo")) +} diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_manual.out b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_manual.out new file mode 100644 index 00000000000..1441fc26086 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_append_manual.out @@ -0,0 +1,6 @@ +Foo(bar=42) +null +Foo(bar=42) +foo +null +foo diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_extension.kt b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_extension.kt new file mode 100644 index 00000000000..908d18038d8 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_extension.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2022 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. + */ + +package codegen.stringConcatenationTypeNarrowing.kt53119_plus_extension +import kotlin.test.* + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_extension#manualPlusExtensionAny +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) +// CHECK-NOT: call %struct.ObjHeader* @"kfun:kotlin.String#toString(){}kotlin.String" + +// CHECK: call %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_extension.Foo#toString(){}kotlin.String" + +// CHECK-NOT: call %struct.ObjHeader* @"kfun:kotlin.String#toString(){}kotlin.String" +// CHECK-NOT: Foo#toString(){}kotlin.String" +// CHECK-NOT: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: ret %struct.ObjHeader* + +fun manualPlusExtensionAny(maybeStr: String?, maybeAny: kotlin.Any?): kotlin.String = + maybeStr + maybeAny + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_extension#manualPlusExtensionString +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: call %struct.ObjHeader* @Kotlin_String_plusImpl + +// CHECK-NOT: call %struct.ObjHeader* @"kfun:kotlin.String#toString(){}kotlin.String" +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: ret %struct.ObjHeader* + +fun manualPlusExtensionString(maybeStr: String?, str: String): kotlin.String = + maybeStr + str + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_extension#generatedPlusExtensionAny +// CHECK-NOT: kfun:kotlin#plus__at__kotlin.String?(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: kfun:kotlin#plus__at__kotlin.String?(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_extension.Foo#toString(){}kotlin.String" + +// CHECK-NOT: Foo#toString(){}kotlin.String" +// CHECK-NOT: call %struct.ObjHeader* @"kfun:kotlin.String#toString(){}kotlin.String" +// CHECK-NOT: kfun:kotlin#plus__at__kotlin.String?(kotlin.Any?) +// CHECK-NOT: call %struct.ObjHeader* @Kotlin_String_plusImpl + +// CHECK: ret %struct.ObjHeader* + +fun generatedPlusExtensionAny(maybeStr: String?, maybeAny: Any?): String { + return "$maybeStr$maybeAny" +} + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_extension#generatedPlusExtensionString +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: call %struct.ObjHeader* @Kotlin_String_plusImpl + +// CHECK-NOT: call %struct.ObjHeader* @"kfun:kotlin.String#toString(){}kotlin.String" +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: ret %struct.ObjHeader* + +fun generatedPlusExtensionString(maybeStr: String?, str: String): String { + return "$maybeStr$str" +} + +data class Foo(val bar: Int) + +@Test +fun runTest() { + val foo = Foo(42) + println(manualPlusExtensionAny("foo", foo)) + println(manualPlusExtensionAny(null, null)) + println(manualPlusExtensionString("foo", "bar")) + println(manualPlusExtensionString(null, "bar")) + println(generatedPlusExtensionAny("foo", foo)) + println(generatedPlusExtensionAny(null, null)) + println(generatedPlusExtensionString("foo", "bar")) + println(generatedPlusExtensionString(null, "bar")) +} diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_extension.out b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_extension.out new file mode 100644 index 00000000000..3ef641e3920 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_extension.out @@ -0,0 +1,8 @@ +fooFoo(bar=42) +nullnull +foobar +nullbar +fooFoo(bar=42) +nullnull +foobar +nullbar diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_generated_noescape.kt b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_generated_noescape.kt new file mode 100644 index 00000000000..cdda5787b13 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_generated_noescape.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2022 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. + */ + +package codegen.stringConcatenationTypeNarrowing.kt53119_plus_generated_noescape +import kotlin.test.* +import kotlin.random.* + +// Without "string concatenation type narrowing", pointer to IntRange is conservatively considered to escape, +// since within String.plus(Any?) (within Random.nextInt), the pointer is passed as a receiver of virtual call Any.toString() +// As a result, no stack allocation is possible + +// With "string concatenation type narrowing", IntRange.toString() is explicitly called, that does not escape the receiver. +// Hence, escape analysis can find out the pointer to IntRange does not escape anywhere, and stack allocation is possible. + +// CHECK-LABEL: define i32 @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_generated_noescape#getNextInt(){}kotlin.Int +// CHECK-NOT ret i32 +// CHECK alloca %"kclassbody:kotlin.ranges.IntRange#internal" + +// CHECK ret i32 + +fun getNextInt(): Int { + return Random.nextInt(IntRange(42153, 42153)) +} + +@Test +fun runTest() { + println(getNextInt()) +} diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_generated_noescape.out b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_generated_noescape.out new file mode 100644 index 00000000000..2bbddb07247 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_generated_noescape.out @@ -0,0 +1 @@ +42153 diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_member.kt b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_member.kt new file mode 100644 index 00000000000..088b41145fa --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_member.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2022 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. + */ + +package codegen.stringConcatenationTypeNarrowing.kt53119_plus_member +import kotlin.test.* + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_member#manualPlusMemberAny +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_member.Foo#toString(){}kotlin.String" +// CHECK-NOT: Foo#toString(){}kotlin.String" +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: ret %struct.ObjHeader* + +fun manualPlusMemberAny(str: String, maybeAny: kotlin.Any?): kotlin.String = + str + maybeAny + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_member#manualPlusMemberString +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK-NOT: call %struct.ObjHeader* @"kfun:kotlin.String#toString(){}kotlin.String" + +// CHECK: ret %struct.ObjHeader* + +fun manualPlusMemberString(str1: String, str2: String): kotlin.String = + str1 + str2 + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_member#generatedPlusMemberAny +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_member.Foo#toString(){}kotlin.String" +// CHECK-NOT: Foo#toString(){}kotlin.String" + +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: ret %struct.ObjHeader* + +fun generatedPlusMemberAny(str: String, maybeAny: Any?): String { + return "$str$maybeAny" +} + +// CHECK-LABEL: define %struct.ObjHeader* @"kfun:codegen.stringConcatenationTypeNarrowing.kt53119_plus_member#generatedPlusMemberString +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: call %struct.ObjHeader* @Kotlin_String_plusImpl +// CHECK-NOT: call %struct.ObjHeader* @Kotlin_String_plusImpl + +// CHECK-NOT: call %struct.ObjHeader* @"kfun:kotlin.String#toString(){}kotlin.String" +// CHECK-NOT: kfun:kotlin.String#plus(kotlin.Any?) + +// CHECK: ret %struct.ObjHeader* + +fun generatedPlusMemberString(str1: String, str2: String): String { + return "$str1$str2" +} + +data class Foo(val bar: Int) + +@Test +fun runTest() { + val foo = Foo(42) + println(manualPlusMemberAny("foo", foo)) + println(manualPlusMemberAny("foo", null)) + println(manualPlusMemberString("foo", "bar")) + println(generatedPlusMemberAny("foo", null)) + println(generatedPlusMemberAny("foo", foo)) + println(generatedPlusMemberString("foo", "bar")) +} diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_member.out b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_member.out new file mode 100644 index 00000000000..c4e143efd35 --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_plus_member.out @@ -0,0 +1,6 @@ +fooFoo(bar=42) +foonull +foobar +foonull +fooFoo(bar=42) +foobar diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_side_effect.kt b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_side_effect.kt new file mode 100644 index 00000000000..c45f8b2d51b --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_side_effect.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2010-2022 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. + */ + +// The same tests are also tested with FileCheck in folder ../../filecheck/kt53119_*.kt +// Please keep them in sync + +package codegen.stringConcatenationTypeNarrowing.kt53119_side_effect +import kotlin.test.* + +fun withSideEffect(): String? { + println("Side effect") + return "Result." +} + +@Test +fun runTest() { + println("Retval is: " + withSideEffect()) + println("Retval is: " + withSideEffect() + "!") + println(withSideEffect() + withSideEffect()) + println(withSideEffect() + withSideEffect() + withSideEffect()) +} diff --git a/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_side_effect.out b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_side_effect.out new file mode 100644 index 00000000000..b497d6d6bab --- /dev/null +++ b/kotlin-native/backend.native/tests/codegen/stringConcatenationTypeNarrowing/kt53119_side_effect.out @@ -0,0 +1,11 @@ +Side effect +Retval is: Result. +Side effect +Retval is: Result.! +Side effect +Side effect +Result.Result. +Side effect +Side effect +Side effect +Result.Result.Result. diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_class.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_class.kt index 66bb8f630d9..5148e7b2eb3 100644 --- a/kotlin-native/backend.native/tests/filecheck/kt49847_class.kt +++ b/kotlin-native/backend.native/tests/filecheck/kt49847_class.kt @@ -7,7 +7,7 @@ class C { fun foo(x: Int) = x } -// CHECK: define void @"kfun:#main(){}"() +// CHECK-LABEL: define void @"kfun:#main(){}"() // CHECK-NOT: Int-box // TODO Remove next check after fix of https://youtrack.jetbrains.com/issue/KT-53100/Optimization-needed-T-unboxCONSTANTPRIMITIVEx-T-x // CHECK: Int-unbox diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_generic.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_generic.kt index 6b929d1c9fc..19f3ffb813e 100644 --- a/kotlin-native/backend.native/tests/filecheck/kt49847_generic.kt +++ b/kotlin-native/backend.native/tests/filecheck/kt49847_generic.kt @@ -7,7 +7,7 @@ class C { fun foo(x: T) = x } -// CHECK: define void @"kfun:#main(){}"() +// CHECK-LABEL: define void @"kfun:#main(){}"() // CHECK-NOT: Int-box // TODO Remove next check after fix of https://youtrack.jetbrains.com/issue/KT-53100/Optimization-needed-T-unboxCONSTANTPRIMITIVEx-T-x // CHECK: Int-unbox diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt index cfc2828c45c..f830b47f29c 100644 --- a/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt +++ b/kotlin-native/backend.native/tests/filecheck/kt49847_generic_receiver.kt @@ -3,7 +3,7 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -// CHECK: define internal void @"kfun:$foo$FUNCTION_REFERENCE$0.#internal" +// CHECK-LABEL: define internal void @"kfun:$foo$FUNCTION_REFERENCE$0.#internal" // CHECK-SAME: i32 fun T.foo() { println(this) } diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any.kt index c213e98e49d..9e712f2f203 100644 --- a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any.kt +++ b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any.kt @@ -9,7 +9,7 @@ fun interface Foo { fun baz(x: Any): Int = x.hashCode() -// CHECK: define void @"kfun:#main(){}"() +// CHECK-LABEL: define void @"kfun:#main(){}"() // Boxing needs to be used now due to non-devirtualized call // CHECK: Int-box diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any_generic.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any_generic.kt index 2a93439f4b3..f249072c1e3 100644 --- a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any_generic.kt +++ b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Any_generic.kt @@ -9,7 +9,7 @@ fun interface Foo { fun baz(x: Any): Int = x.hashCode() -// CHECK: define void @"kfun:#main(){}"() +// CHECK-LABEL: define void @"kfun:#main(){}"() // CHECK-NOT: Int-box // CHECK-NOT: Int-unbox // CHECK: ret void diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int.kt index b0abed2fede..0803c1aa511 100644 --- a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int.kt +++ b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int.kt @@ -9,7 +9,7 @@ fun interface Foo { fun baz(x: Int): Int = x.hashCode() -// CHECK: define void @"kfun:#main(){}"() +// CHECK-LABEL: define void @"kfun:#main(){}"() // CHECK-NOT: Int-box // CHECK-NOT: Int-unbox // CHECK: ret void diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int_generic.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int_generic.kt index c8575471443..fa47a4c4e88 100644 --- a/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int_generic.kt +++ b/kotlin-native/backend.native/tests/filecheck/kt49847_sam_Int_generic.kt @@ -9,7 +9,7 @@ fun interface Foo { fun baz(x: Any): Int = x.hashCode() -// CHECK: define void @"kfun:#main(){}"() +// CHECK-LABEL: define void @"kfun:#main(){}"() // CHECK-NOT: Int-box // CHECK-NOT: Int-unbox // CHECK: ret void diff --git a/kotlin-native/backend.native/tests/filecheck/kt49847_simple_function_reference.kt b/kotlin-native/backend.native/tests/filecheck/kt49847_simple_function_reference.kt index 703546bb0d4..3bb5bf3f816 100644 --- a/kotlin-native/backend.native/tests/filecheck/kt49847_simple_function_reference.kt +++ b/kotlin-native/backend.native/tests/filecheck/kt49847_simple_function_reference.kt @@ -5,7 +5,7 @@ fun plus1(x: Int) = x + 1 -// CHECK: define void @"kfun:#main(){}"() +// CHECK-LABEL: define void @"kfun:#main(){}"() // CHECK-NOT: Int-box // CHECK-NOT: Int-unbox // CHECK: ret void diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/String.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/String.kt index 0c0ede4c472..742e17902bd 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/String.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/String.kt @@ -8,6 +8,8 @@ package kotlin import kotlin.native.internal.ExportTypeInfo import kotlin.native.internal.Frozen import kotlin.native.internal.GCUnsafeCall +import kotlin.native.internal.IntrinsicType +import kotlin.native.internal.TypedIntrinsic @ExportTypeInfo("theStringTypeInfo") @OptIn(FreezingIsDeprecated::class) @@ -23,6 +25,7 @@ public final class String : Comparable, CharSequence { return plusImpl(other.toString()) } + @TypedIntrinsic(IntrinsicType.IDENTITY) override public fun toString(): String { return this } @@ -47,8 +50,9 @@ public final class String : Comparable, CharSequence { @GCUnsafeCall("Kotlin_String_getStringLength") private external fun getStringLength(): Int + @PublishedApi @GCUnsafeCall("Kotlin_String_plusImpl") - private external fun plusImpl(other: String): String + internal external fun plusImpl(other: String): String @GCUnsafeCall("Kotlin_String_equals") external override fun equals(other: Any?): Boolean