/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicBoolean;

import sun.misc.Unsafe;

/**
 * Checker test on the 1.8 unsafe operations. Note, this is by no means an
 * exhaustive unit test for these CAS (compare-and-swap) and fence operations.
 * Instead, this test ensures the methods are recognized as intrinsic and behave
 * as expected.
 */
public class Main {

  private static final Unsafe unsafe = getUnsafe();

  private static Thread[] sThreads = new Thread[10];

  //
  // Fields accessed by setters and adders, and by memory fence tests.
  //

  public int i = 0;
  public long l = 0;
  public Object o = null;

  public int x_value;
  public int y_value;
  public volatile boolean running;

  //
  // Setters.
  //

  /// CHECK-START: int Main.set32(java.lang.Object, long, int) intrinsics_recognition (after)
  /// CHECK-DAG: <<Result:i\d+>> InvokeVirtual intrinsic:UnsafeGetAndSetInt
  /// CHECK-DAG:                 Return [<<Result>>]
  private static int set32(Object o, long offset, int newValue) {
    return unsafe.getAndSetInt(o, offset, newValue);
  }

  /// CHECK-START: long Main.set64(java.lang.Object, long, long) intrinsics_recognition (after)
  /// CHECK-DAG: <<Result:j\d+>> InvokeVirtual intrinsic:UnsafeGetAndSetLong
  /// CHECK-DAG:                 Return [<<Result>>]
  private static long set64(Object o, long offset, long newValue) {
    return unsafe.getAndSetLong(o, offset, newValue);
  }

  /// CHECK-START: java.lang.Object Main.setObj(java.lang.Object, long, java.lang.Object) intrinsics_recognition (after)
  /// CHECK-DAG: <<Result:l\d+>> InvokeVirtual intrinsic:UnsafeGetAndSetObject
  /// CHECK-DAG:                 Return [<<Result>>]
  private static Object setObj(Object o, long offset, Object newValue) {
    return unsafe.getAndSetObject(o, offset, newValue);
  }

  //
  // Adders.
  //

  /// CHECK-START: int Main.add32(java.lang.Object, long, int) intrinsics_recognition (after)
  /// CHECK-DAG: <<Result:i\d+>> InvokeVirtual intrinsic:UnsafeGetAndAddInt
  /// CHECK-DAG:                 Return [<<Result>>]
  private static int add32(Object o, long offset, int delta) {
    return unsafe.getAndAddInt(o, offset, delta);
  }

  /// CHECK-START: long Main.add64(java.lang.Object, long, long) intrinsics_recognition (after)
  /// CHECK-DAG: <<Result:j\d+>> InvokeVirtual intrinsic:UnsafeGetAndAddLong
  /// CHECK-DAG:                 Return [<<Result>>]
  private static long add64(Object o, long offset, long delta) {
    return unsafe.getAndAddLong(o, offset, delta);
  }

  //
  // Fences (native).
  //

  /// CHECK-START: void Main.load() intrinsics_recognition (after)
  /// CHECK-DAG: InvokeVirtual intrinsic:UnsafeLoadFence
  //
  /// CHECK-START: void Main.load() instruction_simplifier (after)
  /// CHECK-NOT: InvokeVirtual intrinsic:UnsafeLoadFence
  //
  /// CHECK-START: void Main.load() instruction_simplifier (after)
  /// CHECK-DAG: MemoryBarrier kind:LoadAny
  private static void load() {
    unsafe.loadFence();
  }

  /// CHECK-START: void Main.store() intrinsics_recognition (after)
  /// CHECK-DAG: InvokeVirtual intrinsic:UnsafeStoreFence
  //
  /// CHECK-START: void Main.store() instruction_simplifier (after)
  /// CHECK-NOT: InvokeVirtual intrinsic:UnsafeStoreFence
  //
  /// CHECK-START: void Main.store() instruction_simplifier (after)
  /// CHECK-DAG: MemoryBarrier kind:AnyStore
  private static void store() {
    unsafe.storeFence();
  }

  /// CHECK-START: void Main.full() intrinsics_recognition (after)
  /// CHECK-DAG: InvokeVirtual intrinsic:UnsafeFullFence
  //
  /// CHECK-START: void Main.full() instruction_simplifier (after)
  /// CHECK-NOT: InvokeVirtual intrinsic:UnsafeFullFence
  //
  /// CHECK-START: void Main.full() instruction_simplifier (after)
  /// CHECK-DAG: MemoryBarrier kind:AnyAny
  private static void full() {
    unsafe.fullFence();
  }

  //
  // Thread fork/join.
  //

  private static void fork(Runnable r) {
    for (int i = 0; i < 10; i++) {
      sThreads[i] = new Thread(r);
    }
    // Start the threads only after the full array has been written with new threads,
    // because one test relies on the contents of this array to be consistent.
    for (int i = 0; i < 10; i++) {
      sThreads[i].start();
    }
  }

  private static void join() {
    try {
      for (int i = 0; i < 10; i++) {
        sThreads[i].join();
      }
    } catch (InterruptedException e) {
      throw new Error("Failed join: " + e);
    }
  }

  //
  // Driver.
  //

