paradoxes of the value class

194 views
Skip to first unread message

Paul Phillips

unread,
Aug 29, 2012, 4:02:05 PM8/29/12
to scala-i...@googlegroups.com
Consider the following:

class A { def f = 5 max 10 }

Let's make RichInt a value class and see what happens. The implicit
which creates RichInt is:

implicit def intWrapper(x: Int) = new runtime.RichInt(x)

The first thing to notice is that said implicit becomes the identity function:

public int intWrapper(int);
0: iload_1
1: ireturn

However scalac doesn't understand this, and stubbornly hangs onto the
call to intWrapper:

// M7
0: getstatic #11; //Field scala/Predef$.MODULE$:Lscala/Predef$;
3: iconst_5
4: invokevirtual #16; //Method
scala/Predef$.intWrapper:(I)Lscala/runtime/RichInt;
7: bipush 10
9: invokevirtual #22; //Method scala/runtime/RichInt.max:(I)I
12: ireturn

// After I make RichInt a value class
0: getstatic #16; //Field
scala/runtime/RichInt$.MODULE$:Lscala/runtime/RichInt$;
3: getstatic #21; //Field scala/Predef$.MODULE$:Lscala/Predef$;
6: iconst_5
7: invokevirtual #27; //Method scala/LowPriorityImplicits.intWrapper:(I)I
10: bipush 10
12: invokevirtual #31; //Method scala/runtime/RichInt$.extension$max:(II)I
15: ireturn

We can @inline intWrapper, and then the deadness of the dead code
becomes apparent at the call site:

// Throw in @inline intWrapper, and -optimise
0: getstatic #16; //Field
scala/runtime/RichInt$.MODULE$:Lscala/runtime/RichInt$;
3: getstatic #21; //Field scala/Predef$.MODULE$:Lscala/Predef$;
6: astore_1
7: iconst_5
8: bipush 10
10: invokevirtual #25; //Method scala/runtime/RichInt$.extension$max:(II)I
13: ireturn

But look at what remains:

3: getstatic #21; //Field scala/Predef$.MODULE$:Lscala/Predef$;
6: astore_1

Why, you might ask, are we obtaining a static reference to
Predef$.MODULE$ only to cast it aside unused? The reason is that
Predef is an object, and objects are lazily loaded, and therefore
every single call to Predef is potentially side-effecting (as far as
the optimizer understands) and can never be eliminated. Even after we
have eliminated the method call itself. There does not appear to be
any way to avoid this, because implicit is not allowed as a top level
modifier, so even implicit classes can't save you from a
side-effect-threatening enclosing class. Although that explanation
must be wrong, because here's what happens if I @inline max in
RichInt:

0: getstatic #16; //Field scala/Predef$.MODULE$:Lscala/Predef$;
3: astore_1
4: iconst_5
5: bipush 10
7: if_icmpgt 15
10: bipush 10
12: goto 16
15: iconst_5
16: ireturn

Somehow the optimizer is willing to ignore the loading of
RichInt$.MODULE$ but not Predef$.MODULE$, anyone know the story on
that? Are extension methods special cased?

The other suboptimality is that these static methods are still
implemented as virtual methods on a statically accessible instance.
The optimal max-not-inlined bytecode would be, off the cuff:

0: iconst_5
1: bipush 10
3: invokestatic scala/runtime/RichInt$.extension$max:(II)I
6: ireturn

We could eliminate the Predef$ no-ops in the short-term if we only had
an annotation for objects ("@pure") which allowed the optimizer to
assume that loading them is side-effect free.

iulian dragos

unread,
Aug 30, 2012, 12:51:41 AM8/30/12
to scala-i...@googlegroups.com
I'm not 100% sure, but I think some things in `scala.runtime` are assumed to be pure.

The other suboptimality is that these static methods are still
implemented as virtual methods on a statically accessible instance.
The optimal max-not-inlined bytecode would be, off the cuff:

   0: iconst_5
   1: bipush 10
   3: invokestatic scala/runtime/RichInt$.extension$max:(II)I
   6: ireturn

We could eliminate the Predef$ no-ops in the short-term if we only had
an annotation for objects ("@pure") which allowed the optimizer to
assume that loading them is side-effect free.

That would be much better.

iulian
 



--
« Je déteste la montagne, ça cache le paysage »
Alphonse Allais

Paul Phillips

unread,
Aug 30, 2012, 2:27:41 PM8/30/12
to scala-i...@googlegroups.com

On Wed, Aug 29, 2012 at 9:51 PM, iulian dragos <jagu...@gmail.com> wrote:
> That would be much better.

Since this was easy to do, I tried it out.  The value of value classes... the two representations of "calc" in the following differ only in whether the various referenced classes extend AnyVal.

def calc(x: Meter, y: Double): MetersSquared = x * y.km

public double calc(double, double);
   0: dload_3
   1: sipush   1000
   4: i2d
   5: dmul
   6: dstore   5
   8: dload_1
   9: dload 5
   11:   dmul
   12:   dreturn

public scala.MetersSquared calc(scala.Meter, double);
   0: new #31; //class scala/foo$MeterOps
   3: dup
   4: dload_2
   5: invokespecial #34; //Method scala/foo$MeterOps."<init>":(D)V
   8: astore  4
   10:  new #36; //class scala/Kilometer
   13:  dup
   14:  aload 4
   16:  invokevirtual #40; //Method scala/foo$MeterOps.value:()D
   19:  invokespecial #41; //Method scala/Kilometer."<init>":(D)V
   22:  astore  5
   24:  new #43; //class scala/Meter
   27:  dup
   28:  aload 5
   30:  invokevirtual #44; //Method scala/Kilometer.value:()D
   33:  sipush  1000
   36:  i2d
   37:  dmul
   38:  invokespecial #45; //Method scala/Meter."<init>":(D)V
   41:  astore  6
   43:  new #47; //class scala/MetersSquared
   46:  dup
   47:  aload_1
   48:  invokevirtual #48; //Method scala/Meter.value:()D
   51:  aload 6
   53:  invokevirtual #48; //Method scala/Meter.value:()D
   56:  dmul
   57:  invokespecial #49; //Method scala/MetersSquared."<init>":(D)V
   60:  areturn

Ismael Juma

unread,
Aug 31, 2012, 6:16:02 AM8/31/12
to scala-i...@googlegroups.com
On Thu, Aug 30, 2012 at 7:27 PM, Paul Phillips <pa...@improving.org> wrote:

On Wed, Aug 29, 2012 at 9:51 PM, iulian dragos <jagu...@gmail.com> wrote:
> That would be much better.

Since this was easy to do, I tried it out.  The value of value classes... the two representations of "calc" in the following differ only in whether the various referenced classes extend AnyVal.

Nice!

Ismael
Reply all
Reply to author
Forward
0 new messages