Suboptimal C2 code generation
Vitaly Davidovich
vitalyd at gmail.com
Fri Nov 20 13:19:01 UTC 2015
Hi Vladimir,
Yes, this is exactly it -- "halfway to desired shape" is a great
description.
The motivating example, from which I extracted this small illustrative one,
is wrapping an instance of some interface and providing a
different/narrowed view on top of it; the wrapped interface object has
strong profile towards one type, with a very small occurrence of it being
null. After wrapping it, the wrapper is then used to invoke another method
in the following manner:
Wrapper w = ...;
callAnotherMethod(w.value(), w.value2(), w.value3(), w.value4()...);
Each of these valueX() methods internally does that null check. I wanted
to see if the JIT would effectively transform that chain into:
if (w.i != null) {
callAnotherMethod(<no repeated null checks, just call into the
interface methods>);
} else {
callAnotherMethod(<use the null value branches from all those
accessors>);
}
After the above transformation and knowing that there's a single receiver
type (when it's not null) with very simple code backing the interface
methods, I was checking if the interface calls were devirtualized. What I
found was a bit surprising, which warranted this email :).
Would it be difficult to enhance this?
Thanks
On Fri, Nov 20, 2015 at 7:54 AM, Vladimir Ivanov <
vladimir.x.ivanov at oracle.com> wrote:
> It looks like C2 stops halfway to desired shape.
> The load is commoned, but repeated null check is not eliminated.
>
> Final IR shape looks like:
> w = Load this._w
>
> If (w == NULL) deopt
>
> i = Load w._i
>
> If (i != NULL) {
> If (!i instanceof C) deopt
> } v1 = Phi(T:1,F:-1);
>
> If (i != NULL) {
> If (!i instanceof C) deopt
> } v2 = Phi(T:2,F:-1)
>
> return v1+v2
>
> The next transformation would be:
> w = Load this._w
> If (w == NULL) deopt
> i = Load w._i
> If (i != NULL) {
> If (!i instanceof C) deopt
> If (!i instanceof C) deopt
> } v1 = Phi(T:1,F:-1)
> v2 = Phi(T:2,F:-1)
> return v1+v2
>
> And finally:
> w = Load this._w
> If (w == NULL) deopt
> i = Load w._i
> If (i != NULL) {
> If (!i instanceof C) deopt
> } v1 = Phi(T:1,F:-1)
> v2 = Phi(T:2,F:-1)
> return i1+i2;
>
> Best regards,
> Vladimir Ivanov
>
> On 11/20/15 5:29 AM, John Rose wrote:
>
>> On Nov 19, 2015, at 2:58 PM, Vitaly Davidovich <vitalyd at gmail.com
>> <mailto:vitalyd at gmail.com>> wrote:
>>
>>>
>>> return _w.value() + _w.value2();
>>>
>>
>> Which is (ignoring non-taken null branches):
>>
>> return _w._i.value() + _w._i.value2();
>>
>> There are two independent fetches of _w._i in the bytecode.
>> The machine code is treating them as independent, where
>> we would want the optimizer to use a common value.
>>
>> The machine code is optimistic that _w._i is always a C,
>> but it appears to be leaving open the possibility that some
>> activity not tracked by the JIT could store some other C2 <: I.
>>
>> What happens at 0x00007ff5f82e00f9? Does it de-opt,
>> or does it merge back into 0x00007ff5f82e00b2? In the
>> latter case, the JIT cannot assume that _w._i is a C.
>>
>> So, it could be a failure to de-opt, or it could be some
>> fluff in the IR which is preventing the two _w._i from
>> commoning. Or something else.
>>
>> — John
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/hotspot-compiler-dev/attachments/20151120/b7b10a29/attachment.html>
More information about the hotspot-compiler-dev
mailing list