From e91a16556c1c53732956cedee822c62140b357ae Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Wed, 30 Jan 2019 16:18:59 -0800 Subject: [PATCH] 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. --- .../backend/jvm/codegen/ExpressionCodegen.kt | 46 ++++++++- .../bytecodeText/redundantValInitializer.kt | 94 +++++++++++++++++++ .../codegen/BytecodeTextTestGenerated.java | 5 + .../codegen/IrBytecodeTextTestGenerated.java | 5 + 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 compiler/testData/codegen/bytecodeText/redundantValInitializer.kt diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index cf9deef4620..af1bac0a72d 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -33,6 +33,8 @@ import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* 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.toKotlinType import org.jetbrains.kotlin.ir.util.dump @@ -455,12 +457,54 @@ class ExpressionCodegen( } 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) val fieldValue = generateFieldValue(expression, data) - fieldValue.store(expression.value.accept(this, data), mv) + fieldValue.store(expressionValue.accept(this, data), mv) 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 { val index = findLocalIndex(symbol) StackValue.local(index, type).put(type, mv) diff --git a/compiler/testData/codegen/bytecodeText/redundantValInitializer.kt b/compiler/testData/codegen/bytecodeText/redundantValInitializer.kt new file mode 100644 index 00000000000..024b730c092 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/redundantValInitializer.kt @@ -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 diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index dbd26c98f70..36e6b270a1c 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -329,6 +329,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { 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") public void testReifiedAsCheck() throws Exception { runTest("compiler/testData/codegen/bytecodeText/reifiedAsCheck.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/IrBytecodeTextTestGenerated.java index 42a57c372be..f04ac3294e3 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/IrBytecodeTextTestGenerated.java @@ -329,6 +329,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { 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") public void testReifiedAsCheck() throws Exception { runTest("compiler/testData/codegen/bytecodeText/reifiedAsCheck.kt");