@@ -3,6 +3,7 @@ package backend
3
3
package jvm
4
4
5
5
import scala .annotation .switch
6
+ import scala .collection .mutable .SortedMap
6
7
7
8
import scala .tools .asm
8
9
import scala .tools .asm .{Handle , Label , Opcodes }
@@ -826,61 +827,170 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
826
827
generatedType
827
828
}
828
829
829
- /*
830
- * A Match node contains one or more case clauses,
831
- * each case clause lists one or more Int values to use as keys, and a code block.
832
- * Except the "default" case clause which (if it exists) doesn't list any Int key.
833
- *
834
- * On a first pass over the case clauses, we flatten the keys and their targets (the latter represented with asm.Labels).
835
- * That representation allows JCodeMethodV to emit a lookupswitch or a tableswitch.
836
- *
837
- * On a second pass, we emit the switch blocks, one for each different target.
830
+ /* A Match node contains one or more case clauses, each case clause lists one or more
831
+ * Int/String values to use as keys, and a code block. The exception is the "default" case
832
+ * clause which doesn't list any key (there is exactly one of these per match).
838
833
*/
839
834
private def genMatch (tree : Match ): BType = tree match {
840
835
case Match (selector, cases) =>
841
836
lineNumber(tree)
842
- genLoad(selector, INT )
843
837
val generatedType = tpeTK(tree)
838
+ val postMatch = new asm.Label
844
839
845
- var flatKeys : List [Int ] = Nil
846
- var targets : List [asm.Label ] = Nil
847
- var default : asm.Label = null
848
- var switchBlocks : List [(asm.Label , Tree )] = Nil
849
-
850
- // collect switch blocks and their keys, but don't emit yet any switch-block.
851
- for (caze @ CaseDef (pat, guard, body) <- cases) {
852
- assert(guard == tpd.EmptyTree , guard)
853
- val switchBlockPoint = new asm.Label
854
- switchBlocks ::= (switchBlockPoint, body)
855
- pat match {
856
- case Literal (value) =>
857
- flatKeys ::= value.intValue
858
- targets ::= switchBlockPoint
859
- case Ident (nme.WILDCARD ) =>
860
- assert(default == null , s " multiple default targets in a Match node, at ${tree.span}" )
861
- default = switchBlockPoint
862
- case Alternative (alts) =>
863
- alts foreach {
864
- case Literal (value) =>
865
- flatKeys ::= value.intValue
866
- targets ::= switchBlockPoint
867
- case _ =>
868
- abort(s " Invalid alternative in alternative pattern in Match node: $tree at: ${tree.span}" )
869
- }
870
- case _ =>
871
- abort(s " Invalid pattern in Match node: $tree at: ${tree.span}" )
840
+ // Only two possible selector types exist in `Match` trees at this point: Int and String
841
+ if (tpeTK(selector) == INT ) {
842
+
843
+ /* On a first pass over the case clauses, we flatten the keys and their
844
+ * targets (the latter represented with asm.Labels). That representation
845
+ * allows JCodeMethodV to emit a lookupswitch or a tableswitch.
846
+ *
847
+ * On a second pass, we emit the switch blocks, one for each different target.
848
+ */
849
+
850
+ var flatKeys : List [Int ] = Nil
851
+ var targets : List [asm.Label ] = Nil
852
+ var default : asm.Label = null
853
+ var switchBlocks : List [(asm.Label , Tree )] = Nil
854
+
855
+ genLoad(selector, INT )
856
+
857
+ // collect switch blocks and their keys, but don't emit yet any switch-block.
858
+ for (caze @ CaseDef (pat, guard, body) <- cases) {
859
+ assert(guard == tpd.EmptyTree , guard)
860
+ val switchBlockPoint = new asm.Label
861
+ switchBlocks ::= (switchBlockPoint, body)
862
+ pat match {
863
+ case Literal (value) =>
864
+ flatKeys ::= value.intValue
865
+ targets ::= switchBlockPoint
866
+ case Ident (nme.WILDCARD ) =>
867
+ assert(default == null , s " multiple default targets in a Match node, at ${tree.span}" )
868
+ default = switchBlockPoint
869
+ case Alternative (alts) =>
870
+ alts foreach {
871
+ case Literal (value) =>
872
+ flatKeys ::= value.intValue
873
+ targets ::= switchBlockPoint
874
+ case _ =>
875
+ abort(s " Invalid alternative in alternative pattern in Match node: $tree at: ${tree.span}" )
876
+ }
877
+ case _ =>
878
+ abort(s " Invalid pattern in Match node: $tree at: ${tree.span}" )
879
+ }
872
880
}
873
- }
874
881
875
- bc.emitSWITCH(mkArrayReverse(flatKeys), mkArrayL(targets.reverse), default, MIN_SWITCH_DENSITY )
882
+ bc.emitSWITCH(mkArrayReverse(flatKeys), mkArrayL(targets.reverse), default, MIN_SWITCH_DENSITY )
876
883
877
- // emit switch-blocks.
878
- val postMatch = new asm.Label
879
- for (sb <- switchBlocks.reverse) {
880
- val (caseLabel, caseBody) = sb
881
- markProgramPoint(caseLabel)
882
- genLoad(caseBody, generatedType)
883
- bc goTo postMatch
884
+ // emit switch-blocks.
885
+ for (sb <- switchBlocks.reverse) {
886
+ val (caseLabel, caseBody) = sb
887
+ markProgramPoint(caseLabel)
888
+ genLoad(caseBody, generatedType)
889
+ bc goTo postMatch
890
+ }
891
+ } else {
892
+
893
+ /* Since the JVM doesn't have a way to switch on a string, we switch
894
+ * on the `hashCode` of the string then do an `equals` check (with a
895
+ * possible second set of jumps if blocks can be reach from multiple
896
+ * string alternatives).
897
+ *
898
+ * This mirrors the way that Java compiles `switch` on Strings.
899
+ */
900
+
901
+ var default : asm.Label = null
902
+ var indirectBlocks : List [(asm.Label , Tree )] = Nil
903
+
904
+ import scala .collection .mutable
905
+
906
+ // Cases grouped by their hashCode
907
+ val casesByHash = SortedMap .empty[Int , List [(String , Either [asm.Label , Tree ])]]
908
+ var caseFallback : Tree = null
909
+
910
+ for (caze @ CaseDef (pat, guard, body) <- cases) {
911
+ assert(guard == tpd.EmptyTree , guard)
912
+ pat match {
913
+ case Literal (value) =>
914
+ val strValue = value.stringValue
915
+ casesByHash.updateWith(strValue.## ) { existingCasesOpt =>
916
+ val newCase = (strValue, Right (body))
917
+ Some (newCase :: existingCasesOpt.getOrElse(Nil ))
918
+ }
919
+ case Ident (nme.WILDCARD ) =>
920
+ assert(default == null , s " multiple default targets in a Match node, at ${tree.span}" )
921
+ default = new asm.Label
922
+ indirectBlocks ::= (default, body)
923
+ case Alternative (alts) =>
924
+ // We need an extra basic block since multiple strings can lead to this code
925
+ val indirectCaseGroupLabel = new asm.Label
926
+ indirectBlocks ::= (indirectCaseGroupLabel, body)
927
+ alts foreach {
928
+ case Literal (value) =>
929
+ val strValue = value.stringValue
930
+ casesByHash.updateWith(strValue.## ) { existingCasesOpt =>
931
+ val newCase = (strValue, Left (indirectCaseGroupLabel))
932
+ Some (newCase :: existingCasesOpt.getOrElse(Nil ))
933
+ }
934
+ case _ =>
935
+ abort(s " Invalid alternative in alternative pattern in Match node: $tree at: ${tree.span}" )
936
+ }
937
+
938
+ case _ =>
939
+ abort(s " Invalid pattern in Match node: $tree at: ${tree.span}" )
940
+ }
941
+ }
942
+
943
+ // Organize the hashCode options into switch cases
944
+ var flatKeys : List [Int ] = Nil
945
+ var targets : List [asm.Label ] = Nil
946
+ var hashBlocks : List [(asm.Label , List [(String , Either [asm.Label , Tree ])])] = Nil
947
+ for ((hashValue, hashCases) <- casesByHash) {
948
+ val switchBlockPoint = new asm.Label
949
+ hashBlocks ::= (switchBlockPoint, hashCases)
950
+ flatKeys ::= hashValue
951
+ targets ::= switchBlockPoint
952
+ }
953
+
954
+ // Push the hashCode of the string (or `0` it is `null`) onto the stack and switch on it
955
+ genLoadIf(
956
+ If (
957
+ tree.selector.select(defn.Any_== ).appliedTo(nullLiteral),
958
+ Literal (Constant (0 )),
959
+ tree.selector.select(defn.Any_hashCode ).appliedToNone
960
+ ),
961
+ INT
962
+ )
963
+ bc.emitSWITCH(mkArrayReverse(flatKeys), mkArrayL(targets.reverse), default, MIN_SWITCH_DENSITY )
964
+
965
+ // emit blocks for each hash case
966
+ for ((hashLabel, caseAlternatives) <- hashBlocks.reverse) {
967
+ markProgramPoint(hashLabel)
968
+ for ((caseString, indirectLblOrBody) <- caseAlternatives) {
969
+ val comparison = if (caseString == null ) defn.Any_== else defn.Any_equals
970
+ val condp = Literal (Constant (caseString)).select(defn.Any_== ).appliedTo(tree.selector)
971
+ val keepGoing = new asm.Label
972
+ indirectLblOrBody match {
973
+ case Left (jump) =>
974
+ genCond(condp, jump, keepGoing, targetIfNoJump = keepGoing)
975
+
976
+ case Right (caseBody) =>
977
+ val thisCaseMatches = new asm.Label
978
+ genCond(condp, thisCaseMatches, keepGoing, targetIfNoJump = thisCaseMatches)
979
+ markProgramPoint(thisCaseMatches)
980
+ genLoad(caseBody, generatedType)
981
+ bc goTo postMatch
982
+ }
983
+ markProgramPoint(keepGoing)
984
+ }
985
+ bc goTo default
986
+ }
987
+
988
+ // emit blocks for common patterns
989
+ for ((caseLabel, caseBody) <- indirectBlocks.reverse) {
990
+ markProgramPoint(caseLabel)
991
+ genLoad(caseBody, generatedType)
992
+ bc goTo postMatch
993
+ }
884
994
}
885
995
886
996
markProgramPoint(postMatch)
0 commit comments