JVM IR: Lower IrStringConcatenation

This commit is contained in:
Steven Schäfer
2019-10-04 14:12:40 +02:00
committed by Alexander Udalov
parent 78b4024ccb
commit c905209504
8 changed files with 211 additions and 79 deletions
@@ -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 }
@@ -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()
@@ -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,5 +1,4 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND: JVM_IR
// FILE: Z.kt
inline class Z(val value: Int)
@@ -1,5 +1,4 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND: JVM_IR
// FILE: Z.kt
inline class Z(val value: Int)
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME
// FILE: test.kt
fun test() {