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
This commit is contained in:
committed by
max-kammerer
parent
1a9ed88be4
commit
f6cf434650
+250
-63
@@ -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<Pair<IrExpression, Label>>()
|
||||
val expressionToLabels = ArrayList<ExpressionToLabel>()
|
||||
var elseExpression: IrExpression? = null
|
||||
val allConditions = ArrayList<Pair<IrCall, Label>>()
|
||||
val callToLabels = ArrayList<CallToLabel>()
|
||||
|
||||
// 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<IrCall>): Boolean {
|
||||
// 1. All branches are CALL 'EQEQ(Any?, Any?)': Boolean
|
||||
private fun areConstComparisons(conditions: List<IrCall>): 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
|
||||
|
||||
// 4. All arg1 are IrConst<*>.
|
||||
val rhs = conditions.map { it.getValueArgument(1) as? IrConst<*> }
|
||||
if (rhs.any { it == null || it.kind != IrConstKind.Int })
|
||||
// All RHS are constants
|
||||
if (conditions.any { it.getValueArgument(1) !is IrConst<*> })
|
||||
return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun areConstIntComparisons(conditions: List<IrCall>): Boolean {
|
||||
return checkTypeSpecifics(conditions, { it.isInt() }, { it.kind == IrConstKind.Int })
|
||||
}
|
||||
|
||||
private fun areConstStringComparisons(conditions: List<IrCall>): Boolean {
|
||||
return checkTypeSpecifics(
|
||||
conditions,
|
||||
{ it.isString() || it.isNullableString() },
|
||||
{ it.kind == IrConstKind.String || it.kind == IrConstKind.Null })
|
||||
}
|
||||
|
||||
private fun checkTypeSpecifics(
|
||||
conditions: List<IrCall>,
|
||||
subjectTypePredicate: (IrType) -> Boolean,
|
||||
irConstPredicate: (IrConst<*>) -> Boolean
|
||||
): Boolean {
|
||||
val lhs = conditions.map { it.getValueArgument(0) as IrGetValue }
|
||||
if (lhs.any { !subjectTypePredicate(it.type) })
|
||||
return false
|
||||
|
||||
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<Pair<Int, Label>>,
|
||||
abstract inner class Switch(
|
||||
val subject: IrGetValue,
|
||||
val defaultLabel: Label,
|
||||
val endLabel: Label,
|
||||
val elseExpression: IrExpression?,
|
||||
val expressionToLabels: ArrayList<ExpressionToLabel>
|
||||
) {
|
||||
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<ValueToLabel>) {
|
||||
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<Pair<IrExpression, Label>>
|
||||
): StackValue {
|
||||
cases.sortBy { it.first }
|
||||
expressionToLabels: ArrayList<ExpressionToLabel>,
|
||||
private val cases: List<ValueToLabel>
|
||||
) : Switch(subject, defaultLabel, endLabel, elseExpression, expressionToLabels) {
|
||||
|
||||
// Emit the temporary variable for subject.
|
||||
gen(subject, data)
|
||||
// IF is more compact when there are only 1 or fewer branches, in addition to else.
|
||||
override fun shouldOptimize() = cases.size > 1
|
||||
|
||||
val caseMin = cases.first().first
|
||||
val caseMax = cases.last().first
|
||||
val rangeLength = caseMax - caseMin + 1L
|
||||
override fun genSubject() {
|
||||
gen(subject, data)
|
||||
}
|
||||
|
||||
// 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 }
|
||||
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<ExpressionToLabel>,
|
||||
private val cases: List<ValueToLabel>
|
||||
) : Switch(subject, defaultLabel, endLabel, elseExpression, expressionToLabels) {
|
||||
|
||||
private val hashToStringAndExprLabels = HashMap<Int, ArrayList<ValueToLabel>>()
|
||||
private val hashAndSwitchLabels = ArrayList<ValueToLabel>()
|
||||
|
||||
init {
|
||||
for (case in cases)
|
||||
labels[case.first - caseMin] = case.second
|
||||
mv.tableswitch(caseMin, caseMax, defaultLabel, *labels)
|
||||
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()))
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
override fun genSwitch() {
|
||||
genIntSwitch(hashAndSwitchLabels)
|
||||
|
||||
mv.mark(endLabel)
|
||||
return result
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
@@ -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"
|
||||
}
|
||||
+31
@@ -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"
|
||||
}
|
||||
+33
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
-2
@@ -1,5 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
fun foo() : Int {
|
||||
val x : String = "dsa"
|
||||
when (x) {
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
fun foo(x : String) : String {
|
||||
|
||||
Vendored
+19
@@ -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
|
||||
Vendored
+19
@@ -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"
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
fun foo(x : String) : String {
|
||||
return when (x) {
|
||||
"abc", "cde" -> "abc_cde"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
fun foo1(x : String?) : String {
|
||||
when (x) {
|
||||
"abc", "cde" -> return "abc_cde"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
fun foo(x : String) : String {
|
||||
assert("abz]".hashCode() == "aby|".hashCode())
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
fun foo1(x : String) : String {
|
||||
when (x) {
|
||||
"abc", "cde" -> return "abc_cde"
|
||||
|
||||
+20
@@ -24625,6 +24625,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
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 class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
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 class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
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");
|
||||
|
||||
@@ -3418,6 +3418,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
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 class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
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 class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
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");
|
||||
|
||||
+20
@@ -24625,6 +24625,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
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 class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
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 class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
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");
|
||||
|
||||
+20
@@ -24630,6 +24630,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
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 class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
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 class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
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");
|
||||
|
||||
+20
@@ -3418,6 +3418,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
||||
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 class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
||||
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 class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
||||
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");
|
||||
|
||||
Generated
+20
@@ -18990,6 +18990,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
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 class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
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 class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
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");
|
||||
|
||||
+20
@@ -20090,6 +20090,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
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 class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
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 class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user