Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding volatile and non-volatile read/write in Java

Tags:

java

x86

assembly

My java code is as follows

public class MyClass {
    volatile int voltile ;  //7
    int nonVoltile ;   //8

    public static void main(String[] args) {
        for(int i=1; i<100000; i++){
            f();
        }
    }

    static void f(){   //16
        MyClass t = new MyClass();  //17
        t.voltile = t.nonVoltile;  //18

        t.nonVoltile = 0x11111; //20
        t.voltile = 0x22222;  //21

        t.nonVoltile = t.nonVoltile + 1; //23
        t.voltile = t.voltile + 1; //24

    }
}

Generated assembly snippets for function "f" is as follows

For volatile write

  0x024c73ff: mov    0xc(%esi),%eax
  0x024c7402: mov    %eax,0x8(%esi)
  0x024c7405: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@13 (line 18)

I have following questions

  1. Why two move instructions above? Why not directly mov 0xc(%esi) to 0x8(%esi)
  2. What is the use of 0x024c7405: lock addl $0x0,(%esp)?

For volatile read

  0x024c7425: mov    0x8(%esi),%eax     ;*getfield voltile
                                        ; - j.assembly.MyClass::f@40 (line 24)

Only a single read instruction is generated

  1. Is this volatile read happening from main memory?
  2. Why nothing like lock addl instruction lock is used which was there for volatile write?

As asked I am also pasting the complete assembly

CompilerOracle: print *MyClass.f
Compiled method (c1)     142   14   !         j.assembly.MyClass::f (81 bytes)
 total in heap  [0x024c72c8,0x024c77d8] = 1296
 relocation     [0x024c7398,0x024c73bc] = 36
 main code      [0x024c73c0,0x024c7600] = 576
 stub code      [0x024c7600,0x024c7620] = 32
 oops           [0x024c7620,0x024c7624] = 4
 metadata       [0x024c7624,0x024c7628] = 4
 scopes data    [0x024c7628,0x024c76a4] = 124
 scopes pcs     [0x024c76a4,0x024c77d4] = 304
 dependencies   [0x024c77d4,0x024c77d8] = 4
