JVM IR: Avoid redundant coercions in ExpressionCodegen

This commit is contained in:
Steven Schäfer
2020-02-12 16:42:11 +01:00
committed by Dmitry Petrov
parent 2b2ae8a5f1
commit 5760c0be9c
10 changed files with 51 additions and 59 deletions
@@ -15,11 +15,8 @@ import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry
import org.jetbrains.kotlin.backend.jvm.lower.constantValue
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionOriginal
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil.*
import org.jetbrains.kotlin.codegen.BaseExpressionCodegen
import org.jetbrains.kotlin.codegen.CallGenerator
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.extractReificationArgument
import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.Companion.putNeedClassReificationMarker
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.AS
@@ -361,7 +358,7 @@ class ExpressionCodegen(
visitStatementContainer(body, data).discard()
override fun visitContainerExpression(expression: IrContainerExpression, data: BlockInfo) =
visitStatementContainer(expression, data).coerce(expression.type)
visitStatementContainer(expression, data)
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: BlockInfo): PromisedValue {
classCodegen.context.irIntrinsics.getIntrinsic(expression.symbol)
@@ -459,14 +456,23 @@ class ExpressionCodegen(
}
expression is IrConstructorCall ->
MaterialValue(this, asmType, expression.type)
expression.type.isUnit() && callable.asmMethod.returnType == Type.VOID_TYPE ->
//don't generate redundant UNIT/pop instructions
immaterialUnitValue
expression.type.isUnit() ->
expression.type.isUnit() -> {
// NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail.
MaterialValue(this, callable.asmMethod.returnType, callee.returnType).discard().coerce(expression.type)
if (callable.asmMethod.returnType != Type.VOID_TYPE)
MaterialValue(this, callable.asmMethod.returnType, callee.returnType).discard()
// don't generate redundant UNIT/pop instructions
immaterialUnitValue
}
callee.parentAsClass.isAnnotationClass && callable.asmMethod.returnType == AsmTypes.JAVA_CLASS_TYPE -> {
wrapJavaClassIntoKClass(mv)
MaterialValue(this, AsmTypes.K_CLASS_TYPE, expression.type)
}
callee.parentAsClass.isAnnotationClass && callable.asmMethod.returnType == AsmTypes.JAVA_CLASS_ARRAY_TYPE -> {
wrapJavaClassesIntoKClasses(mv)
MaterialValue(this, AsmTypes.K_CLASS_ARRAY_TYPE, expression.type)
}
else ->
MaterialValue(this, callable.asmMethod.returnType, callee.returnType).coerce(expression.type)
MaterialValue(this, callable.asmMethod.returnType, callee.returnType)
}
}
@@ -529,24 +535,28 @@ class ExpressionCodegen(
val realField = callee.resolveFakeOverride()!!
val fieldType = typeMapper.mapType(realField.type)
val ownerType = typeMapper.mapClass(callee.parentAsClass).internalName
val fieldName = realField.name.asString()
val isStatic = expression.receiver == null
expression.markLineNumber(startOffset = true)
expression.receiver?.accept(this, data)?.materialize()
val ownerName = expression.receiver?.let { receiver ->
val ownerType = typeMapper.mapTypeAsDeclaration(receiver.type)
receiver.accept(this, data).coerce(ownerType, receiver.type).materialize()
ownerType.internalName
} ?: typeMapper.mapClass(callee.parentAsClass).internalName
return if (expression is IrSetField) {
expression.value.accept(this, data).coerce(fieldType, callee.type).materialize()
when {
isStatic -> mv.putstatic(ownerType, fieldName, fieldType.descriptor)
else -> mv.putfield(ownerType, fieldName, fieldType.descriptor)
isStatic -> mv.putstatic(ownerName, fieldName, fieldType.descriptor)
else -> mv.putfield(ownerName, fieldName, fieldType.descriptor)
}
defaultValue(expression.type)
assert(expression.type.isUnit())
immaterialUnitValue
} else {
when {
isStatic -> mv.getstatic(ownerType, fieldName, fieldType.descriptor)
else -> mv.getfield(ownerType, fieldName, fieldType.descriptor)
isStatic -> mv.getstatic(ownerName, fieldName, fieldType.descriptor)
else -> mv.getfield(ownerName, fieldName, fieldType.descriptor)
}
MaterialValue(this, fieldType, callee.type).coerce(expression.type)
MaterialValue(this, fieldType, callee.type)
}
}
@@ -721,7 +731,7 @@ class ExpressionCodegen(
val kotlinType = typeOperand.toKotlinType()
return when (expression.operator) {
IrTypeOperator.IMPLICIT_CAST ->
expression.argument.accept(this, data).coerce(expression.type)
expression.argument.accept(this, data)
IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST -> {
val result = expression.argument.accept(this, data)
@@ -737,7 +747,7 @@ class ExpressionCodegen(
assert(expression.operator == IrTypeOperator.CAST) { "IrTypeOperator.SAFE_CAST should have been lowered." }
TypeIntrinsics.checkcast(mv, kotlinType, boxedRightType, false)
}
MaterialValue(this, boxedRightType, expression.type).coerce(expression.type)
MaterialValue(this, boxedRightType, expression.type)
}
IrTypeOperator.INSTANCEOF -> {
@@ -978,7 +988,13 @@ class ExpressionCodegen(
override fun visitThrow(expression: IrThrow, data: BlockInfo): PromisedValue {
expression.markLineNumber(startOffset = true)
expression.value.accept(this, data).materialize()
val exception = expression.value.accept(this, data)
// Avoid unecessary CHECKCASTs to java/lang/Throwable. If the exception is not of type Object
// then it must be some subtype of throwable and we don't need to coerce it.
if (exception.type == OBJECT_TYPE)
exception.coerce(context.irBuiltIns.throwableType).materialize()
else
exception.materialize()
mv.athrow()
return immaterialUnitValue
}
@@ -117,28 +117,11 @@ fun PromisedValue.coerce(target: Type, irTarget: IrType): PromisedValue {
}
}
return when {
// All unsafe coercions between irTypes should use the UnsafeCoerce intrinsic
type == target -> this
type == AsmTypes.JAVA_CLASS_TYPE && target == AsmTypes.K_CLASS_TYPE && irType.isKClass() ->
object : PromisedValue(codegen, target, irTarget) {
override fun materialize() {
this@coerce.materialize()
AsmUtil.wrapJavaClassIntoKClass(mv)
}
}
type == AsmTypes.JAVA_CLASS_ARRAY_TYPE && target == AsmTypes.K_CLASS_ARRAY_TYPE && irType.isArrayOfKClass() ->
object : PromisedValue(codegen, target, irTarget) {
override fun materialize() {
this@coerce.materialize()
AsmUtil.wrapJavaClassesIntoKClasses(mv)
}
}
else -> object : PromisedValue(codegen, target, irTarget) {
override fun materialize() {
val value = this@coerce.materialized
StackValue.coerce(value.type, type, mv)
}
// All unsafe coercions between irTypes should use the UnsafeCoerce intrinsic
return if (type == target) this else object : PromisedValue(codegen, target, irTarget) {
override fun materialize() {
val value = this@coerce.materialized
StackValue.coerce(value.type, type, mv)
}
}
}
@@ -149,10 +132,6 @@ fun PromisedValue.coerce(irTarget: IrType) =
fun PromisedValue.coerceToBoxed(irTarget: IrType) =
coerce(typeMapper.boxType(irTarget), irTarget)
fun PromisedValue.boxInlineClasses(irTarget: IrType) =
if (irTarget.classOrNull?.owner?.isInline == true)
coerceToBoxed(irTarget) else this
// Same as above, but with a return type that allows conditional jumping.
fun PromisedValue.coerceToBoolean() =
when (val coerced = coerce(Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType)) {
@@ -23,7 +23,8 @@ import org.jetbrains.org.objectweb.asm.Type
object ArrayGet : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val receiver = expression.dispatchReceiver!!.accept(codegen, data).materialized
val dispatchReceiver = expression.dispatchReceiver!!
val receiver = dispatchReceiver.accept(codegen, data).coerce(dispatchReceiver.type).materialized
val elementType = AsmUtil.correctElementType(receiver.type)
expression.getValueArgument(0)!!.accept(codegen, data)
.coerce(Type.INT_TYPE, codegen.context.irBuiltIns.intType)
@@ -24,7 +24,8 @@ import org.jetbrains.org.objectweb.asm.Type
object ArraySet : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
val receiver = expression.dispatchReceiver!!.accept(codegen, data).materialized
val dispatchReceiver = expression.dispatchReceiver!!
val receiver = dispatchReceiver.accept(codegen, data).coerce(dispatchReceiver.type).materialized
val elementType = AsmUtil.correctElementType(receiver.type)
val elementIrType = receiver.irType.getArrayElementType(codegen.context.irBuiltIns)
expression.getValueArgument(0)!!.accept(codegen, data).coerce(Type.INT_TYPE, codegen.context.irBuiltIns.intType).materialize()
@@ -1,5 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND_MULTI_MODULE: JVM_IR
// TARGET_BACKEND: JVM
// WITH_REFLECT
// FILE: 1.kt
@@ -12,6 +12,6 @@ fun f() {
// 1 LOCALVARIABLE c C L3 L\d+ 0
// JVM_IR_TEMPLATES
// 1 ISTORE 2\s+L5
// 1 ISTORE 2\s+L4
// 1 ILOAD 2\s+INVOKEVIRTUAL java/io/PrintStream.print \(C\)V
// 1 LOCALVARIABLE c C L5 L\d+ 2
// 1 LOCALVARIABLE c C L4 L\d+ 2
@@ -1,5 +1,4 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND: JVM_IR
// FILE: utils.kt
@@ -1,5 +1,4 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND: JVM_IR
// FILE: utils.kt
@@ -10,7 +10,7 @@ fun <T> takeVarargs(vararg e: T) {}
fun test(u1: UInt, u2: UInt, us: Array<UInt>) {
takeVarargs(*us) // copy + checkcast (on non-ir backend)
takeVarargs(u1, u2, *us) // 2 box + ...
takeVarargs(u1, u2, *us) // 2 box + checkcast (on non-ir backend)
}
// @TestKt.class:
@@ -26,4 +26,4 @@ fun test(u1: UInt, u2: UInt, us: Array<UInt>) {
// 2 CHECKCAST \[LUInt
// JVM_IR_TEMPLATES
// 1 CHECKCAST \[LUInt
// 0 CHECKCAST \[LUInt
@@ -1,5 +1,4 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND: JVM_IR
// FILE: utils.kt