Generate proper 'compareTo' calls for non-primitive 'a in x .. y'

When we have some custom implementation of Comparable, it's important
that we compare values exactly as 'lowBound <= a && a <= highBound'.

Make sure that evaluation order and compareTo calls match for
optimized and non-optimized case.
This commit is contained in:
Dmitry Petrov
2017-07-07 14:41:09 +03:00
parent dd5bb78178
commit 0ce6bac7eb
5 changed files with 164 additions and 14 deletions
@@ -18,9 +18,8 @@ package org.jetbrains.kotlin.codegen.range
import org.jetbrains.kotlin.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.isClosedRangeContains
import org.jetbrains.kotlin.codegen.range.comparison.ObjectComparisonGenerator
import org.jetbrains.kotlin.codegen.range.forLoop.IteratorForLoopGenerator
import org.jetbrains.kotlin.codegen.range.inExpression.InContinuousRangeExpressionGenerator
import org.jetbrains.kotlin.codegen.range.inExpression.InContinuousRangeOfComparableExpressionGenerator
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
@@ -38,6 +37,9 @@ class ComparableRangeLiteralRangeValue(
override fun isIntrinsicInCall(resolvedCallForIn: ResolvedCall<out CallableDescriptor>) =
isClosedRangeContains(resolvedCallForIn.resultingDescriptor)
override fun createIntrinsicInExpressionGenerator(codegen: ExpressionCodegen, operatorReference: KtSimpleNameExpression, resolvedCall: ResolvedCall<out CallableDescriptor>)=
InContinuousRangeExpressionGenerator(operatorReference, boundedValue, ObjectComparisonGenerator)
override fun createIntrinsicInExpressionGenerator(
codegen: ExpressionCodegen,
operatorReference: KtSimpleNameExpression,
resolvedCall: ResolvedCall<out CallableDescriptor>
) = InContinuousRangeOfComparableExpressionGenerator(operatorReference, boundedValue)
}
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.codegen.range
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.range.comparison.getComparisonGeneratorForRangeContainsCall
import org.jetbrains.kotlin.codegen.range.inExpression.CallBasedInExpressionGenerator
import org.jetbrains.kotlin.codegen.range.inExpression.InContinuousRangeExpressionGenerator
import org.jetbrains.kotlin.codegen.range.inExpression.InPrimitiveContinuousRangeExpressionGenerator
import org.jetbrains.kotlin.codegen.range.inExpression.InExpressionGenerator
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
@@ -42,7 +42,7 @@ abstract class PrimitiveNumberRangeIntrinsicRangeValue(rangeCall: ResolvedCall<o
): InExpressionGenerator {
val comparisonGenerator = getComparisonGeneratorForRangeContainsCall(codegen, resolvedCall)
return if (comparisonGenerator != null)
InContinuousRangeExpressionGenerator(operatorReference, getBoundedValue(codegen), comparisonGenerator)
InPrimitiveContinuousRangeExpressionGenerator(operatorReference, getBoundedValue(codegen), comparisonGenerator)
else
CallBasedInExpressionGenerator(codegen, operatorReference)
}
@@ -0,0 +1,136 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen.range.inExpression
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.BranchedValue
import org.jetbrains.kotlin.codegen.Invert
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.range.BoundedValue
import org.jetbrains.kotlin.codegen.range.comparison.ObjectComparisonGenerator
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class InContinuousRangeOfComparableExpressionGenerator(
operatorReference: KtSimpleNameExpression,
private val boundedValue: BoundedValue
) : InExpressionGenerator {
private val isNotIn = operatorReference.getReferencedNameElementType() == KtTokens.NOT_IN
private val comparisonGenerator = ObjectComparisonGenerator
override fun generate(argument: StackValue): BranchedValue =
gen(argument).let { if (isNotIn) Invert(it) else it }
private fun gen(argument: StackValue): BranchedValue =
object : BranchedValue(argument, null, comparisonGenerator.comparedType, Opcodes.IFEQ) {
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
if (jumpIfFalse) {
genJumpIfFalse(v, jumpLabel)
}
else {
genJumpIfTrue(v, jumpLabel)
}
}
private fun genJumpIfTrue(v: InstructionAdapter, jumpLabel: Label) {
// if (arg is in range) goto jumpLabel
val exitLabel1 = Label()
val exitLabel2 = Label()
boundedValue.putHighLow(v, operandType)
arg1.put(operandType, v)
AsmUtil.dupx(v, operandType)
// On stack: high arg low arg
// if (low bound is NOT satisfied) goto exitLabel1
if (boundedValue.isLowInclusive) {
// arg < low
v.swap()
comparisonGenerator.jumpIfLess(v, exitLabel1)
}
else {
// arg <= low
v.swap()
comparisonGenerator.jumpIfLessOrEqual(v, exitLabel1)
}
// On stack: high arg
// if (high bound is satisfied) goto jumpLabel
if (boundedValue.isHighInclusive) {
// arg <= high
v.swap()
comparisonGenerator.jumpIfLessOrEqual(v, jumpLabel)
}
else {
// arg < high
v.swap()
comparisonGenerator.jumpIfLess(v, jumpLabel)
}
v.goTo(exitLabel2)
v.mark(exitLabel1)
AsmUtil.pop2(v, operandType)
v.mark(exitLabel2)
}
private fun genJumpIfFalse(v: InstructionAdapter, jumpLabel: Label) {
// if (arg is NOT in range) goto jumpLabel
val cmpHighLabel = Label()
boundedValue.putHighLow(v, operandType)
arg1.put(operandType, v)
AsmUtil.dupx(v, operandType)
// On stack: high arg low arg
// if ([low bound is satisfied]) goto cmpHighLabel
if (boundedValue.isLowInclusive) {
// arg >= low
v.swap()
comparisonGenerator.jumpIfGreaterOrEqual(v, cmpHighLabel)
}
else {
// arg > low
v.swap()
comparisonGenerator.jumpIfGreater(v, cmpHighLabel)
}
// Low bound is NOT satisfied, clear stack and goto jumpLabel
AsmUtil.pop2(v, operandType)
v.goTo(jumpLabel)
v.mark(cmpHighLabel)
// On stack: high arg
// if ([high bound is NOT satisfied]) goto jumpLabel
if (boundedValue.isHighInclusive) {
// arg > high
v.swap()
comparisonGenerator.jumpIfGreater(v, jumpLabel)
}
else {
// arg >= high
v.swap()
comparisonGenerator.jumpIfGreaterOrEqual(v, jumpLabel)
}
}
}
}
@@ -28,7 +28,7 @@ import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class InContinuousRangeExpressionGenerator(
class InPrimitiveContinuousRangeExpressionGenerator(
operatorReference: KtSimpleNameExpression,
private val boundedValue: BoundedValue,
private val comparisonGenerator: ComparisonGenerator
@@ -22,14 +22,26 @@ fun z(i: Int): Z {
return Z(i)
}
fun box(): String {
expectOrder("z0 in z1 .. z3", "z:1 z:3 z:0 c:1,0 ") { z(0) in z(1) .. z(3) }
expectOrder("z2 in z1 .. z3", "z:1 z:3 z:2 c:1,2 c:3,2 ") { z(2) in z(1) .. z(3) }
expectOrder("z4 in z1 .. z3", "z:1 z:4 z:2 c:1,2 c:4,2 ") { z(2) in z(1) .. z(4) }
fun zr(i: Int, j: Int) = z(i) .. z(j)
expectOrder("z0 !in z1 .. z3", "z:1 z:3 z:0 c:1,0 ") { z(0) !in z(1) .. z(3) }
expectOrder("z2 !in z1 .. z3", "z:1 z:3 z:2 c:1,2 c:3,2 ") { z(2) !in z(1) .. z(3) }
expectOrder("z4 !in z1 .. z3", "z:1 z:4 z:2 c:1,2 c:4,2 ") { z(2) !in z(1) .. z(4) }
fun box(): String {
expectOrder("#1", "z:1 z:3 z:0 c:0,1 ") { z(0) in z(1) .. z(3) }
expectOrder("#1a", "z:1 z:3 z:0 c:0,1 ") { z(0) in zr(1, 3) }
expectOrder("#2", "z:1 z:3 z:2 c:2,1 c:2,3 ") { z(2) in z(1) .. z(3) }
expectOrder("#2a", "z:1 z:3 z:2 c:2,1 c:2,3 ") { z(2) in zr(1, 3) }
expectOrder("#3", "z:1 z:3 z:4 c:4,1 c:4,3 ") { z(4) in z(1) .. z(3) }
expectOrder("#3a", "z:1 z:3 z:4 c:4,1 c:4,3 ") { z(4) in zr(1, 3) }
expectOrder("#4", "z:1 z:3 z:0 c:0,1 ") { z(0) !in z(1) .. z(3) }
expectOrder("#4a", "z:1 z:3 z:0 c:0,1 ") { z(0) !in zr(1, 3) }
expectOrder("#5", "z:1 z:3 z:2 c:2,1 c:2,3 ") { z(2) !in z(1) .. z(3) }
expectOrder("#5a", "z:1 z:3 z:2 c:2,1 c:2,3 ") { z(2) !in zr(1, 3) }
expectOrder("#6", "z:1 z:3 z:4 c:4,1 c:4,3 ") { z(4) !in z(1) .. z(3) }
expectOrder("#6a", "z:1 z:3 z:4 c:4,1 c:4,3 ") { z(4) !in zr(1, 3) }
return "OK"
}