[K/N] Support properties in BCE
This commit is contained in:
+78
-11
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user