Loaded disassembler from hsdis-i386.dll
Decoding compiled method 0x024c72c8:
Code:
[Disassembling for mach='i386']
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x14830304} 'f' '()V' in 'j/assembly/MyClass'
  #           [sp+0x40]  (sp of caller)
  0x024c73c0: mov    %eax,0xffffc000(%esp)
  0x024c73c7: push   %ebp
  0x024c73c8: sub    $0x38,%esp
  0x024c73cb: mov    $0x14830350,%edx   ;   {metadata('j/assembly/MyClass')}
  0x024c73d0: mov    %fs:0x0,%ecx
  0x024c73d8: mov    0xfffffff4(%ecx),%ecx
  0x024c73db: mov    0x34(%ecx),%eax
  0x024c73de: lea    0x10(%eax),%edi
  0x024c73e1: cmp    0x3c(%ecx),%edi
  0x024c73e4: ja     0x024c7589
  0x024c73ea: mov    %edi,0x34(%ecx)
  0x024c73ed: mov    0x60(%edx),%ecx
  0x024c73f0: mov    %ecx,(%eax)
  0x024c73f2: mov    %edx,0x4(%eax)
  0x024c73f5: xor    %ecx,%ecx
  0x024c73f7: mov    %ecx,0x8(%eax)
  0x024c73fa: mov    %ecx,0xc(%eax)
  0x024c73fd: mov    %eax,%esi          ;*new  ; - j.assembly.MyClass::f@0 (line 17)

  0x024c73ff: mov    0xc(%esi),%eax
  0x024c7402: mov    %eax,0x8(%esi)
  0x024c7405: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@13 (line 18)

  0x024c740a: movl   $0x11111,0xc(%esi)  ;*putfield nonVoltile
                                        ; - j.assembly.MyClass::f@19 (line 20)

  0x024c7411: mov    $0x22222,%eax
  0x024c7416: mov    %eax,0x8(%esi)
  0x024c7419: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@25 (line 21)

  0x024c741e: movl   $0x11112,0xc(%esi)  ;*putfield nonVoltile
                                        ; - j.assembly.MyClass::f@35 (line 23)

  0x024c7425: mov    0x8(%esi),%eax     ;*getfield voltile
                                        ; - j.assembly.MyClass::f@40 (line 24)

  0x024c7428: inc    %eax
  0x024c7429: mov    %eax,0x8(%esi)
  0x024c742c: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@45 (line 24)

  0x024c7431: lea    0x20(%esp),%edi
  0x024c7435: mov    %esi,0x4(%edi)
  0x024c7438: mov    (%esi),%eax
  0x024c743a: mov    %eax,%ebx
  0x024c743c: and    $0x7,%ebx
  0x024c743f: cmp    $0x5,%ebx
  0x024c7442: jne    0x024c74ca
  0x024c7448: mov    %eax,(%edi)
  0x024c744a: mov    0x4(%esi),%ebx
  0x024c744d: mov    0x60(%ebx),%ebx
  0x024c7450: xor    %eax,%ebx
  0x024c7452: mov    %fs:0x0,%eax
  0x024c745a: mov    0xfffffff4(%eax),%eax
  0x024c745d: xor    %ebx,%eax
  0x024c745f: and    $0xffffff87,%eax
  0x024c7462: je     0x024c74eb
  0x024c7468: test   $0x7,%eax
  0x024c746d: jne    0x024c74be
  0x024c746f: test   $0x180,%eax
  0x024c7474: jne    0x024c749a
  0x024c7476: mov    (%edi),%eax
  0x024c7478: and    $0x1ff,%eax
  0x024c747e: mov    %fs:0x0,%ebx
  0x024c7486: mov    0xfffffff4(%ebx),%ebx
  0x024c7489: or     %eax,%ebx
  0x024c748b: lock cmpxchg %ebx,(%esi)
  0x024c748f: jne    0x024c7595
  0x024c7495: jmp    0x024c74eb
  0x024c749a: mov    0x4(%esi),%ebx
  0x024c749d: mov    0x60(%ebx),%ebx
  0x024c74a0: mov    %fs:0x0,%eax
  0x024c74a8: mov    0xfffffff4(%eax),%eax
  0x024c74ab: or     %eax,%ebx
  0x024c74ad: mov    (%edi),%eax
  0x024c74af: lock cmpxchg %ebx,(%esi)
  0x024c74b3: jne    0x024c7595
  0x024c74b9: jmp    0x024c74eb
  0x024c74be: mov    (%edi),%eax
  0x024c74c0: mov    0x4(%esi),%ebx
  0x024c74c3: mov    0x60(%ebx),%ebx
  0x024c74c6: lock cmpxchg %ebx,(%esi)
  0x024c74ca: mov    (%esi),%eax
  0x024c74cc: or     $0x1,%eax
  0x024c74cf: mov    %eax,(%edi)
  0x024c74d1: lock cmpxchg %edi,(%esi)
  0x024c74d5: je     0x024c74eb
  0x024c74db: sub    %esp,%eax
  0x024c74dd: and    $0xfffff003,%eax
  0x024c74e3: mov    %eax,(%edi)
  0x024c74e5: jne    0x024c7595         ;*monitorenter
                                        ; - j.assembly.MyClass::f@51 (line 26)

  0x024c74eb: mov    0xc(%esi),%eax     ;*getfield nonVoltile
                                        ; - j.assembly.MyClass::f@54 (line 27)

  0x024c74ee: inc    %eax
  0x024c74ef: mov    %eax,0xc(%esi)     ;*putfield nonVoltile
                                        ; - j.assembly.MyClass::f@59 (line 27)

  0x024c74f2: mov    0x8(%esi),%eax     ;*getfield voltile
                                        ; - j.assembly.MyClass::f@64 (line 28)

  0x024c74f5: inc    %eax
  0x024c74f6: mov    %eax,0x8(%esi)
  0x024c74f9: lock addl $0x0,(%esp)     ;*putfield voltile
                                        ; - j.assembly.MyClass::f@69 (line 28)

  0x024c74fe: lea    0x20(%esp),%eax
  0x024c7502: mov    0x4(%eax),%edi
  0x024c7505: mov    (%edi),%esi
  0x024c7507: and    $0x7,%esi
  0x024c750a: cmp    $0x5,%esi
  0x024c750d: je     0x024c7527
  0x024c7513: mov    (%eax),%esi
  0x024c7515: test   %esi,%esi
  0x024c7517: je     0x024c7527
  0x024c751d: lock cmpxchg %esi,(%edi)
  0x024c7521: jne    0x024c75a6         ;*monitorexit
                                        ; - j.assembly.MyClass::f@73 (line 26)

  0x024c7527: add    $0x38,%esp
  0x024c752a: pop    %ebp
  0x024c752b: test   %eax,0xdd0100      ;   {poll_return}
  0x024c7531: ret                       ;*return
                                        ; - j.assembly.MyClass::f@80 (line 30)

  0x024c7532: mov    %fs:0x0,%esi
  0x024c753a: mov    0xfffffff4(%esi),%esi
  0x024c753d: mov    0x1a4(%esi),%eax
  0x024c7543: movl   $0x0,0x1a4(%esi)
  0x024c754d: movl   $0x0,0x1a8(%esi)
  0x024c7557: mov    %eax,%esi
  0x024c7559: lea    0x20(%esp),%eax
  0x024c755d: mov    0x4(%eax),%ebx
  0x024c7560: mov    (%ebx),%edi
  0x024c7562: and    $0x7,%edi
  0x024c7565: cmp    $0x5,%edi
  0x024c7568: je     0x024c7582
  0x024c756e: mov    (%eax),%edi
  0x024c7570: test   %edi,%edi
  0x024c7572: je     0x024c7582
  0x024c7578: lock cmpxchg %edi,(%ebx)
  0x024c757c: jne    0x024c75b7         ;*monitorexit
                                        ; - j.assembly.MyClass::f@78 (line 26)

  0x024c7582: mov    %esi,%eax
  0x024c7584: jmp    0x024c75ec
  0x024c7589: mov    %edx,%edx
  0x024c758b: call   0x024bc740         ; OopMap{off=464}
                                        ;*new  ; - j.assembly.MyClass::f@0 (line 17)
                                        ;   {runtime_call}
  0x024c7590: jmp    0x024c73fd
  0x024c7595: mov    %esi,0x4(%esp)
  0x024c7599: mov    %edi,(%esp)
  0x024c759c: call   0x024bdc40         ; OopMap{esi=Oop [36]=Oop off=481}
                                        ;*monitorenter
                                        ; - j.assembly.MyClass::f@51 (line 26)
                                        ;   {runtime_call}
  0x024c75a1: jmp    0x024c74eb
  0x024c75a6: lea    0x20(%esp),%eax
  0x024c75aa: mov    %eax,(%esp)
  0x024c75ad: call   0x024bde00         ;   {runtime_call}
  0x024c75b2: jmp    0x024c7527
  0x024c75b7: lea    0x20(%esp),%eax
  0x024c75bb: mov    %eax,(%esp)
  0x024c75be: call   0x024bde00         ;   {runtime_call}
  0x024c75c3: jmp    0x024c7582
  0x024c75c5: nop    
  0x024c75c6: nop    
  0x024c75c7: mov    %fs:0x0,%esi
  0x024c75cf: mov    0xfffffff4(%esi),%esi
  0x024c75d2: mov    0x1a4(%esi),%eax
  0x024c75d8: movl   $0x0,0x1a4(%esi)
  0x024c75e2: movl   $0x0,0x1a8(%esi)
  0x024c75ec: add    $0x38,%esp
  0x024c75ef: pop    %ebp
  0x024c75f0: jmp    0x024bbec0         ;   {runtime_call}
  0x024c75f5: hlt    
  0x024c75f6: hlt    
  0x024c75f7: hlt    
  0x024c75f8: hlt    
  0x024c75f9: hlt    
  0x024c75fa: hlt    
  0x024c75fb: hlt    
  0x024c75fc: hlt    
  0x024c75fd: hlt    
  0x024c75fe: hlt    
  0x024c75ff: hlt    
