Description
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!