[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.CommonBackendContext
|
||||||
import org.jetbrains.kotlin.backend.common.lower.loops.*
|
import org.jetbrains.kotlin.backend.common.lower.loops.*
|
||||||
import org.jetbrains.kotlin.backend.konan.ir.KonanNameConventions
|
import org.jetbrains.kotlin.backend.konan.ir.KonanNameConventions
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrVariable
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
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.symbols.IrValueSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.IrType
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
import org.jetbrains.kotlin.ir.types.getClass
|
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.ir.visitors.transformChildrenVoid
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
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.
|
// 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.
|
// 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() {
|
class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() {
|
||||||
lateinit var mainLoopVariable: IrVariable
|
lateinit var mainLoopVariable: IrVariable
|
||||||
@@ -93,8 +107,11 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() {
|
|||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
val array = ((functionCall.dispatchReceiver as? IrCall)?.dispatchReceiver as? IrGetValue)?.symbol
|
return BoundsCheckAnalysisResult(boundsAreSafe,
|
||||||
return BoundsCheckAnalysisResult(boundsAreSafe, array)
|
(functionCall.dispatchReceiver as? IrCall)?.dispatchReceiver?.takeIf{ boundsAreSafe }?.let {
|
||||||
|
findExpressionValueDescription(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun checkIrGetValue(value: IrGetValue, condition: (IrExpression) -> BoundsCheckAnalysisResult): BoundsCheckAnalysisResult {
|
private inline fun checkIrGetValue(value: IrGetValue, condition: (IrExpression) -> BoundsCheckAnalysisResult): BoundsCheckAnalysisResult {
|
||||||
@@ -113,10 +130,60 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() {
|
|||||||
else -> BoundsCheckAnalysisResult(false, null)
|
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 =
|
private fun checkLastElement(last: IrExpression, loopHeader: ProgressionLoopHeader): BoundsCheckAnalysisResult =
|
||||||
checkIrCallCondition(last) { call ->
|
checkIrCallCondition(last) { call ->
|
||||||
if (call.isGetSizeCall() && !loopHeader.headerInfo.isLastInclusive) {
|
if (call.isGetSizeCall() && !loopHeader.headerInfo.isLastInclusive) {
|
||||||
BoundsCheckAnalysisResult(true, (call.dispatchReceiver as? IrGetValue)?.symbol)
|
BoundsCheckAnalysisResult(true, call.dispatchReceiver?.let { findExpressionValueDescription(it) })
|
||||||
} else {
|
} else {
|
||||||
lessThanSize(call)
|
lessThanSize(call)
|
||||||
}
|
}
|
||||||
@@ -194,7 +261,7 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() {
|
|||||||
// `isLastInclusive` for current case is set to true.
|
// `isLastInclusive` for current case is set to true.
|
||||||
// This case isn't fully optimized in ForLoopsLowering.
|
// This case isn't fully optimized in ForLoopsLowering.
|
||||||
if (call.isGetSizeCall())
|
if (call.isGetSizeCall())
|
||||||
BoundsCheckAnalysisResult(true, (call.dispatchReceiver as? IrGetValue)?.symbol)
|
BoundsCheckAnalysisResult(true, call.dispatchReceiver?.let { findExpressionValueDescription(it) } )
|
||||||
else
|
else
|
||||||
lessThanSize(call)
|
lessThanSize(call)
|
||||||
}
|
}
|
||||||
@@ -207,7 +274,7 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() {
|
|||||||
when (loopHeader.nestedLoopHeader) {
|
when (loopHeader.nestedLoopHeader) {
|
||||||
is IndexedGetLoopHeader -> {
|
is IndexedGetLoopHeader -> {
|
||||||
analysisResult = BoundsCheckAnalysisResult(true,
|
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)
|
is ProgressionLoopHeader -> analysisResult = analyzeLoopHeader(loopHeader.nestedLoopHeader)
|
||||||
}
|
}
|
||||||
@@ -241,8 +308,8 @@ class KonanBCEForLoopBodyTransformer : ForLoopBodyTransformer() {
|
|||||||
require(newExpression is IrCall)
|
require(newExpression is IrCall)
|
||||||
if (expression.symbol.owner.name != OperatorNameConventions.SET && expression.symbol.owner.name != OperatorNameConventions.GET)
|
if (expression.symbol.owner.name != OperatorNameConventions.SET && expression.symbol.owner.name != OperatorNameConventions.GET)
|
||||||
return newExpression
|
return newExpression
|
||||||
if (expression.dispatchReceiver?.type?.isBasicArray() != true ||
|
if (expression.dispatchReceiver == null || expression.dispatchReceiver?.type?.isBasicArray() != true ||
|
||||||
(expression.dispatchReceiver as? IrGetValue)?.symbol != analysisResult.arrayInLoop)
|
findExpressionValueDescription(expression.dispatchReceiver!!)?.equals(analysisResult.arrayInLoop!!) != true)
|
||||||
return newExpression
|
return newExpression
|
||||||
// Analyze arguments of set/get operator.
|
// Analyze arguments of set/get operator.
|
||||||
val index = newExpression.getValueArgument(0)!!
|
val index = newExpression.getValueArgument(0)!!
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
package codegen.bce.arraysForLoops
|
package codegen.bce.arraysForLoops
|
||||||
|
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
@Test fun forEachIndexedTest() {
|
@Test fun forEachIndexedTest() {
|
||||||
val array = Array(10) { 0 }
|
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) {
|
for (i in 0..array.size - 2) {
|
||||||
array[i+1] = array[i]
|
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:
|
// CHECK-LABEL: {{^}}epilogue:
|
||||||
|
|
||||||
// define void @"kfun:#smallLoop(){}"()
|
// CHECK-LABEL: define void @"kfun:#smallLoop(){}"()
|
||||||
fun smallLoop() {
|
fun smallLoop() {
|
||||||
val array = Array(10) { 100 }
|
val array = Array(10) { 100 }
|
||||||
|
|
||||||
@@ -205,6 +205,75 @@ fun smallLoop() {
|
|||||||
}
|
}
|
||||||
// CHECK-LABEL: {{^}}epilogue:
|
// 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() {
|
fun main() {
|
||||||
forEachIndicies()
|
forEachIndicies()
|
||||||
forUntilSize()
|
forUntilSize()
|
||||||
@@ -221,4 +290,8 @@ fun main() {
|
|||||||
innerLoop()
|
innerLoop()
|
||||||
argsInFunctionCall()
|
argsInFunctionCall()
|
||||||
smallLoop()
|
smallLoop()
|
||||||
|
topLevelObject()
|
||||||
|
topLevelProperty()
|
||||||
|
childClassWithFakeOverride()
|
||||||
|
chainedReceivers()
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user