Skip to content
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

repls depends on interactive #12

Merged
merged 1 commit into from
Jun 5, 2015

Conversation

lrytz
Copy link

@lrytz lrytz commented Jun 5, 2015

No description provided.

retronym added a commit that referenced this pull request Jun 5, 2015
@retronym retronym merged commit 5b4b177 into retronym:topic/code-retreat Jun 5, 2015
retronym added a commit that referenced this pull request Aug 4, 2016
Dmitry learned that we've been relying on a bug in the
verifier that will be fixed in JDK 9 under the new
classfile format.

Assignment to a static final must occur lexically
within the <clinit>. We were performing this assignment
from the constructor of the module class.

This commit moves the assignment to <clinit>.

Before:

```
public final class O$ {
  public static final O$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class O$
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  private O$();
    Code:
       0: aload_0
       1: invokespecial #13                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: putstatic     #15                 // Field MODULE$:LO$;
       8: return
}
```

After:

```
public final class O$ {
  public static final O$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class O$
       3: dup
       4: invokespecial #12                 // Method "<init>":()V
       7: putstatic     #14                 // Field MODULE$:LO$;
      10: return

  private O$();
    Code:
       0: aload_0
       1: invokespecial #15                 // Method java/lang/Object."<init>":()V
       4: return
}
```

The difference is observable is the constructor is called a second
time with reflection/setAccessible. That used to overwrite the
`MODULE$` field. I think few would argue that the new semantics
are a clear improvement.
retronym added a commit that referenced this pull request Aug 18, 2016
Top level modules in Scala currently desugar as:

```
class C; object O extends C { toString }
```

```
public final class O$ extends C {
  public static final O$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class O$
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  private O$();
    Code:
       0: aload_0
       1: invokespecial #13                 // Method C."<init>":()V
       4: aload_0
       5: putstatic     #15                 // Field MODULE$:LO$;
       8: aload_0
       9: invokevirtual #21                 // Method java/lang/Object.toString:()Ljava/lang/String;
      12: pop
      13: return
}
```

The static initalizer `<clinit>` calls the constructor `<init>`, which
invokes superclass constructor, assigns `MODULE$= this`, and then runs
the remainder of the object's constructor (`toString` in the example
above.)

It turns out that this relies on a bug in the JVM's verifier: assignment to a
static final must occur lexically within the <clinit>, not from within `<init>`
(even if the latter is happens to be called by the former).

I'd like to move the assignment to <clinit> but that would
change behaviour of "benign" cyclic references between modules.

Example:

```
package p1; class CC { def foo = O.bar}; object O {new CC().foo; def bar = println(1)};

// Exiting paste mode, now interpreting.

scala> p1.O
1
```

This relies on the way that we assign MODULE$ field after the super class constructors
are finished, but before the rest of the module constructor is called.

Instead, this commit removes the ACC_FINAL bit from the field. It actually wasn't
behaving as final at all, precisely the issue that the stricter verifier
now alerts us to.

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

package p1; object O

// Exiting paste mode, now interpreting.

scala> val O1 = p1.O
O1: p1.O.type = p1.O$@ee7d9f1

scala> scala.reflect.ensureAccessible(p1.O.getClass.getDeclaredConstructor()).newInstance()
res0: p1.O.type = p1.O$@64cee07

scala> O1 eq p1.O
res1: Boolean = false
```

We will still achieve safe publication of the assignment to other threads
by virtue of the fact that `<clinit>` is executed within the scope of
an initlization lock, as specified by:

  https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5

Fixes scala/scala-dev#SD-194
retronym added a commit that referenced this pull request Aug 18, 2016
Top level modules in Scala currently desugar as:

```
class C; object O extends C { toString }
```

```
public final class O$ extends C {
  public static final O$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class O$
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  private O$();
    Code:
       0: aload_0
       1: invokespecial #13                 // Method C."<init>":()V
       4: aload_0
       5: putstatic     #15                 // Field MODULE$:LO$;
       8: aload_0
       9: invokevirtual #21                 // Method java/lang/Object.toString:()Ljava/lang/String;
      12: pop
      13: return
}
```

The static initalizer `<clinit>` calls the constructor `<init>`, which
invokes superclass constructor, assigns `MODULE$= this`, and then runs
the remainder of the object's constructor (`toString` in the example
above.)

It turns out that this relies on a bug in the JVM's verifier: assignment to a
static final must occur lexically within the <clinit>, not from within `<init>`
(even if the latter is happens to be called by the former).

I'd like to move the assignment to <clinit> but that would
change behaviour of "benign" cyclic references between modules.

Example:

```
package p1; class CC { def foo = O.bar}; object O {new CC().foo; def bar = println(1)};

// Exiting paste mode, now interpreting.

scala> p1.O
1
```

This relies on the way that we assign MODULE$ field after the super class constructors
are finished, but before the rest of the module constructor is called.

Instead, this commit removes the ACC_FINAL bit from the field. It actually wasn't
behaving as final at all, precisely the issue that the stricter verifier
now alerts us to.

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

package p1; object O

// Exiting paste mode, now interpreting.

scala> val O1 = p1.O
O1: p1.O.type = p1.O$@ee7d9f1

scala> scala.reflect.ensureAccessible(p1.O.getClass.getDeclaredConstructor()).newInstance()
res0: p1.O.type = p1.O$@64cee07

scala> O1 eq p1.O
res1: Boolean = false
```

We will still achieve safe publication of the assignment to other threads
by virtue of the fact that `<clinit>` is executed within the scope of
an initlization lock, as specified by:

  https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5

Fixes scala/scala-dev#SD-194
retronym added a commit that referenced this pull request Aug 19, 2016
  - 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
}
```
retronym pushed a commit that referenced this pull request Mar 27, 2018
retronym pushed a commit that referenced this pull request Jun 24, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants