Skip to content

Too much redundant lazy vals code. #676

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

Closed
odersky opened this issue Jun 19, 2015 · 5 comments
Closed

Too much redundant lazy vals code. #676

odersky opened this issue Jun 19, 2015 · 5 comments

Comments

@odersky
Copy link
Contributor

odersky commented Jun 19, 2015

Have a look at copnstructors.scala (only one example of many). We find:

final lazy var B$lzy1: A.this.B$ = null.asInstanceOf[A.this.B$]
final lazy <accessor> module def B(): A.this.B$ = {
  lazy var result: A.this.B$ = null.asInstanceOf[A.this.B$]
  lazy var retry: Boolean = true
  lazy var flag: Long = 0L
  {
    def while$(): Unit = 
      if retry then {
        flag = dotty.runtime.LazyVals.get(this, A.OFFSET$0)
        dotty.runtime.LazyVals.STATE(flag, 0) match {
          case 0 => 
            if dotty.runtime.LazyVals.CAS(this, A.OFFSET$0, flag, 1, 0) then

            {
              try result = new A.this.B$() catch {
                case ex$ @ ex$ => 
                  dotty.runtime.LazyVals.setFlag(this, A.OFFSET$0, 0, 0)
                  throw ex$
              }
              this.B$lzy1 = result
              dotty.runtime.LazyVals.setFlag(this, A.OFFSET$0, 0, 0)
              retry = false
              ()
            }
             else ()
          case 1 => 
            dotty.runtime.LazyVals.wait4Notification(this, A.OFFSET$0, flag
              , 
            0)
          case 2 => 
            dotty.runtime.LazyVals.wait4Notification(this, A.OFFSET$0, flag
              , 
            0)
          case 3 => 
            retry = false
            result = this.B$lzy1
            ()
          case _ => ()
        }
        while$()
      } else ()
    while$()
  }
  result
}
final module class B$ extends Object { this: A.this.B$ => 
  def <init>(): Unit = {
    super()
    ()
  }
}

This is all for a module that was not mentioned in source and that is in fact empty! This is wrong on many levels

  • empty modules should not generate code at all.
  • non-empty modules should not need bitmaps, since they are treated as non-volatile, and null is good enough as an indication that the module is not initialized.
  • even if this is eliminated later it is still wasteful for compile time. Why generate a code avalanche in the first place?
@smarter
Copy link
Member

smarter commented Jun 26, 2015

The issue seems to be that dotty, unlike scalac, always generates companion objects for classes. I guess that the problem is that FirstTransform does not know if a companion object is going to be needed by a future phase (for example, ExtensionMethods). but we could avoid generating the companion if we're sure that we won't need it. For example, is there any case where we need a companion for an anonymous class? We get one currently:

class Foo

object Test {
  def test = {
    val x = new Foo {}
  }
}

Becomes after FirstTransform:

package <empty> {
  class Foo() extends Object() {}
  final lazy module val Foo: Foo$ = new Foo$()
  final module class Foo$() extends Object() { this: Foo.type =>}
  final lazy module val Test: Test$ = new Test$()
  final module class Test$() extends Object() { this: Test.type => 
    def test: Unit = {
      val x: Foo = {
        final class $anon() extends Foo() {}
        final lazy module val $anon: Object{...} = new Object{...}()
        final module class $anon$() extends Object() { this: $anon.type =>}
        new Foo{...}(): Foo
      }
      ()
    }
  }
}

So in the end we get:

