C2 verification pass shenandoah barriers

Roland Westrelin rwestrel at redhat.com
Tue Jun 7 09:09:51 UTC 2016


http://cr.openjdk.java.net/~roland/shenandoah-verif/webrev.00/

The code of the verification pass is in
ShenandoahBarrierNode::verify()/ShenandoahBarrierNode::verify_helper().
It walks the ideal graph and whenever it encounters a Load node, it
verifies that all paths leading to the Address input have a read or
write barrier or don't need one. When it's a store, it checks for a
write barrier on the Address input and if it's an oop store it looks for
a read or write barrier on the ValueIn input etc. It also covers
intrinsics implemented as a call or their own node type. If a new node
type/runtime call that manipulates oops is added or if an existing
type/runtime call is modified so some of its inputs are now oops, the
verification code should detect the change (and fail) so the
verification code can be updated and kept in sync with the rest of the
code. The verification pass can also check that there's no useless
barrier in the IR graph but I left that off for now as it may require
more work. -XX:+ShenandoahVerifyOptoBarriers turns the verification pass
on. It runs towards then end of the optimization passes before macro
expansion (because it makes finding allocation easier). I had to use a
simple graph pattern for pointer comparison when
ShenandoahVerifyOptoBarriers is on to avoid complex pattern matching in
the verification pass.

c1_LIRGenerator_x86.cpp has unrelated fixes I found required during testing.

The addnode.cpp change is related to unsafe support. See below.

The arraycopynode.cpp change disables some graph transformations that
require barrier to be added and are illegal as is.

In graphKit.cpp, I changed the Phi input for the object null path at a
barrier to be the null object. It could help the compiler optimize code
better (and also makes the code of the verification pass simpler)

With that change to graphKit.cpp the read barrier code (for instance) is:

if (obj == null) {
  obj = null;
} else {
  obj = shenandoah_readbarrier(obj);
}

A number of intrinsics know their input is non null so don't emit a null
check but nothing tells the compiler the input is non null. So we
sometimes have the barrier followed my a memory access:

if (obj == null) {
  obj = null;
} else {
  obj = shenandoah_readbarrier(obj);
}

value = obj.field;

which the compiler sometimes changes to:

if (obj == null) {
  obj = null;
  value = obj.field;
} else {
  obj = shenandoah_readbarrier(obj);
  value = obj.field;
}

The access to the field of a null object (in the first branch) is
impossible in java and that makes the compiler crash. So
library_call.cpp has a number of changes with cast_not_null calls. The
code becomes:

obj = not_null(obj);

if (obj == null) {
  obj = null;
} else {
  obj = shenandoah_readbarrier(obj);
}

value = obj.field;

and the if goes away.

The same problem exist with unsafe accesses. So for instance an unsafe
field load is:

if (obj == null) {
  obj = null;
} else {
  obj = shenandoah_readbarrier(obj);
}

value = Unsafe.getInt(obj, offset);

can be transformed by the compiler as:

if (obj == null) {
  obj = null;
  value = Unsafe.getInt(obj, offset);
} else {
  obj = shenandoah_readbarrier(obj);
  value = Unsafe.getInt(obj, offset);
}

And the compiler sees a null + offset object access again. obj can be
null in case of an out of heap access. The addnode.cpp detects the:
obj = null;
value = Unsafe.getInt(obj, offset);

and turns it into a raw memory access the compiler doesn't get confused.

The library_call.cpp change also adds some missing barriers and removes
some that are not required in some cases.

Roland.


More information about the shenandoah-dev mailing list