RangeContainsLowering: Handle Comparable ranges.

This commit is contained in:
Mark Punzalan
2020-08-06 15:45:54 -07:00
committed by Alexander Udalov
parent 1c1b1b4b0f
commit 60a2f7d03f
5 changed files with 86 additions and 36 deletions
@@ -223,6 +223,7 @@ open class BuiltinSymbolsBase(protected val irBuiltIns: IrBuiltIns, protected va
val mutableIterable = symbolTable.referenceClass(builtIns.mutableIterable) val mutableIterable = symbolTable.referenceClass(builtIns.mutableIterable)
val mutableIterator = symbolTable.referenceClass(builtIns.mutableIterator) val mutableIterator = symbolTable.referenceClass(builtIns.mutableIterator)
val mutableListIterator = symbolTable.referenceClass(builtIns.mutableListIterator) val mutableListIterator = symbolTable.referenceClass(builtIns.mutableListIterator)
val comparable = symbolTable.referenceClass(builtIns.comparable)
private val binaryOperatorCache = mutableMapOf<Triple<Name, KotlinType, KotlinType>, IrSimpleFunctionSymbol>() private val binaryOperatorCache = mutableMapOf<Triple<Name, KotlinType, KotlinType>, IrSimpleFunctionSymbol>()
@@ -35,12 +35,10 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
// TODO: // TODO:
// 1. Handle (kotlin.ranges.)ClosedRange.contains on (kotlin.ranges.)Comparable.rangeTo(Comparable). // - Handle contains on ClosedFloatingPointRange non-literal expression (similar to DefaultProgressionHandler)
// Make sure right extension function was called! // - Note for unsigned until, the decremented last is signed (see UntilHandler.kt:81). Rather than converting back to unsigned,
// 2. Handle contains on ClosedFloatingPointRange non-literal expression (similar to DefaultProgressionHandler)
// 3. Note for unsigned until, the decremented last is signed (see UntilHandler.kt:81). Rather than converting back to unsigned,
// should we remove the conversion call instead? // should we remove the conversion call instead?
// 4. Unsigned step has similar concerns as well. getProgressionLastElement return value is signed. // - Unsigned step has similar concerns as well. getProgressionLastElement return value is signed.
val rangeContainsLoweringPhase = makeIrFilePhase( val rangeContainsLoweringPhase = makeIrFilePhase(
::RangeContainsLowering, ::RangeContainsLowering,
@@ -133,6 +131,7 @@ private class Transformer(
val upper: IrExpression val upper: IrExpression
val shouldUpperComeFirst: Boolean val shouldUpperComeFirst: Boolean
val useCompareTo: Boolean val useCompareTo: Boolean
val isNumericRange: Boolean
val additionalNotEmptyCondition: IrExpression? val additionalNotEmptyCondition: IrExpression?
val additionalStatements = mutableListOf<IrStatement>() val additionalStatements = mutableListOf<IrStatement>()
@@ -184,6 +183,7 @@ private class Transformer(
// `compareTo` must be used for UInt/ULong; they don't have intrinsic comparison operators. // `compareTo` must be used for UInt/ULong; they don't have intrinsic comparison operators.
useCompareTo = headerInfo.progressionType is UnsignedProgressionType useCompareTo = headerInfo.progressionType is UnsignedProgressionType
isNumericRange = true
additionalNotEmptyCondition = headerInfo.additionalNotEmptyCondition additionalNotEmptyCondition = headerInfo.additionalNotEmptyCondition
} }
is FloatingPointRangeHeaderInfo -> { is FloatingPointRangeHeaderInfo -> {
@@ -191,6 +191,15 @@ private class Transformer(
upper = headerInfo.endInclusive upper = headerInfo.endInclusive
shouldUpperComeFirst = false shouldUpperComeFirst = false
useCompareTo = false useCompareTo = false
isNumericRange = true
additionalNotEmptyCondition = null
}
is ComparableRangeInfo -> {
lower = headerInfo.start
upper = headerInfo.endInclusive
shouldUpperComeFirst = false
useCompareTo = true
isNumericRange = false
additionalNotEmptyCondition = null additionalNotEmptyCondition = null
} }
else -> return null else -> return null
@@ -226,9 +235,9 @@ private class Transformer(
// ** - Bound with side effect is stored in a temp variable to ensure evaluation even if right side is short-circuited. // ** - Bound with side effect is stored in a temp variable to ensure evaluation even if right side is short-circuited.
// *** - Bound with side effect is on left side of && to make sure it always gets evaluated. // *** - Bound with side effect is on left side of && to make sure it always gets evaluated.
val (argVar, argExpression) = createTemporaryVariableIfNecessary(argument, "containsArg") var (argVar, argExpression) = createTemporaryVariableIfNecessary(argument, "containsArg")
val lowerExpression: IrExpression var lowerExpression: IrExpression
val upperExpression: IrExpression var upperExpression: IrExpression
val useLowerClauseOnLeftSide: Boolean val useLowerClauseOnLeftSide: Boolean
if (argVar != null) { if (argVar != null) {
val (lowerVar, tmpLowerExpression) = createTemporaryVariableIfNecessary(lower, "containsLower") val (lowerVar, tmpLowerExpression) = createTemporaryVariableIfNecessary(lower, "containsLower")
@@ -266,44 +275,57 @@ private class Transformer(
// TODO: Handle unsigned (comparisonClass is currently null). // TODO: Handle unsigned (comparisonClass is currently null).
val builtIns = context.irBuiltIns val builtIns = context.irBuiltIns
val comparisonClass = val comparisonClass = if (isNumericRange) {
computeComparisonClass(this@Transformer.context.ir.symbols, lowerExpression.type, upperExpression.type, argExpression.type) computeComparisonClass(this@Transformer.context.ir.symbols, lowerExpression.type, upperExpression.type, argExpression.type)
?: return null ?: return null
} else {
assert(headerInfo is ComparableRangeInfo)
this@Transformer.context.ir.symbols.comparable.owner
}
if (isNumericRange) {
lowerExpression = lowerExpression.castIfNecessary(comparisonClass)
upperExpression = upperExpression.castIfNecessary(comparisonClass)
argExpression = argExpression.castIfNecessary(comparisonClass)
}
val lessOrEqualFun = builtIns.lessOrEqualFunByOperandType.getValue(if (useCompareTo) builtIns.intClass else comparisonClass.symbol) val lessOrEqualFun = builtIns.lessOrEqualFunByOperandType.getValue(if (useCompareTo) builtIns.intClass else comparisonClass.symbol)
val compareToFun = comparisonClass.functions.singleOrNull { val compareToFun = comparisonClass.functions.singleOrNull {
it.name == OperatorNameConventions.COMPARE_TO && it.name == OperatorNameConventions.COMPARE_TO &&
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null && it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null &&
it.valueParameters.size == 1 && it.valueParameters[0].type == argument.type.makeNotNull() it.valueParameters.size == 1 && (!isNumericRange || it.valueParameters[0].type == argument.type.makeNotNull())
} ?: return null } ?: return null
// TODO: Handle null. This can happen with an extension fun, e.g., IntRange.compareTo(String) (See rangeContainsString.kt).
// contains() function for ComparableRange is implemented as `value >= start && value <= endInclusive` (`value` is the argument).
// Therefore the dispatch receiver for the compareTo() calls should be the argument. This is important since the implementation
// for compareTo() may have side effects dependent on which expressions are the receiver and argument
// (see evaluationOrderForComparableRange.kt test).
val lowerClause = if (useCompareTo) { val lowerClause = if (useCompareTo) {
irCall(lessOrEqualFun).apply {
putValueArgument(0, irInt(0))
putValueArgument(1, irCall(compareToFun).apply {
dispatchReceiver = argExpression
putValueArgument(0, lowerExpression)
})
}
} else {
irCall(lessOrEqualFun).apply {
putValueArgument(0, lowerExpression)
putValueArgument(1, argExpression)
}
}
val upperClause = if (useCompareTo) {
irCall(lessOrEqualFun).apply { irCall(lessOrEqualFun).apply {
putValueArgument(0, irCall(compareToFun).apply { putValueArgument(0, irCall(compareToFun).apply {
dispatchReceiver = lowerExpression.castIfNecessary(comparisonClass) dispatchReceiver = argExpression.deepCopyWithSymbols()
putValueArgument(0, argExpression.castIfNecessary(comparisonClass)) putValueArgument(0, upperExpression)
}) })
putValueArgument(1, irInt(0)) putValueArgument(1, irInt(0))
} }
} else { } else {
irCall(lessOrEqualFun).apply { irCall(lessOrEqualFun).apply {
putValueArgument(0, lowerExpression.castIfNecessary(comparisonClass)) putValueArgument(0, argExpression.deepCopyWithSymbols())
putValueArgument(1, argExpression.castIfNecessary(comparisonClass)) putValueArgument(1, upperExpression)
}
}
val upperClause = if (useCompareTo) {
irCall(lessOrEqualFun).apply {
putValueArgument(0, irInt(0))
putValueArgument(1, irCall(compareToFun).apply {
dispatchReceiver = upperExpression.castIfNecessary(comparisonClass)
putValueArgument(0, argExpression.deepCopyWithSymbols().castIfNecessary(comparisonClass))
})
}
} else {
irCall(lessOrEqualFun).apply {
putValueArgument(0, argExpression.deepCopyWithSymbols().castIfNecessary(comparisonClass))
putValueArgument(1, upperExpression.castIfNecessary(comparisonClass))
} }
} }
@@ -378,7 +400,11 @@ internal open class RangeHeaderInfoBuilder(context: CommonBackendContext, scopeO
RangeToHandler(context) RangeToHandler(context)
) )
override val callHandlers = listOf(ReversedHandler(context, this), FloatingPointRangeToHandler) override val callHandlers = listOf(
FloatingPointRangeToHandler,
ComparableRangeToHandler(context),
ReversedHandler(context, this)
)
override val expressionHandlers = listOf(DefaultProgressionHandler(context)) override val expressionHandlers = listOf(DefaultProgressionHandler(context))
} }
@@ -387,7 +413,7 @@ internal open class RangeHeaderInfoBuilder(context: CommonBackendContext, scopeO
internal object FloatingPointRangeToHandler : HeaderInfoFromCallHandler<Nothing?> { internal object FloatingPointRangeToHandler : HeaderInfoFromCallHandler<Nothing?> {
override val matcher = SimpleCalleeMatcher { override val matcher = SimpleCalleeMatcher {
fqName { it == FqName("kotlin.ranges.rangeTo") } fqName { it == FqName("kotlin.ranges.${OperatorNameConventions.RANGE_TO}") }
extensionReceiver { it != null && it.type.run { isFloat() || isDouble() } } extensionReceiver { it != null && it.type.run { isFloat() || isDouble() } }
parameterCount { it == 1 } parameterCount { it == 1 }
parameter(0) { it.type.run { isFloat() || isDouble() } } parameter(0) { it.type.run { isFloat() || isDouble() } }
@@ -398,4 +424,20 @@ internal object FloatingPointRangeToHandler : HeaderInfoFromCallHandler<Nothing?
start = expression.extensionReceiver!!, start = expression.extensionReceiver!!,
endInclusive = expression.getValueArgument(0)!! endInclusive = expression.getValueArgument(0)!!
) )
}
/** Builds a [HeaderInfo] for ranges of Comparables built using the `rangeTo` extension function. */
internal class ComparableRangeToHandler(context: CommonBackendContext) : HeaderInfoFromCallHandler<Nothing?> {
override val matcher = SimpleCalleeMatcher {
fqName { it == FqName("kotlin.ranges.${OperatorNameConventions.RANGE_TO}") }
extensionReceiver { it != null && it.type.isSubtypeOfClass(context.ir.symbols.comparable) }
parameterCount { it == 1 }
}
override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol) =
ComparableRangeInfo(
start = expression.extensionReceiver!!,
endInclusive = expression.getValueArgument(0)!!
)
} }
@@ -55,6 +55,13 @@ internal class FloatingPointRangeHeaderInfo(
override fun asReversed(): HeaderInfo? = null override fun asReversed(): HeaderInfo? = null
} }
internal class ComparableRangeInfo(
val start: IrExpression,
val endInclusive: IrExpression
) : HeaderInfo() {
override fun asReversed(): HeaderInfo? = null
}
internal sealed class NumericHeaderInfo( internal sealed class NumericHeaderInfo(
val progressionType: ProgressionType, val progressionType: ProgressionType,
val first: IrExpression, val first: IrExpression,
@@ -440,7 +440,7 @@ internal class WithIndexLoopHeader(
is ProgressionHeaderInfo -> ProgressionLoopHeader(nestedInfo, this@with, context) is ProgressionHeaderInfo -> ProgressionLoopHeader(nestedInfo, this@with, context)
is IterableHeaderInfo -> IterableLoopHeader(nestedInfo) is IterableHeaderInfo -> IterableLoopHeader(nestedInfo)
is WithIndexHeaderInfo -> throw IllegalStateException("Nested WithIndexHeaderInfo not allowed for WithIndexLoopHeader") is WithIndexHeaderInfo -> throw IllegalStateException("Nested WithIndexHeaderInfo not allowed for WithIndexLoopHeader")
is FloatingPointRangeHeaderInfo -> error("Unexpected FloatingPointRangeHeaderInfo for loops") is FloatingPointRangeHeaderInfo, is ComparableRangeInfo -> error("Unexpected ${nestedInfo::class.simpleName} for loops")
} }
// Do not build own indexVariable if the nested loop header has an inductionVariable == 0 and step == 1. // Do not build own indexVariable if the nested loop header has an inductionVariable == 0 and step == 1.
@@ -669,7 +669,7 @@ internal class HeaderProcessor(
is ProgressionHeaderInfo -> ProgressionLoopHeader(headerInfo, builder, context) is ProgressionHeaderInfo -> ProgressionLoopHeader(headerInfo, builder, context)
is WithIndexHeaderInfo -> WithIndexLoopHeader(headerInfo, builder, context) is WithIndexHeaderInfo -> WithIndexLoopHeader(headerInfo, builder, context)
is IterableHeaderInfo -> IterableLoopHeader(headerInfo) is IterableHeaderInfo -> IterableLoopHeader(headerInfo)
is FloatingPointRangeHeaderInfo -> error("Unexpected FloatingPointRangeHeaderInfo for loops") is FloatingPointRangeHeaderInfo, is ComparableRangeInfo -> error("Unexpected ${headerInfo::class.simpleName} for loops")
} }
} }
} }
@@ -1,8 +1,8 @@
// IGNORE_BACKEND: JVM_IR
// TODO KT-36829 Optimize 'in' expressions in JVM_IR
fun test1(a: String) = a in "alpha" .. "omega" fun test1(a: String) = a in "alpha" .. "omega"
fun test2(a: String) = a !in "alpha" .. "omega" fun test2(a: String) = a !in "alpha" .. "omega"
fun <T : Comparable<T>> test3(x: T, left: T, right: T) = x in left .. right
fun <T : Enum<T>> test4(x: T, left: T, right: T) = x in left .. right
// 0 rangeTo // 0 rangeTo
// 0 contains // 0 contains
// 4 compareTo // 8 compareTo