Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Boxing code generated by opaque types #12009

Closed
erikerlandson opened this issue Apr 6, 2021 · 3 comments
Closed

Boxing code generated by opaque types #12009

erikerlandson opened this issue Apr 6, 2021 · 3 comments

Comments

@erikerlandson
Copy link

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.

@erikerlandson
Copy link
Author

see also #12011

@johnynek
Copy link

johnynek commented Apr 7, 2021

To avoid boxing here you also need specialization don't you?

I think you won't see boxing if opaque type Wrap = Int. Have you tried that? Otherwise, I think these Wrap[V] are ultimately represented at java.lang.Object in bytecode.

@odersky
Copy link
Contributor

odersky commented Apr 7, 2021

I think this should be continued in discussions. I agree it would be great if we could do better here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants