benf.org : other : cfr : Kotlin Switch (when) On String |
I previously looked at how java 7+ compiles switch on string.
Kotlin has a very similar construct, (except that when is an expression-switch, though that's hopefully coming to java in JSR-325.)
fun whenSwitch(str : String) = when (str) { "Aa", "BB" -> 111; "cc" -> 222; else -> 444; }
I initially expected this to be compiled very similarly to the java, i.e. a code implementation of an open hash/separate chaining, with a second hash.
Interestingly (and perfectly reasonably!) Kotlin compiles this slightly differently....
public static final int whenSwitch(java.lang.String); Code: 0: aload_0 1: ldc #9 // String str 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_0 7: astore_1 8: aload_1 9: invokevirtual #21 // Method java/lang/String.hashCode:()I 12: lookupswitch { // 2 LB : First, switch on hashcode..... 2112: 40 3168: 64 default: 87 } 40: aload_1 41: ldc #23 // String Aa - LB: Then, check the buckets for that hash 43: invokevirtual #27 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 46: ifeq 52 49: goto 76 // LB: Then, branch directly to the target....... 52: aload_1 53: ldc #29 // String BB 55: invokevirtual #27 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 58: ifeq 87 61: goto 76 64: aload_1 65: ldc #31 // String cc 67: invokevirtual #27 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 70: ifeq 87 73: goto 81 76: bipush 111 78: goto 90 81: sipush 222 84: goto 90 87: sipush 444 // LB: With the default way down here. 90: ireturn }
It's interesting (normal caveat: to me!) - Instead of having the second switch statement, Kotlin's compiler has generated jumps directly to the target branches - same behaviour, but effectively bypassing the need for a second switch.
This is equivalent to
int res; switch (str.hashCode()) { case 2112: if (str.equals("Aa")) goto LABEL-76; if (str.equals("BB")) goto LABEL-76; goto default; case 3168: if (str.equals("cc")) goto LABEL-81; goto default; LABEL-76: res = 111; break; LABEL-81: res = 222; break; default: res = 444; } return res;
This is kind of nice - the second switch in java has no real value. For me, that's the really interesting thing here - thinking again about why Javac generates it - I assume it's there because the string-switch to int-switch transformation was originally described as a pure Java source transform in the project Coin spec, and the implementors followed that description literally!
The above is of course, not valid Java at all, and can't be. (Which is why cfr < 129 blew up horribly on it!).
As of CFR 129, I pattern match for the above code, and re-introduce the secondary switch, allowing subsequent transforms to tidy up as normal - this means we generate reasonable java!
public final class WhenTest3Kt { public static final int whenSwitch(@NotNull String str) { int n; Intrinsics.checkParameterIsNotNull((Object)str, (String)"str"); switch (str) { case "Aa": case "BB": { n = 111; break; } case "cc": { n = 222; break; } default: { n = 444; } } return n; } }
Hurrah! (ish)
Now CFR (141+) supports JSR 325 Switch expressions, we can also get them back when decompiling Kotlin bytecode! (in CFR 142+)
Note that because this is an experimental feature until java 13, you will need to specify --switchexpression true ... and you'll get:
public final class WhenTest3Kt { public static final int whenSwitch(@NotNull String str) { Intrinsics.checkParameterIsNotNull((Object)str, (String)"str"); int n = switch (str) { "Aa", "BB" -> 111; "cc" -> 222; default -> 444; }; return n; } }
Last updated 05/2018 |