diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt index 9b1d136ba28..e92a4ab9802 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InitializersLowering.kt @@ -51,7 +51,7 @@ abstract class InitializersLoweringBase(open val context: CommonBackendContext) irClass.declarations.removeAll { it is IrAnonymousInitializer && filter(it) } } - protected open fun shouldEraseFieldInitializer(irField: IrField): Boolean = true + protected open fun shouldEraseFieldInitializer(irField: IrField): Boolean = irField.correspondingPropertySymbol?.owner?.isConst != true private fun handleField(irClass: IrClass, declaration: IrField): IrStatement? = declaration.initializer?.run { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/PropertyAccessorInlineLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/PropertyAccessorInlineLowering.kt new file mode 100644 index 00000000000..c3755136a7b --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/PropertyAccessorInlineLowering.kt @@ -0,0 +1,134 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.common.lower.optimizations + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.ir.isTopLevel +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl +import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import org.jetbrains.kotlin.ir.util.isEffectivelyExternal +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.FqName + +class PropertyAccessorInlineLowering(private val context: CommonBackendContext) : FileLoweringPass { + + private val IrProperty.isSafeToInline: Boolean get() = isTopLevel || (modality === Modality.FINAL || visibility == Visibilities.PRIVATE) || (parent as IrClass).modality === Modality.FINAL + + // TODO: implement general function inlining optimization and replace it with + private inner class AccessorInliner : IrElementTransformerVoid() { + + private val unitType = context.irBuiltIns.unitType + + override fun visitCall(expression: IrCall): IrExpression { + expression.transformChildrenVoid(this) + + val callee = expression.symbol.owner as IrSimpleFunction + val property = callee.correspondingPropertySymbol?.owner ?: return expression + + // Some devirtualization required here + if (!property.isSafeToInline) return expression + + val parent = property.parent + if (parent is IrClass) { + // TODO: temporary workarounds + if (parent.isExpect || property.isExpect) return expression + if (parent.parent is IrExternalPackageFragment) return expression + if (parent.isInline) return expression + } + if (property.isEffectivelyExternal()) return expression + + if (property.isConst) { + val initializer = + (property.backingField?.initializer ?: error("Constant property has to have a backing field with initializer")) + return initializer.expression.deepCopyWithSymbols() + } + + val backingField = property.backingField ?: return expression + + if (property.getter === callee) { + return tryInlineSimpleGetter(expression, callee, backingField) ?: expression + } + + if (property.setter === callee) { + return tryInlineSimpleSetter(expression, callee, backingField) ?: expression + } + + return expression + } + + private fun tryInlineSimpleGetter(call: IrCall, callee: IrSimpleFunction, backingField: IrField): IrExpression? { + if (!isSimpleGetter(callee, backingField)) return null + + return call.run { + IrGetFieldImpl(startOffset, endOffset, backingField.symbol, backingField.type, call.dispatchReceiver, origin) + } + } + + private fun isSimpleGetter(callee: IrSimpleFunction, backingField: IrField): Boolean { + val body = callee.body?.let { it as IrBlockBody } ?: return false + + val stmt = body.statements.singleOrNull() ?: return false + val returnStmt = stmt as? IrReturn ?: return false + val getFieldStmt = returnStmt.value as? IrGetField ?: return false + if (getFieldStmt.symbol !== backingField.symbol) return false + val receiver = getFieldStmt.receiver + + if (receiver == null) { + assert(callee.dispatchReceiverParameter == null) + return true + } + + if (receiver is IrGetValue) return receiver.symbol.owner === callee.dispatchReceiverParameter + + return false + } + + private fun tryInlineSimpleSetter(call: IrCall, callee: IrSimpleFunction, backingField: IrField): IrExpression? { + if (!isSimpleSetter(callee, backingField)) return null + + return call.run { + val value = getValueArgument(0) ?: error("Setter should have a value argument") + IrSetFieldImpl(startOffset, endOffset, backingField.symbol, call.dispatchReceiver, value, unitType, origin) + } + } + + private fun isSimpleSetter(callee: IrSimpleFunction, backingField: IrField): Boolean { + val body = callee.body?.let { it as IrBlockBody } ?: return false + + val stmt = body.statements.singleOrNull() ?: return false + val setFieldStmt = stmt as? IrSetField ?: return false + if (setFieldStmt.symbol !== backingField.symbol) return false + + // TODO: support constant setters + val setValue = setFieldStmt.value as? IrGetValue ?: return false + val valueSymbol = callee.valueParameters.single().symbol + if (setValue.symbol !== valueSymbol) return false + + val receiver = setFieldStmt.receiver + + if (receiver == null) { + assert(callee.dispatchReceiverParameter == null) + return true + } + + if (receiver is IrGetValue) return receiver.symbol.owner === callee.dispatchReceiverParameter + + return false + } + } + + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(AccessorInliner()) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index d7ac0c75ef1..64a336d5705 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -9,6 +9,8 @@ import org.jetbrains.kotlin.backend.common.* import org.jetbrains.kotlin.backend.common.lower.* import org.jetbrains.kotlin.backend.common.lower.inline.FunctionInlining import org.jetbrains.kotlin.backend.common.lower.loops.ForLoopsLowering +import org.jetbrains.kotlin.backend.common.lower.optimizations.FoldConstantLowering +import org.jetbrains.kotlin.backend.common.lower.optimizations.PropertyAccessorInlineLowering import org.jetbrains.kotlin.backend.common.phaser.* import org.jetbrains.kotlin.ir.backend.js.lower.* import org.jetbrains.kotlin.ir.backend.js.lower.calls.CallsLowering @@ -161,7 +163,20 @@ private val returnableBlockLoweringPhase = makeJsModulePhase( private val forLoopsLoweringPhase = makeJsModulePhase( ::ForLoopsLowering, name = "ForLoopsLowering", - description = "For loops lowering" + description = "[Optimization] For loops lowering" +) + +private val propertyAccessorInlinerLoweringPhase = makeJsModulePhase( + ::PropertyAccessorInlineLowering, + name = "PropertyAccessorInlineLowering", + description = "[Optimization] Inline property accessors" +) + +private val foldConstantLoweringPhase = makeJsModulePhase( + ::FoldConstantLowering, + name = "FoldConstantLowering", + description = "[Optimization] Constant Folding", + prerequisite = setOf(propertyAccessorInlinerLoweringPhase) ) private val localDelegatedPropertiesLoweringPhase = makeJsModulePhase( @@ -418,6 +433,9 @@ val jsPhases = namedIrModulePhase( suspendFunctionsLoweringPhase then returnableBlockLoweringPhase then forLoopsLoweringPhase then + primitiveCompanionLoweringPhase then + propertyAccessorInlinerLoweringPhase then + foldConstantLoweringPhase then privateMembersLoweringPhase then callableReferenceLoweringPhase then defaultArgumentStubGeneratorPhase then @@ -436,7 +454,6 @@ val jsPhases = namedIrModulePhase( inlineClassLoweringPhase then autoboxingTransformerPhase then blockDecomposerLoweringPhase then - primitiveCompanionLoweringPhase then constLoweringPhase then objectDeclarationLoweringPhase then objectUsageLoweringPhase then diff --git a/compiler/testData/codegen/box/intrinsics/tostring.kt b/compiler/testData/codegen/box/intrinsics/tostring.kt index 6c121a8f9d6..cfacc542f57 100644 --- a/compiler/testData/codegen/box/intrinsics/tostring.kt +++ b/compiler/testData/codegen/box/intrinsics/tostring.kt @@ -1,6 +1,7 @@ // IGNORE_BACKEND_FIR: JVM_IR // TODO: muted automatically, investigate should it be ran for JS or not // IGNORE_BACKEND: JS +// IGNORE_BACKEND: JS_IR fun box(): String { if (239.toByte().toString() != (239.toByte() as Byte?).toString()) return "byte failed" diff --git a/compiler/testData/codegen/box/strings/stringBuilderAppend.kt b/compiler/testData/codegen/box/strings/stringBuilderAppend.kt index db24ac1b285..30c2eb17951 100644 --- a/compiler/testData/codegen/box/strings/stringBuilderAppend.kt +++ b/compiler/testData/codegen/box/strings/stringBuilderAppend.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: JS_IR -// TODO: muted automatically, investigate should it be ran for JS or not // IGNORE_BACKEND: JS class A() {