Skip to content

Commit 5a42fed

Browse files
committed
Avoid boxes in local lazies
- Avoid boxing the {Object, Int, ...}Ref itself by storing it in a val, not a var - Avoid box/unbox of primitive lazy expressions due which are added to "adapt" it to the erased type of the fictional syncronized method, by using a `return`. We have to add a dummy throw after the synchronized block, but this is elimnated by the always-on DCE in the code generator. ``` ⚡ qscalac -Xprint:mixin $(f "class C { def foo = { lazy val x = 42; x }}"); javap -private -c -cp . C [[syntax trees at end of mixin]] // a.scala package <empty> { class C extends Object { def foo(): Int = { lazy <artifact> val x$lzy: scala.runtime.LazyInt = new scala.runtime.LazyInt(); C.this.x$1(x$lzy) }; final private[this] def x$1(x$lzy$1: scala.runtime.LazyInt): Int = { x$lzy$1.synchronized({ if (x$lzy$1.initialized().unary_!()) { x$lzy$1.initialized_=(true); x$lzy$1.value_=(42) }; return x$lzy$1.value() }); throw null }; def <init>(): C = { C.super.<init>(); () } } } Compiled from "a.scala" public class C { public int foo(); Code: 0: new #12 // class scala/runtime/LazyInt 3: dup 4: invokespecial #16 // Method scala/runtime/LazyInt."<init>":()V 7: astore_1 8: aload_1 9: invokestatic #20 // Method x$1:(Lscala/runtime/LazyInt;)I 12: ireturn private static final int x$1(scala.runtime.LazyInt); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: aload_0 5: invokevirtual #31 // Method scala/runtime/LazyInt.initialized:()Z 8: ifne 22 11: aload_0 12: iconst_1 13: invokevirtual #35 // Method scala/runtime/LazyInt.initialized_$eq:(Z)V 16: aload_0 17: bipush 42 19: invokevirtual #39 // Method scala/runtime/LazyInt.value_$eq:(I)V 22: aload_0 23: invokevirtual #42 // Method scala/runtime/LazyInt.value:()I 26: istore_2 27: goto 33 30: aload_1 31: monitorexit 32: athrow 33: aload_1 34: monitorexit 35: iload_2 36: ireturn Exception table: from to target type 4 30 30 Class java/lang/Throwable public C(); Code: 0: aload_0 1: invokespecial #43 // Method java/lang/Object."<init>":()V 4: return } ```
1 parent 63bd181 commit 5a42fed

File tree

1 file changed

+4
-3
lines changed

1 file changed

+4
-3
lines changed

src/compiler/scala/tools/nsc/transform/Fields.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
515515
val refClass = lazyHolders.getOrElse(lazyValType.typeSymbol, LazyRefClass)
516516
val refTpe = if (refClass != LazyRefClass) refClass.tpe else appliedType(refClass.typeConstructor, List(lazyValType))
517517

518-
val flags = (lazyVal.flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) // TODO: why include MUTABLE???
518+
val flags = (lazyVal.flags & FieldFlags | ARTIFACT) & ~(IMPLICIT | STABLE)
519519
val name = lazyVal.name.toTermName.append(nme.LAZY_LOCAL_SUFFIX_STRING)
520520
val holderSym =
521521
lazyVal.owner.newValue(name, lazyVal.pos, flags) setInfo refTpe
@@ -529,15 +529,16 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
529529
val valueSetter = if (isUnit) NoSymbol else valueGetter.setterIn(refClass)
530530
val setValue = if (isUnit) rhs else Apply(Select(Ident(holderSym), valueSetter), rhs :: Nil)
531531
val getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueGetter), Nil)
532-
gen.mkSynchronized(Ident(holderSym),
532+
val syncBlock = gen.mkSynchronized(Ident(holderSym),
533533
Block(List(
534534
If(NOT(Ident(holderSym) DOT initializedGetter),
535535
Block(List(
536536
setInitialized),
537537
setValue),
538538
EmptyTree)),
539-
getValue)) // must read the value within the synchronized block since it's not volatile
539+
Return(getValue))) // must read the value within the synchronized block since it's not volatile
540540
// (there's no happens-before relation with the read of the volatile initialized field)
541+
Block(syncBlock :: Nil, Throw(Literal(Constant(null))))
541542
// TODO: double-checked locking
542543
}
543544

0 commit comments

Comments
 (0)