  public static void main(String[] args) {
    System.out.println("starting");

    final Main m = new Main();

    // Get the offsets.

    final long intOffset, longOffset, objOffset;
    try {
      Field intField = Main.class.getDeclaredField("i");
      Field longField = Main.class.getDeclaredField("l");
      Field objField = Main.class.getDeclaredField("o");

      intOffset = unsafe.objectFieldOffset(intField);
      longOffset = unsafe.objectFieldOffset(longField);
      objOffset = unsafe.objectFieldOffset(objField);

    } catch (NoSuchFieldException e) {
      throw new Error("No offset: " + e);
    }

    // Some sanity on setters and adders within same thread.

    set32(m, intOffset, 3);
    expectEqual32(3, m.i);

    set64(m, longOffset, 7L);
    expectEqual64(7L, m.l);

    setObj(m, objOffset, m);
    expectEqualObj(m, m.o);

    add32(m, intOffset, 11);
    expectEqual32(14, m.i);

    add64(m, longOffset, 13L);
    expectEqual64(20L, m.l);

    // Some sanity on setters within different threads.

    fork(new Runnable() {
      public void run() {
        for (int i = 0; i < 10; i++)
          set32(m, intOffset, i);
      }
    });
    join();
    expectEqual32(9, m.i);  // one thread's last value wins

    fork(new Runnable() {
      public void run() {
        for (int i = 0; i < 10; i++)
          set64(m, longOffset, (long) (100 + i));
      }
    });
    join();
    expectEqual64(109L, m.l);  // one thread's last value wins

    fork(new Runnable() {
      public void run() {
        for (int i = 0; i < 10; i++)
          setObj(m, objOffset, sThreads[i]);
      }
    });
    join();
    expectEqualObj(sThreads[9], m.o);  // one thread's last value wins

    // Some sanity on adders within different threads.

    fork(new Runnable() {
      public void run() {
        for (int i = 0; i < 10; i++)
          add32(m, intOffset, i + 1);
      }
    });
    join();
    expectEqual32(559, m.i);  // all values accounted for

    fork(new Runnable() {
      public void run() {
        for (int i = 0; i < 10; i++)
          add64(m, longOffset, (long) (i + 1));
      }
    });
    join();
    expectEqual64(659L, m.l);  // all values accounted for

    // Some sanity on fences within same thread. Note that memory fences within one
    // thread make little sense, but the sanity check ensures nothing bad happens.

    m.i = -1;
    m.l = -2L;
    m.o = null;

    load();
    store();
    full();

    expectEqual32(-1, m.i);
    expectEqual64(-2L, m.l);
    expectEqualObj(null, m.o);

    // Some sanity on full fence within different threads. We write the non-volatile m.l after
    // the fork(), which means there is no happens-before relation in the Java memory model
    // with respect to the read in the threads. This relation is enforced by the memory fences
    // and the weak-set() -> get() guard. Note that the guard semantics used here are actually
    // too strong and already enforce total memory visibility, but this test illustrates what
    // should still happen if Java had a true relaxed memory guard.

    final AtomicBoolean guard1 = new AtomicBoolean();
    m.l = 0L;

    fork(new Runnable() {
      public void run() {
        while (!guard1.get());  // busy-waiting
        full();
        expectEqual64(-123456789L, m.l);
      }
    });

    m.l = -123456789L;
    full();
    while (!guard1.weakCompareAndSet(false, true));  // relaxed memory order
    join();

    // Some sanity on release/acquire fences within different threads. We write the non-volatile
    // m.l after the fork(), which means there is no happens-before relation in the Java memory
    // model with respect to the read in the threads. This relation is enforced by the memory fences
    // and the weak-set() -> get() guard. Note that the guard semantics used here are actually
    // too strong and already enforce total memory visibility, but this test illustrates what
    // should still happen if Java had a true relaxed memory guard.

    final AtomicBoolean guard2 = new AtomicBoolean();
    m.l = 0L;

    fork(new Runnable() {
      public void run() {
        while (!guard2.get());  // busy-waiting
        load();
        expectEqual64(-987654321L, m.l);
      }
    });

    m.l = -987654321L;
    store();
    while (!guard2.weakCompareAndSet(false, true));  // relaxed memory order
    join();

    // Some sanity on release/acquire fences within different threads using a test suggested by
    // Hans Boehm. Even this test remains with the realm of sanity only, since having the threads
    // read the same value consistently would be a valid outcome.

    m.x_value = -1;
    m.y_value = -1;
    m.running = true;

    fork(new Runnable() {
      public void run() {
        while (m.running) {
          for (int few_times = 0; few_times < 1000; few_times++) {
            // Read y first, then load fence, then read x.
            // They should appear in order, if seen at all.
            int local_y = m.y_value;
            load();
            int local_x = m.x_value;
            expectLessThanOrEqual32(local_y, local_x);
          }
        }
      }
    });

    for (int many_times = 0; many_times < 100000; many_times++) {
      m.x_value = many_times;
      store();
      m.y_value = many_times;
    }
    m.running = false;
    join();

    // All done!

    System.out.println("passed");
  }

  // Use reflection to implement "Unsafe.getUnsafe()";
  private static Unsafe getUnsafe() {
    try {
      Class<?> unsafeClass = Unsafe.class;
      Field f = unsafeClass.getDeclaredField("theUnsafe");
      f.setAccessible(true);
      return (Unsafe) f.get(null);
    } catch (Exception e) {
      throw new Error("Cannot get Unsafe instance");
    }
  }

  private static void expectEqual32(int expected, int result) {
    if (expected != result) {
      throw new Error("Expected: " + expected + ", found: " + result);
    }
  }

  private static void expectLessThanOrEqual32(int val1, int val2) {
    if (val1 > val2) {
      throw new Error("Expected: " + val1 + " <= " + val2);
    }
  }

  private static void expectEqual64(long expected, long result) {
    if (expected != result) {
      throw new Error("Expected: " + expected + ", found: " + result);
    }
  }

  private static void expectEqualObj(Object expected, Object result) {
    if (expected != result) {
      throw new Error("Expected: " + expected + ", found: " + result);
    }
  }
}