There is currently a subtle case where I found reachabilityFence not to work, since the patch that removed the inlining restriction we used to have.
Consider this example:
for (…) {
var bar = foo.bar;
int someInt = bar.id;
doStuff(id);
reachabilityFence(bar);
}
Let’s say id identifies the bar object and we want doStuff to execute before the corresponding bar object is finalized.
The example can transform in the JIT to:
var bar = foo.bar;
int id = bar.id;
for (…) {
doStuff(id);
reachabilityFence(bar);
// safepoint
}
Now when the reachabilityFence is inlined, it will evaporate to nothing. And bar has no uses at all in the loop. The possible deoptimization that can happen at the safepoint inside the loop doesn’t need to keep bar alive, because the interpreter will reload bar inside of the interpreted version of the loop. Since the foo.bar load is a plain load, it is assumed that loading the value in the interpreter will yield the same value.
One might reason that as long as foo is kept alive at the safepoint, foo.bar is implicitly kept alive. But a racing plain store can clear foo.bar. Then the contract of the reachabilityFence is only valid, if the provided value isn’t acquired with a plain load racing with a plain store.
In practice I suppose this is typically good enough, but it doesn’t seem like a strong enough contract.
In this little example, if a concurrent thread clears foo.bar, then doStuff(id) can execute after the corresponding bar object has been finalized, even though the reachabilityFence promises to keep it alive.