diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/PrimitiveNumberRangeIntrinsicRangeValue.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/PrimitiveNumberRangeIntrinsicRangeValue.kt index 42979e90b55..d164fea09ec 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/PrimitiveNumberRangeIntrinsicRangeValue.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/PrimitiveNumberRangeIntrinsicRangeValue.kt @@ -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): CallIntrinsicRangeValue(rangeCall) { protected val asmElementType = getAsmRangeElementTypeForPrimitiveRangeOrProgression(rangeCall.resultingDescriptor) @@ -41,12 +43,22 @@ abstract class PrimitiveNumberRangeIntrinsicRangeValue(rangeCall: ResolvedCall ): 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 diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/SimpleBoundedValue.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/SimpleBoundedValue.kt index 378f429ea9c..a6dc038d8ae 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/SimpleBoundedValue.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/SimpleBoundedValue.kt @@ -25,9 +25,9 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter class SimpleBoundedValue( codegen: ExpressionCodegen, rangeCall: ResolvedCall, - private val lowBound: StackValue, + val lowBound: StackValue, isLowInclusive: Boolean, - private val highBound: StackValue, + val highBound: StackValue, isHighInclusive: Boolean ): AbstractBoundedValue(codegen, rangeCall, isLowInclusive, isHighInclusive) { constructor( diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/comparison/FloatingPointComparisonGenerator.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/comparison/FloatingPointComparisonGenerator.kt index c76eda7f9b7..a7f5f737736 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/comparison/FloatingPointComparisonGenerator.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/comparison/FloatingPointComparisonGenerator.kt @@ -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) } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/inExpression/InFloatingPointRangeLiteralExpressionGenerator.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/inExpression/InFloatingPointRangeLiteralExpressionGenerator.kt new file mode 100644 index 00000000000..4953f94b63b --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/inExpression/InFloatingPointRangeLiteralExpressionGenerator.kt @@ -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 { + 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) + } + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/inExpression/InPrimitiveContinuousRangeExpressionGenerator.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/inExpression/InIntegralContinuousRangeExpressionGenerator.kt similarity index 99% rename from compiler/backend/src/org/jetbrains/kotlin/codegen/range/inExpression/InPrimitiveContinuousRangeExpressionGenerator.kt rename to compiler/backend/src/org/jetbrains/kotlin/codegen/range/inExpression/InIntegralContinuousRangeExpressionGenerator.kt index efe058814f9..fc9710d0f16 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/inExpression/InPrimitiveContinuousRangeExpressionGenerator.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/inExpression/InIntegralContinuousRangeExpressionGenerator.kt @@ -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, diff --git a/compiler/testData/codegen/box/ranges/contains/inFloatingPointRangeWithNaNBound.kt b/compiler/testData/codegen/box/ranges/contains/inFloatingPointRangeWithNaNBound.kt new file mode 100644 index 00000000000..234c068b4b0 --- /dev/null +++ b/compiler/testData/codegen/box/ranges/contains/inFloatingPointRangeWithNaNBound.kt @@ -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" +} \ No newline at end of file diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index d5418d24b5e..e7aa24af31f 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -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"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index aa3b34436b5..c39012c5712 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -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"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 05591383a9e..4afc45bcab7 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -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"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index d488d4c1765..b577fdd0260 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -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");