Scala 3 pattern matching on Enum is not tableSwitch

Hi, I just found when doing pattern matching on Scala 3’ enum, the bytecode is not a tableswitch when it’s possible:

Where the enum is just a tag object.

enum CompOp {

  /** `==` */
  case Eq

  /** `!=` */
  case Neq
  //...
}

and user side:

  private def evalCompare(compOp: CompOp, lhs: Expr, rhs: Expr, env: Environment): Value = {
    val left  = eval(lhs, env)
    val right = eval(rhs, env)

    compOp match
      case CompOp.Eq  => VBool(left.eq(right))
      case CompOp.Neq => VBool(!left.eq(right))
      case CompOp.Lt  => left < right
      case CompOp.Lte => left <= right
      case CompOp.Gt  => left > right
      case CompOp.Gte => left >= right
      case CompOp.In  => VBool(left.isIn(right))
  }

and the bytecode:

// access flags 0x2
  private evalCompare(Lcom/taobao/amdc2/condition/optimizer/CompOp;Lcom/taobao/amdc2/condition/optimizer/Expr;Lcom/taobao/amdc2/condition/optimizer/Expr;Lcom/taobao/amdc2/condition/optimizer/eval/Environment;)Lcom/taobao/amdc2/condition/optimizer/eval/Value;
    // parameter final  compOp
    // parameter final  lhs
    // parameter final  rhs
    // parameter final  env
   L0
    LINENUMBER 56 L0
    ALOAD 0
    ALOAD 2
    ALOAD 4
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/ExprEvaluator.eval (Lcom/taobao/amdc2/condition/optimizer/Expr;Lcom/taobao/amdc2/condition/optimizer/eval/Environment;)Lcom/taobao/amdc2/condition/optimizer/eval/Value;
    ASTORE 5
   L1
    LINENUMBER 57 L1
    ALOAD 0
    ALOAD 3
    ALOAD 4
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/ExprEvaluator.eval (Lcom/taobao/amdc2/condition/optimizer/Expr;Lcom/taobao/amdc2/condition/optimizer/eval/Environment;)Lcom/taobao/amdc2/condition/optimizer/eval/Value;
    ASTORE 6
   L2
    LINENUMBER 59 L2
    ALOAD 1
    ASTORE 7
   L3
    LINENUMBER 60 L3
    GETSTATIC com/taobao/amdc2/condition/optimizer/CompOp$.Eq : Lcom/taobao/amdc2/condition/optimizer/CompOp;
    ALOAD 7
    ASTORE 8
    DUP
    IFNONNULL L4
    POP
    ALOAD 8
    IFNULL L5
    GOTO L6
   L4
   FRAME FULL [com/taobao/amdc2/condition/optimizer/ExprEvaluator com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/eval/Environment com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp] [com/taobao/amdc2/condition/optimizer/CompOp]
    ALOAD 8
    INVOKEVIRTUAL java/lang/Object.equals (Ljava/lang/Object;)Z
    IFEQ L6
   L5
   FRAME SAME
    GETSTATIC com/taobao/amdc2/condition/optimizer/eval/Value$VBool$.MODULE$ : Lcom/taobao/amdc2/condition/optimizer/eval/Value$VBool$;
    ALOAD 5
    ALOAD 6
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value.eq (Lcom/taobao/amdc2/condition/optimizer/eval/Value;)Z
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value$VBool$.apply (Z)Lcom/taobao/amdc2/condition/optimizer/eval/Value$VBool;
    ARETURN
   L6
    LINENUMBER 61 L6
   FRAME SAME
    GETSTATIC com/taobao/amdc2/condition/optimizer/CompOp$.Neq : Lcom/taobao/amdc2/condition/optimizer/CompOp;
    ALOAD 7
    ASTORE 9
    DUP
    IFNONNULL L7
    POP
    ALOAD 9
    IFNULL L8
    GOTO L9
   L7
   FRAME FULL [com/taobao/amdc2/condition/optimizer/ExprEvaluator com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/eval/Environment com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp] [com/taobao/amdc2/condition/optimizer/CompOp]
    ALOAD 9
    INVOKEVIRTUAL java/lang/Object.equals (Ljava/lang/Object;)Z
    IFEQ L9
   L8
   FRAME SAME
    GETSTATIC com/taobao/amdc2/condition/optimizer/eval/Value$VBool$.MODULE$ : Lcom/taobao/amdc2/condition/optimizer/eval/Value$VBool$;
    ALOAD 5
    ALOAD 6
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value.eq (Lcom/taobao/amdc2/condition/optimizer/eval/Value;)Z
    IFNE L10
    ICONST_1
    GOTO L11
   L10
   FRAME SAME1 com/taobao/amdc2/condition/optimizer/eval/Value$VBool$
    ICONST_0
   L11
   FRAME FULL [com/taobao/amdc2/condition/optimizer/ExprEvaluator com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/eval/Environment com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp] [com/taobao/amdc2/condition/optimizer/eval/Value$VBool$ I]
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value$VBool$.apply (Z)Lcom/taobao/amdc2/condition/optimizer/eval/Value$VBool;
    ARETURN
   L9
    LINENUMBER 62 L9
   FRAME SAME
    GETSTATIC com/taobao/amdc2/condition/optimizer/CompOp$.Lt : Lcom/taobao/amdc2/condition/optimizer/CompOp;
    ALOAD 7
    ASTORE 10
    DUP
    IFNONNULL L12
    POP
    ALOAD 10
    IFNULL L13
    GOTO L14
   L12
   FRAME FULL [com/taobao/amdc2/condition/optimizer/ExprEvaluator com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/eval/Environment com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp] [com/taobao/amdc2/condition/optimizer/CompOp]
    ALOAD 10
    INVOKEVIRTUAL java/lang/Object.equals (Ljava/lang/Object;)Z
    IFEQ L14
   L13
   FRAME SAME
    ALOAD 5
    ALOAD 6
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value.Lt (Lcom/taobao/amdc2/condition/optimizer/eval/Value;)Lcom/taobao/amdc2/condition/optimizer/eval/Value;
    ARETURN
   L14
    LINENUMBER 63 L14
   FRAME SAME
    GETSTATIC com/taobao/amdc2/condition/optimizer/CompOp$.Lte : Lcom/taobao/amdc2/condition/optimizer/CompOp;
    ALOAD 7
    ASTORE 11
    DUP
    IFNONNULL L15
    POP
    ALOAD 11
    IFNULL L16
    GOTO L17
   L15
   FRAME FULL [com/taobao/amdc2/condition/optimizer/ExprEvaluator com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/eval/Environment com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp] [com/taobao/amdc2/condition/optimizer/CompOp]
    ALOAD 11
    INVOKEVIRTUAL java/lang/Object.equals (Ljava/lang/Object;)Z
    IFEQ L17
   L16
   FRAME SAME
    ALOAD 5
    ALOAD 6
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value.Lte (Lcom/taobao/amdc2/condition/optimizer/eval/Value;)Lcom/taobao/amdc2/condition/optimizer/eval/Value;
    ARETURN
   L17
    LINENUMBER 64 L17
   FRAME SAME
    GETSTATIC com/taobao/amdc2/condition/optimizer/CompOp$.Gt : Lcom/taobao/amdc2/condition/optimizer/CompOp;
    ALOAD 7
    ASTORE 12
    DUP
    IFNONNULL L18
    POP
    ALOAD 12
    IFNULL L19
    GOTO L20
   L18
   FRAME FULL [com/taobao/amdc2/condition/optimizer/ExprEvaluator com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/eval/Environment com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp] [com/taobao/amdc2/condition/optimizer/CompOp]
    ALOAD 12
    INVOKEVIRTUAL java/lang/Object.equals (Ljava/lang/Object;)Z
    IFEQ L20
   L19
   FRAME SAME
    ALOAD 5
    ALOAD 6
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value.Gt (Lcom/taobao/amdc2/condition/optimizer/eval/Value;)Lcom/taobao/amdc2/condition/optimizer/eval/Value;
    ARETURN
   L20
    LINENUMBER 65 L20
   FRAME SAME
    GETSTATIC com/taobao/amdc2/condition/optimizer/CompOp$.Gte : Lcom/taobao/amdc2/condition/optimizer/CompOp;
    ALOAD 7
    ASTORE 13
    DUP
    IFNONNULL L21
    POP
    ALOAD 13
    IFNULL L22
    GOTO L23
   L21
   FRAME FULL [com/taobao/amdc2/condition/optimizer/ExprEvaluator com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/eval/Environment com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp] [com/taobao/amdc2/condition/optimizer/CompOp]
    ALOAD 13
    INVOKEVIRTUAL java/lang/Object.equals (Ljava/lang/Object;)Z
    IFEQ L23
   L22
   FRAME SAME
    ALOAD 5
    ALOAD 6
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value.Gte (Lcom/taobao/amdc2/condition/optimizer/eval/Value;)Lcom/taobao/amdc2/condition/optimizer/eval/Value;
    ARETURN
   L23
    LINENUMBER 66 L23
   FRAME SAME
    GETSTATIC com/taobao/amdc2/condition/optimizer/CompOp$.In : Lcom/taobao/amdc2/condition/optimizer/CompOp;
    ALOAD 7
    ASTORE 14
    DUP
    IFNONNULL L24
    POP
    ALOAD 14
    IFNULL L25
    GOTO L26
   L24
   FRAME FULL [com/taobao/amdc2/condition/optimizer/ExprEvaluator com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/Expr com/taobao/amdc2/condition/optimizer/eval/Environment com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/eval/Value com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp com/taobao/amdc2/condition/optimizer/CompOp] [com/taobao/amdc2/condition/optimizer/CompOp]
    ALOAD 14
    INVOKEVIRTUAL java/lang/Object.equals (Ljava/lang/Object;)Z
    IFEQ L26
   L25
   FRAME SAME
    GETSTATIC com/taobao/amdc2/condition/optimizer/eval/Value$VBool$.MODULE$ : Lcom/taobao/amdc2/condition/optimizer/eval/Value$VBool$;
    ALOAD 5
    ALOAD 6
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value.isIn (Lcom/taobao/amdc2/condition/optimizer/eval/Value;)Z
    INVOKEVIRTUAL com/taobao/amdc2/condition/optimizer/eval/Value$VBool$.apply (Z)Lcom/taobao/amdc2/condition/optimizer/eval/Value$VBool;
    ARETURN
   L26
   FRAME SAME
    NEW scala/MatchError
    DUP
    ALOAD 7
    INVOKESPECIAL scala/MatchError.<init> (Ljava/lang/Object;)V
    ATHROW
   L27
    LOCALVARIABLE left Lcom/taobao/amdc2/condition/optimizer/eval/Value; L1 L27 5
    LOCALVARIABLE right Lcom/taobao/amdc2/condition/optimizer/eval/Value; L2 L27 6
    LOCALVARIABLE this Lcom/taobao/amdc2/condition/optimizer/ExprEvaluator; L0 L27 0
    LOCALVARIABLE compOp Lcom/taobao/amdc2/condition/optimizer/CompOp; L0 L27 1
    LOCALVARIABLE lhs Lcom/taobao/amdc2/condition/optimizer/Expr; L0 L27 2
    LOCALVARIABLE rhs Lcom/taobao/amdc2/condition/optimizer/Expr; L0 L27 3
    LOCALVARIABLE env Lcom/taobao/amdc2/condition/optimizer/eval/Environment; L0 L27 4
    MAXSTACK = 3
    MAXLOCALS = 15

I believe this is intended.

IIRC, the reasoning is so that reordering enum entries (or adding new entries in the middle) maintains binary compatibility.

Java does some trickery with an helper array to avoid that issue, but that’s not used by Scala.

Arguably, you can pattern match on ordinal if you don’t care about the bincompat guarantees.

1 Like

Isn’t there also scala.annotation.switch for this purpose?

No, switch will just trigger a warning (which might be a fatal warning) to say that the code was not optimized. It’s similar to @tailrec in that it doesn’t actually change the generated code.

See Scastie - An interactive playground for Scala.

Note that it also does not guarantee that a tableswitch is generated, just that one could be generated. Namely, small match expressions will still use an if/else chain.

2 Likes

That’s what I found too.

My current play is using a final val Eq= 0 instead of the enum for performance reason.