[Exception Handler]
[Stub Code]
  0x024c7600: call   0x024bd6c0         ;   {no_reloc}
  0x024c7605: push   $0x77f387fc        ;   {external_word}
  0x024c760a: call   0x024c760f
  0x024c760f: pusha  
  0x024c7610: call   0x77e22130         ;   {runtime_call}
  0x024c7615: hlt    
[Deopt Handler Code]
  0x024c7616: push   $0x24c7616         ;   {section_word}
  0x024c761b: jmp    0x0245c2b0         ;   {runtime_call}
OopMapSet contains 2 OopMaps

#0 
OopMap{off=464}
#1 
OopMap{esi=Oop [36]=Oop off=481}
Picked up _JAVA_OPTIONS: -Djava.net.preferIPv4Stack=true
Java HotSpot(TM) Client VM warning: printing of assembly code is enabled; turning on DebugNonSafepoints to gain additional output
like image 454
Mangoose Avatar asked Feb 06 '16 09:02

Mangoose


1 Answers

What you see in the generated disassembly is a peculiarity of Java volatile fields.
The topic cannot be explained briefly because it arises from the the way CPUs evolved and works, but simply put, given the instructions:

 THREAD 0                       THREAD 1
 int x = 0, y = 0;              while (y==0);
 x = 1;                         int z = x, w = y;
 y = x + 1;

