JVM IR: Avoid redundant coercions in ExpressionCodegen
This commit is contained in:
committed by
Dmitry Petrov
parent
2b2ae8a5f1
commit
5760c0be9c
+38
-22
@@ -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.constantValue
|
||||||
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
|
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
|
||||||
import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionOriginal
|
import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionOriginal
|
||||||
|
import org.jetbrains.kotlin.codegen.*
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil.*
|
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.*
|
||||||
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.Companion.putNeedClassReificationMarker
|
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.Companion.putNeedClassReificationMarker
|
||||||
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.AS
|
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.OperationKind.AS
|
||||||
@@ -361,7 +358,7 @@ class ExpressionCodegen(
|
|||||||
visitStatementContainer(body, data).discard()
|
visitStatementContainer(body, data).discard()
|
||||||
|
|
||||||
override fun visitContainerExpression(expression: IrContainerExpression, data: BlockInfo) =
|
override fun visitContainerExpression(expression: IrContainerExpression, data: BlockInfo) =
|
||||||
visitStatementContainer(expression, data).coerce(expression.type)
|
visitStatementContainer(expression, data)
|
||||||
|
|
||||||
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: BlockInfo): PromisedValue {
|
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: BlockInfo): PromisedValue {
|
||||||
classCodegen.context.irIntrinsics.getIntrinsic(expression.symbol)
|
classCodegen.context.irIntrinsics.getIntrinsic(expression.symbol)
|
||||||
@@ -459,14 +456,23 @@ class ExpressionCodegen(
|
|||||||
}
|
}
|
||||||
expression is IrConstructorCall ->
|
expression is IrConstructorCall ->
|
||||||
MaterialValue(this, asmType, expression.type)
|
MaterialValue(this, asmType, expression.type)
|
||||||
expression.type.isUnit() && callable.asmMethod.returnType == Type.VOID_TYPE ->
|
expression.type.isUnit() -> {
|
||||||
//don't generate redundant UNIT/pop instructions
|
|
||||||
immaterialUnitValue
|
|
||||||
expression.type.isUnit() ->
|
|
||||||
// NewInference allows casting `() -> T` to `() -> Unit`. A CHECKCAST here will fail.
|
// 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 ->
|
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 realField = callee.resolveFakeOverride()!!
|
||||||
val fieldType = typeMapper.mapType(realField.type)
|
val fieldType = typeMapper.mapType(realField.type)
|
||||||
val ownerType = typeMapper.mapClass(callee.parentAsClass).internalName
|
|
||||||
val fieldName = realField.name.asString()
|
val fieldName = realField.name.asString()
|
||||||
val isStatic = expression.receiver == null
|
val isStatic = expression.receiver == null
|
||||||
expression.markLineNumber(startOffset = true)
|
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) {
|
return if (expression is IrSetField) {
|
||||||
expression.value.accept(this, data).coerce(fieldType, callee.type).materialize()
|
expression.value.accept(this, data).coerce(fieldType, callee.type).materialize()
|
||||||
when {
|
when {
|
||||||
isStatic -> mv.putstatic(ownerType, fieldName, fieldType.descriptor)
|
isStatic -> mv.putstatic(ownerName, fieldName, fieldType.descriptor)
|
||||||
else -> mv.putfield(ownerType, fieldName, fieldType.descriptor)
|
else -> mv.putfield(ownerName, fieldName, fieldType.descriptor)
|
||||||
}
|
}
|
||||||
defaultValue(expression.type)
|
assert(expression.type.isUnit())
|
||||||
|
immaterialUnitValue
|
||||||
} else {
|
} else {
|
||||||
when {
|
when {
|
||||||
isStatic -> mv.getstatic(ownerType, fieldName, fieldType.descriptor)
|
isStatic -> mv.getstatic(ownerName, fieldName, fieldType.descriptor)
|
||||||
else -> mv.getfield(ownerType, 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()
|
val kotlinType = typeOperand.toKotlinType()
|
||||||
return when (expression.operator) {
|
return when (expression.operator) {
|
||||||
IrTypeOperator.IMPLICIT_CAST ->
|
IrTypeOperator.IMPLICIT_CAST ->
|
||||||
expression.argument.accept(this, data).coerce(expression.type)
|
expression.argument.accept(this, data)
|
||||||
|
|
||||||
IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST -> {
|
IrTypeOperator.CAST, IrTypeOperator.SAFE_CAST -> {
|
||||||
val result = expression.argument.accept(this, data)
|
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." }
|
assert(expression.operator == IrTypeOperator.CAST) { "IrTypeOperator.SAFE_CAST should have been lowered." }
|
||||||
TypeIntrinsics.checkcast(mv, kotlinType, boxedRightType, false)
|
TypeIntrinsics.checkcast(mv, kotlinType, boxedRightType, false)
|
||||||
}
|
}
|
||||||
MaterialValue(this, boxedRightType, expression.type).coerce(expression.type)
|
MaterialValue(this, boxedRightType, expression.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
IrTypeOperator.INSTANCEOF -> {
|
IrTypeOperator.INSTANCEOF -> {
|
||||||
@@ -978,7 +988,13 @@ class ExpressionCodegen(
|
|||||||
|
|
||||||
override fun visitThrow(expression: IrThrow, data: BlockInfo): PromisedValue {
|
override fun visitThrow(expression: IrThrow, data: BlockInfo): PromisedValue {
|
||||||
expression.markLineNumber(startOffset = true)
|
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()
|
mv.athrow()
|
||||||
return immaterialUnitValue
|
return immaterialUnitValue
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-26
@@ -117,28 +117,11 @@ fun PromisedValue.coerce(target: Type, irTarget: IrType): PromisedValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return when {
|
// All unsafe coercions between irTypes should use the UnsafeCoerce intrinsic
|
||||||
// All unsafe coercions between irTypes should use the UnsafeCoerce intrinsic
|
return if (type == target) this else object : PromisedValue(codegen, target, irTarget) {
|
||||||
type == target -> this
|
override fun materialize() {
|
||||||
type == AsmTypes.JAVA_CLASS_TYPE && target == AsmTypes.K_CLASS_TYPE && irType.isKClass() ->
|
val value = this@coerce.materialized
|
||||||
object : PromisedValue(codegen, target, irTarget) {
|
StackValue.coerce(value.type, type, mv)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,10 +132,6 @@ fun PromisedValue.coerce(irTarget: IrType) =
|
|||||||
fun PromisedValue.coerceToBoxed(irTarget: IrType) =
|
fun PromisedValue.coerceToBoxed(irTarget: IrType) =
|
||||||
coerce(typeMapper.boxType(irTarget), irTarget)
|
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.
|
// Same as above, but with a return type that allows conditional jumping.
|
||||||
fun PromisedValue.coerceToBoolean() =
|
fun PromisedValue.coerceToBoolean() =
|
||||||
when (val coerced = coerce(Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType)) {
|
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() {
|
object ArrayGet : IntrinsicMethod() {
|
||||||
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
|
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 elementType = AsmUtil.correctElementType(receiver.type)
|
||||||
expression.getValueArgument(0)!!.accept(codegen, data)
|
expression.getValueArgument(0)!!.accept(codegen, data)
|
||||||
.coerce(Type.INT_TYPE, codegen.context.irBuiltIns.intType)
|
.coerce(Type.INT_TYPE, codegen.context.irBuiltIns.intType)
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import org.jetbrains.org.objectweb.asm.Type
|
|||||||
|
|
||||||
object ArraySet : IntrinsicMethod() {
|
object ArraySet : IntrinsicMethod() {
|
||||||
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
|
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 elementType = AsmUtil.correctElementType(receiver.type)
|
||||||
val elementIrType = receiver.irType.getArrayElementType(codegen.context.irBuiltIns)
|
val elementIrType = receiver.irType.getArrayElementType(codegen.context.irBuiltIns)
|
||||||
expression.getValueArgument(0)!!.accept(codegen, data).coerce(Type.INT_TYPE, codegen.context.irBuiltIns.intType).materialize()
|
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
|
// TARGET_BACKEND: JVM
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ fun f() {
|
|||||||
// 1 LOCALVARIABLE c C L3 L\d+ 0
|
// 1 LOCALVARIABLE c C L3 L\d+ 0
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
// 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 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
|
||||||
Vendored
-1
@@ -1,5 +1,4 @@
|
|||||||
// !LANGUAGE: +InlineClasses
|
// !LANGUAGE: +InlineClasses
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
|
|
||||||
// FILE: utils.kt
|
// FILE: utils.kt
|
||||||
|
|
||||||
|
|||||||
-1
@@ -1,5 +1,4 @@
|
|||||||
// !LANGUAGE: +InlineClasses
|
// !LANGUAGE: +InlineClasses
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
|
|
||||||
// FILE: utils.kt
|
// FILE: utils.kt
|
||||||
|
|
||||||
|
|||||||
compiler/testData/codegen/bytecodeText/inlineClasses/passInlineClassesWithSpreadOperatorToVarargs.kt
Vendored
+2
-2
@@ -10,7 +10,7 @@ fun <T> takeVarargs(vararg e: T) {}
|
|||||||
|
|
||||||
fun test(u1: UInt, u2: UInt, us: Array<UInt>) {
|
fun test(u1: UInt, u2: UInt, us: Array<UInt>) {
|
||||||
takeVarargs(*us) // copy + checkcast (on non-ir backend)
|
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:
|
// @TestKt.class:
|
||||||
@@ -26,4 +26,4 @@ fun test(u1: UInt, u2: UInt, us: Array<UInt>) {
|
|||||||
// 2 CHECKCAST \[LUInt
|
// 2 CHECKCAST \[LUInt
|
||||||
|
|
||||||
// JVM_IR_TEMPLATES
|
// JVM_IR_TEMPLATES
|
||||||
// 1 CHECKCAST \[LUInt
|
// 0 CHECKCAST \[LUInt
|
||||||
|
|||||||
Vendored
-1
@@ -1,5 +1,4 @@
|
|||||||
// !LANGUAGE: +InlineClasses
|
// !LANGUAGE: +InlineClasses
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
|
|
||||||
// FILE: utils.kt
|
// FILE: utils.kt
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user