Slide 1

Slide 1 text

The sinuous path to Valhalla Rémi Forax

Slide 2

Slide 2 text

Me, Myself and I Assistant Prof at Paris East University Open Source dev OpenJDK, ASM, Tatoo, Pro, etc Java Spec expert One of the Father of invokedynamic (Java 7) lambda (Java 8), module (Java 9), constant dynamic (Java 11) record (Java 14) and valhalla (Java ??)

Slide 3

Slide 3 text

I’m not an Oracle Employee ! Java is open source since a long time now

Slide 4

Slide 4 text

Don’t believe what I’m saying !

Slide 5

Slide 5 text

The sinuous path to Valhalla Rémi Forax

Slide 6

Slide 6 text

Intro

Slide 7

Slide 7 text

At Java inception Hardware in 1993 – Intel 486 SX 25 Mhz / 8 MB of RAM – Pointer dereference ~~ one addition Java Model – Every objects should be hold by references

Slide 8

Slide 8 text

Nowadays Hardware – Intel Kaby Lake 2.4 Ghz / 16 GB of RAM – Pointer dereference ~~ 300 additions Java Model – Every objects are still hold by references ● Unless the JIT can prove that the identity is not used Escape Analysis (if … )

Slide 9

Slide 9 text

On heap representation An array of class Complex header length header re im header re im header re im header re im ...

Slide 10

Slide 10 text

Inline object (first draft) An inline object – no identity (no address) – not nullable (no pointer) Runtime model – inlined into another object/an array cells on heap – scalarization into registers on stack

Slide 11

Slide 11 text

On heap representation An array of an inline class Complex no pointer no header at runtime header length re im re im ... re im re im

Slide 12

Slide 12 text

C struct ? typedef struct _complex { double re; double im; } complex; in C – An array of struct is not an array of pointers – Stack allocation can be scalarize ● depends on inlining

Slide 13

Slide 13 text

