0

Recently, I read here about JVM optimizations , which are great,
but i have a problem with one optimization, namely Null Check Elimination (or Uncommon trap)

To sum up Null Check Elimination removes a if (obj == null) ... and hopes for the best, while if encounters a Segmentation Fault it recompiles the code and this time includes the neglected if (obj == null) ....

My question is, Given:

bool foo(MyClass obj)
{
   if(obj == null)
      return false;
   m_someVar++;
   obj.doSomething(m_someVar);
}

because the Null-Check is eliminated, and required only after m_someVar++.
will foo execute m_someVar++ an extra time when c will be null?

Edit:

Is there some source for deeper explanation on the implementation of this optimizations, which explain how does this optimization keeps the code semantically same?

Thanks

Tomer W
  • 3,395
  • 2
  • 29
  • 44
  • 1
    Have you tested what happens? – Carcigenicate Sep 04 '17 at 19:42
  • There are other optimizations that were out there for quite some tmie like changing the if-then-else orders etc. that could have similar effects like the one you described, so I assume that this is covered (haven't tested it though). As Carcigenicate suggested, do a test and find out for yourself. – Lothar Sep 04 '17 at 20:07
  • 5
    You can be sure that all the optimizations done by the Java HotSpot JIT compiler leave the language semantics intact. So maybe in your case it won't apply Null-Check Elimination, or delay the `++` until after a modified `obj.doSomething(m_someVar+1)` or doing a `m_someVar--` after the segmentation fault. – Ralf Kleberhoff Sep 04 '17 at 20:13
  • 1
    Yes, it is seems like it executes the command twice on assembler level. But it does not spoil the end result (otherwise it would be improper JVM implementation). Take a look at this article http://jcdav.is/2015/10/06/SIGSEGV-as-control-flow/ This guy analyses the C2 output line by line for exactly the same case. – Sasha Shpota Sep 04 '17 at 22:45
  • @RalfKleberhoff thanks, I was pretty sure it would, but didn't get how the JVM can know when the "middle code" is apathetic to being run twice. is there some source for deeper explanation on the implementation? – Tomer W Sep 05 '17 at 03:42

1 Answers1

1

There are many possibilities ho to avoid the problem: Delaying or undoing the increment was already mentioned in a comment. The JVM has a big bag of tricks and some of them applies:

  • When obj.doSomething(m_someVar) is a non-virtual method call (a private or final method, or a never overridden method), then it probably requires an explicit null check(*) as there would be no SEGV, but the NPE must be thrown before the call. I'm assuming the method was not inlined, as otherwise this analysis should be applied after inlining.
  • When obj.doSomething(m_someVar) is a virtual method call, then you have to do "method dispatch", i.e., something like obj.getClass().getPointerToMethod("doSomething(int)") in order to determine what concrete method to call. I wrote "something like" as doing this literally is very time-consuming and gets optimized away as much as possible. This dispatch can be moved above the increment and it itself will throw an NPE, if obj happens to be null.
  • When you're lucky, then the dispatch may look like if (obj.getClass() != MyClass.class) uncommon_trap();, which is the simplest case (called "monomorphic call site"), but even this involves dereferencing obj and you again get an NPE for null.

(*) A null check may look like obj.getClass() which in assembler is a single instruction loading from a fixed offset relative to the obj pointer. When obj == null then an unmapped page ist hit.

maaartinus
  • 44,714
  • 32
  • 161
  • 320