JVM_IR. Support runtime string concatenation
This commit is contained in:
+31
-1
@@ -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)
|
||||||
|
|
||||||
|
|||||||
+98
-56
@@ -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,6 +40,61 @@ internal val jvmStringConcatenationLowering = makeIrFilePhase(
|
|||||||
prerequisite = setOf(flattenStringConcatenationPhase, forLoopsPhase)
|
prerequisite = setOf(flattenStringConcatenationPhase, forLoopsPhase)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val IrClass.toStringFunction: IrSimpleFunction
|
||||||
|
get() = functions.single {
|
||||||
|
with(FlattenStringConcatenationLowering) { it.isToString }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun IrBuilderWithScope.normalizeArgument(expression: IrExpression): IrExpression =
|
||||||
|
if (expression.type.isByte() || expression.type.isShort()) {
|
||||||
|
// There is no special append or valueOf function for byte and short on the JVM.
|
||||||
|
irImplicitCast(expression, context.irBuiltIns.intType)
|
||||||
|
} else if (expression is IrConst<*> && expression.kind == IrConstKind.String && (expression.value as String).length == 1) {
|
||||||
|
// PSI2IR generates const Strings for 1-length literals in string templates (e.g., the space between x and y in "$x $y").
|
||||||
|
// We want to use the more efficient `append(Char)` function in such cases. This mirrors the behavior of the non-IR backend.
|
||||||
|
//
|
||||||
|
// In addition, this also means `append(Char)` will be used for the space in the following case: `x + " " + y`. The non-IR
|
||||||
|
// backend will still use `append(String)` in this case.
|
||||||
|
irChar((expression.value as String)[0])
|
||||||
|
} else {
|
||||||
|
expression
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JvmIrBuilder.callToString(expression: IrExpression): IrExpression {
|
||||||
|
val argument = normalizeArgument(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 (InlineClassAbi.unboxType(expression.type) == null)
|
||||||
|
return null
|
||||||
|
val toStringFunction = expression.type.classOrNull?.owner?.toStringFunction
|
||||||
|
?: return null
|
||||||
|
val toStringReplacement = backendContext.inlineClassReplacements.getReplacementFunction(toStringFunction)
|
||||||
|
?: return null
|
||||||
|
// `C?` can only be unboxed if it wraps a reference type `T!!`, in which case the unboxed type
|
||||||
|
// is `T?`. We can't pass that to `C.toString-impl` without checking for `null`.
|
||||||
|
return if (expression.type.isNullable())
|
||||||
|
irLetS(expression) {
|
||||||
|
irIfNull(context.irBuiltIns.stringType, irGet(it.owner), irString(null.toString()), irCall(toStringReplacement).apply {
|
||||||
|
putValueArgument(0, irGet(it.owner))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else
|
||||||
|
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 lowering pass replaces [IrStringConcatenation]s with StringBuilder appends.
|
||||||
*
|
*
|
||||||
@@ -50,11 +111,6 @@ private class JvmStringConcatenationLowering(val context: JvmBackendContext) : F
|
|||||||
it.valueParameters.size == 0
|
it.valueParameters.size == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private val IrClass.toStringFunction: IrSimpleFunction
|
|
||||||
get() = functions.single {
|
|
||||||
with(FlattenStringConcatenationLowering) { it.isToString }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val toStringFunction = stringBuilder.toStringFunction
|
private val toStringFunction = stringBuilder.toStringFunction
|
||||||
|
|
||||||
private val defaultAppendFunction = stringBuilder.functions.single {
|
private val defaultAppendFunction = stringBuilder.functions.single {
|
||||||
@@ -73,49 +129,6 @@ private class JvmStringConcatenationLowering(val context: JvmBackendContext) : F
|
|||||||
private fun typeToAppendFunction(type: IrType): IrSimpleFunction =
|
private fun typeToAppendFunction(type: IrType): IrSimpleFunction =
|
||||||
appendFunctions[type] ?: defaultAppendFunction
|
appendFunctions[type] ?: defaultAppendFunction
|
||||||
|
|
||||||
private fun IrBuilderWithScope.normalizeArgument(expression: IrExpression): IrExpression =
|
|
||||||
if (expression.type.isByte() || expression.type.isShort()) {
|
|
||||||
// There is no special append or valueOf function for byte and short on the JVM.
|
|
||||||
irImplicitCast(expression, context.irBuiltIns.intType)
|
|
||||||
} else if (expression is IrConst<*> && expression.kind == IrConstKind.String && (expression.value as String).length == 1) {
|
|
||||||
// PSI2IR generates const Strings for 1-length literals in string templates (e.g., the space between x and y in "$x $y").
|
|
||||||
// We want to use the more efficient `append(Char)` function in such cases. This mirrors the behavior of the non-IR backend.
|
|
||||||
//
|
|
||||||
// In addition, this also means `append(Char)` will be used for the space in the following case: `x + " " + y`. The non-IR
|
|
||||||
// backend will still use `append(String)` in this case.
|
|
||||||
irChar((expression.value as String)[0])
|
|
||||||
} else {
|
|
||||||
expression
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun JvmIrBuilder.callToString(expression: IrExpression): IrExpression {
|
|
||||||
val argument = normalizeArgument(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 (InlineClassAbi.unboxType(expression.type) == null)
|
|
||||||
return null
|
|
||||||
val toStringFunction = expression.type.classOrNull?.owner?.toStringFunction
|
|
||||||
?: return null
|
|
||||||
val toStringReplacement = backendContext.inlineClassReplacements.getReplacementFunction(toStringFunction)
|
|
||||||
?: return null
|
|
||||||
// `C?` can only be unboxed if it wraps a reference type `T!!`, in which case the unboxed type
|
|
||||||
// is `T?`. We can't pass that to `C.toString-impl` without checking for `null`.
|
|
||||||
return if (expression.type.isNullable())
|
|
||||||
irLetS(expression) {
|
|
||||||
irIfNull(context.irBuiltIns.stringType, irGet(it.owner), irString(null.toString()), irCall(toStringReplacement).apply {
|
|
||||||
putValueArgument(0, irGet(it.owner))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else
|
|
||||||
irCall(toStringReplacement).apply { putValueArgument(0, expression) }
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
+1
-1
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user