JavaMemoryModel: Most (all?) JVM's incorrectly handle volatile reads-after-writes

From: Bill Pugh (pugh@cs.umd.edu)
Date: Sat Nov 20 1999 - 19:18:11 EST


In an off-line email, David Detlefs said that the ExactVM doesn't
generate memory barriers for volatiles, and they thought that the
strong nature of Sparc's TSO memory model would let them get away
with that.

He's not the only one to make that mistake.

Most processors allow writes to be delayed in write buffers. The
effect of this is to allow writes to be reordered with following
reads. This can occur in Sparc TSO mode, Intel IA-32, and pretty much
any processor that doesn't enforce sequential consistency.

This means that on a multiprocessor, you _must_ have a memory barrier
between a write to a volatile variable and a read of a volatile
variable.

For TSO order, those are the only memory barriers required, even with
my new proposed semantics for volatile (read acts as an acquire,
write acts as a release).

Below is a test program. ExactVM, HotSpot, Microsoft's vm, and IBM's
WinTel JVM all fail on multiprocessor systems.

Quick explanation of the program. There are two volatile variables, a and b.
One thread writes 0, 1, 2... to a, and reads b after each write. The
other threads writes to b and reads a. Each thread does this
write/read operation one million times, and then the program looks
for any evidence of a write and a following read being reordered.

        Bill Pugh

ReadAfterWrite.java:

// Check to see if JVM enforces order of
// Reads after Writes of volatile variables.
// All memory operations on volatile variables
// should be sequentially consistent (according
// to existing semantics).

// On many processors, a write and a following read
// can be effectively reorder due to the write buffer.
// Even under Sparc TSO this can be reordered.
// They can also be reordered under Intel IA-64, Intel
// IA-32, Alpha and PowerPC.

// Thus, between a write to a volatile variable and
// a read of a volatile variable, a memory barrier instruction
// is required.

// From Doug Lea's: FJTaskRunner.java
// * (This relies on the JVM properly dealing with read-after-write
// * of two volatiles.)

// In my tests, I haven't found any JVM that correctly implements
// read-after-write on volatiles. I tested HotSpot and ExactVM on
// Sparc Solaric, HotSpot, IBM's JVM and Microsoft's JVM on Windows
// NT.

public class ReadAfterWrite {

   static volatile int a;
   static final int n = 1000000;
   static final int[] AA = new int[n];
   static final int[] BB = new int[n];
   // Force a and b to be on different cache lines
   // don't know if this matters
   static int tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8;
   static volatile int b;

   static class WriteA extends Thread {
        public void run() {
          yield();
          final int [] mem = BB;
          for(int i = 0; i < n; i++) {
                a = i;
                mem[i] = b;
                }
        }
        }
   static class WriteB extends Thread {
        public void run() {
          final int [] mem = AA;
          for(int i = 0; i < n; i++) {
                b = i;
                mem[i] = a;
                }
        }
        }

   public static void main(String args[]) throws Exception {
        test();
        for(int i = 0; i < n; i++) {
                AA[i] = 0;
                BB[i] = 0;
                }
        a = 0;
        b = 0;
        System.out.println();
        test();
        }

   public static void dump(String name, String name2, int [] mem, int i) {
        int j = i;
        int count = 0;
        while (j > 1 && count < 4) {
                if (mem[j-1] != mem[j]) count++;
                j--;
                }
        count = 0;
        while (j < n && count < 8) {
            if (j == i) System.out.print("* ");
            j++;
            if (mem[j] != mem[j-1]) {
                 System.out.println("After writing " +
                                        (j-1)
                                        + " to " + name
                                        + ", read "
                                        + mem[j-1]
                                        + " from " + name2);
                 count++;
                 }
           }
        }

   public static void test() throws Exception {
        Thread t1 = new WriteA();
        Thread t2 = new WriteB();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        for(int i = 100; i < n; i++) {
                int j = AA[i]+1;
                if ( 100 < j && j < n && BB[j] < i) {
                        System.out.println("Thread 1");
                        dump("a","b", BB,j);
                        System.out.println("Thread 2");
                        dump("b","a",AA,i);
                        return;
                        }
        }
        }
        }
-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:23 EDT