Skip to content

Commit

Permalink
Keep SAM body in anonfun method in enclosing class
Browse files Browse the repository at this point in the history
Rather than in implementation of the abstract method in the
expanded anonymous class.

This leads to more more efficient use of the constant pool,
code shapes more amenable to SAM inlining, and is compatible
with the old behaviour of `-Xexperimental` in Scala 2.11,
which ScalaJS now relies upon.

Manual test:

```
scala> :paste -raw
// Entering paste mode (ctrl-D to finish)

package p1; trait T { val x = 0; def apply(): Any }; class DelambdafyInline { def t: T = (() => "") }

// Exiting paste mode, now interpreting.

scala> :javap -c p1.DelambdafyInline
Compiled from "<pastie>"
public class p1.DelambdafyInline {
  public p1.T t();
    Code:
       0: new           #10                 // class p1/DelambdafyInline$$anonfun$t$1
       3: dup
       4: aload_0
       5: invokespecial #16                 // Method p1/DelambdafyInline$$anonfun$t$1."<init>":(Lp1/DelambdafyInline;)V
       8: areturn

  public final java.lang.Object p1$DelambdafyInline$$$anonfun$1();
    Code:
       0: ldc           #22                 // String
       2: areturn

  public p1.DelambdafyInline();
    Code:
       0: aload_0
       1: invokespecial #25                 // Method java/lang/Object."<init>":()V
       4: return
}

scala> :javap -c p1.DelambdafyInline$$anonfun$t$1
Compiled from "<pastie>"
public final class p1.DelambdafyInline$$anonfun$t$1 implements p1.T,scala.Serializable {
  public static final long serialVersionUID;

  public int x();
    Code:
       0: aload_0
       1: getfield      #25                 // Field x:I
       4: ireturn

  public void p1$T$_setter_$x_$eq(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #25                 // Field x:I
       5: return

  public final java.lang.Object apply();
    Code:
       0: aload_0
       1: getfield      #34                 // Field $outer:Lp1/DelambdafyInline;
       4: invokevirtual #37                 // Method p1/DelambdafyInline.p1$DelambdafyInline$$$anonfun$1:()Ljava/lang/Object;
       7: areturn

  public p1.DelambdafyInline$$anonfun$t$1(p1.DelambdafyInline);
    Code:
       0: aload_1
       1: ifnonnull     6
       4: aconst_null
       5: athrow
       6: aload_0
       7: aload_1
       8: putfield      #34                 // Field $outer:Lp1/DelambdafyInline;
      11: aload_0
      12: invokespecial #42                 // Method java/lang/Object."<init>":()V
      15: aload_0
      16: invokespecial #45                 // Method p1/T.$init$:()V
      19: return
}

scala> :quit
```

Adriaan is to `git blame` for `reflection-mem-typecheck.scala`.
  • Loading branch information
retronym authored and adriaanm committed Mar 30, 2016
1 parent 3904c32 commit aec2b94
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 6 deletions.
16 changes: 11 additions & 5 deletions src/compiler/scala/tools/nsc/transform/UnCurry.scala
Expand Up @@ -75,9 +75,9 @@ abstract class UnCurry extends InfoTransform

// Expand `Function`s in constructors to class instance creation (SI-6666, SI-8363)
// We use Java's LambdaMetaFactory (LMF), which requires an interface for the sam's owner
private def mustExpandFunction(fun: Function) = forceExpandFunction || {
private def mustExpandFunction(fun: Function) = {
// (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding)
val canUseLambdaMetaFactory = inConstructorFlag == 0 && (fun.attachments.get[SAMFunction] match {
val canUseLambdaMetaFactory = (fun.attachments.get[SAMFunction] match {
case Some(SAMFunction(userDefinedSamTp, sam)) =>
// LambdaMetaFactory cannot mix in trait members for us, or instantiate classes -- only pure interfaces need apply
erasure.compilesToPureInterface(erasure.javaErasure(userDefinedSamTp).typeSymbol) &&
Expand Down Expand Up @@ -207,8 +207,10 @@ abstract class UnCurry extends InfoTransform
def transformFunction(fun: Function): Tree =
// Undo eta expansion for parameterless and nullary methods
if (fun.vparams.isEmpty && isByNameRef(fun.body)) { noApply += fun.body ; fun.body }
else if (mustExpandFunction(fun)) gen.expandFunction(localTyper)(fun, inConstructorFlag)
else {
else if (forceExpandFunction || inConstructorFlag != 0) {
// Expand the function body into an anonymous class
gen.expandFunction(localTyper)(fun, inConstructorFlag)
} else {
// method definition with the same arguments, return type, and body as the original lambda
val liftedMethod = gen.mkLiftedFunctionBodyMethod(localTyper)(fun.symbol.owner, fun)

Expand All @@ -217,7 +219,11 @@ abstract class UnCurry extends InfoTransform
gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), (fun.vparams map (_.symbol)) :: Nil)
))

localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun)))
val typedNewFun = localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun)))
if (mustExpandFunction(fun)) {
val Block(stats, expr : Function) = typedNewFun
treeCopy.Block(typedNewFun, stats, gen.expandFunction(localTyper)(expr, inConstructorFlag))
} else typedNewFun
}

def transformArgs(pos: Position, fun: Symbol, args: List[Tree], formals: List[Type]) = {
Expand Down
4 changes: 3 additions & 1 deletion test/files/run/reflection-mem-typecheck.scala
Expand Up @@ -11,7 +11,9 @@ object Test extends MemoryTest {
cm.mkToolBox()
}

override def maxDelta = 10
// I'm not sure this is a great way to test for memory leaks,
// since we're also testing how good the JVM's GC is, and this is not easily reproduced between machines/over time
override def maxDelta = 12
override def calcsPerIter = 8
override def calc() {
var snippet = """
Expand Down

0 comments on commit aec2b94

Please sign in to comment.