diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/KonanBCEForLoopBodyTransformer.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/KonanBCEForLoopBodyTransformer.kt index deaee249332..9cf583d13b1 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/KonanBCEForLoopBodyTransformer.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/KonanBCEForLoopBodyTransformer.kt @@ -8,10 +8,12 @@ package org.jetbrains.kotlin.backend.konan.optimizations import org.jetbrains.kotlin.backend.common.CommonBackendContext import org.jetbrains.kotlin.backend.common.lower.loops.* import org.jetbrains.kotlin.backend.konan.ir.KonanNameConventions -import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin -import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.symbols.IrValueSymbol import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.getClass @@ -20,11 +22,23 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.util.OperatorNameConventions +// Base class describing value of expression. +sealed class ValueDescription + +// Contains information about base variable symbol. +data class LocalValueDescription(val variableSymbol: IrValueSymbol) : ValueDescription() + +// Contains information about property symbol and receiver's value description. +data class PropertyValueDescription(val receiver: ValueDescription?, val propertySymbol: IrPropertySymbol) : ValueDescription() + +data class ObjectValueDescription(val classSymbol: IrClassSymbol) : ValueDescription() + // Class contains information about analyzed loop. -internal data class BoundsCheckAnalysisResult(val boundsAreSafe: Boolean, val arrayInLoop: IrValueSymbol?) +internal class BoundsCheckAnalysisResult(val boundsAreSafe: Boolean, val arrayInLoop: ValueDescription?) + // TODO: support `forEachIndexed`. Function is inlined and index is separate variable which isn't connected with loop induction variable. /** - * Transformer for for loops bodies replacing get/set operators on analogs without bounds check where it's possible. + * Transformer for loops bodies replacing get/set operators on analogs without bounds check where it's possible. */ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() { lateinit var mainLoopVariable: IrVariable @@ -93,8 +107,11 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() { } else -> false } - val array = ((functionCall.dispatchReceiver as? IrCall)?.dispatchReceiver as? IrGetValue)?.symbol - return BoundsCheckAnalysisResult(boundsAreSafe, array) + return BoundsCheckAnalysisResult(boundsAreSafe, + (functionCall.dispatchReceiver as? IrCall)?.dispatchReceiver?.takeIf{ boundsAreSafe }?.let { + findExpressionValueDescription(it) + } + ) } private inline fun checkIrGetValue(value: IrGetValue, condition: (IrExpression) -> BoundsCheckAnalysisResult): BoundsCheckAnalysisResult { @@ -113,10 +130,60 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() { else -> BoundsCheckAnalysisResult(false, null) } + private val IrProperty.canChangeValue: Boolean + get() { + if (isVar || isDelegated) + return true + + val overrideBackingField = backingField?.let { + getter != null && getter?.origin != IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR + } ?: + // Analyze inheritance. + if (isFakeOverride) + resolveFakeOverride()?.canChangeValue + else true + + + return overrideBackingField ?: true + } + + // Find base symbol with value or property and the main(first) dispatch receiver in the chain. + // Top-level properties accessors and local variables/parameters have null receivers. + private fun findExpressionValueDescription(expression: IrExpression): ValueDescription? { + return when (expression) { + is IrGetValue -> { + when (val declaration = expression.symbol.owner) { + is IrVariable -> { + if (declaration.isVar) return null + val initializerDescription = declaration.initializer?.let { findExpressionValueDescription(it) } + initializerDescription ?: LocalValueDescription(expression.symbol) + } + is IrValueParameter -> LocalValueDescription(expression.symbol) + else -> null + } + } + is IrCall -> { + val propertySymbol = expression.symbol.owner.correspondingPropertySymbol + + if (propertySymbol == null || propertySymbol.owner.canChangeValue) + return null + + // Get all list of dispatch receivers used in expression. + val valueDescriptionFromDispatchReceiver = expression.dispatchReceiver?.let { findExpressionValueDescription(it) ?: return null } + + PropertyValueDescription(valueDescriptionFromDispatchReceiver, propertySymbol) + } + is IrGetObjectValue -> { + ObjectValueDescription(expression.symbol) + } + else -> null + } + } + private fun checkLastElement(last: IrExpression, loopHeader: ProgressionLoopHeader): BoundsCheckAnalysisResult = checkIrCallCondition(last) { call -> if (call.isGetSizeCall() && !loopHeader.headerInfo.isLastInclusive) { - BoundsCheckAnalysisResult(true, (call.dispatchReceiver as? IrGetValue)?.symbol) + BoundsCheckAnalysisResult(true, call.dispatchReceiver?.let { findExpressionValueDescription(it) }) } else { lessThanSize(call) } @@ -194,7 +261,7 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() { // `isLastInclusive` for current case is set to true. // This case isn't fully optimized in ForLoopsLowering. if (call.isGetSizeCall()) - BoundsCheckAnalysisResult(true, (call.dispatchReceiver as? IrGetValue)?.symbol) + BoundsCheckAnalysisResult(true, call.dispatchReceiver?.let { findExpressionValueDescription(it) } ) else lessThanSize(call) } @@ -207,7 +274,7 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() { when (loopHeader.nestedLoopHeader) { is IndexedGetLoopHeader -> { analysisResult = BoundsCheckAnalysisResult(true, - ((loopHeader.loopInitStatements[0] as? IrVariable)?.initializer as? IrGetValue)?.symbol) + (loopHeader.loopInitStatements[0] as? IrVariable)?.initializer?.let { findExpressionValueDescription(it) }) } is ProgressionLoopHeader -> analysisResult = analyzeLoopHeader(loopHeader.nestedLoopHeader) } @@ -241,8 +308,8 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() { require(newExpression is IrCall) if (expression.symbol.owner.name != OperatorNameConventions.SET && expression.symbol.owner.name != OperatorNameConventions.GET) return newExpression - if (expression.dispatchReceiver?.type?.isBasicArray() != true || - (expression.dispatchReceiver as? IrGetValue)?.symbol != analysisResult.arrayInLoop) + if (expression.dispatchReceiver == null || expression.dispatchReceiver?.type?.isBasicArray() != true || + findExpressionValueDescription(expression.dispatchReceiver!!)?.equals(analysisResult.arrayInLoop!!) != true) return newExpression // Analyze arguments of set/get operator. val index = newExpression.getValueArgument(0)!! diff --git a/kotlin-native/backend.native/tests/codegen/bce/arraysForLoops.kt b/kotlin-native/backend.native/tests/codegen/bce/arraysForLoops.kt index 9e4e6e71f21..bb1589bfcf0 100644 --- a/kotlin-native/backend.native/tests/codegen/bce/arraysForLoops.kt +++ b/kotlin-native/backend.native/tests/codegen/bce/arraysForLoops.kt @@ -5,6 +5,7 @@ package codegen.bce.arraysForLoops import kotlin.test.* +import kotlin.reflect.KProperty @Test fun forEachIndexedTest() { val array = Array(10) { 0 } @@ -469,4 +470,153 @@ fun foo(a: Int, b : Int): Int = a + b * 2 for (i in 0..array.size - 2) { array[i+1] = array[i] } +} + +var needSmallArray = true + +class WithGetter() { + val array: Array + get() = if (needSmallArray) + Array(10) { 100 } + else + Array(100) { 100 } +} + +class Delegate { + operator fun getValue(thisRef: Any?, property: KProperty<*>): Array { + return if (needSmallArray) + Array(10) { 100 } + else + Array(100) { 100 } + } +} + +class WithDelegates { + val array by Delegate() +} + +open class Base { + open val array = Array(10) { 100 } + val array1 by Delegate() +} + +class Child : Base() { + override val array: Array + get() = if (needSmallArray) + Array(10) { 100 } + else + Array(100) { 100 } +} + +@Test fun withGetter() { + val obj = WithGetter() + needSmallArray = false + assertFailsWith { + for (i in 0..obj.array.size-1) { + needSmallArray = true + obj.array[i] = 6 + needSmallArray = false + } + } +} + +@Test fun delegatedProperty() { + val obj = WithDelegates() + needSmallArray = false + assertFailsWith { + for (i in 0..obj.array.size-1) { + needSmallArray = true + obj.array[i] = 6 + needSmallArray = false + } + } +} + +@Test fun inheritance() { + val obj = Child() + val base = Base() + needSmallArray = false + assertFailsWith { + for (i in 0..obj.array.size-1) { + needSmallArray = true + obj.array[i] = 6 + needSmallArray = false + } + } + + needSmallArray = false + assertFailsWith { + for (i in 0..obj.array1.size-1) { + needSmallArray = true + obj.array1[i] = 6 + needSmallArray = false + } + } + + needSmallArray = false + assertFailsWith { + for (i in 0..obj.array.size-1) { + needSmallArray = true + base.array[i] = 6 + needSmallArray = false + } + } +} + +val array: Array = arrayOf(1) + get() = if (needSmallArray) field else arrayOf(1, 2, 3) + +@Test fun customeGetter() { + val a = array + needSmallArray = false + assertFailsWith { + for (index in 0 until array.size) { + a[index] = 6 + } + } +} + +class First(initArray: Array) { + val array = initArray +} + +class Second(initArray: Array){ + val first = First(initArray) +} + +class Third(initArray: Array) { + val second = Second(initArray) +} + +@Test fun differentObjects() { + val a = Third(arrayOf(1, 2, 3, 4, 5)) + val b = Third(arrayOf(1, 2)) + + assertFailsWith { + for (i in 0..a.second.first.array.size-1) { + b.second.first.array[i] = 6 + } + } +} + +class Foo(size: Int) { + val array = IntArray(size) +} + +class Bar { + val smallFoo = Foo(1) + val largeFoo = Foo(10) + + val smallArray = smallFoo.array + val largeArray = largeFoo.array +} + +@Test fun differentArrays() { + val bar = Bar() + + assertFailsWith { + for (index in 0 until bar.largeArray.size) { + bar.smallArray[index] = 6 + } + } } \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/filecheck/bce.kt b/kotlin-native/backend.native/tests/filecheck/bce.kt index 01cbdab83b8..b71f942718d 100644 --- a/kotlin-native/backend.native/tests/filecheck/bce.kt +++ b/kotlin-native/backend.native/tests/filecheck/bce.kt @@ -193,7 +193,7 @@ fun argsInFunctionCall() { } // CHECK-LABEL: {{^}}epilogue: -// define void @"kfun:#smallLoop(){}"() +// CHECK-LABEL: define void @"kfun:#smallLoop(){}"() fun smallLoop() { val array = Array(10) { 100 } @@ -205,6 +205,75 @@ fun smallLoop() { } // CHECK-LABEL: {{^}}epilogue: +object TopLevelObject { + val array = Array(10) { 100 } +} + +// CHECK-LABEL: define void @"kfun:#topLevelObject(){}"() +fun topLevelObject() { + // CHECK: {{^}}do_while_loop{{.*}}: + for (i in 0 until TopLevelObject.array.size) { + // CHECK: {{call|invoke}} void @Kotlin_Array_set_without_BoundCheck + TopLevelObject.array[i] = 6 + } +} +// CHECK-LABEL: {{^}}epilogue: + +val array = Array(10) { 100 } + +// CHECK-LABEL: define void @"kfun:#topLevelProperty(){}"() +fun topLevelProperty() { + // CHECK: {{^}}do_while_loop{{.*}}: + for (i in 0..array.size - 2) { + // CHECK: {{call|invoke}} void @Kotlin_Array_set_without_BoundCheck + array[i] = 6 + } +} +// CHECK-LABEL: {{^}}epilogue: + +open class Base() { + open val array = Array(10) { 100 } +} + +class Child() : Base() + +// CHECK-LABEL: define void @"kfun:#childClassWithFakeOverride(){}"() +fun childClassWithFakeOverride() { + val child = Child() + // CHECK: {{^}}do_while_loop{{.*}}: + for (i in 0..child.array.size - 1) { + // CHECK: {{call|invoke}} void @Kotlin_Array_set_without_BoundCheck + child.array[i] = 6 + } +} +// CHECK-LABEL: {{^}}epilogue: + +class First { + val child = Child() +} + +class Second{ + val first = First() +} + +class Third { + val second = Second() +} + +// CHECK-LABEL: define void @"kfun:#chainedReceivers(){}"() +fun chainedReceivers() { + val obj = Third() + val obj1 = obj + val obj2 = obj1 + + // CHECK: {{^}}do_while_loop{{.*}}: + for (i in 0 until obj1.second.first.child.array.size) { + // CHECK: {{call|invoke}} void @Kotlin_Array_set_without_BoundCheck + obj2.second.first.child.array[i] = 6 + } +} +// CHECK-LABEL: {{^}}epilogue: + fun main() { forEachIndicies() forUntilSize() @@ -221,4 +290,8 @@ fun main() { innerLoop() argsInFunctionCall() smallLoop() + topLevelObject() + topLevelProperty() + childClassWithFakeOverride() + chainedReceivers() } \ No newline at end of file