scala2.11编译环节的一些变动: delambdafy

scala2.11在编译环节与2.10相比,默认去掉了cps和一些优化环节(phase),同时也引入了delambdafy这个“去lambda化”的环节,见下面的22:

    % scalac -Xshow-phases
        phase name  id  description
    ----------  --  -----------
        parser   1  parse source into ASTs, perform simple desugaring
         namer   2  resolve names, attach symbols to named trees
packageobjects   3  load package objects
         typer   4  the meat and potatoes: type the trees
        patmat   5  translate match expressions
superaccessors   6  add super accessors in traits and nested classes
    extmethods   7  add extension methods for inline classes
       pickler   8  serialize symbol tables
     refchecks   9  reference/override checking, translate nested objects
       uncurry  10  uncurry, translate function values to anonymous classes
     tailcalls  11  replace tail calls by jumps
    specialize  12  @specialized-driven class and method specialization
 explicitouter  13  this refs to outer pointers
       erasure  14  erase types, add interfaces for traits
   posterasure  15  clean up erased inline classes
      lazyvals  16  allocate bitmaps, translate lazy vals into lazified defs
    lambdalift  17  move nested functions to top level
  constructors  18  move field definitions into constructors
       flatten  19  eliminate inner classes
         mixin  20  mixin composition
       cleanup  21  platform-specific cleanups, generate reflective calls
    delambdafy  22  remove lambdas
         icode  23  generate portable intermediate code
           jvm  24  generate JVM bytecode
      terminal  25  the last phase during a compilation run

通过 -Ydebug 选项可以把没有使用的phases也显示出来:

 % scalac -Xshow-phases -Ydebug
   ...
    selectiveanf  xx  ANF pre-transform for @cps
    selectivecps  xx  @cps-driven transform of selectiveanf assignments
   ...
         inliner  xx  optimization: do inlining
  inlinehandlers  xx  optimization: inline exception handlers
        closelim  xx  optimization: eliminate uncalled closures
        constopt  xx  optimization: optimize null and other constants
             dce  xx  optimization: eliminate dead code

