JVM_IR: Do not add redundant field initializers.
Initializers are "set field" expressions and are considered redundant when they are: 1. In the primary constructor; and 2. Set the field to `0`, `false`, or `null`; and 3. Have a `null` origin. I.e., not in an initializer block or constructor body, and therefore the field could not have been set by a prior expression.
This commit is contained in:
committed by
max-kammerer
parent
9e8972f1f9
commit
e91a16556c
+45
-1
@@ -33,6 +33,8 @@ import org.jetbrains.kotlin.ir.IrElement
|
|||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
|
import org.jetbrains.kotlin.ir.types.isMarkedNullable
|
||||||
import org.jetbrains.kotlin.ir.types.isNothing
|
import org.jetbrains.kotlin.ir.types.isNothing
|
||||||
import org.jetbrains.kotlin.ir.types.toKotlinType
|
import org.jetbrains.kotlin.ir.types.toKotlinType
|
||||||
import org.jetbrains.kotlin.ir.util.dump
|
import org.jetbrains.kotlin.ir.util.dump
|
||||||
@@ -455,12 +457,54 @@ class ExpressionCodegen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitSetField(expression: IrSetField, data: BlockInfo): StackValue {
|
override fun visitSetField(expression: IrSetField, data: BlockInfo): StackValue {
|
||||||
|
val expressionValue = expression.value
|
||||||
|
if (irFunction is IrConstructor && irFunction.isPrimary) {
|
||||||
|
// Do not add redundant field initializers that initialize to default values.
|
||||||
|
// "expression.origin == null" means that the field is initialized when it is declared,
|
||||||
|
// i.e., not in an initializer block or constructor body.
|
||||||
|
if (expression.origin == null && expressionValue is IrConst<*> && isDefaultValueForType(
|
||||||
|
expression.symbol.owner.type, expressionValue
|
||||||
|
)
|
||||||
|
) return none()
|
||||||
|
}
|
||||||
expression.markLineNumber(startOffset = true)
|
expression.markLineNumber(startOffset = true)
|
||||||
val fieldValue = generateFieldValue(expression, data)
|
val fieldValue = generateFieldValue(expression, data)
|
||||||
fieldValue.store(expression.value.accept(this, data), mv)
|
fieldValue.store(expressionValue.accept(this, data), mv)
|
||||||
return none()
|
return none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given constant value is the JVM's default value for the given type.
|
||||||
|
* See: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.3
|
||||||
|
*/
|
||||||
|
private fun isDefaultValueForType(fieldType: IrType, constExpression: IrConst<*>): Boolean {
|
||||||
|
val value = constExpression.value
|
||||||
|
val type = constExpression.asmType
|
||||||
|
if (isPrimitive(type)) {
|
||||||
|
if (!fieldType.isMarkedNullable() && value is Number) {
|
||||||
|
if (type in setOf(Type.INT_TYPE, Type.BYTE_TYPE, Type.LONG_TYPE, Type.SHORT_TYPE) && value.toLong() == 0L) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (type === Type.DOUBLE_TYPE && value.toDouble().equals(0.0)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (type === Type.FLOAT_TYPE && value.toFloat().equals(0.0f)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === Type.BOOLEAN_TYPE && value is Boolean && !value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (type === Type.CHAR_TYPE && value is Char && value.toInt() == 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if (value == null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
private fun generateLocal(symbol: IrSymbol, type: Type): StackValue {
|
private fun generateLocal(symbol: IrSymbol, type: Type): StackValue {
|
||||||
val index = findLocalIndex(symbol)
|
val index = findLocalIndex(symbol)
|
||||||
StackValue.local(index, type).put(type, mv)
|
StackValue.local(index, type).put(type, mv)
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// No initializers for this class because the fields/properties are initialized to defaults.
|
||||||
|
class RedundantInitializersToDefault {
|
||||||
|
companion object {
|
||||||
|
// Constants
|
||||||
|
const val constInt: Int = 0
|
||||||
|
const val constByte: Byte = 0
|
||||||
|
const val constLong: Long = 0L
|
||||||
|
const val constShort: Short = 0
|
||||||
|
const val constDouble: Double = 0.0
|
||||||
|
const val constFloat: Float = 0.0f
|
||||||
|
const val constBoolean: Boolean = false
|
||||||
|
const val constChar: Char = '\u0000'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
|
||||||
|
val myIntPropFromConst: Int = constInt
|
||||||
|
val myBytePropFromConst: Byte = constByte
|
||||||
|
val myLongPropFromConst: Long = constLong
|
||||||
|
val myShortPropFromConst: Short = constShort
|
||||||
|
val myDoublePropFromConst: Double = constDouble
|
||||||
|
val myFloatPropFromConst: Float = constFloat
|
||||||
|
val myBooleanPropFromConst: Boolean = constBoolean
|
||||||
|
val myCharPropFromConst: Char = constChar
|
||||||
|
|
||||||
|
val myIntProp: Int = 0
|
||||||
|
val myByteProp: Byte = 0
|
||||||
|
val myLongProp: Long = 0L
|
||||||
|
val myShortProp: Short = 0
|
||||||
|
val myDoubleProp: Double = 0.0
|
||||||
|
val myFloatProp: Float = 0.0f
|
||||||
|
val myBooleanProp: Boolean = false
|
||||||
|
val myCharProp: Char = '\u0000'
|
||||||
|
|
||||||
|
val myStringProp: String? = null
|
||||||
|
val myAnyProp: Any? = null
|
||||||
|
val myObjectProp: java.lang.Object? = null
|
||||||
|
val myIntegerProp: java.lang.Integer? = null
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val myIntFieldFromConst: Int = constInt
|
||||||
|
@JvmField
|
||||||
|
val myByteFieldFromConst: Byte = constByte
|
||||||
|
@JvmField
|
||||||
|
val myLongFieldFromConst: Long = constLong
|
||||||
|
@JvmField
|
||||||
|
val myShortFieldFromConst: Short = constShort
|
||||||
|
@JvmField
|
||||||
|
val myDoubleFieldFromConst: Double = constDouble
|
||||||
|
@JvmField
|
||||||
|
val myFloatFieldFromConst: Float = constFloat
|
||||||
|
@JvmField
|
||||||
|
val myBooleanFieldFromConst: Boolean = constBoolean
|
||||||
|
@JvmField
|
||||||
|
val myCharFieldFromConst: Char = constChar
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val myIntField: Int = 0
|
||||||
|
@JvmField
|
||||||
|
val myByteField: Byte = 0
|
||||||
|
@JvmField
|
||||||
|
val myLongField: Long = 0L
|
||||||
|
@JvmField
|
||||||
|
val myShortField: Short = 0
|
||||||
|
@JvmField
|
||||||
|
val myDoubleField: Double = 0.0
|
||||||
|
@JvmField
|
||||||
|
val myFloatField: Float = 0.0f
|
||||||
|
@JvmField
|
||||||
|
val myBooleanField: Boolean = false
|
||||||
|
@JvmField
|
||||||
|
val myCharField: Char = '\u0000'
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val myStringField: String? = null
|
||||||
|
@JvmField
|
||||||
|
val myAnyField: Any? = null
|
||||||
|
@JvmField
|
||||||
|
val myObjectField: java.lang.Object? = null
|
||||||
|
@JvmField
|
||||||
|
val myIntegerField: java.lang.Integer? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonRedundantInitializers {
|
||||||
|
// NOT redundant because the JVM's default values for floating-point types are positive 0.0.
|
||||||
|
// See: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.3
|
||||||
|
val myDouble: Double = -0.0
|
||||||
|
val myFloat: Float = -0.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 PUTFIELD RedundantInitializersToDefault
|
||||||
|
// 2 PUTFIELD NonRedundantInitializers
|
||||||
@@ -329,6 +329,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/redundantInitializerNumber.kt");
|
runTest("compiler/testData/codegen/bytecodeText/redundantInitializerNumber.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("redundantValInitializer.kt")
|
||||||
|
public void testRedundantValInitializer() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/redundantValInitializer.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("reifiedAsCheck.kt")
|
@TestMetadata("reifiedAsCheck.kt")
|
||||||
public void testReifiedAsCheck() throws Exception {
|
public void testReifiedAsCheck() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/reifiedAsCheck.kt");
|
runTest("compiler/testData/codegen/bytecodeText/reifiedAsCheck.kt");
|
||||||
|
|||||||
@@ -329,6 +329,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/redundantInitializerNumber.kt");
|
runTest("compiler/testData/codegen/bytecodeText/redundantInitializerNumber.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("redundantValInitializer.kt")
|
||||||
|
public void testRedundantValInitializer() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/redundantValInitializer.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("reifiedAsCheck.kt")
|
@TestMetadata("reifiedAsCheck.kt")
|
||||||
public void testReifiedAsCheck() throws Exception {
|
public void testReifiedAsCheck() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/reifiedAsCheck.kt");
|
runTest("compiler/testData/codegen/bytecodeText/reifiedAsCheck.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user