Description
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.