THREAD 1 can very well ends with z=0 and w=2.
This is not what you generally want. To better understand why this happen refer to this great blog or this nice text.

To address this problem Java define a Memory Model, you can read more about it in the chapter 17.4 of Java Language Specification.
In that model it is defined a relationship between instructions called happen-before and such relationship ensures that every side-effect of an instruction that happen-before another instruction is visible to the latter.
Simply put this guarantees that you have no nasty surprise like in the example above, where, being in two different thread, the instructions x = 1 and int z = x are not in an happen-before relationship and there is no guarantee that the write to x is visible to another thread (there is however the guarantee that it is visible to THREAD 0).

The chapter 17.4 lists what makes two instructions in a happen-before relationship, for example being in the same thread.
Another condition that make two instructions in such a relationship is being into another kind of relationship: the synchronize-with relationship.
Java volatile field generate a synchronize-with between a write to and a read from that field. This is a very strong condition!
Simply put: volatile -> synchronize-with -> happen-before -> serialization of side effects.

So Java volatile is a mechanism of synchronization that can be often used instead of a lock.

In order to satisfy the happen-before relationship between threads it is necessary to use a memory barrier.

You compiled your Java code for IA32 architecture, a.k.a. x86. There are various fence instructions in such architecture that are dedicated to fine tuning. And there are raw serializing instructions: these are instructions that guarantee that every other instruction before, including their side effects, are completed before the serializing one is completed.
They are the barriers of the poor man.

  lock addl $0x0,(%esp)

As you can see this instruction do nothing, it adds zero to the DWORD at esp, but it does it using the lock prefix, which is serializing, therefore acting as a memory barrier. An heavy one to be honest.

The other questions of your are simply to answer:

  • There are two moves because you are reading from nonVoltile (first move) and writing to voltile (second move).
  • The volatile read don't need a barrier because the one used by every write to voltile serializes both write and read operations.
  • The volatile read happens from the main memory if you don't consider the cache hierarchy. Note that you are considering just the first of the three instructions that increment the field.
like image 73
Margaret Bloom Avatar answered Nov 02 '22 03:11

Margaret Bloom