[K/N] Support properties in BCE

This commit is contained in:
Elena Lepilkina
2021-11-10 15:53:26 +03:00
committed by Space
parent ab9747ed6d
commit f650311b11
3 changed files with 302 additions and 12 deletions
@@ -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)!!
@@ -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<Int>
get() = if (needSmallArray)
Array(10) { 100 }
else
Array(100) { 100 }
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Array<Int> {
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<Int>
get() = if (needSmallArray)
Array(10) { 100 }
else
Array(100) { 100 }
}
@Test fun withGetter() {
val obj = WithGetter()
needSmallArray = false
assertFailsWith<ArrayIndexOutOfBoundsException> {
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<ArrayIndexOutOfBoundsException> {
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<ArrayIndexOutOfBoundsException> {
for (i in 0..obj.array.size-1) {
needSmallArray = true
obj.array[i] = 6
needSmallArray = false
}
}
needSmallArray = false
assertFailsWith<ArrayIndexOutOfBoundsException> {
for (i in 0..obj.array1.size-1) {
needSmallArray = true
obj.array1[i] = 6
needSmallArray = false
}
}
needSmallArray = false
assertFailsWith<ArrayIndexOutOfBoundsException> {
for (i in 0..obj.array.size-1) {
needSmallArray = true
base.array[i] = 6
needSmallArray = false
}
}
}
val array: Array<Int> = arrayOf(1)
get() = if (needSmallArray) field else arrayOf(1, 2, 3)
@Test fun customeGetter() {
val a = array
needSmallArray = false
assertFailsWith<ArrayIndexOutOfBoundsException> {
for (index in 0 until array.size) {
a[index] = 6
}
}
}
class First(initArray: Array<Int>) {
val array = initArray
}
class Second(initArray: Array<Int>){
val first = First(initArray)
}
class Third(initArray: Array<Int>) {
val second = Second(initArray)
}
@Test fun differentObjects() {
val a = Third(arrayOf(1, 2, 3, 4, 5))
val b = Third(arrayOf(1, 2))
assertFailsWith<ArrayIndexOutOfBoundsException> {
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<ArrayIndexOutOfBoundsException> {
for (index in 0 until bar.largeArray.size) {
bar.smallArray[index] = 6
}
}
}
@@ -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()
}