上面的这些phases是2.11编译过程中默认没有启用的,而在scala2.10默认编译过程是有启用的(2.10的phases参考这里

selectiveanfselectivecps是针对cps的,在2.11中如果要用,需要显示的增加-P:continuations:enable编译选项;另外5个optimization也需要显示的增加-optimise选项:

    % scalac -Xshow-phases -optimise -P:continuations:enable
        phase name  id  description
    ----------  --  -----------
        parser   1  parse source into ASTs, perform simple desugaring
         namer   2  resolve names, attach symbols to named trees
packageobjects   3  load package objects
         typer   4  the meat and potatoes: type the trees
        patmat   5  translate match expressions
superaccessors   6  add super accessors in traits and nested classes
    extmethods   7  add extension methods for inline classes
       pickler   8  serialize symbol tables
     refchecks   9  reference/override checking, translate nested objects
  selectiveanf  10  ANF pre-transform for @cps
  selectivecps  11  @cps-driven transform of selectiveanf assignments
       uncurry  12  uncurry, translate function values to anonymous classes
     tailcalls  13  replace tail calls by jumps
    specialize  14  @specialized-driven class and method specialization
 explicitouter  15  this refs to outer pointers
       erasure  16  erase types, add interfaces for traits
   posterasure  17  clean up erased inline classes
      lazyvals  18  allocate bitmaps, translate lazy vals into lazified defs
    lambdalift  19  move nested functions to top level
  constructors  20  move field definitions into constructors
       flatten  21  eliminate inner classes
         mixin  22  mixin composition
       cleanup  23  platform-specific cleanups, generate reflective calls
    delambdafy  24  remove lambdas
         icode  25  generate portable intermediate code
       inliner  26  optimization: do inlining
inlinehandlers  27  optimization: inline exception handlers
      closelim  28  optimization: eliminate uncalled closures
      constopt  29  optimization: optimize null and other constants
           dce  30  optimization: eliminate dead code
           jvm  31  generate JVM bytecode
      terminal  32  the last phase during a compilation run

之所以默认放弃了optimization的几个选项大概是因为很难实现好,导致的问题较多收益不大(参考这里这里);而放弃cps选项则是因为scala后续2.12将不再包含continuations插件和库,转而采用Async来替代,因为基于CPS重写(CPS-based rewriting)的异步代码每次暂停时会产生一个闭包,同时也可能导致类型错误并且难以理解。

现在来看一下delambdafy这个新增的phase,借用adriaanm的例子

object Test {
    def takeLambda(f: Int => Int ): Int = f(12)

    def main(args: Array[String]): Unit = {
        println(takeLambda(x => x+1))
        println(takeLambda(x => x*2))
    }
}

先不启用delambdafy选项看看编译结果:

% scalac -print /tmp/Test.scala
...

def main(args: Array[String]): Unit = {
  scala.this.Predef.println(scala.Int.box(Test.this.takeLambda({
    (new <$anon: Function1>(): Function1)
  })));
  scala.this.Predef.println(scala.Int.box(Test.this.takeLambda({
    (new <$anon: Function1>(): Function1)
  })))
};

...

@SerialVersionUID(0) final <synthetic> class anonfun$main$1 extends 
                    scala.runtime.AbstractFunction1$mcII$sp with Serializable {
    final def apply(x: Int): Int = anonfun$main$1.this.apply$mcII$sp(x);
    <specialized> def apply$mcII$sp(x: Int): Int = x.+(1);
    final <bridge> <artifact> def apply(v1: Object): Object = 
                scala.Int.box(anonfun$main$1.this.apply(scala.Int.unbox(v1)));

    def <init>(): <$anon: Function1> = {
        anonfun$main$1.super.<init>();
        ()
    }
};
@SerialVersionUID(0) final <synthetic> class anonfun$main$2 extends 
                    scala.runtime.AbstractFunction1$mcII$sp with Serializable {
    final def apply(x: Int): Int = anonfun$main$2.this.apply$mcII$sp(x);
    <specialized> def apply$mcII$sp(x: Int): Int = x.*(2);
    final <bridge> <artifact> def apply(v1: Object): Object = 
                scala.Int.box(anonfun$main$2.this.apply(scala.Int.unbox(v1)));

    def <init>(): <$anon: Function1> = {
        anonfun$main$2.super.<init>();
        ()
    }
}
...

再看看使用delambdafy:method选项:

% scalac -Xprint:jvm -Ydelambdafy:method /tmp/Test.scala
...

def main(args: Array[String]): Unit = {
  scala.this.Predef.println(scala.Int.box(Test.this.takeLambda({
    (new main1(): runtime.AbstractFunction1).$asInstanceOf[Function1]()
  })));
  scala.this.Predef.println(scala.Int.box(Test.this.takeLambda({
    (new main2(): runtime.AbstractFunction1).$asInstanceOf[Function1]()
  })))
};

final <artifact> private[this] def $anonfun$1(x: Int): Int = x.+(1);
final <artifact> private[this] def $anonfun$2(x: Int): Int = x.*(2);

final <synthetic> <static> <bridge> protected def accessor1(x: Int): Int = 
                                    Test.this.$anonfun$1(x);
final <synthetic> <static> <bridge> protected def accessor2(x: Int): Int = 
                                    Test.this.$anonfun$2(x)
...

@SerialVersionUID(0) final <synthetic> class main2 extends 
                    runtime.AbstractFunction1 with Serializable {
    <synthetic> def <init>(): main2 = {
        main2.super.<init>();
        ()
    };
    final <synthetic> def apply(x: Int): Int = Test.this.accessor2(x);
    final <synthetic> <bridge> def apply(x: Object): Object = 
                            scala.Int.box(main2.this.apply(unbox(x)))
};
@SerialVersionUID(0) final <synthetic> class main1 extends 
                    runtime.AbstractFunction1 with Serializable {
    <synthetic> def <init>(): main1 = {
        main1.super.<init>();
        ()
    };
    final <synthetic> def apply(x: Int): Int = Test.this.accessor1(x);
    final <synthetic> <bridge> def apply(x: Object): Object = 
                            scala.Int.box(main1.this.apply(unbox(x)))
}

在开启delambdafy:method选项之后,原先在匿名类中实现的方法,被移到了当前对象中,并提供了静态accessor方法,在scala.tools.nsc.transform.Delambdafy的文档里是这么描述的:

1) a static forwarder at the top level of the class that contained the lambda 
2) a new top level class that 
    a) has fields and a constructor taking the captured environment 
    b) an apply method that calls the static forwarder 
    c) if needed a bridge method for the apply method 
3) an instantiation of the newly created class which replaces the lambda

目前还是一个实验性质,这个做法更接近用method handles,显然是为了下一步支持jsr292做准备的。

scala2.11字节码仍兼容jdk1.6,不会采用invokedynamic,而scala2.12的字节码会直接跳到 jdk1.8。

scala2.11编译环节的一些变动: delambdafy》上有3条评论

  1. hongjiang 文章作者

    http://www.scala-lang.org/news/2014/03/06/release-notes-2.11.0-RC1.html

    A new experimental way of compiling closures, implemented by @JamesIry. With -Ydelambdafy:method anonymous functions are compiled faster, with a smaller bytecode footprint. This works by keeping the function body as a private (static, if no this reference is needed) method of the enclosing class, and at the last moment during compilation emitting a small anonymous class that extends FunctionN and delegates to it. This sets the scene for a smooth migration to Java 8-style lambdas (not yet implemented).

    回复
  2. jilen

    Ydelambdafy:method 这个貌似是实验性功能,不知道会不会出一些奇葩的bug

    回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注