Skip to content

Make MODULE$ Final Again? #537

Closed
Closed
@retronym

Description

@retronym

We had to (#194) make the module field non-final to prepare for JDK9/Classfile 53 verifier, which enforces the (long standing) rule that static final field assignments must occur in <clinit> (not <init>).

However, this prevents the JIT from eliding the access of this field in calling code. For example, in scala.math.max(1, 2), the JIT can't rule out that scala.math.package$.MODULE$ hasn't been nulled out, so it must be checked.

For some special cases, like above, I've managed to restore finality of that field in scala/scala#6523. But important modules don't qualify for the optimization, e.g Predef and Nil.

The JDK has an internal annotation, @Stable, that would tell the JIT that this field is 'effectively final', ie it is assign once. That's not available for our purposes. It can be simulated with invokedynamic, which might be worth trying, but we need to ensure it is really zero-overhead across different JDK versions.

Another idea came to mind yesterday.

Given:

trait T {
  println("T")
}
class C {
  println("C")
}
object O extends C with T {
  println("body")
}

We currently emit:

public final class O$ extends C  implements T  {

  public static LO$; MODULE$

  public static <clinit>()V
    NEW O$
    INVOKESPECIAL O$.<init> ()V
    RETURN

  private <init>()V
    ALOAD 0
    INVOKESPECIAL C.<init> ()V
    ALOAD 0
    PUTSTATIC O$.MODULE$ : LO$;
    ALOAD 0
    INVOKESTATIC T.$init$ (LT;)V (itf)
    GETSTATIC scala/Predef$.MODULE$ : Lscala/Predef$;
    LDC "body"
    INVOKEVIRTUAL scala/Predef$.println (Ljava/lang/Object;)V
    RETURN
}

But why not:

public final class O$ extends C  implements T  {

  public static final LO$; MODULE$

  public static <clinit>()V
    NEW O$
    DUP
    INVOKESPECIAL O$.<init> ()V
    DUP
    PUTSTATIC O$.MODULE$ : LO$;
    INVOKESPECIAL O$.$init$
    RETURN

  private <init>()V
    ALOAD 0
    INVOKESPECIAL C.<init> ()V

  private $init$()V
    ALOAD 0
    INVOKESTATIC T.$init$ (LT;)V (itf)
    GETSTATIC scala/Predef$.MODULE$ : Lscala/Predef$;
    LDC "body"
    INVOKEVIRTUAL scala/Predef$.println (Ljava/lang/Object;)V

}

ie, split the constructor up just after the super-class constructor call and move the rest into a private method that would be called after <clinit> assigns the module field.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions