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] */
|
||||
private fun isStringPlusCall(expression: IrCall): Boolean {
|
||||
val function = expression.symbol.owner
|
||||
val receiver = expression.dispatchReceiver ?: expression.extensionReceiver
|
||||
private val IrCall.isStringPlusCall: Boolean
|
||||
get() {
|
||||
val function = symbol.owner
|
||||
val receiver = dispatchReceiver ?: extensionReceiver
|
||||
|
||||
return receiver != null
|
||||
&& receiver.type.isStringClassType()
|
||||
&& function.returnType.isStringClassType()
|
||||
&& function.valueParameters.size == 1
|
||||
&& function.name.asString() == "plus"
|
||||
&& function.fqNameWhenAvailable?.parent() in PARENT_NAMES
|
||||
}
|
||||
return receiver != null
|
||||
&& receiver.type.isStringClassType()
|
||||
&& function.returnType.isStringClassType()
|
||||
&& function.valueParameters.size == 1
|
||||
&& function.name.asString() == "plus"
|
||||
&& 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] */
|
||||
private fun isToStringCall(expression: IrCall): Boolean {
|
||||
if (expression.superQualifierSymbol != null)
|
||||
return false
|
||||
private val IrCall.isToStringCall: Boolean
|
||||
get() {
|
||||
if (superQualifierSymbol != null)
|
||||
return false
|
||||
|
||||
val function = expression.symbol.owner
|
||||
if (function.name.asString() != "toString" || function.valueParameters.size != 0 || !function.returnType.isString())
|
||||
return false
|
||||
val function = symbol.owner as? IrSimpleFunction
|
||||
?: return false
|
||||
|
||||
// Check for Any?.toString, which is an extension method defined in the kotlin package.
|
||||
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 function.isToString || function.isNullableToString
|
||||
}
|
||||
|
||||
/** @return true if the given expression is a [IrStringConcatenation], or an [IrCall] to [toString] or [String.plus]. */
|
||||
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. */
|
||||
private fun collectStringConcatenationArguments(expression: IrExpression): List<IrExpression> {
|
||||
|
||||
@@ -218,6 +218,7 @@ private val jvmFilePhases =
|
||||
flattenStringConcatenationPhase then
|
||||
foldConstantLoweringPhase then
|
||||
computeStringTrimPhase then
|
||||
jvmStringConcatenationLowering then
|
||||
|
||||
defaultArgumentStubPhase then
|
||||
defaultArgumentInjectorPhase then
|
||||
|
||||
@@ -104,6 +104,10 @@ class JvmSymbols(
|
||||
addValueParameter("value", irBuiltIns.anyNType)
|
||||
addValueParameter("expression", irBuiltIns.stringType)
|
||||
}
|
||||
klass.addFunction("stringPlus", irBuiltIns.stringType, isStatic = true).apply {
|
||||
addValueParameter("self", irBuiltIns.stringType.makeNullable())
|
||||
addValueParameter("other", irBuiltIns.anyNType)
|
||||
}
|
||||
}
|
||||
|
||||
val checkExpressionValueIsNotNull: IrSimpleFunctionSymbol =
|
||||
@@ -115,6 +119,9 @@ class JvmSymbols(
|
||||
override val ThrowUninitializedPropertyAccessException: IrSimpleFunctionSymbol =
|
||||
intrinsicsClass.functions.single { it.owner.name.asString() == "throwUninitializedPropertyAccessException" }
|
||||
|
||||
val intrinsicStringPlus: IrFunctionSymbol =
|
||||
intrinsicsClass.functions.single { it.owner.name.asString() == "stringPlus" }
|
||||
|
||||
override val stringBuilder: IrClassSymbol
|
||||
get() = context.getTopLevelClass(FqName("java.lang.StringBuilder"))
|
||||
|
||||
@@ -437,9 +444,26 @@ class JvmSymbols(
|
||||
addValueParameter("v", irBuiltIns.anyNType)
|
||||
returnType = irBuiltIns.stringType
|
||||
}.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 =
|
||||
functions.single { it.owner.name.asString() == name }
|
||||
|
||||
private fun IrClassSymbol.fieldByName(name: String): IrFieldSymbol =
|
||||
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 {
|
||||
val continueLabel = markNewLabel()
|
||||
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
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
// FILE: Z.kt
|
||||
inline class Z(val value: Int)
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
// !LANGUAGE: +InlineClasses
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
// FILE: Z.kt
|
||||
inline class Z(val value: Int)
|
||||
|
||||
Vendored
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// WITH_RUNTIME
|
||||
// FILE: test.kt
|
||||
fun test() {
|
||||
|
||||
Reference in New Issue
Block a user