From f6cf4346502aeb13b67eb275a34b3068d533da05 Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Thu, 7 Mar 2019 17:26:58 -0800 Subject: [PATCH] when: emit switch for String if possible Effectively, the following when structure: when (s) { s1, s2 -> e1, s3 -> e2, s4 -> e3, ... else -> e } is implemented as: when (s.hashCode()) { h1 -> { if (s == s1) e1 else if (s == s2) e1 else if (s == s3) e2 else e } h2 -> if (s == s3) e2 else e, ... else -> e } where s1.hashCode() == s2.hashCode() == s3.hashCode() == h1, s4.hashCode() == h2. A tableswitch or lookupswitch is used for the hash code lookup. Change-Id: I087bf623dbb4a41d3cc64399a1b42342a50757a6 --- .../backend/jvm/codegen/SwitchGenerator.kt | 319 ++++++++++++++---- .../testData/codegen/box/when/edgeCases.kt | 20 ++ .../duplicatingItemsSameHashCode2.kt | 31 ++ .../duplicatingItemsSameHashCode3.kt | 33 ++ .../box/when/switchOptimizationDuplicates.kt | 29 ++ .../codegen/bytecodeText/when/edgeCases.kt | 10 + .../when/switchOptimizationDuplicates.kt | 19 ++ .../whenStringOptimization/denseHashCode.kt | 2 - .../duplicatingItems.kt | 1 - .../duplicatingItemsSameHashCode2.kt | 19 ++ .../duplicatingItemsSameHashCode3.kt | 19 ++ .../whenStringOptimization/expression.kt | 1 - .../whenStringOptimization/nullability.kt | 1 - .../whenStringOptimization/sameHashCode.kt | 1 - .../whenStringOptimization/statement.kt | 1 - .../codegen/BlackBoxCodegenTestGenerated.java | 20 ++ .../codegen/BytecodeTextTestGenerated.java | 20 ++ .../LightAnalysisModeTestGenerated.java | 20 ++ .../ir/IrBlackBoxCodegenTestGenerated.java | 20 ++ .../ir/IrBytecodeTextTestGenerated.java | 20 ++ .../IrJsCodegenBoxTestGenerated.java | 20 ++ .../semantics/JsCodegenBoxTestGenerated.java | 20 ++ 22 files changed, 573 insertions(+), 73 deletions(-) create mode 100644 compiler/testData/codegen/box/when/edgeCases.kt create mode 100644 compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode2.kt create mode 100644 compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode3.kt create mode 100644 compiler/testData/codegen/box/when/switchOptimizationDuplicates.kt create mode 100644 compiler/testData/codegen/bytecodeText/when/edgeCases.kt create mode 100644 compiler/testData/codegen/bytecodeText/when/switchOptimizationDuplicates.kt create mode 100644 compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode2.kt create mode 100644 compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode3.kt diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt index 543d8d5a12e4d..6dd27df5dde3b 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/SwitchGenerator.kt @@ -22,13 +22,17 @@ import java.util.* class SwitchGenerator(private val expression: IrWhen, private val data: BlockInfo, private val codegen: ExpressionCodegen) { private val mv = codegen.mv + data class ExpressionToLabel(val expression: IrExpression, val label: Label) + data class CallToLabel(val call: IrCall, val label: Label) + data class ValueToLabel(val value: Any?, val label: Label) + // @return null if the IrWhen cannot be emitted as lookupswitch or tableswitch. fun generate(): StackValue? { val endLabel = Label() var defaultLabel = endLabel - val thenExpressions = ArrayList>() + val expressionToLabels = ArrayList() var elseExpression: IrExpression? = null - val allConditions = ArrayList>() + val callToLabels = ArrayList() // Parse the when structure. Note that the condition can be nested. See matchConditions() for details. for (branch in expression.branches) { @@ -38,41 +42,95 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf } else { val conditions = matchConditions(branch.condition) ?: return null val thenLabel = Label() - thenExpressions.add(Pair(branch.result, thenLabel)) - allConditions += conditions.map { Pair(it, thenLabel) } + expressionToLabels.add(ExpressionToLabel(branch.result, thenLabel)) + callToLabels += conditions.map { CallToLabel(it, thenLabel) } } } - // IF is more compact when there are only 1 or fewer branches, in addition to else. - if (allConditions.size <= 1) + // switch isn't applicable if there's no case at all, e.g., when() { else -> ... } + if (callToLabels.size == 0) return null - if (areConstIntComparisons(allConditions.map { it.first })) { - // if all conditions are CALL EQEQ(tmp_variable, some_int_constant) - val cases = allConditions.mapTo(ArrayList()) { Pair((it.first.getValueArgument(1) as IrConst<*>).value as Int, it.second) } - val subject = allConditions[0].first.getValueArgument(0)!! as IrGetValue - return gen(cases, subject, defaultLabel, endLabel, elseExpression, thenExpressions) - } + val calls = callToLabels.map { it.call } - // TODO: String, Enum, etc. - return null + // Checks if all conditions are CALL EQEQ(tmp_variable, some_constant) + if (!areConstComparisons(calls)) + return null + + // Subject should be the same for all conditions. Let's pick the first. + val subject = callToLabels[0].call.getValueArgument(0)!! as IrGetValue + + // Don't generate repeated cases, which are unreachable but allowed in Kotlin. + // Only keep the first encountered case: + val cases = + callToLabels.map { ValueToLabel((it.call.getValueArgument(1) as IrConst<*>).value, it.label) }.distinctBy { it.value } + + // Remove labels and "then expressions" that are not reachable. + val reachableLabels = HashSet(cases.map { it.label }) + expressionToLabels.removeIf { it.label !in reachableLabels } + + return when { + areConstIntComparisons(calls) -> + IntSwitch( + subject, + defaultLabel, + endLabel, + elseExpression, + expressionToLabels, + cases + ) + areConstStringComparisons(calls) -> + StringSwitch( + subject, + defaultLabel, + endLabel, + elseExpression, + expressionToLabels, + cases + ) + else -> null // TODO: Enum, etc. + }?.genOptimizedIfEnoughCases() } - // A lookup/table switch can be used if... - private fun areConstIntComparisons(conditions: List): Boolean { - // 1. All branches are CALL 'EQEQ(Any?, Any?)': Boolean + private fun areConstComparisons(conditions: List): Boolean { + // All branches must be CALL 'EQEQ(Any?, Any?)': Boolean if (conditions.any { it.symbol != codegen.classCodegen.context.irBuiltIns.eqeqSymbol }) return false - // 2. All types of variables involved in comparison are Int. - // 3. All arg0 refer to the same value. + // All LHS refer to the same tmp variable. val lhs = conditions.map { it.getValueArgument(0) as? IrGetValue } - if (lhs.any { it == null || it.symbol != lhs[0]!!.symbol || !it.type.isInt() }) + if (lhs.any { it == null || it.symbol != lhs[0]!!.symbol }) + return false + + // All RHS are constants + if (conditions.any { it.getValueArgument(1) !is IrConst<*> }) + return false + + return true + } + + private fun areConstIntComparisons(conditions: List): Boolean { + return checkTypeSpecifics(conditions, { it.isInt() }, { it.kind == IrConstKind.Int }) + } + + private fun areConstStringComparisons(conditions: List): Boolean { + return checkTypeSpecifics( + conditions, + { it.isString() || it.isNullableString() }, + { it.kind == IrConstKind.String || it.kind == IrConstKind.Null }) + } + + private fun checkTypeSpecifics( + conditions: List, + subjectTypePredicate: (IrType) -> Boolean, + irConstPredicate: (IrConst<*>) -> Boolean + ): Boolean { + val lhs = conditions.map { it.getValueArgument(0) as IrGetValue } + if (lhs.any { !subjectTypePredicate(it.type) }) return false - // 4. All arg1 are IrConst<*>. - val rhs = conditions.map { it.getValueArgument(1) as? IrConst<*> } - if (rhs.any { it == null || it.kind != IrConstKind.Int }) + val rhs = conditions.map { it.getValueArgument(1) as IrConst<*> } + if (rhs.any { !irConstPredicate(it) }) return false return true @@ -137,58 +195,187 @@ class SwitchGenerator(private val expression: IrWhen, private val data: BlockInf private fun coerceNotToUnit(fromType: Type, fromKotlinType: KotlinType?, toKotlinType: KotlinType): StackValue = codegen.coerceNotToUnit(fromType, fromKotlinType, toKotlinType) - private fun gen( - cases: ArrayList>, + abstract inner class Switch( + val subject: IrGetValue, + val defaultLabel: Label, + val endLabel: Label, + val elseExpression: IrExpression?, + val expressionToLabels: ArrayList + ) { + open fun shouldOptimize() = false + + open fun genOptimizedIfEnoughCases(): StackValue? { + if (!shouldOptimize()) + return null + + genSubject() + genSwitch() + genThenExpressions() + val result = genElseExpression() + + mv.mark(endLabel) + return result + } + + protected abstract fun genSubject() + + protected abstract fun genSwitch() + + protected fun genIntSwitch(unsortedIntCases: List) { + val intCases = unsortedIntCases.sortedBy { it.value as Int } + val caseMin = intCases.first().value as Int + val caseMax = intCases.last().value as Int + val rangeLength = caseMax.toLong() - caseMin.toLong() + 1L + + // Emit either tableswitch or lookupswitch, depending on the code size. + // + // lookupswitch is 2X as large as tableswitch with the same entries. However, lookupswitch is sparse while tableswitch must + // enumerate all the entries in the range. + if (preferLookupOverSwitch(intCases.size, rangeLength)) { + mv.lookupswitch(defaultLabel, intCases.map { it.value as Int }.toIntArray(), intCases.map { it.label }.toTypedArray()) + } else { + val labels = Array(rangeLength.toInt()) { defaultLabel } + for (case in intCases) + labels[case.value as Int - caseMin] = case.label + mv.tableswitch(caseMin, caseMax, defaultLabel, *labels) + } + } + + protected fun genThenExpressions() { + for ((thenExpression, label) in expressionToLabels) { + mv.visitLabel(label) + val stackValue = thenExpression.run { gen(this, data) } + coerceNotToUnit(stackValue.type, stackValue.kotlinType, expression.type.toKotlinType()) + mv.goTo(endLabel) + } + } + + protected fun genElseExpression(): StackValue { + return if (elseExpression == null) { + // There's no else part. No stack value will be generated. + StackValue.putUnitInstance(mv) + onStack(Type.VOID_TYPE) + } else { + // Generate the else part. + mv.visitLabel(defaultLabel) + val stackValue = elseExpression.run { gen(this, data) } + coerceNotToUnit(stackValue.type, stackValue.kotlinType, expression.type.toKotlinType()) + } + } + } + + inner class IntSwitch( subject: IrGetValue, defaultLabel: Label, endLabel: Label, elseExpression: IrExpression?, - thenExpressions: ArrayList> - ): StackValue { - cases.sortBy { it.first } - - // Emit the temporary variable for subject. - gen(subject, data) - - val caseMin = cases.first().first - val caseMax = cases.last().first - val rangeLength = caseMax - caseMin + 1L - - // Emit either tableswitch or lookupswitch, depending on the code size. - // - // lookupswitch is 2X as large as tableswitch with the same entries. However, lookupswitch is sparse while tableswitch must - // enumerate all the entries in the range. - if (preferLookupOverSwitch(cases.size, rangeLength)) { - mv.lookupswitch(defaultLabel, cases.map { it.first }.toIntArray(), cases.map { it.second }.toTypedArray()) - } else { - val labels = Array(rangeLength.toInt()) { defaultLabel } - for (case in cases) - labels[case.first - caseMin] = case.second - mv.tableswitch(caseMin, caseMax, defaultLabel, *labels) + expressionToLabels: ArrayList, + private val cases: List + ) : Switch(subject, defaultLabel, endLabel, elseExpression, expressionToLabels) { + + // IF is more compact when there are only 1 or fewer branches, in addition to else. + override fun shouldOptimize() = cases.size > 1 + + override fun genSubject() { + gen(subject, data) } - // all entries except else - for (thenExpression in thenExpressions) { - mv.visitLabel(thenExpression.second) - val stackValue = thenExpression.first.run { gen(this, data) } - coerceNotToUnit(stackValue.type, stackValue.kotlinType, expression.type.toKotlinType()) - mv.goTo(endLabel) + override fun genSwitch() { + genIntSwitch(cases) + } + } + + // The following when structure: + // + // when (s) { + // s1, s2 -> e1, + // s3 -> e2, + // s4 -> e3, + // ... + // else -> e + // } + // + // is implemented as: + // + // // if s is String?, generate the following null check: + // if (s == null) + // // jump to the case where null is handled, if defined. + // // otherwise, jump out of the when(). + // ... + // ... + // when (s.hashCode()) { + // h1 -> { + // if (s == s1) + // e1 + // else if (s == s2) + // e1 + // else if (s == s3) + // e2 + // else + // e + // } + // h2 -> if (s == s3) e2 else e, + // ... + // else -> e + // } + // + // where s1.hashCode() == s2.hashCode() == s3.hashCode() == h1, + // s4.hashCode() == h2. + // + // A tableswitch or lookupswitch is then used for the hash code lookup. + + inner class StringSwitch( + subject: IrGetValue, + defaultLabel: Label, + endLabel: Label, + elseExpression: IrExpression?, + expressionToLabels: ArrayList, + private val cases: List + ) : Switch(subject, defaultLabel, endLabel, elseExpression, expressionToLabels) { + + private val hashToStringAndExprLabels = HashMap>() + private val hashAndSwitchLabels = ArrayList() + + init { + for (case in cases) + if (case.value != null) // null is handled specially and will never be dispatched from the switch. + hashToStringAndExprLabels.getOrPut(case.value.hashCode()) { ArrayList() }.add( + ValueToLabel(case.value, case.label) + ) + + for (key in hashToStringAndExprLabels.keys) + hashAndSwitchLabels.add(ValueToLabel(key, Label())) } - // else - val result = if (elseExpression == null) { - // There's no else part. No stack value will be generated. - StackValue.putUnitInstance(mv) - onStack(Type.VOID_TYPE) - } else { - // Generate the else part. - mv.visitLabel(defaultLabel) - val stackValue = elseExpression.run { gen(this, data) } - coerceNotToUnit(stackValue.type, stackValue.kotlinType, expression.type.toKotlinType()) + // Using a switch, the subject string has to be traversed at least twice (hash + comparison * N, where N is #strings hashed into the + // same bucket). The optimization isn't better than an IF cascade when #switch-targets <= 2. + override fun shouldOptimize() = hashAndSwitchLabels.size > 2 + + override fun genSubject() { + if (subject.type.isNullableString()) { + val nullLabel = cases.find { it.value == null }?.label ?: defaultLabel + gen(subject, data) + mv.ifnull(nullLabel) + } + gen(subject, data) + mv.invokevirtual("java/lang/String", "hashCode", "()I", false) } - mv.mark(endLabel) - return result + override fun genSwitch() { + genIntSwitch(hashAndSwitchLabels) + + // Multiple strings can be hashed into the same bucket. + // Generate an if cascade to resolve that for each bucket. + for ((hash, switchLabel) in hashAndSwitchLabels) { + mv.visitLabel(switchLabel) + for ((string, label) in hashToStringAndExprLabels[hash]!!) { + gen(subject, data) + mv.aconst(string) + mv.invokevirtual("java/lang/String", "equals", "(Ljava/lang/Object;)Z", false) + mv.ifne(label) + } + mv.goTo(defaultLabel) + } + } } } - diff --git a/compiler/testData/codegen/box/when/edgeCases.kt b/compiler/testData/codegen/box/when/edgeCases.kt new file mode 100644 index 0000000000000..df5289d5a5a08 --- /dev/null +++ b/compiler/testData/codegen/box/when/edgeCases.kt @@ -0,0 +1,20 @@ +fun foo(x: Int): String { + return when (x) { + 2_147_483_647 -> "MAX" + -2_147_483_648 -> "MIN" + else -> "else" + } +} + +fun box(): String { + if (foo(0) != "else") + return "0: " + foo(0).toString() + + if (foo(Int.MAX_VALUE) != "MAX") + return "Int.MAX_VALUE: " + foo(Int.MAX_VALUE).toString() + + if (foo(Int.MIN_VALUE) != "MIN") + return "Int.MIN_VALUE: " + foo(Int.MIN_VALUE).toString() + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode2.kt b/compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode2.kt new file mode 100644 index 0000000000000..cce22d88c45de --- /dev/null +++ b/compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode2.kt @@ -0,0 +1,31 @@ +// IGNORE_BACKEND: NATIVE +// IGNORE_BACKEND: JS_IR +// TODO: muted automatically, investigate should it be ran for JS or not +// IGNORE_BACKEND: JS + +// WITH_RUNTIME + +import kotlin.test.assertEquals + +fun foo(x : String) : String { + assert("abz]".hashCode() == "aby|".hashCode()) + + when (x) { + "abz]" -> return "abz" + "ghi" -> return "ghi" + "aby|" -> return "aby" + "abz]" -> return "fail" + } + + return "other" +} + +fun box() : String { + assertEquals("abz", foo("abz]")) + assertEquals("aby", foo("aby|")) + assertEquals("ghi", foo("ghi")) + + assertEquals("other", foo("xyz")) + + return "OK" +} diff --git a/compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode3.kt b/compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode3.kt new file mode 100644 index 0000000000000..8dbdbb7691882 --- /dev/null +++ b/compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode3.kt @@ -0,0 +1,33 @@ +// IGNORE_BACKEND: NATIVE +// IGNORE_BACKEND: JS_IR +// TODO: muted automatically, investigate should it be ran for JS or not +// IGNORE_BACKEND: JS + +// WITH_RUNTIME + +import kotlin.test.assertEquals + +fun foo(x : String) : String { + assert("abz]".hashCode() == "aby|".hashCode()) + + when (x) { + "abz]" -> return "abz" + "ghi" -> return "ghi" + "aby|" -> return "aby" + "abz]" -> return "fail" + "uvw" -> return "uvw" + } + + return "other" +} + +fun box() : String { + assertEquals("abz", foo("abz]")) + assertEquals("aby", foo("aby|")) + assertEquals("ghi", foo("ghi")) + assertEquals("uvw", foo("uvw")) + + assertEquals("other", foo("xyz")) + + return "OK" +} diff --git a/compiler/testData/codegen/box/when/switchOptimizationDuplicates.kt b/compiler/testData/codegen/box/when/switchOptimizationDuplicates.kt new file mode 100644 index 0000000000000..79f583153fdd0 --- /dev/null +++ b/compiler/testData/codegen/box/when/switchOptimizationDuplicates.kt @@ -0,0 +1,29 @@ +fun foo(x: Int): Int { + return when (x) { + 1, 1, 2 -> 1001 + 1, 2 -> 1002 + 1 -> 1003 + 2 -> 1004 + 3 -> 1005 + else -> 1006 + } +} + +fun box(): String { + if (foo(0) != 1006) + return "0: " + foo(0) + + if (foo(1) != 1001) + return "1: " + foo(1) + + if (foo(2) != 1001) + return "2: " + foo(2) + + if (foo(3) != 1005) + return "3: " + foo(3) + + if (foo(4) != 1006) + return "4: " + foo(4) + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/when/edgeCases.kt b/compiler/testData/codegen/bytecodeText/when/edgeCases.kt new file mode 100644 index 0000000000000..54e45c0ea560c --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/when/edgeCases.kt @@ -0,0 +1,10 @@ +fun foo(x: Int): String { + return when (x) { + 2_147_483_647 -> "MAX" + -2_147_483_648 -> "MIN" + else -> "else" + } +} + +// 1 LOOKUPSWITCH +// 0 TABLESWITCH \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/when/switchOptimizationDuplicates.kt b/compiler/testData/codegen/bytecodeText/when/switchOptimizationDuplicates.kt new file mode 100644 index 0000000000000..a6438315fb767 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/when/switchOptimizationDuplicates.kt @@ -0,0 +1,19 @@ +fun foo(x: Int): Int { + return when (x) { + 1, 1, 2 -> 1001 + 1, 2 -> 1002 + 1 -> 1003 + 2 -> 1004 + 3 -> 1005 + else -> 1006 + } +} + +// 1 SIPUSH 1001 +// 0 SIPUSH 1002 +// 0 SIPUSH 1003 +// 0 SIPUSH 1004 +// 1 SIPUSH 1005 +// 1 SIPUSH 1006 +// 1 TABLESWITCH +// 0 LOOKUPSWITCH \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/whenStringOptimization/denseHashCode.kt b/compiler/testData/codegen/bytecodeText/whenStringOptimization/denseHashCode.kt index 9e03ac0394f68..eb2a8f4d4272d 100644 --- a/compiler/testData/codegen/bytecodeText/whenStringOptimization/denseHashCode.kt +++ b/compiler/testData/codegen/bytecodeText/whenStringOptimization/denseHashCode.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: JVM_IR - fun foo() : Int { val x : String = "dsa" when (x) { diff --git a/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItems.kt b/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItems.kt index 85bf56ed65556..15a2ebde39203 100644 --- a/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItems.kt +++ b/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItems.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR import kotlin.test.assertEquals fun foo(x : String) : String { diff --git a/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode2.kt b/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode2.kt new file mode 100644 index 0000000000000..147fea74443f6 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode2.kt @@ -0,0 +1,19 @@ +// IGNORE_BACKEND: JVM +import kotlin.test.assertEquals + +fun foo(x : String) : String { + assert("abz]".hashCode() == "aby|".hashCode()) + + when (x) { + "abz]" -> return "abz" + "ghi" -> return "ghi" + "aby|" -> return "aby" + "abz]" -> return "fail" + } + + return "other" +} + +// Expecting 0 LOOKUPSWITCH as there is only 2 different hash codes. +// An IF casecade is more efficient. +// 0 LOOKUPSWITCH diff --git a/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode3.kt b/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode3.kt new file mode 100644 index 0000000000000..f2800eab8b5d8 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode3.kt @@ -0,0 +1,19 @@ +// IGNORE_BACKEND: JVM +import kotlin.test.assertEquals + +fun foo(x : String) : String { + assert("abz]".hashCode() == "aby|".hashCode()) + + when (x) { + "abz]" -> return "abz" + "ghi" -> return "ghi" + "aby|" -> return "aby" + "abz]" -> return "fail" + "uvw" -> return "uvw" + } + + return "other" +} + +// 1 LOOKUPSWITCH +// 0 LDC "fail" diff --git a/compiler/testData/codegen/bytecodeText/whenStringOptimization/expression.kt b/compiler/testData/codegen/bytecodeText/whenStringOptimization/expression.kt index e7cee4711cd22..4831cfb6df9f3 100644 --- a/compiler/testData/codegen/bytecodeText/whenStringOptimization/expression.kt +++ b/compiler/testData/codegen/bytecodeText/whenStringOptimization/expression.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR fun foo(x : String) : String { return when (x) { "abc", "cde" -> "abc_cde" diff --git a/compiler/testData/codegen/bytecodeText/whenStringOptimization/nullability.kt b/compiler/testData/codegen/bytecodeText/whenStringOptimization/nullability.kt index 10dd4cff09533..52905d3a900a6 100644 --- a/compiler/testData/codegen/bytecodeText/whenStringOptimization/nullability.kt +++ b/compiler/testData/codegen/bytecodeText/whenStringOptimization/nullability.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR fun foo1(x : String?) : String { when (x) { "abc", "cde" -> return "abc_cde" diff --git a/compiler/testData/codegen/bytecodeText/whenStringOptimization/sameHashCode.kt b/compiler/testData/codegen/bytecodeText/whenStringOptimization/sameHashCode.kt index 498f213be9d48..f5f0d55b265de 100644 --- a/compiler/testData/codegen/bytecodeText/whenStringOptimization/sameHashCode.kt +++ b/compiler/testData/codegen/bytecodeText/whenStringOptimization/sameHashCode.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR fun foo(x : String) : String { assert("abz]".hashCode() == "aby|".hashCode()) diff --git a/compiler/testData/codegen/bytecodeText/whenStringOptimization/statement.kt b/compiler/testData/codegen/bytecodeText/whenStringOptimization/statement.kt index bc50b93cd9c3d..7e07e9961d7e6 100644 --- a/compiler/testData/codegen/bytecodeText/whenStringOptimization/statement.kt +++ b/compiler/testData/codegen/bytecodeText/whenStringOptimization/statement.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR fun foo1(x : String) : String { when (x) { "abc", "cde" -> return "abc_cde" diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index d6c93d8d553a8..a2771d5f12f61 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -24625,6 +24625,11 @@ public void testCallProperty() throws Exception { runTest("compiler/testData/codegen/box/when/callProperty.kt"); } + @TestMetadata("edgeCases.kt") + public void testEdgeCases() throws Exception { + runTest("compiler/testData/codegen/box/when/edgeCases.kt"); + } + @TestMetadata("emptyWhen.kt") public void testEmptyWhen() throws Exception { runTest("compiler/testData/codegen/box/when/emptyWhen.kt"); @@ -24755,6 +24760,11 @@ public void testSwitchOptimizationDense() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationDense.kt"); } + @TestMetadata("switchOptimizationDuplicates.kt") + public void testSwitchOptimizationDuplicates() throws Exception { + runTest("compiler/testData/codegen/box/when/switchOptimizationDuplicates.kt"); + } + @TestMetadata("switchOptimizationMultipleConditions.kt") public void testSwitchOptimizationMultipleConditions() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationMultipleConditions.kt"); @@ -24925,6 +24935,16 @@ public void testDuplicatingItemsSameHashCode() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode.kt"); } + @TestMetadata("duplicatingItemsSameHashCode2.kt") + public void testDuplicatingItemsSameHashCode2() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode2.kt"); + } + + @TestMetadata("duplicatingItemsSameHashCode3.kt") + public void testDuplicatingItemsSameHashCode3() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode3.kt"); + } + @TestMetadata("expression.kt") public void testExpression() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/expression.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 7c8ea9ed05dc6..89bfd239fc995 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -3418,6 +3418,11 @@ public void testAllFilesPresentInWhen() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/when"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("edgeCases.kt") + public void testEdgeCases() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/when/edgeCases.kt"); + } + @TestMetadata("exhaustiveWhenInitialization.kt") public void testExhaustiveWhenInitialization() throws Exception { runTest("compiler/testData/codegen/bytecodeText/when/exhaustiveWhenInitialization.kt"); @@ -3498,6 +3503,11 @@ public void testSubjectValInStringWhenHasLocalVariableSlot() throws Exception { runTest("compiler/testData/codegen/bytecodeText/when/subjectValInStringWhenHasLocalVariableSlot.kt"); } + @TestMetadata("switchOptimizationDuplicates.kt") + public void testSwitchOptimizationDuplicates() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/when/switchOptimizationDuplicates.kt"); + } + @TestMetadata("tableSwitch.kt") public void testTableSwitch() throws Exception { runTest("compiler/testData/codegen/bytecodeText/when/tableSwitch.kt"); @@ -3629,6 +3639,16 @@ public void testDuplicatingItemsSameHashCode() throws Exception { runTest("compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode.kt"); } + @TestMetadata("duplicatingItemsSameHashCode2.kt") + public void testDuplicatingItemsSameHashCode2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode2.kt"); + } + + @TestMetadata("duplicatingItemsSameHashCode3.kt") + public void testDuplicatingItemsSameHashCode3() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode3.kt"); + } + @TestMetadata("expression.kt") public void testExpression() throws Exception { runTest("compiler/testData/codegen/bytecodeText/whenStringOptimization/expression.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 4ed6b61cedc81..bef3783edd76d 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -24625,6 +24625,11 @@ public void testCallProperty() throws Exception { runTest("compiler/testData/codegen/box/when/callProperty.kt"); } + @TestMetadata("edgeCases.kt") + public void testEdgeCases() throws Exception { + runTest("compiler/testData/codegen/box/when/edgeCases.kt"); + } + @TestMetadata("emptyWhen.kt") public void testEmptyWhen() throws Exception { runTest("compiler/testData/codegen/box/when/emptyWhen.kt"); @@ -24755,6 +24760,11 @@ public void testSwitchOptimizationDense() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationDense.kt"); } + @TestMetadata("switchOptimizationDuplicates.kt") + public void testSwitchOptimizationDuplicates() throws Exception { + runTest("compiler/testData/codegen/box/when/switchOptimizationDuplicates.kt"); + } + @TestMetadata("switchOptimizationMultipleConditions.kt") public void testSwitchOptimizationMultipleConditions() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationMultipleConditions.kt"); @@ -24925,6 +24935,16 @@ public void testDuplicatingItemsSameHashCode() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode.kt"); } + @TestMetadata("duplicatingItemsSameHashCode2.kt") + public void testDuplicatingItemsSameHashCode2() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode2.kt"); + } + + @TestMetadata("duplicatingItemsSameHashCode3.kt") + public void testDuplicatingItemsSameHashCode3() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode3.kt"); + } + @TestMetadata("expression.kt") public void testExpression() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/expression.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index a7ea79b69c4f4..6e7e0a8e3f57f 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -24630,6 +24630,11 @@ public void testCallProperty() throws Exception { runTest("compiler/testData/codegen/box/when/callProperty.kt"); } + @TestMetadata("edgeCases.kt") + public void testEdgeCases() throws Exception { + runTest("compiler/testData/codegen/box/when/edgeCases.kt"); + } + @TestMetadata("emptyWhen.kt") public void testEmptyWhen() throws Exception { runTest("compiler/testData/codegen/box/when/emptyWhen.kt"); @@ -24760,6 +24765,11 @@ public void testSwitchOptimizationDense() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationDense.kt"); } + @TestMetadata("switchOptimizationDuplicates.kt") + public void testSwitchOptimizationDuplicates() throws Exception { + runTest("compiler/testData/codegen/box/when/switchOptimizationDuplicates.kt"); + } + @TestMetadata("switchOptimizationMultipleConditions.kt") public void testSwitchOptimizationMultipleConditions() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationMultipleConditions.kt"); @@ -24930,6 +24940,16 @@ public void testDuplicatingItemsSameHashCode() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode.kt"); } + @TestMetadata("duplicatingItemsSameHashCode2.kt") + public void testDuplicatingItemsSameHashCode2() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode2.kt"); + } + + @TestMetadata("duplicatingItemsSameHashCode3.kt") + public void testDuplicatingItemsSameHashCode3() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode3.kt"); + } + @TestMetadata("expression.kt") public void testExpression() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/expression.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java index 34d7b5507553e..071d84e29436a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -3418,6 +3418,11 @@ public void testAllFilesPresentInWhen() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/when"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true); } + @TestMetadata("edgeCases.kt") + public void testEdgeCases() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/when/edgeCases.kt"); + } + @TestMetadata("exhaustiveWhenInitialization.kt") public void testExhaustiveWhenInitialization() throws Exception { runTest("compiler/testData/codegen/bytecodeText/when/exhaustiveWhenInitialization.kt"); @@ -3498,6 +3503,11 @@ public void testSubjectValInStringWhenHasLocalVariableSlot() throws Exception { runTest("compiler/testData/codegen/bytecodeText/when/subjectValInStringWhenHasLocalVariableSlot.kt"); } + @TestMetadata("switchOptimizationDuplicates.kt") + public void testSwitchOptimizationDuplicates() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/when/switchOptimizationDuplicates.kt"); + } + @TestMetadata("tableSwitch.kt") public void testTableSwitch() throws Exception { runTest("compiler/testData/codegen/bytecodeText/when/tableSwitch.kt"); @@ -3629,6 +3639,16 @@ public void testDuplicatingItemsSameHashCode() throws Exception { runTest("compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode.kt"); } + @TestMetadata("duplicatingItemsSameHashCode2.kt") + public void testDuplicatingItemsSameHashCode2() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode2.kt"); + } + + @TestMetadata("duplicatingItemsSameHashCode3.kt") + public void testDuplicatingItemsSameHashCode3() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/whenStringOptimization/duplicatingItemsSameHashCode3.kt"); + } + @TestMetadata("expression.kt") public void testExpression() throws Exception { runTest("compiler/testData/codegen/bytecodeText/whenStringOptimization/expression.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index 5eff889816277..ce72e8a886712 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -18990,6 +18990,11 @@ public void testCallProperty() throws Exception { runTest("compiler/testData/codegen/box/when/callProperty.kt"); } + @TestMetadata("edgeCases.kt") + public void testEdgeCases() throws Exception { + runTest("compiler/testData/codegen/box/when/edgeCases.kt"); + } + @TestMetadata("emptyWhen.kt") public void testEmptyWhen() throws Exception { runTest("compiler/testData/codegen/box/when/emptyWhen.kt"); @@ -19120,6 +19125,11 @@ public void testSwitchOptimizationDense() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationDense.kt"); } + @TestMetadata("switchOptimizationDuplicates.kt") + public void testSwitchOptimizationDuplicates() throws Exception { + runTest("compiler/testData/codegen/box/when/switchOptimizationDuplicates.kt"); + } + @TestMetadata("switchOptimizationMultipleConditions.kt") public void testSwitchOptimizationMultipleConditions() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationMultipleConditions.kt"); @@ -19290,6 +19300,16 @@ public void testDuplicatingItemsSameHashCode() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode.kt"); } + @TestMetadata("duplicatingItemsSameHashCode2.kt") + public void testDuplicatingItemsSameHashCode2() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode2.kt"); + } + + @TestMetadata("duplicatingItemsSameHashCode3.kt") + public void testDuplicatingItemsSameHashCode3() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode3.kt"); + } + @TestMetadata("expression.kt") public void testExpression() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/expression.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 847da919c26fb..794040c0975e3 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 @@ -20090,6 +20090,11 @@ public void testCallProperty() throws Exception { runTest("compiler/testData/codegen/box/when/callProperty.kt"); } + @TestMetadata("edgeCases.kt") + public void testEdgeCases() throws Exception { + runTest("compiler/testData/codegen/box/when/edgeCases.kt"); + } + @TestMetadata("emptyWhen.kt") public void testEmptyWhen() throws Exception { runTest("compiler/testData/codegen/box/when/emptyWhen.kt"); @@ -20220,6 +20225,11 @@ public void testSwitchOptimizationDense() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationDense.kt"); } + @TestMetadata("switchOptimizationDuplicates.kt") + public void testSwitchOptimizationDuplicates() throws Exception { + runTest("compiler/testData/codegen/box/when/switchOptimizationDuplicates.kt"); + } + @TestMetadata("switchOptimizationMultipleConditions.kt") public void testSwitchOptimizationMultipleConditions() throws Exception { runTest("compiler/testData/codegen/box/when/switchOptimizationMultipleConditions.kt"); @@ -20390,6 +20400,16 @@ public void testDuplicatingItemsSameHashCode() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode.kt"); } + @TestMetadata("duplicatingItemsSameHashCode2.kt") + public void testDuplicatingItemsSameHashCode2() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode2.kt"); + } + + @TestMetadata("duplicatingItemsSameHashCode3.kt") + public void testDuplicatingItemsSameHashCode3() throws Exception { + runTest("compiler/testData/codegen/box/when/stringOptimization/duplicatingItemsSameHashCode3.kt"); + } + @TestMetadata("expression.kt") public void testExpression() throws Exception { runTest("compiler/testData/codegen/box/when/stringOptimization/expression.kt");