/*
|
* 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) builder (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) builder (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) builder (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) builder (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) builder (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() builder (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() builder (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() builder (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);
|
}
|
}
|
}
|