Skip to content

Trait encoding vs the persnickety JVM 9 verifier: final fields only updatable from within <init> #408

Closed
@retronym

Description

@retronym

Final Fields

JVM 9 closes a loophole in the verifier around final field updates outside of <init>. It is predicated on the new classfile version for backwards compat. Some JIT optimizations are disabled for such almost-final fields (at least, FoldStableValues).

Scala's trait encoding violates this rule. Below, we assign to x in T$_setter_$x_$eq.

⚡ scalac $(f "trait T { final val x: Int = 42 }; class C extends T") && javap -c -private C T
Compiled from "a.scala"
public class C implements T {
  private final int x;

  public final int x();
    Code:
       0: aload_0
       1: getfield      #15                 // Field x:I
       4: ireturn

  public final void T$_setter_$x_$eq(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #15                 // Field x:I
       5: return

  public C();
    Code:
       0: aload_0
       1: invokespecial #24                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: invokestatic  #28                 // InterfaceMethod T.$init$:(LT;)V
       8: return
}
Compiled from "a.scala"
public interface T {
  public abstract void T$_setter_$x_$eq(int);

  public abstract int x();

  public static void $init$(T);
    Code:
       0: aload_0
       1: bipush        42
       3: invokeinterface #18,  2           // InterfaceMethod T$_setter_$x_$eq:(I)V
       8: return
}

Option 1: Status Quo

Don't support emitting classes with the new classfile version. This robs users of a valuable mechanism to fail fast when running classes that depend on the Java 9 standard library. We're also likely to run into a case where some desirable VM feature is only available under the new version.

Option 2: Drop the final modifier in bytecode

We would lose a guarantee under the memory model, but we could get this back with an explicit VarHandle.xxxFence at the end of the constructor of a class with an "almost final" field.

We'd also miss out on some JIT optimizations. These could be regained if/when the @Stable annotation was exposed outside of the JDK, which is under discussion for a future JEP

Option 3: link time trait constructor inlining ?

If we could inline the T.$init$ into C.<init>, but somehow do this at link time so as not to violate separate compilation constraints, we'd be in better shape. C.<init> could use a bootstrap method that delegated to a bootstrap method helper in each trait that builds up the right sequence of calls.

Option 4: rearrange trait encoding, dotty style

Leave the assignments in the class constructor and have it call initializer methods for each val. Don't forget to execute side-effects (statements in the trait body) in the right order! Consider binary compatibility pros/cons!

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions