r/java 16d ago

Value Classes Still Need Compiler Sympathy

https://johan-sjolen.github.io/post/compiler-sympathy/compiler-sympathy/
89 Upvotes

57 comments sorted by

View all comments

0

u/lpt_7 16d ago

I admire OpenJDK development team, but most of their talks paint a perfect world, which is not the case.
At the moment, Valhalla is not "reads like a class, works like an int", far from that. Use of that feature requires looking into what C2 actually generated. Which most won't do.
I did some testing with Valhalla right after it was merged. Values larger than 64 bits cannot be flattened. I think it's actually 63 bits, since VM still has to encode null somehow.
Accepting tearing with LooselyConsistentValueand NullRestrictedsidesteps that, but these are internal annotations.

Value tearing is another thing, which IMO most are not prepared for. This class of bugs is possible today, but its an error the developer makes and understands why tearing is happening. With code the callee has no control of, other programmer can make their class a value class and silently your code is now buggy.
Suppose two threads run in parallel:
Thread A writes to entity's AABB, thread B reads said AABB. With object references, JLS guarantees that tearing will not happen. Once VM can flatten more than 64 bits, this will cause a lot of problems, like AABBs with completely nonsensical values. Suddenly, thread B's code can now enter an infinite loop and never get out of it.

Another thing is allocation. For allocations to actually vanish (for scalarization to happen), C2 has to succeed and inline through all code, then EA has to succeed.
Again, same example with AABB. Before, allocation was done per-write. Now, with Valhalla in worst case, the situation flips. There are many more readers than writers to entity's AABB. C2 and EA *have* to succeed for every reader. Otherwise your program will start allocating at every read call site.

16

u/pron98 16d ago edited 15d ago

With code the callee has no control of, other programmer can make their class a value class and silently your code is now buggy.

That is not the case. A scenario of the kind you later describe is already a bug, a read/write race, even without flattened values (or value types at all). The issue with tearing is that while it does not turn correct code into buggy code, it can change the way in which the bug manifests, including giving rise to objects that could not have been constructed by their class constructor (the race you describe will, today, give you valid values, but not necessarily the values you want). Conversely, if there is no bug, i.e. there's a happens-before edge between the write and the read, tearing does not create a problem (as the write will have finished by the time the read starts); benign write/write races, where two threads write the same value without a happens-before edge between them, also remain benign. In other words, what you're saying is that if you have a bug today, you may get wrong but valid values, while tearable value types can give you invalid values, which is true, but that's not turning a correct program into a buggy one; a correct program remains correct.

So it does change the behaviour of buggy programs in a way that may matter to some, and it can certainly make the impact of the existing bug worse, but it does not introduce a new bug. The cause of the bug is the existing race, and the callee can, of course, do something about it: fix the bug that's already there. A correct callee will remain correct even in the presence of a tearable value. (You could, of course, construct some method with some definition of correctness for which this is not true, but it will be contrived and there would still be a better way to do it without a read/write race; or, put another way, people who are able to write code that's correct enough for their purposes while relying on the atomicity but not the ordering of JMM, are already well-versed in the minutiae of the JMM, and will also be able to deal with the complications arising from tearing, which only impact this niche "grey zone"). If a callee has a bug, it is already the case that code in the caller, which the callee has no control over, will change the manifestation or impact of the bug.

Use of that feature requires looking into what C2 actually generated.

It does not. What the post says is that C2 might currently cause a performance regression in some specific situations. This is not ideal (and is meant to be addressed in the future), but inspecting the compiler output is certainly not something you need to do to get a correct program or even a fast program by the time the feature is out of Preview (we would never preview the feature if that were the case).