Fix floating point comparison generation for range literals

This commit is contained in:
Dmitry Petrov
2017-11-13 18:09:20 +03:00
parent 354d54aef6
commit 4193fae9fa
10 changed files with 216 additions and 14 deletions
@@ -19,11 +19,13 @@ 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.InPrimitiveContinuousRangeExpressionGenerator
import org.jetbrains.kotlin.codegen.range.inExpression.InExpressionGenerator
import org.jetbrains.kotlin.codegen.range.inExpression.InFloatingPointRangeLiteralExpressionGenerator
import org.jetbrains.kotlin.codegen.range.inExpression.InIntegralContinuousRangeExpressionGenerator
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.org.objectweb.asm.Type
abstract class PrimitiveNumberRangeIntrinsicRangeValue(rangeCall: ResolvedCall<out CallableDescriptor>): CallIntrinsicRangeValue(rangeCall) {
protected val asmElementType = getAsmRangeElementTypeForPrimitiveRangeOrProgression(rangeCall.resultingDescriptor)
@@ -41,12 +43,22 @@ abstract class PrimitiveNumberRangeIntrinsicRangeValue(rangeCall: ResolvedCall<o
resolvedCall: ResolvedCall<out CallableDescriptor>
): InExpressionGenerator {
val comparisonGenerator = getComparisonGeneratorForRangeContainsCall(codegen, resolvedCall)
return if (comparisonGenerator != null)
InPrimitiveContinuousRangeExpressionGenerator(
operatorReference, getBoundedValue(codegen), comparisonGenerator, codegen.frameMap
)
else
CallBasedInExpressionGenerator(codegen, operatorReference)
val comparedType = comparisonGenerator?.comparedType
return when {
comparisonGenerator == null -> CallBasedInExpressionGenerator(codegen, operatorReference)
comparedType == Type.DOUBLE_TYPE || comparedType == Type.FLOAT_TYPE -> {
val rangeLiteral = getBoundedValue(codegen) as? SimpleBoundedValue ?:
throw AssertionError("Floating point intrinsic range value should be a range literal")
InFloatingPointRangeLiteralExpressionGenerator(operatorReference, rangeLiteral, comparisonGenerator, codegen.frameMap)
}
else ->
InIntegralContinuousRangeExpressionGenerator(
operatorReference, getBoundedValue(codegen), comparisonGenerator, codegen.frameMap
)
}
}
protected abstract fun getBoundedValue(codegen: ExpressionCodegen): BoundedValue
@@ -25,9 +25,9 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class SimpleBoundedValue(
codegen: ExpressionCodegen,
rangeCall: ResolvedCall<out CallableDescriptor>,
private val lowBound: StackValue,
val lowBound: StackValue,
isLowInclusive: Boolean,
private val highBound: StackValue,
val highBound: StackValue,
isHighInclusive: Boolean
): AbstractBoundedValue(codegen, rangeCall, isLowInclusive, isHighInclusive) {
constructor(
@@ -22,22 +22,22 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
sealed class FloatingPointComparisonGenerator(override val comparedType: Type): ComparisonGenerator {
override fun jumpIfGreaterOrEqual(v: InstructionAdapter, label: Label) {
v.cmpl(comparedType)
v.cmpg(comparedType)
v.ifge(label)
}
override fun jumpIfLessOrEqual(v: InstructionAdapter, label: Label) {
v.cmpg(comparedType)
v.cmpl(comparedType)
v.ifle(label)
}
override fun jumpIfGreater(v: InstructionAdapter, label: Label) {
v.cmpl(comparedType)
v.cmpg(comparedType)
v.ifgt(label)
}
override fun jumpIfLess(v: InstructionAdapter, label: Label) {
v.cmpg(comparedType)
v.cmpl(comparedType)
v.iflt(label)
}
}
@@ -0,0 +1,117 @@
/*
* 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.*
import org.jetbrains.kotlin.codegen.range.SimpleBoundedValue
import org.jetbrains.kotlin.codegen.range.comparison.ComparisonGenerator
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.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class InFloatingPointRangeLiteralExpressionGenerator(
operatorReference: KtSimpleNameExpression,
private val rangeLiteral: SimpleBoundedValue,
private val comparisonGenerator: ComparisonGenerator,
private val frameMap: FrameMap
) : InExpressionGenerator {
init {
assert(rangeLiteral.isLowInclusive && rangeLiteral.isHighInclusive) { "Floating point range literal bounds should be inclusive" }
}
private val isNotIn = operatorReference.getReferencedNameElementType() == KtTokens.NOT_IN
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
// =>
// if (arg is NOT in range) goto exitLabel
// goto jumpLabel
// exitLabel:
frameMap.useTmpVar(operandType) { argVar ->
val exitLabel = Label()
genJumpIfFalse(v, exitLabel)
v.goTo(jumpLabel)
v.mark(exitLabel)
}
}
private fun genJumpIfFalse(v: InstructionAdapter, jumpLabel: Label) {
// if (arg is NOT in range) goto jumpLabel
frameMap.useTmpVar(operandType) { argVar ->
// Evaluate low and high bounds once (unless they have no side effects)
val (lowValue, lowTmpType) = introduceTemporaryIfRequired(v, rangeLiteral.lowBound, operandType)
val (highValue, highTmpType) = introduceTemporaryIfRequired(v, rangeLiteral.highBound, operandType)
val argValue = StackValue.local(argVar, operandType)
argValue.store(arg1, v)
// if (low bound is NOT satisfied) goto jumpLabel
// arg < low
argValue.put(operandType, v)
lowValue.put(operandType, v)
comparisonGenerator.jumpIfLess(v, jumpLabel)
// if (high bound is NOT satisfied) goto jumpLabel
// arg > high
argValue.put(operandType, v)
highValue.put(operandType, v)
comparisonGenerator.jumpIfGreater(v, jumpLabel)
highTmpType?.let { frameMap.leaveTemp(it) }
lowTmpType?.let { frameMap.leaveTemp(it) }
}
}
private fun introduceTemporaryIfRequired(v: InstructionAdapter, value: StackValue, type: Type): Pair<StackValue, Type?> {
val resultValue: StackValue
val resultType: Type?
if (value.canHaveSideEffects()) {
val index = frameMap.enterTemp(type)
resultValue = StackValue.local(index, type)
resultType = type
resultValue.store(value, v)
}
else {
resultValue = value
resultType = null
}
return Pair(resultValue, resultType)
}
}
}
@@ -25,7 +25,7 @@ import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class InPrimitiveContinuousRangeExpressionGenerator(
class InIntegralContinuousRangeExpressionGenerator(
operatorReference: KtSimpleNameExpression,
private val boundedValue: BoundedValue,
private val comparisonGenerator: ComparisonGenerator,
@@ -0,0 +1,49 @@
// WITH_RUNTIME
import kotlin.test.*
fun box(): String {
val NAN = Double.NaN
val M_NAN = -Double.NaN
val range1 = 0.0 .. NAN
val range2 = M_NAN .. 0.0
val range3 = M_NAN .. NAN
assertEquals(1.0 in range1, 1.0 in 0.0 .. NAN)
assertEquals(-1.0 in range1, -1.0 in 0.0 .. NAN)
assertEquals(0.0 in range1, 0.0 in 0.0 .. NAN)
assertEquals(NAN in range1, NAN in 0.0 .. NAN)
assertEquals(M_NAN in range1, M_NAN in 0.0 .. NAN)
assertEquals(1.0 !in range1, 1.0 !in 0.0 .. NAN)
assertEquals(-1.0 !in range1, -1.0 !in 0.0 .. NAN)
assertEquals(0.0 !in range1, 0.0 !in 0.0 .. NAN)
assertEquals(NAN !in range1, NAN !in 0.0 .. NAN)
assertEquals(M_NAN !in range1, M_NAN !in 0.0 .. NAN)
assertEquals(1.0 in range2, 1.0 in M_NAN .. 0.0)
assertEquals(-1.0 in range2, -1.0 in M_NAN .. 0.0)
assertEquals(0.0 in range2, 0.0 in M_NAN .. 0.0)
assertEquals(NAN in range2, NAN in M_NAN .. 0.0)
assertEquals(M_NAN in range2, M_NAN in M_NAN .. 0.0)
assertEquals(1.0 !in range2, 1.0 !in M_NAN .. 0.0)
assertEquals(-1.0 !in range2, -1.0 !in M_NAN .. 0.0)
assertEquals(0.0 !in range2, 0.0 !in M_NAN .. 0.0)
assertEquals(NAN !in range2, NAN !in M_NAN .. 0.0)
assertEquals(M_NAN !in range2, M_NAN !in M_NAN .. 0.0)
assertEquals(1.0 in range3, 1.0 in M_NAN .. NAN)
assertEquals(-1.0 in range3, -1.0 in M_NAN .. NAN)
assertEquals(0.0 in range3, 0.0 in M_NAN .. NAN)
assertEquals(NAN in range3, NAN in M_NAN .. NAN)
assertEquals(M_NAN in range3, M_NAN in M_NAN .. NAN)
assertEquals(1.0 !in range3, 1.0 !in M_NAN .. NAN)
assertEquals(-1.0 !in range3, -1.0 !in M_NAN .. NAN)
assertEquals(0.0 !in range3, 0.0 !in M_NAN .. NAN)
assertEquals(NAN !in range3, NAN !in M_NAN .. NAN)
assertEquals(M_NAN !in range3, M_NAN !in M_NAN .. NAN)
return "OK"
}
@@ -14575,6 +14575,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName);
}
@TestMetadata("inFloatingPointRangeWithNaNBound.kt")
public void testInFloatingPointRangeWithNaNBound() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inFloatingPointRangeWithNaNBound.kt");
doTest(fileName);
}
@TestMetadata("inIntRange.kt")
public void testInIntRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inIntRange.kt");
@@ -14575,6 +14575,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("inFloatingPointRangeWithNaNBound.kt")
public void testInFloatingPointRangeWithNaNBound() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inFloatingPointRangeWithNaNBound.kt");
doTest(fileName);
}
@TestMetadata("inIntRange.kt")
public void testInIntRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inIntRange.kt");
@@ -14575,6 +14575,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName);
}
@TestMetadata("inFloatingPointRangeWithNaNBound.kt")
public void testInFloatingPointRangeWithNaNBound() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inFloatingPointRangeWithNaNBound.kt");
doTest(fileName);
}
@TestMetadata("inIntRange.kt")
public void testInIntRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inIntRange.kt");
@@ -15877,6 +15877,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
}
@TestMetadata("inFloatingPointRangeWithNaNBound.kt")
public void testInFloatingPointRangeWithNaNBound() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inFloatingPointRangeWithNaNBound.kt");
doTest(fileName);
}
@TestMetadata("inIntRange.kt")
public void testInIntRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/contains/inIntRange.kt");