result of try/anonclass.scala after genBCode:
package <empty> {
  class Foo extends Object { 
    def <init>(): Unit = {
      super()
      ()
    }
  }
  final lazy module val Foo: Foo$ = new Foo$()
  final module class Foo$ extends Object { this: Foo$ => 
    def <init>(): Unit = {
      super()
      ()
    }
  }
  final lazy module val Test: Test$ = new Test$()
  final module class Test$ extends Object { this: <notype> => 
    def <init>(): Unit = {
      super()
      ()
    }
    def test(): Unit = {
      val x: Foo = {
        lazy var $anon$lzy1: dotty.runtime.LazyRef = new dotty.runtime.LazyRef()
        new Foo{...}(): Foo
      }
      ()
    }
    private def $anon$lzyINIT1$1($anon$lzy1$1: dotty.runtime.LazyRef): 
      Object{...}
     = 
      $anon$lzy1$1.synchronized(
        if $anon$lzy1$1.initialized() then $anon$lzy1$1.value() else {
          $anon$lzy1$1.initialized_=(true)
          $anon$lzy1$1.value_=(new Object{...}())
          $anon$lzy1$1.value()
        }
      ).asInstanceOf[Object{...}]
    private final lazy <accessor> module def $anon$1(
      $anon$lzy1$2: dotty.runtime.LazyRef
    ): Object{...} = 
      (if $anon$lzy1$2.initialized() then $anon$lzy1$2.value() else 
        Test.$anon$lzyINIT1$1($anon$lzy1$2)
      ).asInstanceOf[Object{...}]
  }
  private final <static> class $anon$2 extends Foo { 
    def <init>(): Unit = {
      super()
      ()
    }
  }
  private final module <static> class $anon$1$1$ extends Object { 
    this: Object{...} =>

    def <init>(): Unit = {
      super()
      ()
    }
  }
}

@DarkDimius
Copy link
Contributor

I know how to fix this, additionally fixing #549. I'll do it after getting to the bottom of patmat bugs.

@DarkDimius
Copy link
Contributor

@smarter, you are presenting a different example, where the behavior is correct and required. In your case you have a non-used lazy val inside a method. It doesn't cost much during tree creation and will be eliminated by backend(private non-used methods).

@DarkDimius
Copy link
Contributor

It's correct to mitigate such example:

def method = {
    class A(var b: B) {
      @volatile lazy val a1: Int = {println("a1"); b.b1}
      @volatile lazy val a2: Int = {println("a2"); 1}
    }
    class B(a: A) {
      @volatile lazy val b1: Int = {println("b1"); a.a2}
    }
    val as = Array.fill(1000)(new A(null))
    val bs = as.map(x => new B(x))
    (as zip bs).forall(x => x._1.b = x._2)
    val runnableA = new Runnable {
      def run(): Unit = as.foreach(_.a1)
    }
    val runnableB = new Runnable {
      def run(): Unit = bs.foreach(_.b1)
    }

    new Thread(runnableA).start
    new Thread(runnableB).start
  }

DarkDimius added a commit to dotty-staging/dotty that referenced this issue Jul 1, 2015
Modules which are entirely synthetic now are eager
This fix additionally makes inner objects of other objects remain static classes, instead of becoming lazy vals.
DarkDimius added a commit to dotty-staging/dotty that referenced this issue Jul 1, 2015
Modules which are entirely synthetic now are eager
This fix additionally makes inner objects of other objects remain static classes, instead of becoming lazy vals.
DarkDimius added a commit to dotty-staging/dotty that referenced this issue Jul 2, 2015
Modules which are entirely synthetic now are eager
This fix additionally makes inner objects of other objects remain static classes, instead of becoming lazy vals.
@smarter
Copy link
Member

smarter commented Oct 25, 2015

Fixed by #706 a while ago, closing.

@smarter smarter closed this as completed Oct 25, 2015
OlivierBlanvillain pushed a commit to OlivierBlanvillain/dotty that referenced this issue Dec 8, 2016
OlivierBlanvillain pushed a commit to OlivierBlanvillain/dotty that referenced this issue Dec 8, 2016
Improved the fix of bug scala#676 - only emit interface call if the target is Java annotation and not just arbitrary attribute
OlivierBlanvillain pushed a commit to OlivierBlanvillain/dotty that referenced this issue Dec 12, 2016
OlivierBlanvillain pushed a commit to OlivierBlanvillain/dotty that referenced this issue Dec 12, 2016
Improved the fix of bug scala#676 - only emit interface call if the target is Java annotation and not just arbitrary attribute
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants