JVM_IR. Support runtime string concatenation

This commit is contained in:
Mikhael Bogdanov
2020-09-30 11:25:26 +02:00
parent 1938f9459f
commit cf5bd38bec
4 changed files with 158 additions and 65 deletions
@@ -47,7 +47,7 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE import org.jetbrains.kotlin.resolve.jvm.AsmTypes.*
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext
@@ -1167,6 +1167,36 @@ class ExpressionCodegen(
return unitValue return unitValue
} }
override fun visitStringConcatenation(expression: IrStringConcatenation, data: BlockInfo): PromisedValue {
assert(context.state.runtimeStringConcat.isDynamic) {
"IrStringConcatenation expression should be presented only with dynamic concatenation: ${expression.dump()}"
}
val generator = StringConcatGenerator(context.state.runtimeStringConcat, mv)
expression.arguments.forEach { arg ->
if (arg is IrConst<*>) {
val type = when (arg.kind) {
IrConstKind.Boolean -> Type.BOOLEAN_TYPE
IrConstKind.Char -> Type.CHAR_TYPE
IrConstKind.Int -> Type.INT_TYPE
IrConstKind.Long -> Type.LONG_TYPE
IrConstKind.Float -> Type.FLOAT_TYPE
IrConstKind.Double -> Type.DOUBLE_TYPE
IrConstKind.Byte -> Type.BYTE_TYPE
IrConstKind.Short -> Type.SHORT_TYPE
IrConstKind.String -> JAVA_STRING_TYPE
IrConstKind.Null -> OBJECT_TYPE
}
generator.putValueOrProcessConstant(StackValue.constant(arg.value, type, null))
} else {
val value = arg.accept(this, data)
value.materializeAt(value.type, value.irType)
generator.invokeAppend(value.type)
}
}
generator.genToString()
return MaterialValue(this@ExpressionCodegen, JAVA_STRING_TYPE, context.irBuiltIns.stringType)
}
override fun visitGetClass(expression: IrGetClass, data: BlockInfo) = override fun visitGetClass(expression: IrGetClass, data: BlockInfo) =
generateClassLiteralReference(expression, true, data) generateClassLiteralReference(expression, true, data)
@@ -20,13 +20,19 @@ import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrStringConcatenationImpl
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructors import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.functions import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
internal val jvmStringConcatenationLowering = makeIrFilePhase( internal val jvmStringConcatenationLowering = makeIrFilePhase<JvmBackendContext>(
::JvmStringConcatenationLowering, { context: JvmBackendContext ->
if (!context.state.runtimeStringConcat.isDynamic)
JvmStringConcatenationLowering(context)
else
JvmDynamicStringConcatenationLowering(context)
},
name = "StringConcatenation", name = "StringConcatenation",
description = "Replace IrStringConcatenation with string builders", description = "Replace IrStringConcatenation with string builders",
// flattenStringConcatenationPhase consolidates string concatenation expressions. // flattenStringConcatenationPhase consolidates string concatenation expressions.
@@ -34,44 +40,11 @@ internal val jvmStringConcatenationLowering = makeIrFilePhase(
prerequisite = setOf(flattenStringConcatenationPhase, forLoopsPhase) prerequisite = setOf(flattenStringConcatenationPhase, forLoopsPhase)
) )
/**
* 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 private val IrClass.toStringFunction: IrSimpleFunction
get() = functions.single { get() = functions.single {
with(FlattenStringConcatenationLowering) { it.isToString } 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
private fun IrBuilderWithScope.normalizeArgument(expression: IrExpression): IrExpression = private fun IrBuilderWithScope.normalizeArgument(expression: IrExpression): IrExpression =
if (expression.type.isByte() || expression.type.isShort()) { if (expression.type.isByte() || expression.type.isShort()) {
@@ -116,6 +89,46 @@ private class JvmStringConcatenationLowering(val context: JvmBackendContext) : F
irCall(toStringReplacement).apply { putValueArgument(0, expression) } irCall(toStringReplacement).apply { putValueArgument(0, expression) }
} }
private fun IrExpression.unwrapImplicitNotNull() =
if (this is IrTypeOperatorCall && operator == IrTypeOperator.IMPLICIT_NOTNULL)
argument
else
this
/**
* 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 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
override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression { override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression {
expression.transformChildrenVoid(this) expression.transformChildrenVoid(this)
return context.createJvmIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset).run { return context.createJvmIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset).run {
@@ -124,12 +137,6 @@ private class JvmStringConcatenationLowering(val context: JvmBackendContext) : F
// check (see KT-36625, pending language design decision). To maintain compatibility with the non-IR backend, we remove // check (see KT-36625, pending language design decision). To maintain compatibility with the non-IR backend, we remove
// IMPLICIT_NOTNULL casts from all arguments (nullability checks are generated in JvmArgumentNullabilityAssertionsLowering). // IMPLICIT_NOTNULL casts from all arguments (nullability checks are generated in JvmArgumentNullabilityAssertionsLowering).
fun IrExpression.unwrapImplicitNotNull() =
if (this is IrTypeOperatorCall && operator == IrTypeOperator.IMPLICIT_NOTNULL)
argument
else
this
val arguments = expression.arguments val arguments = expression.arguments
when { when {
arguments.isEmpty() -> arguments.isEmpty() ->
@@ -165,3 +172,38 @@ private class JvmStringConcatenationLowering(val context: JvmBackendContext) : F
} }
} }
} }
/**
* This lowering pass lowers inline classes arguments of [IrStringConcatenation].
* Transformed [IrStringConcatenation] would be used as is in [ExpressionCodegen] for makeConcat/makeConcatWithConstants bytecode generation
*/
private class JvmDynamicStringConcatenationLowering(val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid()
override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression {
expression.transformChildrenVoid(this)
return context.createJvmIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset).run {
// When `String.plus(Any?)` is invoked with receiver of platform type String or String with enhanced nullability, this could
// fail a nullability check (NullPointerException) on the receiver. However, the non-IR backend currently does NOT insert this
// check (see KT-36625, pending language design decision). To maintain compatibility with the non-IR backend, we remove
// IMPLICIT_NOTNULL casts from all arguments (nullability checks are generated in JvmArgumentNullabilityAssertionsLowering).
val arguments = expression.arguments
when {
arguments.isEmpty() ->
irString("")
else -> {
IrStringConcatenationImpl(
expression.startOffset,
expression.endOffset,
expression.type,
arguments.map { argument ->
lowerInlineClassArgument(argument) ?: argument.unwrapImplicitNotNull()
})
}
}
}
}
}
@@ -2,12 +2,15 @@
// JVM_TARGET: 9 // JVM_TARGET: 9
class A class A
inline class IC(val x: String)
inline fun test(s: (String) -> Unit) { inline fun test(s: (String) -> Unit) {
s("456") s("456")
} }
fun box(a: String, b: String?) { fun box(a: String, b: String?, x: IC?) {
val s = a + "1" + "2" + 3 + 4L + b + 5.0 + 6F + '7' + A() + true + false + 1u val p = 3147483648u
val s = a + "1" + "2" + 3 + 4L + b + 5.0 + 6F + '7' + A() + true + false + 3147483647u + p + x
a.plus(b) a.plus(b)
b?.plus(a) b?.plus(a)
@@ -17,9 +20,27 @@ fun box(a: String, b: String?) {
test("123"::plus) test("123"::plus)
} }
// unsigned constant 1u processed as argument (last \u0001) // 7 INVOKEDYNAMIC makeConcatWithConstants
// 1 "IC\(x=\\u0001\)"
// 1 "\\u00011234\\u00015.06.07\\u0001truefalse\\u0001"
// 6 INVOKEDYNAMIC makeConcatWithConstants
// 0 append // 0 append
// 0 stringPlus // 0 stringPlus
// JVM_TEMPLATES
// unsigned constant 3147483647u is processed as argument (\u0001) and it adds additional `UInt.toString-impl` call
// 1 "\\u00011234\\u00015.06.07\\u0001truefalse\\u0001\\u0001\\u0001"
// 2 INVOKESTATIC kotlin/UInt.toString-impl
// Old backend perform inline class boxing...
// 1 INVOKESTATIC IC.box-impl
// one in IC.toString()
// 1 INVOKESTATIC IC.toString-impl
// JVM_IR_TEMPLATES
// unsigned constant 3147483647u is inlined
// 1 "\\u00011234\\u00015.06.07\\u0001truefalse3147483647\\u0001\\u0001"
// 1 INVOKESTATIC kotlin/UInt.toString-impl
// .. but ir backend performs wise `toString-impl` call
// one in IC.toString() + one in concatenation
// 2 INVOKESTATIC IC.toString-impl
@@ -7,7 +7,7 @@ inline fun test(s: (String) -> Unit) {
} }
fun box(a: String, b: String?) { fun box(a: String, b: String?) {
val s = a + "1" + "2" + 3 + 4L + b + 5.0 + 6F + '7' + A() + true + false + 1u val s = a + "1" + "2" + 3 + 4L + b + 5.0 + 6F + '7' + A() + true + false + 3147483647u
a.plus(b) a.plus(b)
b?.plus(a) b?.plus(a)