Skip to content

Boxing code generated by opaque types #12009

Closed
@erikerlandson

Description

@erikerlandson

Compiler version

3.0.0-RC2

Minimized example

A simplified opaque type (Wrap) that uses a typeclass to implement operator + logic.

trait Addable[V]:
    def plus(x: V, y: V): V
object Addable:
    given Addable[Int] with
        inline def plus(x: Int, y: Int): Int = x + y

import wrap.Wrap
extension[V](lhs: Wrap[V])
    inline def +(rhs: Wrap[V])(using va: Addable[V]): Wrap[V] =
        Wrap(va.plus(lhs.value, rhs.value))

object wrap:
    opaque type Wrap[V] = V
    object Wrap:
        def apply[V](v: V): Wrap[V] = v

    extension[V](w: Wrap[V])
        def value: V = w

object testwrap:
    import wrap.*
    val p = Wrap(3) + Wrap(5)

Output

If I compile this code, and then look at the byte-code for object testwrap, I see the following. It is clear from the byte-code that Wrap is being stored as a raw Int (nice), and the inlined type-class compiles down to raw iadd (also nice). However, the code also invokes quite a lot of boxing and unboxing, and I am trying to work out the trade-off in compute cost from the (un)boxing versus efficiency of storing the raw Int.

$ javap -c .../testwrap$.class

  public static {};
    Code:
       0: new           #2                  // class coulomb/testwrap$
       3: dup
       4: invokespecial #23                 // Method "<init>":()V
       7: putstatic     #25                 // Field MODULE$:Lcoulomb/testwrap$;
      10: getstatic     #28                 // Field coulomb/wrap$Wrap$.MODULE$:Lcoulomb/wrap$Wrap$;
      13: iconst_3
      14: invokestatic  #34                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
      17: invokevirtual #38                 // Method coulomb/wrap$Wrap$.apply:(Ljava/lang/Object;)Ljava/lang/Object;
      20: invokestatic  #42                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
      23: istore_0
      24: getstatic     #28                 // Field coulomb/wrap$Wrap$.MODULE$:Lcoulomb/wrap$Wrap$;
      27: iconst_5
      28: invokestatic  #34                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
      31: invokevirtual #38                 // Method coulomb/wrap$Wrap$.apply:(Ljava/lang/Object;)Ljava/lang/Object;
      34: invokestatic  #42                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
      37: istore_1
      38: getstatic     #28                 // Field coulomb/wrap$Wrap$.MODULE$:Lcoulomb/wrap$Wrap$;
      41: getstatic     #47                 // Field coulomb/wrap$.MODULE$:Lcoulomb/wrap$;
      44: iload_0
      45: invokestatic  #34                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
      48: invokevirtual #50                 // Method coulomb/wrap$.value:(Ljava/lang/Object;)Ljava/lang/Object;
      51: invokestatic  #42                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
      54: istore_2
      55: getstatic     #47                 // Field coulomb/wrap$.MODULE$:Lcoulomb/wrap$;
      58: iload_1
      59: invokestatic  #34                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
      62: invokevirtual #50                 // Method coulomb/wrap$.value:(Ljava/lang/Object;)Ljava/lang/Object;
      65: invokestatic  #42                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
      68: istore_3
      69: iload_2
      70: iload_3
      71: iadd
      72: invokestatic  #34                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
      75: invokevirtual #38                 // Method coulomb/wrap$Wrap$.apply:(Ljava/lang/Object;)Ljava/lang/Object;
      78: invokestatic  #42                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
      81: putstatic     #52                 // Field p:I
      84: return

  public int p();
    Code:
       0: getstatic     #52                 // Field p:I
       3: ireturn

Expectation

I'm not sure what a realistic expectation is, but I was hoping the compiler could elide the boxing/unboxing, since the only semantically necessary operations are istore, iload and iadd on the underlying raw Int values.

I am also unsure how much the various calls to the boxing methods cost. They appear to be significant relative to the core integer operations, however I do not know exactly how much.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions