Can i detect instructions reordering by bytecode compilation log?

SlandShow :

I have next sample of code:

class Shared {
    int x;
    int y;

    void increment() {
        x++;
        y++;
    }

    void check() {
        if (y > x) {
            System.out.println("Ooops! y > x");
        }
    }
}

Looks clear? But main problem happens here when i am trying to increment and check in two threads:

Shared shared = new Shared();

        Thread writer = new Thread(() -> {
            for (int i = 0; i < N; i++) {
                shared.increment();
            }
        });

        Thread reader = new Thread(() -> {
            for (int i = 0; i < N; i++) {
                shared.check();
            }
        });

writer.start();
reader.start();

You can notice data race (instructions reordering in some cases?):

1. x++;
2. y++;

And now, i know about special VM flags, which can help me to print JIT compiler logs (-XX:+PrintCompilation).

...
    120  181       3       Shared::increment (21 bytes)
    120  182       3       Shared::check (20 bytes)
    120  183       4       Shared::increment (21 bytes)
    120  184       4       Shared::check (20 bytes)
    121  181       3       Shared::increment (21 bytes)   made not entrant
    121  182       3       Shared::check (20 bytes)   made not entrant
    121  185     n 0       java.lang.invoke.MethodHandle::linkToStatic(L)L (native)   (static)
    121  184       4       Shared::check (20 bytes)   made not entrant
    122  186       3       Shared::check (20 bytes)
    122  187     n 0       java.lang.Object::clone (native)   
    122  188       4       Shared::check (20 bytes)
    122  189 %     3       Main::lambda$main$0 @ 2 (19 bytes)
    122  190       3       Main::lambda$main$0 (19 bytes)
    123  186       3       Shared::check (20 bytes)   made not entrant
...

OK, now i can see how compilation of increment method was processed:

    120  181       3       Shared::increment (21 bytes)
    120  183       4       Shared::increment (21 bytes)
    121  181       3       Shared::increment (21 bytes)   made not entrant

Do i understand correct, that reordering here is due of tiered compilation? Because increment() - hot method, JIT compiler profiles this information and use C2 server compiler. And, as i think, reorder some instructions in such way, but in some cases optimization happens(made not entrant). Or it's wrong?

Also, there are some another logs for compilation:

    138  182       2       Shared::increment (21 bytes)
    138  184       4       Shared::increment (21 bytes)
    138  182       2       Shared::increment (21 bytes)   made not entrant
apangin :

This is not related to Tiered Compilation. The problem also happens without it. Let JVM compile just one method check and see how it looks in C2-compiled code:

java -XX:-TieredCompilation \
     -XX:CompileCommand=compileonly,Shared::check \
     -XX:CompileCommand=print,Shared::check \
     Shared

The output is

    0x00000000031a4160: mov     dword ptr [rsp+0ffffffffffffa000h],eax
    0x00000000031a4167: push    rbp
    0x00000000031a4168: sub     rsp,20h           ;*synchronization entry
                                                  ; - Shared::check@-1 (line 11)

(1) 0x00000000031a416c: mov     r10d,dword ptr [rdx+0ch]
                                                  ;*getfield x
                                                  ; - Shared::check@5 (line 11)

(2) 0x00000000031a4170: mov     r8d,dword ptr [rdx+10h]  ;*getfield y
                                                  ; - Shared::check@1 (line 11)

    0x00000000031a4174: cmp     r8d,r10d
    0x00000000031a4177: jnle    31a4185h          ;*if_icmple
                                                  ; - Shared::check@8 (line 11)

    0x00000000031a4179: add     rsp,20h
    0x00000000031a417d: pop     rbp
    0x00000000031a417e: test    dword ptr [1020000h],eax
                                                  ;   {poll_return}
    0x00000000031a4184: ret

As you can see, x is loaded first (line 1), and y is loaded after (line 2). Between these lines another thread may increase y an aribrary number of times, thus making y seem greater than x.

In this particular case you've guessed about reordering of loads with respect to the original program order (in the bytecode getfield y goes before getfield x). However, as @Andreas mentioned, this is not the only reason why the program may break. Even if JIT compiler emits load(y) before load(x), depending on the CPU architecture, it may happen that the first load gets a newer value, while the second load gets an older one, and this will be absolutely correct from JMM perspective.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=356115&siteId=1