Aliasing issue in C/C++ Same address in memory ?? void foo(complex& c1, complex& c2) { c1.re = 5; // is c2.re changed ? } Solution: make it immutable !

Slide 14

Slide 14 text

Inline object An inline object – no identity (no address) – not nullable (no pointer) – not mutable Runtime model – scalarization into registers on stack – inlined into another object/an array cells on heap

Slide 15

Slide 15 text

Inlineable What if ? – Inline class or array too big ? – Concurrent read/write of an inline object Solution: Inline objects are inlineable – VM choose if it is inlined or not ● volatile/static fields are not inlined

Slide 16

Slide 16 text

Examples

Slide 17

Slide 17 text

Inline class Code like a class, works like an int public inline class Complex { private final double re; private final double im; public Complex(double re, double im) { this.re = re; this.im = im; } public Complex add(Complex c) { return new Complex(re + c.re, im + c.im); } }

Slide 18

Slide 18 text

Inline class a class with – keyword “inline” – no superclass but interfaces – all fields are final ● Can not create a cycle ! – any methods

Slide 19

Slide 19 text

Mandelbrot Complex provides a useful abstraction for (var col = -32; col < +32; col++) { for (var row = -32; row < +32; row++) { var c = new Complex(row, col); var zn = new Complex(0.0, 0.0); var iteration = 0; while (zn.modulus() < 4 && iteration < max) { zn = zn.square().add(c); iteration++; } if (iteration < max) { image.setRGB(col, row, colors[iteration]); } else { image.setRGB(col, row, black); } } }

Slide 20

Slide 20 text

Benchmark with JMH Time to render the Mandelbrot 64x64 with 1000 iterations? With java -XX:+EnableValhalla score error primitive 1540 µs/op 22 µs/op indirect 2816 µs/op 48 µs/op inline 1541 µs/op 12 µs/op

Slide 21

Slide 21 text

Example: IntBox java.lang.Integer written as an inline class public inline class IntBox { private final int value; public IntBox(int value) { this.value = value; } public int intValue() { return value; } public static IntBox zero() { return new IntBox(0); } }

Slide 22

Slide 22 text

Array of inline objects Sum of an array of IntBox private static final IntBox[] ARRAY = new IntBox[100_000]; static { range(0, ARRAY.length).forEach(i -> ARRAY[i] = new IntBox(i)); } @Benchmark public int sum_IntBox() { var sum = 0; for(var value : ARRAY) { sum += value.intValue(); } return sum; }

Slide 23

Slide 23 text

Benchmark with JMH Time to sum an array of 100 000 values With java -XX:+EnableValhalla score error primitive (int) 27.1 µs/op 0.2 µs/op indirect (j.l.Integer) 60.7 µs/op 0.6 µs/op inline (IntBox) 27.0 µs/op 0.1 µs/op

Slide 24

Slide 24 text

Array of inline objects What if we shuffle the array ? private static final IntBox[] ARRAY = new IntBox[100_000]; static { range(0, ARRAY.length).forEach(i -> ARRAY[i] = new IntBox(i)); Collections.shuffle(Arrays.asList(ARRAY)); } @Benchmark public int sum_IntBox() { var sum = 0; for(var value : ARRAY) { sum += value.intValue(); } return sum; }

Slide 25

Slide 25 text

Benchmark with JMH Time to sum an array of 100 000 shuffled values With java -XX:+EnableValhalla score error primitive (int) 27.1 µs/op 0.2 µs/op indirect (j.l.Integer) 121.9 µs/op 5.1 µs/op inline (IntBox) 27.0 µs/op 0.1 µs/op

Slide 26

Slide 26 text

OpenJDK projects dependency a.k.a more use cases

Slide 27

Slide 27 text

OpenJDK projects dependency Panama – Vector API (SIMD, AVX support) ● Long128, Long256, etc – Native Access API (safe/managed malloc) ● Pointer, Slice, etc Amber – Pattern matching with de-construction ● Carrier object

Slide 28

Slide 28 text

Roadmap

Slide 29

Slide 29 text

Current Roadmap Early access prototypes (http://jdk.java.net/) – MVT – Lworld 1, 2 => We are here now ! – Lword 10 - preview release in jdk ● support erased generics – Lworld 100 – finale release ● primitive as inline type + specialized generics

Slide 30

Slide 30 text

Minimum Value Type (first prototype)

Slide 31

Slide 31 text

j.l.Value vs j.l.Object java.lang.Object indirect objects java.lang.Integer java.util.ArrayList java.util.AbstractList

Slide 32

Slide 32 text

j.l.Value vs j.l.Object java.lang.Object indirect objects inline objects Complex IntBox java.lang.Integer java.util.ArrayList java.util.AbstractList java.lang.Value Complex.box IntBox.box boxing boxing

Slide 33

Slide 33 text

MVT Good perf with inline classes only code ! No perf regression in existing code ! Doesn’t work well with real applications Requires too much boxing ● println(Object)

Slide 34

Slide 34 text

L-world (second prototype)

Slide 35

Slide 35 text

Object = root of all indirect objects java.lang.Object indirect objects java.lang.String java.util.Optional java.lang.CharSequence java.util.ArrayList java.util.AbstractList

Slide 36

Slide 36 text

Object = root of every objects java.lang.Object indirect objects inline objects java.lang.String Complex IntBox java.lang.Integer java.lang.CharSequence java.util.ArrayList CompactString java.util.AbstractList

Slide 37

Slide 37 text

L-world conversion Subtype relationship Complex c = ... Object o = c; Downcast (checkcast + nullcheck) Object o2 = … Complex c2 = (Complex) o2;

Slide 38

Slide 38 text

Bytecode representation

Slide 39

Slide 39 text

Problem: class late loading In Java, a class is loaded as late as possible class Holder { … } class AnotherClass { public static Holder getMeAHolder() { return null; } } Even when getMeAHolder() is executed, Holder is not loaded

Slide 40

Slide 40 text

Inline class loading but inline classes need to be loaded early – memory layout – method calling sequence ● Scalarization even if method not inlined ! – verifier checks null pollution solution: prefix class by ‘Q’ instead of ‘L’ ‘Q’ => loaded early

Slide 41

Slide 41 text

IntBox (again) java.lang.Integer written as an inline class public inline class IntBox { private final int value; public IntBox(int value) { this.value = value; } … public static IntBox zero() { return new IntBox(0); } }

Slide 42

Slide 42 text

IntBox as classfile An inline class descriptor is prefixed by ‘Q’ public final inline class IntBox { private final int value; descriptor: I public static IntBox zero(); descriptor: ()QintBox; ... }

Slide 43

Slide 43 text

Problem: class initialization In a constructor, you can see the fields being initialized public inline class IntBox { private final int value; public IntBox(int value) { // here I can see the mutation !! this.value = value; } ... }

Slide 44

Slide 44 text

True immutability ? In a constructor, you can see the fields being initialized => not truly immutable Solution: only initialize through a static factory – Compiler transforms constructors to static methods “this” should not escape ! – Add 2 new opcodes: defaultvalue and withfield

Slide 45

Slide 45 text

Bytecode for IntBox constructor public final inline class IntBox { private final int value; public static (I)QIntBox; 0: defaultvalue #1 // class IntBox 3: astore_1 4: iload_0 5: aload_1 6: swap 7: withfield #3 // Field value:I 10: astore_1 11: aload_1 12: areturn

Slide 46

Slide 46 text

Binary compatibility The source code is mostly the same – Add “inline” The bytecode is different – Use a factory instead of a constructor ● So no binary backward compatibility !

Slide 47

Slide 47 text

IntBox The opcode defaultvalue is accessible in Java public inline class IntBox { private final int value; public IntBox(int value) { this.value = value; } … public static IntBox zero() { return IntBox.default; } }

Slide 48

Slide 48 text

Inline class & concurrency

Slide 49

Slide 49 text

Value tearing Accessing a value type concurrently => several reads/stores

Slide 50

Slide 50 text

Problem: Value tearing inline class Value { final long x, y; public Value(long x, long y) { … } } public static void main(String[] args) { var box = new Object() { Value shared; }; var zero = new Value(0, 0); var one = new Value(1, 1); new Thread(() -> { for(;;) { box.shared = zero; } }).start(); new Thread(() -> { for(;;) { box.shared = one; } }).start(); for(;;) { var value = box.shared; if (value.x != value.y) { throw new AssertionError("oops"); } }

Slide 51

Slide 51 text

Problem: Value tearing inline class Value { final long x, y; public Value(long x, long y) { … } } public static void main(String[] args) { var box = new Object() { Value shared; }; var zero = new Value(0, 0); var one = new Value(1, 1); new Thread(() -> { for(;;) { box.shared = zero; } }).start(); new Thread(() -> { for(;;) { box.shared = one; } }).start(); for(;;) { var value = box.shared; if (value.x != value.y) { throw new AssertionError("oops"); } } Two stores Two reads Unprotected concurrent access

Slide 52

Slide 52 text

Value tearing Unprotected concurrent access => several reads/stores Not a new problem long/double on 32bits VMs has the same issue No real solution => don’t do that !

Slide 53

Slide 53 text

Toward the preview release

Slide 54

Slide 54 text

Problem: array covariance An array of Object ● an array of indirect type (array of pointers) ● an array of inline type (array of structs) => size of an array cell not constant ! Iterating over an array can be megamorphic ! Solution: Aggressive loop hoisting + loop duplication

Slide 55

Slide 55 text

Problem: subtyping relationship All operations available on Object should be available on an inline class but ... – No header ● synchronized, wait/notify ? ● System.identityHashCode ? – No identity ● Meaning of o == o2 if inline objects ?

Slide 56

Slide 56 text

No header synchronized, wait/notify ? Throw an IllegalMonitorStateException ● Avoid perf regression ? System.identityHashCode ? Redirected to hashCode()

Slide 57

Slide 57 text

No identity, == (aka acmp) 3 possible semantics – always return false (no pointer) – do a classcheck + components checks ● perf regression in existing code ? – may stackoverflow / very slow if recursive – redirect to call equals() ● Perf regression ! Still in flux !

Slide 58

Slide 58 text

Problem: erased generics Inside a generics class, T is nullable but inline classes are not nullable Map map = ... map.get() – API contract: Can return null – Inline class not nullable: Can not return null

Slide 59

Slide 59 text

Interface == lightweight box Complex.box is an empty interface implemented by Complex // does not compile Map map = … // ok ! Map map = … + Auto boxing between Complex and Complex.box

Slide 60

Slide 60 text

Complex public inline class Complex { private final double re; private final double im; public Complex add(Complex c) { return new Complex(re + c.re, im + c.im); } }

Slide 61

Slide 61 text

Complex with Complex.box public inline class Complex implements Complex.box { private final double re; private final double im; public Complex add(Complex c) { return new Complex(re + c.re, im + c.im); } // generated automatically public sealed interface box permits Complex { // empty } }

Slide 62

Slide 62 text

Summary

Slide 63

Slide 63 text

Current Roadmap Early access prototypes (http://jdk.java.net/) – MVT – Lworld 1, 2 => We are here now ! – Lword 10 - preview release in Java ● support erased generics – Lworld 100 – finale release ● primitive as inline type + specialized generics

Slide 64

Slide 64 text

Inline class - preview – no identity, not mutable, not nullable, tearable – runtime model ● scalarization into registers on stack ● inlined into another object/array cells on heap – Object as root of everything ● don’t use == – erased generic => lightweight boxing (interface)

Slide 65

Slide 65 text

Questions ?

Slide 66

Slide 66 text

Supplementary slides

Slide 67

Slide 67 text

Eclair interface

Slide 68

Slide 68 text

Eclair interface Write an inline class – derives automatically an empty sealed interface – Add unboxing between eclair and inline Non nullable type → inline class Nullable type → eclair interface

Slide 69

Slide 69 text

Example public inline class Complex { private final double re; private final double im; public Complex add(Complex c) { return new Complex(re + c.re, im + c.im); } }

Slide 70

Slide 70 text

Example public inline class Complex implements Complex.box { private final double re; private final double im; public Complex add(Complex c) { return new Complex(re + c.re, im + c.im); } // generated automatically public sealed interface box permits Complex { // empty } }

Slide 71

Slide 71 text

How to use it Erased generics don’t allow inline class List l = … // doesn’t compile But using the eclair box is ok List l = List.of(new Complex(…)); // subtyping Complex c = l.get(0); // unboxing, may throw NPE

Slide 72

Slide 72 text

After the preview release

Slide 73

Slide 73 text

Problem: Value Based Class Want to retrofit existing JDK classes – java.util.Optional, java.time.LocalDate, etc. Problem: – value based class != an inline class ● vbcs are nullable

Slide 74

Slide 74 text

Solution: hand retrofit as eclair Change the VM spec – So invokevirtual can call an interface Change the VBC to be interface (eclair) – add Optional.val as inline class – JIT: Aggressive non null propagation ● At worth, additional nullcheck

Slide 75

Slide 75 text

Inline class & concurrency

Slide 76

Slide 76 text

Value tearing Accessing a value type concurrently => several reads/stores Not a new problem long/double on 32bits VMs has the same issue No real solution => don’t do that !

Slide 77

Slide 77 text

Problem: Value tearing inline class Value { final int x, y; public Value(int x, int y) { … } } public static void main(String[] args) { var box = new Object() { Value shared; }; var zero = new Value(0, 0); var one = new Value(1, 1); new Thread(() -> { for(;;) { box.shared = zero; } }).start(); new Thread(() -> { for(;;) { box.shared = one; } }).start(); for(;;) { var value = box.shared; if (value.x != value.y) { throw new AssertionError("oops"); } }

Slide 78

Slide 78 text

Problem: Value tearing inline class Value { final int x, y; public Value(int x, int y) { … } } public static void main(String[] args) { var box = new Object() { Value shared; }; var zero = new Value(0, 0); var one = new Value(1, 1); new Thread(() -> { for(;;) { box.shared = zero; } }).start(); new Thread(() -> { for(;;) { box.shared = one; } }).start(); for(;;) { var value = box.shared; if (value.x != value.y) { throw new AssertionError("oops"); } } Two stores Two reads Unprotected concurrent access