JVM IR: Lower IrStringConcatenation
This commit is contained in:
committed by
Alexander Udalov
parent
78b4024ccb
commit
c905209504
+41
-28
@@ -80,42 +80,55 @@ class FlattenStringConcatenationLowering(val context: CommonBackendContext) : Fi
|
|||||||
)
|
)
|
||||||
|
|
||||||
/** @return true if the given expression is a call to [String.plus] */
|
/** @return true if the given expression is a call to [String.plus] */
|
||||||
private fun isStringPlusCall(expression: IrCall): Boolean {
|
private val IrCall.isStringPlusCall: Boolean
|
||||||
val function = expression.symbol.owner
|
get() {
|
||||||
val receiver = expression.dispatchReceiver ?: expression.extensionReceiver
|
val function = symbol.owner
|
||||||
|
val receiver = dispatchReceiver ?: extensionReceiver
|
||||||
|
|
||||||
return receiver != null
|
return receiver != null
|
||||||
&& receiver.type.isStringClassType()
|
&& receiver.type.isStringClassType()
|
||||||
&& function.returnType.isStringClassType()
|
&& function.returnType.isStringClassType()
|
||||||
&& function.valueParameters.size == 1
|
&& function.valueParameters.size == 1
|
||||||
&& function.name.asString() == "plus"
|
&& function.name.asString() == "plus"
|
||||||
&& function.fqNameWhenAvailable?.parent() in PARENT_NAMES
|
&& function.fqNameWhenAvailable?.parent() in PARENT_NAMES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return true if the function is Any.toString or an override of Any.toString */
|
||||||
|
val IrSimpleFunction.isToString: Boolean
|
||||||
|
get() {
|
||||||
|
if (name.asString() != "toString" || valueParameters.size != 0 || !returnType.isString())
|
||||||
|
return false
|
||||||
|
|
||||||
|
return (dispatchReceiverParameter != null && extensionReceiverParameter == null
|
||||||
|
&& (dispatchReceiverParameter?.type?.isAny() == true || this.overriddenSymbols.isNotEmpty()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return true if the function is Any?.toString */
|
||||||
|
private val IrSimpleFunction.isNullableToString: Boolean
|
||||||
|
get() {
|
||||||
|
if (name.asString() != "toString" || valueParameters.size != 0 || !returnType.isString())
|
||||||
|
return false
|
||||||
|
|
||||||
|
return dispatchReceiverParameter == null
|
||||||
|
&& extensionReceiverParameter?.type?.isNullableAny() == true
|
||||||
|
&& fqNameWhenAvailable?.parent() == KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME
|
||||||
|
}
|
||||||
|
|
||||||
/** @return true if the given expression is a call to [toString] */
|
/** @return true if the given expression is a call to [toString] */
|
||||||
private fun isToStringCall(expression: IrCall): Boolean {
|
private val IrCall.isToStringCall: Boolean
|
||||||
if (expression.superQualifierSymbol != null)
|
get() {
|
||||||
return false
|
if (superQualifierSymbol != null)
|
||||||
|
return false
|
||||||
|
|
||||||
val function = expression.symbol.owner
|
val function = symbol.owner as? IrSimpleFunction
|
||||||
if (function.name.asString() != "toString" || function.valueParameters.size != 0 || !function.returnType.isString())
|
?: return false
|
||||||
return false
|
|
||||||
|
|
||||||
// Check for Any?.toString, which is an extension method defined in the kotlin package.
|
return function.isToString || function.isNullableToString
|
||||||
if (function.dispatchReceiverParameter == null
|
}
|
||||||
&& function.extensionReceiverParameter?.type?.isNullableAny() == true
|
|
||||||
&& function.fqNameWhenAvailable?.parent() == KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME)
|
|
||||||
return true
|
|
||||||
|
|
||||||
// Check for Any.toString or an override.
|
|
||||||
return (function.dispatchReceiverParameter != null && function.extensionReceiverParameter == null
|
|
||||||
&& (function.dispatchReceiverParameter?.type?.isAny() == true
|
|
||||||
|| (function as? IrSimpleFunction)?.overriddenSymbols?.isNotEmpty() == true))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return true if the given expression is a [IrStringConcatenation], or an [IrCall] to [toString] or [String.plus]. */
|
/** @return true if the given expression is a [IrStringConcatenation], or an [IrCall] to [toString] or [String.plus]. */
|
||||||
private fun isStringConcatenationExpression(expression: IrExpression): Boolean =
|
private fun isStringConcatenationExpression(expression: IrExpression): Boolean =
|
||||||
(expression is IrStringConcatenation) || (expression is IrCall) && (isStringPlusCall(expression) || isToStringCall(expression))
|
(expression is IrStringConcatenation) || (expression is IrCall) && (expression.isStringPlusCall || expression.isToStringCall)
|
||||||
|
|
||||||
/** Recursively collects string concatenation arguments from the given expression. */
|
/** Recursively collects string concatenation arguments from the given expression. */
|
||||||
private fun collectStringConcatenationArguments(expression: IrExpression): List<IrExpression> {
|
private fun collectStringConcatenationArguments(expression: IrExpression): List<IrExpression> {
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ private val jvmFilePhases =
|
|||||||
flattenStringConcatenationPhase then
|
flattenStringConcatenationPhase then
|
||||||
foldConstantLoweringPhase then
|
foldConstantLoweringPhase then
|
||||||
computeStringTrimPhase then
|
computeStringTrimPhase then
|
||||||
|
jvmStringConcatenationLowering then
|
||||||
|
|
||||||
defaultArgumentStubPhase then
|
defaultArgumentStubPhase then
|
||||||
defaultArgumentInjectorPhase then
|
defaultArgumentInjectorPhase then
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ class JvmSymbols(
|
|||||||
addValueParameter("value", irBuiltIns.anyNType)
|
addValueParameter("value", irBuiltIns.anyNType)
|
||||||
addValueParameter("expression", irBuiltIns.stringType)
|
addValueParameter("expression", irBuiltIns.stringType)
|
||||||
}
|
}
|
||||||
|
klass.addFunction("stringPlus", irBuiltIns.stringType, isStatic = true).apply {
|
||||||
|
addValueParameter("self", irBuiltIns.stringType.makeNullable())
|
||||||
|
addValueParameter("other", irBuiltIns.anyNType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol =
|
val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol =
|
||||||
@@ -115,6 +119,9 @@ class JvmSymbols(
|
|||||||
override val ThrowUninitializedPropertyAccessException: IrSimpleFunctionSymbol =
|
override val ThrowUninitializedPropertyAccessException: IrSimpleFunctionSymbol =
|
||||||
intrinsicsClass.functions.single { it.owner.name.asString() == "throwUninitializedPropertyAccessException" }
|
intrinsicsClass.functions.single { it.owner.name.asString() == "throwUninitializedPropertyAccessException" }
|
||||||
|
|
||||||
|
val intrinsicStringPlus: IrFunctionSymbol =
|
||||||
|
intrinsicsClass.functions.single { it.owner.name.asString() == "stringPlus" }
|
||||||
|
|
||||||
override val stringBuilder: IrClassSymbol
|
override val stringBuilder: IrClassSymbol
|
||||||
get() = context.getTopLevelClass(FqName("java.lang.StringBuilder"))
|
get() = context.getTopLevelClass(FqName("java.lang.StringBuilder"))
|
||||||
|
|
||||||
@@ -437,9 +444,26 @@ class JvmSymbols(
|
|||||||
addValueParameter("v", irBuiltIns.anyNType)
|
addValueParameter("v", irBuiltIns.anyNType)
|
||||||
returnType = irBuiltIns.stringType
|
returnType = irBuiltIns.stringType
|
||||||
}.symbol
|
}.symbol
|
||||||
|
|
||||||
|
private val javaLangString: IrClassSymbol = context.getTopLevelClass(FqName("java.lang.String"))
|
||||||
|
|
||||||
|
private val defaultValueOfFunction = javaLangString.functions.single {
|
||||||
|
it.owner.name.asString() == "valueOf" && it.owner.valueParameters.singleOrNull()?.type?.isNullableAny() == true
|
||||||
|
}
|
||||||
|
|
||||||
|
private val valueOfFunctions: Map<IrType, IrSimpleFunctionSymbol?> =
|
||||||
|
(context.irBuiltIns.primitiveIrTypes + context.irBuiltIns.stringType).associateWith { type ->
|
||||||
|
javaLangString.functions.singleOrNull {
|
||||||
|
it.owner.name.asString() == "valueOf" && it.owner.valueParameters.singleOrNull()?.type == type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun typeToStringValueOfFunction(type: IrType): IrSimpleFunctionSymbol =
|
||||||
|
valueOfFunctions[type] ?: defaultValueOfFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrClassSymbol.functionByName(name: String): IrSimpleFunctionSymbol =
|
private fun IrClassSymbol.functionByName(name: String): IrSimpleFunctionSymbol =
|
||||||
functions.single { it.owner.name.asString() == name }
|
functions.single { it.owner.name.asString() == name }
|
||||||
|
|
||||||
private fun IrClassSymbol.fieldByName(name: String): IrFieldSymbol =
|
private fun IrClassSymbol.fieldByName(name: String): IrFieldSymbol =
|
||||||
fields.single { it.owner.name.asString() == name }
|
fields.single { it.owner.name.asString() == name }
|
||||||
|
|||||||
-48
@@ -671,54 +671,6 @@ class ExpressionCodegen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitStringConcatenation(expression: IrStringConcatenation, data: BlockInfo): PromisedValue {
|
|
||||||
expression.markLineNumber(startOffset = true)
|
|
||||||
val arity = expression.arguments.size
|
|
||||||
when {
|
|
||||||
arity == 0 -> mv.aconst("")
|
|
||||||
arity == 1 -> {
|
|
||||||
// Convert single arg to string.
|
|
||||||
val arg = expression.arguments[0]
|
|
||||||
val result = arg.accept(this, data).boxInlineClasses(arg.type).materialized
|
|
||||||
if (!arg.type.isString()) {
|
|
||||||
result.genToString(mv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arity == 2 && expression.arguments[0].type.isStringClassType() -> {
|
|
||||||
// Call the stringPlus intrinsic
|
|
||||||
for ((index, argument) in expression.arguments.withIndex()) {
|
|
||||||
val result = argument.accept(this, data).boxInlineClasses(argument.type).materialized
|
|
||||||
if (result.type.sort != Type.OBJECT) {
|
|
||||||
result.genToString(mv)
|
|
||||||
} else if (index == 0) {
|
|
||||||
result.coerce(context.irBuiltIns.stringType).materialize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mv.invokestatic(
|
|
||||||
IrIntrinsicMethods.INTRINSICS_CLASS_NAME,
|
|
||||||
"stringPlus",
|
|
||||||
"(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;",
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// Use StringBuilder to concatenate.
|
|
||||||
genStringBuilderConstructor(mv)
|
|
||||||
for (argument in expression.arguments) {
|
|
||||||
genInvokeAppendMethod(mv, argument.accept(this, data).boxInlineClasses(argument.type).materialized.type, null)
|
|
||||||
}
|
|
||||||
mv.invokevirtual("java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expression.onStack
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MaterialValue.genToString(v: InstructionAdapter) {
|
|
||||||
val asmType =
|
|
||||||
if (irType.getClass()?.isInline == true) OBJECT_TYPE else stringValueOfType(type)
|
|
||||||
v.invokestatic("java/lang/String", "valueOf", Type.getMethodDescriptor(AsmTypes.JAVA_STRING_TYPE, asmType), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitWhileLoop(loop: IrWhileLoop, data: BlockInfo): PromisedValue {
|
override fun visitWhileLoop(loop: IrWhileLoop, data: BlockInfo): PromisedValue {
|
||||||
val continueLabel = markNewLabel()
|
val continueLabel = markNewLabel()
|
||||||
val endLabel = Label()
|
val endLabel = Label()
|
||||||
|
|||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 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.jvm.lower
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
|
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.FlattenStringConcatenationLowering
|
||||||
|
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.ir.JvmIrBuilder
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
|
||||||
|
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irCall
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irImplicitCast
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irString
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation
|
||||||
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
|
import org.jetbrains.kotlin.ir.util.constructors
|
||||||
|
import org.jetbrains.kotlin.ir.util.functions
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
|
|
||||||
|
internal val jvmStringConcatenationLowering = makeIrFilePhase(
|
||||||
|
::JvmStringConcatenationLowering,
|
||||||
|
name = "StringConcatenation",
|
||||||
|
description = "Replace IrStringConcatenation with string builders"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This lowering pass replaces [IrStringConcatenation]s with StringBuilder appends.
|
||||||
|
*
|
||||||
|
* This pass is based on [StringConcatenationLowering] in backend.common. The main difference
|
||||||
|
* is that this pass also handles JVM specific optimizations, such as calling stringPlus
|
||||||
|
* for two arguments, and properly handles inline classes.
|
||||||
|
*/
|
||||||
|
private class JvmStringConcatenationLowering(val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
|
||||||
|
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid()
|
||||||
|
|
||||||
|
private val stringBuilder = context.ir.symbols.stringBuilder.owner
|
||||||
|
|
||||||
|
private val constructor = stringBuilder.constructors.single {
|
||||||
|
it.valueParameters.size == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private val IrClass.toStringFunction: IrSimpleFunction
|
||||||
|
get() = functions.single {
|
||||||
|
with(FlattenStringConcatenationLowering) { it.isToString }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val toStringFunction = stringBuilder.toStringFunction
|
||||||
|
|
||||||
|
private val defaultAppendFunction = stringBuilder.functions.single {
|
||||||
|
it.name.asString() == "append" &&
|
||||||
|
it.valueParameters.size == 1 &&
|
||||||
|
it.valueParameters.single().type.isNullableAny()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val appendFunctions: Map<IrType, IrSimpleFunction?> =
|
||||||
|
(context.irBuiltIns.primitiveIrTypes + context.irBuiltIns.stringType).associateWith { type ->
|
||||||
|
stringBuilder.functions.singleOrNull {
|
||||||
|
it.name.asString() == "append" && it.valueParameters.singleOrNull()?.type == type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun typeToAppendFunction(type: IrType): IrSimpleFunction =
|
||||||
|
appendFunctions[type] ?: defaultAppendFunction
|
||||||
|
|
||||||
|
// There is no special append or valueOf function for byte and short on the JVM.
|
||||||
|
private fun IrBuilderWithScope.widenIntegerType(expression: IrExpression): IrExpression =
|
||||||
|
if (expression.type.isByte() || expression.type.isShort()) {
|
||||||
|
irImplicitCast(expression, context.irBuiltIns.intType)
|
||||||
|
} else {
|
||||||
|
expression
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JvmIrBuilder.callToString(expression: IrExpression): IrExpression {
|
||||||
|
if (expression.type.isString())
|
||||||
|
return expression
|
||||||
|
|
||||||
|
val argument = widenIntegerType(expression)
|
||||||
|
val argumentType = if (argument.type.isPrimitiveType()) argument.type else context.irBuiltIns.anyNType
|
||||||
|
|
||||||
|
return irCall(backendContext.ir.symbols.typeToStringValueOfFunction(argumentType)).apply {
|
||||||
|
putValueArgument(0, argument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JvmIrBuilder.lowerInlineClassArgument(expression: IrExpression): IrExpression {
|
||||||
|
if (expression.type.unboxInlineClass() == expression.type)
|
||||||
|
return expression
|
||||||
|
|
||||||
|
val toStringFunction = expression.type.classOrNull?.owner?.toStringFunction
|
||||||
|
?: return expression
|
||||||
|
|
||||||
|
val toStringReplacement = backendContext.inlineClassReplacements.getReplacementFunction(toStringFunction)
|
||||||
|
?: return expression
|
||||||
|
|
||||||
|
return irCall(toStringReplacement.function).apply {
|
||||||
|
putValueArgument(0, expression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression {
|
||||||
|
expression.transformChildrenVoid(this)
|
||||||
|
return context.createJvmIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset).run {
|
||||||
|
val arguments = expression.arguments.map { lowerInlineClassArgument(it) }
|
||||||
|
|
||||||
|
when {
|
||||||
|
arguments.isEmpty() ->
|
||||||
|
irString("")
|
||||||
|
|
||||||
|
arguments.size == 1 ->
|
||||||
|
callToString(arguments.single())
|
||||||
|
|
||||||
|
arguments.size == 2 && arguments[0].type.isStringClassType() ->
|
||||||
|
irCall(backendContext.ir.symbols.intrinsicStringPlus).apply {
|
||||||
|
putValueArgument(0, arguments[0])
|
||||||
|
putValueArgument(1, arguments[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
var stringBuilder = irCall(constructor)
|
||||||
|
for (arg in arguments) {
|
||||||
|
val argument = widenIntegerType(arg)
|
||||||
|
val appendFunction = typeToAppendFunction(argument.type)
|
||||||
|
stringBuilder = irCall(appendFunction).apply {
|
||||||
|
dispatchReceiver = stringBuilder
|
||||||
|
putValueArgument(0, argument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
irCall(toStringFunction).apply {
|
||||||
|
dispatchReceiver = stringBuilder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-1
@@ -1,5 +1,4 @@
|
|||||||
// !LANGUAGE: +InlineClasses
|
// !LANGUAGE: +InlineClasses
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
|
|
||||||
// FILE: Z.kt
|
// FILE: Z.kt
|
||||||
inline class Z(val value: Int)
|
inline class Z(val value: Int)
|
||||||
|
|||||||
-1
@@ -1,5 +1,4 @@
|
|||||||
// !LANGUAGE: +InlineClasses
|
// !LANGUAGE: +InlineClasses
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
|
|
||||||
// FILE: Z.kt
|
// FILE: Z.kt
|
||||||
inline class Z(val value: Int)
|
inline class Z(val value: Int)
|
||||||
|
|||||||
Vendored
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// FILE: test.kt
|
// FILE: test.kt
|
||||||
fun test() {
|
fun test() {
|
||||||
|
|||||||
Reference in New Issue
Block a user