Skip to content

Make object fields static, and move post-super init to <clinit> #7270

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

Merged
merged 3 commits into from
Nov 14, 2018

Conversation

retronym
Copy link
Member

This lets us make the MODULE$ field unconditionally final,
which improves runtime performance marginally by letting
JIT elide null checks.

Fields of objects themselves and now also be JIT-inlinable
constants.

Fixes scala/scala-dev#537

@scala-jenkins scala-jenkins added this to the 2.13.0-RC1 milestone Sep 27, 2018
@retronym retronym requested a review from lrytz September 27, 2018 07:26
@retronym
Copy link
Member Author

retronym commented Sep 27, 2018

TODO:

  • Community build
  • Let's try to foresee problems with reflection, framework code, tooling. E.g. would scalamock still work?
  • Should we offer the legacy encoding with a compiler option?

if (isCZStaticModule || isCZParcelable) {
fabricateStaticInit()
// TODO should we do this transformation earlier, say in Constructors? Or would that just cause
// pain for scala-{js, native}?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sjrd Thoughts?

Copy link
Member

@sjrd sjrd Sep 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't made my mind about it yet. I'll think about it.

Can my input wait until next week? I'm currently at the Scala Symposium.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing.

@retronym retronym force-pushed the topic/static-object-fields branch from ae095f1 to 815e210 Compare September 27, 2018 22:35
@retronym
Copy link
Member Author

retronym commented Sep 27, 2018

There is one interesting failure in test/files/jvm/scala-concurrent-tck.scala that shows a subtle difference in locking during object initialization.

Minimized:

trait CustomExecutionContext {
  val inEC = ""

  def testOnSuccessCustomEC(): Unit = {
    val t = new Thread() {
      override def run(): Unit = {
        println("getting it")
        inEC.length
        // this.outer$.inEC() now blocks until the `Test$.<clinit>` has concluded, because `Test$.inEC()`
        // now differs:
        // public inEC()Ljava/lang/String;
        // -    ALOAD 0
        // -    GETFIELD Test$.inEC : Ljava/lang/String;
        // +    GETSTATIC Test$.inEC : Ljava/lang/String;
        //
        // And the GETSTATIC blocks.
        println("got it")
      }
    }
    t.start()
    t.join()
    println("done")
  }

  testOnSuccessCustomEC()
}

object Test extends CustomExecutionContext {
  def main(args: Array[String]): Unit = {
  }
}

This is reminiscent of the deadlocks that became possible with the 2.12 lambda encoding. Prior to that, lambdas were inner classes, which had access to the module instance field via the outer pointer.

Now, inner classes, even though they have the module instance, are exposed to the static init lock when the call a field accessor method.

@retronym
Copy link
Member Author

Another thing that's changed in the serialized form of top level modules:

$ qscala
Welcome to Scala 2.13.0-20180928-004946-4aa133f (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_172).
Type in expressions for evaluation. Or try :help.

scala> import java.util.Base64; def parseBase64Binary(s: String): Array[Byte] = Base64.getDecoder.decode(s); def printBase64Binary(data: Array[Byte]): String = Base64.getEncoder.encode(data).map(_.toChar).mkString; def serialize(o: AnyRef): String = {val bos = new java.io.ByteArrayOutputStream();val out = new java.io.ObjectOutputStream(bos);out.writeObject(o);out.flush();printBase64Binary(bos.toByteArray())}; serialize(List)
warning: -nc is deprecated: scripts use cold compilation by default; use -Yscriptrunner for custom behavior
import java.util.Base64
parseBase64Binary: (s: String)Array[Byte]
printBase64Binary: (data: Array[Byte])String
serialize: (o: AnyRef)String
res0: String = rO0ABXNyACBzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JAAAAAAAAAADAwAAeHB4

scala> :quit
~/code/scala on topic/static-object-fields*
$ scala-ref 2.13.x
Welcome to Scala 2.13.0-20180927-195543-c277d7d (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_172).
Type in expressions for evaluation. Or try :help.

scala> import java.util.Base64; def parseBase64Binary(s: String): Array[Byte] = Base64.getDecoder.decode(s); def printBase64Binary(data: Array[Byte]): String = Base64.getEncoder.encode(data).map(_.toChar).mkString; def serialize(o: AnyRef): String = {val bos = new java.io.ByteArrayOutputStream();val out = new java.io.ObjectOutputStream(bos);out.writeObject(o);out.flush();printBase64Binary(bos.toByteArray())}; serialize(List)
import java.util.Base64
parseBase64Binary: (s: String)Array[Byte]
printBase64Binary: (data: Array[Byte])String
serialize: (o: AnyRef)String
res0: String = rO0ABXNyACBzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JAAAAAAAAAADAwABTAARcGFydGlhbE5vdEFwcGxpZWR0ABFMc2NhbGEvRnVuY3Rpb24xO3hweA==

This confuses me. The following details of List$ are the same before and after this PR.

public final class scala/collection/immutable/List$ implements scala/collection/StrictOptimizedSeqFactory  {

  // access flags 0x1A
  private final static J serialVersionUID = 3

  // access flags 0x2
  private writeObject(Ljava/io/ObjectOutputStream;)V
    // parameter final  out
    RETURN
    MAXSTACK = 0
    MAXLOCALS = 2


  // access flags 0x2
  private readObject(Ljava/io/ObjectInputStream;)V
    // parameter final  in
    RETURN
    MAXSTACK = 0
    MAXLOCALS = 2

  // access flags 0x2
  private readResolve()Ljava/lang/Object;
    GETSTATIC scala/collection/immutable/List$.MODULE$ : Lscala/collection/immutable/List$;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 1

Aren't these enough to keep serialization stable?

@retronym
Copy link
Member Author

retronym commented Sep 28, 2018

Ah, looking at the implementation of serialization, it records the descriptors of instance fields, even when serialVersionUID is explicitly specified. However, when deserializing, if the svUID in the data stream matches that of the current version of the class, the field descriptors don't have to align.

$ qscala
Welcome to Scala 2.13.0-20180928-004946-4aa133f (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_172).
Type in expressions for evaluation. Or try :help.

scala> import java.util.Base64; def parseBase64Binary(s: String): Array[Byte] = Base64.getDecoder.decode(s); def printBase64Binary(data: Array[Byte]): String = Base64.getEncoder.encode(data).map(_.toChar).mkString; def serialize(o: AnyRef): String = {val bos = new java.io.ByteArrayOutputStream();val out = new java.io.ObjectOutputStream(bos);out.writeObject(o);out.flush();printBase64Binary(bos.toByteArray())}; def deserialize(string: String): AnyRef = {val bis = new java.io.ByteArrayInputStream(parseBase64Binary(string)); val in = new java.io.ObjectInputStream(bis); in.readObject()}; serialize(List);
warning: -nc is deprecated: scripts use cold compilation by default; use -Yscriptrunner for custom behavior
import java.util.Base64
parseBase64Binary: (s: String)Array[Byte]
printBase64Binary: (data: Array[Byte])String
serialize: (o: AnyRef)String
deserialize: (string: String)AnyRef
res0: String = rO0ABXNyACBzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JAAAAAAAAAADAwAAeHB4

scala> deserialize("rO0ABXNyACBzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JAAAAAAAAAADAwABTAARcGFydGlhbE5vdEFwcGxpZWR0ABFMc2NhbGEvRnVuY3Rpb24xO3hweA")
warning: -nc is deprecated: scripts use cold compilation by default; use -Yscriptrunner for custom behavior
res2: AnyRef = scala.collection.immutable.List$@a9f023e
$ scala-ref 2.13.x
Welcome to Scala 2.13.0-20180927-195543-c277d7d (OpenJDK 64-Bit Server VM, Java 1.8.0-adoptopenjdk).
Type in expressions for evaluation. Or try :help.

scala> import java.util.Base64; def parseBase64Binary(s: String): Array[Byte] = Base64.getDecoder.decode(s); def printBase64Binary(data: Array[Byte]): String = Base64.getEncoder.encode(data).map(_.toChar).mkString; def serialize(o: AnyRef): String = {val bos = new java.io.ByteArrayOutputStream();val out = new java.io.ObjectOutputStream(bos);out.writeObject(o);out.flush();printBase64Binary(bos.toByteArray())}; def deserialize(string: String): AnyRef = {val bis = new java.io.ByteArrayInputStream(parseBase64Binary(string)); val in = new java.io.ObjectInputStream(bis); in.readObject()}; serialize(List);
import java.util.Base64
parseBase64Binary: (s: String)Array[Byte]
printBase64Binary: (data: Array[Byte])String
serialize: (o: AnyRef)String
deserialize: (string: String)AnyRef
res0: String = rO0ABXNyACBzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JAAAAAAAAAADAwABTAARcGFydGlhbE5vdEFwcGxpZWR0ABFMc2NhbGEvRnVuY3Rpb24xO3hweA==

scala> deserialize("rO0ABXNyACBzY2FsYS5jb2xsZWN0aW9uLmltbXV0YWJsZS5MaXN0JAAAAAAAAAADAwAAeHB4")
res2: AnyRef = scala.collection.immutable.List$@595f9916

So functionally, we have forwards and backwards serialization compatibility, but the irrelevent deatils in the serialization format make it hard for our tests to show that.

An alternative would be to serialize all top level modules writeReplace/readResolve to a class that reflectively accessed the MODULE$ field on deserialization. For performance, it could cache that lookup in a ClassValue.

package scala.runtime;

public final class SerializedObject {
    private final Class<?> moduleClass;
    private static final ClassValue<Object> instances = new ClassValue<Object>() {
        @Override
        protected Object computeValue(Class<?> type) {
            try {
                return type.getField("MODULE$").get(null);
            } catch (IllegalAccessException | NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
        }
    };

    public SerializedObject(Class<?> moduleClass) {
        this.moduleClass = moduleClass;
    }

    private Object readResolve() {
        return instances.get(moduleClass);
    }
}

@retronym retronym force-pushed the topic/static-object-fields branch 2 times, most recently from f97e9b2 to e9fd345 Compare September 28, 2018 01:45
@retronym
Copy link
Member Author

retronym commented Sep 28, 2018

Here's a demo that shows that object val members can now be JIT inlined:

import java.lang.invoke.{MethodHandle, MethodHandles, MethodType}

class C

object Test {
  val handle = MethodHandles.lookup().findVirtual(Test.getClass, "callMe", MethodType.methodType(classOf[Object]))

  def newObject2 = handle.invoke(this)

  @volatile var z = 0

  def main(args: Array[String]): Unit = {
    while(true) {
      z += newObject2.hashCode()
    }
  }

  def callMe = new Object
}
$ qscalac sandbox/test.scala && jardiff 'Test$.class' && (jabba use [email protected] && qscala -J-XX:+UnlockDiagnosticVMOptions -J-XX:+PrintInlining Test)

+++ Test$.class.asm
// class version 52.0 (52)
// access flags 0x31
public final class Test$ {

  // access flags 0x19
  public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup

  // access flags 0x19
  public final static LTest$; MODULE$

  // access flags 0x1A
  private final static Ljava/lang/invoke/MethodHandle; handle

  // access flags 0x4A
  private static volatile I z

  // access flags 0x9
  public static <clinit>()V
    NEW Test$
    DUP
    INVOKESPECIAL Test$.<init> ()V
    PUTSTATIC Test$.MODULE$ : LTest$;
    INVOKESTATIC java/lang/invoke/MethodHandles.lookup ()Ljava/lang/invoke/MethodHandles$Lookup;
    GETSTATIC Test$.MODULE$ : LTest$;
    INVOKEVIRTUAL Test$.getClass ()Ljava/lang/Class;
    LDC "callMe"
    LDC Ljava/lang/Object;.class
    INVOKESTATIC java/lang/invoke/MethodType.methodType (Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
    INVOKEVIRTUAL java/lang/invoke/MethodHandles$Lookup.findVirtual (Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
    PUTSTATIC Test$.handle : Ljava/lang/invoke/MethodHandle;
    ICONST_0
    PUTSTATIC Test$.z : I
    RETURN
    MAXSTACK = 4
    MAXLOCALS = 0

  // access flags 0x2
  private <init>()V
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public callMe()Ljava/lang/Object;
    NEW java/lang/Object
    DUP
    INVOKESPECIAL java/lang/Object.<init> ()V
    ARETURN
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x1
  public handle()Ljava/lang/invoke/MethodHandle;
    GETSTATIC Test$.handle : Ljava/lang/invoke/MethodHandle;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public main([Ljava/lang/String;)V
    // parameter final  args
   L0
    ALOAD 0
    ALOAD 0
    INVOKEVIRTUAL Test$.z ()I
    ALOAD 0
    INVOKEVIRTUAL Test$.newObject2 ()Ljava/lang/Object;
    INVOKEVIRTUAL java/lang/Object.hashCode ()I
    IADD
    INVOKEVIRTUAL Test$.z_$eq (I)V
    GOTO L0
    MAXSTACK = 3
    MAXLOCALS = 2

  // access flags 0x1
  public newObject2()Ljava/lang/Object;
    ALOAD 0
    INVOKEVIRTUAL Test$.handle ()Ljava/lang/invoke/MethodHandle;
    ALOAD 0
    INVOKEVIRTUAL java/lang/invoke/MethodHandle.invoke (LTest$;)Ljava/lang/Object;
    ARETURN
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x1
  public z()I
    GETSTATIC Test$.z : I
    IRETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public z_$eq(I)V
    // parameter final  x$1
    ILOAD 1
    PUTSTATIC Test$.z : I
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 2
}
...
                                @ 1   Test$::handle (4 bytes)   inline (hot)
                                @ 5   java.lang.invoke.LambdaForm$MH/0x00000008001a1840::invoke_MT (22 bytes)   force inline by annotation
                                  @ 8   java.lang.invoke.Invokers::checkGenericType (6 bytes)   force inline by annotation
                                    @ 2   java.lang.invoke.MethodHandle::asType (28 bytes)   inline (hot)
                                  @ 13   java.lang.invoke.Invokers::checkCustomized (23 bytes)   force inline by annotation
                                    @ 1   java.lang.invoke.MethodHandleImpl::isCompileConstant (2 bytes)   (intrinsic)
                                  @ 18   java.lang.invoke.LambdaForm$DMH/0x00000008001a1c40::invokeSpecial (20 bytes)   force inline by annotation
                                    @ 7   java.lang.invoke.DirectMethodHandle::internalMemberName (8 bytes)   force inline by annotation
                                    @ 16   Test$::callMe (8 bytes)   inline (hot)
                                      @ 4   java.lang.Object::<init> (1 bytes)   inline (hot)

Before:

                              @ 6   Test$::newObject2 (9 bytes)
                                @ 1   Test$::handle (5 bytes)
                                @ 5   java.lang.invoke.LambdaForm$MH/0x00000008001d6040::invoke_MT (22 bytes)   force inline by annotation
                                  @ 8   java.lang.invoke.Invokers::checkGenericType (6 bytes)   force inline by annotation
                                    @ 2   java.lang.invoke.MethodHandle::asType (28 bytes)   callee is too large
                                  @ 13   java.lang.invoke.Invokers::checkCustomized (23 bytes)   force inline by annotation
                                    @ 1   java.lang.invoke.MethodHandleImpl::isCompileConstant (2 bytes)
                                    @ 19   java.lang.invoke.Invokers::maybeCustomize (28 bytes)   don't inline by annotation
                                  @ 18   java.lang.invoke.MethodHandle::invokeBasic(L)L (0 bytes)   receiver not constant
                              @ 9   java.lang.Object::hashCode (0 bytes)   no static binding

@retronym
Copy link
Member Author

retronym commented Sep 28, 2018

An alternative would be to serialize all top level modules ... [with a serialization proxy]

I see @szeiger has proposed this already in #6877

@retronym
Copy link
Member Author

Benchmarks points to appear here.

The first will show the improvement from using this PR as STARR, rather than the preceding commit. The second point will show the subsequent change from using this PR as STARR and this PR as the compiler-under-performance test, to see if this extra work in the backend makes a difference.

@@ -37,7 +37,7 @@ trait TestBase {
}


trait FutureCallbacks extends TestBase {
class FutureCallbacks extends TestBase {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@retronym Out of curiosity, why did these need to change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#7270 (comment)

As written, the tests ran during the Test$.<clinit>, during which time they started threads that called back into accessors of mixed-in fields (like inEc) in Test$. That didn't deadlock because those fields were non-static and the reference wasn't made via the static Test$.MODULE$, but rather through the outer$ reference of an inner class in the mixed-in trait.

After this PR, pattern leads to a deadlock: the <clinit> thread is waiting for the other thread to stop, and the other thread is blocked on the GETSTATIC instruction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@retronym That makes me feel skeptical about this encoding. It would be interesting to see the community build running on this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's unfortunate. But code that publishes this for use on other threads before initialization is complete is asking for trouble, and tends to run afoul of subtle differences in the way things are compiled. We had the same problem with the lambda encoding on 2.12.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@retronym Let's see what community-build says :)

}
val (pre, rest0) = stats span (!isConstr(_))
val (supercalls, rest) = rest0 span (isConstr(_))
(pre ::: supercalls, rest)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a lot of iteration and a lot of allocation. Would this not be possible to solve with a custom Ordering + a sort?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it could be done with a bit less garbage, but this isn't a hotspot based on profiles I've collected.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's your call :) I always err on the side of efficiency :)

Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the object inherits val fields from traits, the <clinit> now gets a runtime.Statics.releaseFence() call. Given the class init lock, this is redundant, no?

The changes LGTM. Let's see what others say (and the community build).

val constructorDefDef = treeInfo.firstConstructor(cd0.impl.body).asInstanceOf[DefDef]
val (uptoSuperStats, remainingConstrStats) = treeInfo.splitAtSuper(constructorDefDef.rhs.asInstanceOf[Block].stats, classOnly = true)
val clInitSymbol = claszSymbol.newMethod(TermName("<clinit>"), claszSymbol.pos, Flags.STATIC).setInfo(NullaryMethodType(definitions.UnitTpe))
val moduleField = claszSymbol.newValue(nme.MODULE_INSTANCE_FIELD, claszSymbol.pos, Flags.STATIC | Flags.PRIVATE).setInfo(claszSymbol.tpeHK)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's maybe a little confusing to create the field symbol but not enter it in the decls and rely on addModuleInstanceField. A comment would be helpful.

(Class fields are generated based on BCodeHelpers.fieldSymbols, so on the decls. Methods on the other hand are generated based on DefDefs.)

// pain for scala-{js, native}?

for (f <- fieldSymbols(claszSymbol)) {
f.setFlag(Flags.STATIC)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little bit funny that this is enough to make code gen emit getstatic/putstatic and avoid loading the module instance. The trees accessing the fields still have the shape Select(This("T"), "y"). It's fine with me; we don't really have a "correct" way to represent a static selection in trees, I think we discussed this before.

@smarter
Copy link
Member

smarter commented Oct 1, 2018

I asked Dmitry about this encoding since he spent some time on this problem a while ago, he said:

The scheme makes sense.
I'd tune it a bit to change behaviour of inner classes of modules, so that
they fall back to outer references. I think this will allow to optimise the
behaviour by JIT, while keeping current semantics.

@retronym retronym force-pushed the topic/static-object-fields branch from e9fd345 to f7cadba Compare October 1, 2018 23:54
@retronym
Copy link
Member Author

retronym commented Oct 1, 2018

Rebased on #7297, which locks down the serialized form of top level modules, making it insensitive to the changes to field modifiers.

@retronym retronym force-pushed the topic/static-object-fields branch from f7cadba to c14e477 Compare October 2, 2018 00:09
@retronym
Copy link
Member Author

retronym commented Oct 2, 2018

making it insensitive to the changes to field modifiers.

Bzzzt, wrong again, me. The java serialiazation for List$.class actually changes depending on what serializable (non-static/transient) fields it contains! So this pull request does change the serialized form, by changing List$.partialNotApplied to static. Such a change is forwards/backwards serialization compatible (not that we aspire to that until we lock down 2.13.0.)

;tl;dr Going forwards, we will no longer incur checkfile updates to the serialization stability test when we add or remove fields. But we need one last update now!

@retronym retronym force-pushed the topic/static-object-fields branch from c14e477 to 5ce1767 Compare October 2, 2018 05:07
@SethTisue
Copy link
Member

@retronym retronym force-pushed the topic/static-object-fields branch from 5ce1767 to 423816d Compare October 18, 2018 23:18
@retronym
Copy link
Member Author

Rebased.

@retronym
Copy link
Member Author

There appear to be unrelated failures in the community build run above related to collections changes and scala-java8-compat.

I've re-run as https://scala-ci.typesafe.com/job/scala-2.13.x-integrate-community-build/1522/ after rebasing this PR.

I've also prepared a backport to 2.12.x to facilitate testing with a cleaner, and more comphrehensive (?) community build.

@SethTisue
Copy link
Member

There appear to be unrelated failures in the community build run above related to collections changes and scala-java8-compat

confirm, unrelated

@mkeskells
Copy link
Contributor

is there a performance run with this PR?
I have a couple of branches that should benefit from this change that I will hope to benchmark this week

1 similar comment
@mkeskells
Copy link
Contributor

is there a performance run with this PR?
I have a couple of branches that should benefit from this change that I will hope to benchmark this week

This lets us make the MODULE$ field unconditionally final,
which improves runtime performance marginally by letting
JIT elide null checks.

Fields of objects themselves and now also be JIT-inlinable
constants.

=====================================================================

Adapts scala-concurrent-tck.scala to avoid a new deadlock. This
pattern of code will deadlock if module fields are static.

```
trait CustomExecutionContext {
  val inEC = ""

  def testOnSuccessCustomEC(): Unit = {
    val t = new Thread() {
      override def run(): Unit = {
        println("getting it")
        inEC.length
        // this.outer$.inEC() now blocks until the `Test$.<clinit>` has concluded, because `Test$.inEC()`
        // now differs:
        // public inEC()Ljava/lang/String;
        // -    ALOAD 0
        // -    GETFIELD Test$.inEC : Ljava/lang/String;
        // +    GETSTATIC Test$.inEC : Ljava/lang/String;
        //
        // And the GETSTATIC blocks.
        println("got it")
      }
    }
    t.start()
    t.join()
    println("done")
  }

  testOnSuccessCustomEC()
}

object Test extends CustomExecutionContext {
  def main(args: Array[String]): Unit = {
  }
}
```

We accepted a similar change as an acceptable consequence of the new
lambda encoding.
@retronym retronym force-pushed the topic/static-object-fields branch from 5052d10 to 5e109ff Compare November 14, 2018 04:20
@retronym
Copy link
Member Author

@lrytz I've pushed a change that only allows GETSTATIC-s of fields from classes that differ from the methods owner.

Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Thanks!

@lrytz lrytz merged commit 6e0cba7 into scala:2.13.x Nov 14, 2018
@SethTisue SethTisue added performance the need for speed. usually compiler performance, sometimes runtime performance. release-notes worth highlighting in next release notes labels Nov 14, 2018
xuwei-k added a commit to xuwei-k/specs2 that referenced this pull request Apr 24, 2020
maybe scala/scala#7270 ?

```
package example

import scala.reflect.NameTransformer

object A{
  Main.a = 1
}

object B{
  Main.b = 1
}

object Main {
  var a: Int = 0
  var b: Int = 0

  def initializeByConstructor(): Int = {
    this.a = 0
    val List(constructor) = Class.forName("example.A$").getDeclaredConstructors.toList
    constructor.setAccessible(true)
    constructor.newInstance()
    val result = Main.a
    this.a = 0
    result
  }

  def initializeByMODULE_field(): Int = {
    this.b = 0
    Class.forName("example.B$").getDeclaredField(NameTransformer.MODULE_INSTANCE_NAME).get(null)
    val result = Main.b
    this.b = 0
    result
  }

  def main(args: Array[String]): Unit = {
    println((A, B))
    println(scala.util.Properties.versionString)
    println(initializeByConstructor())
    println(initializeByMODULE_field())
  }
}
```

```
[info] version 2.12.11
[info] 1
[info] 0
```

```
[info] version 2.13.2
[info] 0
[info] 0
```
etorreborre pushed a commit to etorreborre/specs2 that referenced this pull request Apr 26, 2020
maybe scala/scala#7270 ?

```
package example

import scala.reflect.NameTransformer

object A{
  Main.a = 1
}

object B{
  Main.b = 1
}

object Main {
  var a: Int = 0
  var b: Int = 0

  def initializeByConstructor(): Int = {
    this.a = 0
    val List(constructor) = Class.forName("example.A$").getDeclaredConstructors.toList
    constructor.setAccessible(true)
    constructor.newInstance()
    val result = Main.a
    this.a = 0
    result
  }

  def initializeByMODULE_field(): Int = {
    this.b = 0
    Class.forName("example.B$").getDeclaredField(NameTransformer.MODULE_INSTANCE_NAME).get(null)
    val result = Main.b
    this.b = 0
    result
  }

  def main(args: Array[String]): Unit = {
    println((A, B))
    println(scala.util.Properties.versionString)
    println(initializeByConstructor())
    println(initializeByMODULE_field())
  }
}
```

```
[info] version 2.12.11
[info] 1
[info] 0
```

```
[info] version 2.13.2
[info] 0
[info] 0
```
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jun 15, 2020
This is a port of the following PR from Scala 2:
scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jun 15, 2020
This is a port of the following PR from Scala 2:
scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jul 10, 2020
This is a port of the following PR from Scala 2:
scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jul 10, 2020
The problem can be seen from the following example:

trait A { def foo() = ??? }
trait B { def foo() = ??? }

object C extends A with B {
	super[A].foo()
	super[B].foo()
}

In the code above, we cannot translate the following calls
from <init> to <clinit>:

  super[A].foo()
  super[B].foo()

  super[A].$iinit$()
  super[B].$init$()

More details can be found here: scala#5928

A principled way would be to generage super accessors as it
is done in posttyper.

However, the backend has a magic to support
prefix to super trees in [1], which is exploited
in the Scala 2 fix [2].

[1] scala/scala#5944
[2] scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jul 15, 2020
This is a port of the following PR from Scala 2:
scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jul 15, 2020
The problem can be seen from the following example:

trait A { def foo() = ??? }
trait B { def foo() = ??? }

object C extends A with B {
	super[A].foo()
	super[B].foo()
}

In the code above, we cannot translate the following calls
from <init> to <clinit>:

  super[A].foo()
  super[B].foo()

  super[A].$iinit$()
  super[B].$init$()

More details can be found here: scala#5928

A principled way would be to generage super accessors as it
is done in posttyper.

However, the backend has a magic to support
prefix to super trees in [1], which is exploited
in the Scala 2 fix [2].

[1] scala/scala#5944
[2] scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jul 21, 2020
This is a port of the following PR from Scala 2:
scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jul 21, 2020
The problem can be seen from the following example:

trait A { def foo() = ??? }
trait B { def foo() = ??? }

object C extends A with B {
	super[A].foo()
	super[B].foo()
}

In the code above, we cannot translate the following calls
from <init> to <clinit>:

  super[A].foo()
  super[B].foo()

  super[A].$iinit$()
  super[B].$init$()

More details can be found here: scala#5928

A principled way would be to generage super accessors as it
is done in posttyper.

However, the backend has a magic to support
prefix to super trees in [1], which is exploited
in the Scala 2 fix [2].

[1] scala/scala#5944
[2] scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jul 22, 2020
This is a port of the following PR from Scala 2:
scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Jul 22, 2020
The problem can be seen from the following example:

trait A { def foo() = ??? }
trait B { def foo() = ??? }

object C extends A with B {
	super[A].foo()
	super[B].foo()
}

In the code above, we cannot translate the following calls
from <init> to <clinit>:

  super[A].foo()
  super[B].foo()

  super[A].$iinit$()
  super[B].$init$()

More details can be found here: scala#5928

A principled way would be to generage super accessors as it
is done in posttyper.

However, the backend has a magic to support
prefix to super trees in [1], which is exploited
in the Scala 2 fix [2].

[1] scala/scala#5944
[2] scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Aug 3, 2020
This is a port of the following PR from Scala 2:
scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Aug 3, 2020
The problem can be seen from the following example:

trait A { def foo() = ??? }
trait B { def foo() = ??? }

object C extends A with B {
	super[A].foo()
	super[B].foo()
}

In the code above, we cannot translate the following calls
from <init> to <clinit>:

  super[A].foo()
  super[B].foo()

  super[A].$iinit$()
  super[B].$init$()

More details can be found here: scala#5928

A principled way would be to generage super accessors as it
is done in posttyper.

However, the backend has a magic to support
prefix to super trees in [1], which is exploited
in the Scala 2 fix [2].

[1] scala/scala#5944
[2] scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Aug 6, 2020
This is a port of the following PR from Scala 2:
scala/scala#7270
liufengyun added a commit to dotty-staging/dotty that referenced this pull request Aug 6, 2020
The problem can be seen from the following example:

trait A { def foo() = ??? }
trait B { def foo() = ??? }

object C extends A with B {
	super[A].foo()
	super[B].foo()
}

In the code above, we cannot translate the following calls
from <init> to <clinit>:

  super[A].foo()
  super[B].foo()

  super[A].$iinit$()
  super[B].$init$()

More details can be found here: scala#5928

A principled way would be to generage super accessors as it
is done in posttyper.

However, the backend has a magic to support
prefix to super trees in [1], which is exploited
in the Scala 2 fix [2].

[1] scala/scala#5944
[2] scala/scala#7270
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance the need for speed. usually compiler performance, sometimes runtime performance. release-notes worth highlighting in next release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants