| /* |
| * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * @test id=z |
| * @bug 8059022 |
| * @modules java.base/jdk.internal.misc:+open |
| * @summary Validate barriers after Unsafe getObject, CAS and swap (GetAndSet) |
| * @requires vm.gc.Z & !vm.graal.enabled |
| * @run main/othervm -Xmx256m |
| * -XX:+UnlockExperimentalVMOptions |
| * -XX:+UseZGC |
| * -XX:+UnlockDiagnosticVMOptions |
| * -XX:+ZUnmapBadViews -XX:ZCollectionInterval=1 |
| * -XX:-CreateCoredumpOnCrash |
| * -XX:CompileCommand=dontinline,*::mergeImpl* compiler.gcbarriers.UnsafeIntrinsicsTest |
| */ |
| |
| /* |
| * @test id=shenandoah |
| * @bug 8059022 |
| * @modules java.base/jdk.internal.misc:+open |
| * @summary Validate barriers after Unsafe getObject, CAS and swap (GetAndSet) |
| * @requires vm.gc.Shenandoah & !vm.graal.enabled |
| * @run main/othervm -Xmx256m |
| * -XX:+UseShenandoahGC |
| * -XX:+UnlockDiagnosticVMOptions |
| * -XX:-CreateCoredumpOnCrash |
| * -XX:+ShenandoahVerify |
| * -XX:+IgnoreUnrecognizedVMOptions -XX:+ShenandoahVerifyOptoBarriers |
| * -XX:CompileCommand=dontinline,*::mergeImpl* compiler.gcbarriers.UnsafeIntrinsicsTest |
| */ |
| |
| package compiler.gcbarriers; |
| |
| import java.lang.reflect.Field; |
| import java.util.ArrayList; |
| import java.util.Random; |
| import sun.misc.Unsafe; |
| |
| public class UnsafeIntrinsicsTest { |
| |
| /* |
| * This test triggers the loadbarriers by allocating a lot, keeping the objects alive and then |
| * letting them die in a way that maximizes fragmentation. |
| * |
| * All subtests (OperationType's) could run in parallel. |
| */ |
| |
| static int node_count = 133700; |
| static int thread_count = 4; |
| static int time = Integer.getInteger("time", 4); // seconds per subtest |
| |
| static Runner r = new Runner(null, 1, 1, Runner.OperationType.CAS); |
| |
| static Node first_node; |
| int epoch = 0; |
| |
| public static void main(String[] args) { |
| UnsafeIntrinsicsTest t = new UnsafeIntrinsicsTest(); |
| |
| t.testWithLocalData(Runner.OperationType.CAS); |
| t.testWithLocalData(Runner.OperationType.Weak_CAS); |
| t.testWithLocalData(Runner.OperationType.CMPX); |
| |
| t.testWithSharedData(Runner.OperationType.Swap); |
| t.testWithSharedData(Runner.OperationType.Load); |
| } |
| |
| public UnsafeIntrinsicsTest() { |
| |
| } |
| |
| public void testWithLocalData(Runner.OperationType optype) { |
| System.out.println("Testing " + optype.name() + " with " + thread_count +" thread and " + node_count + " nodes"); |
| |
| // start mutator threads |
| ArrayList<Thread> thread_list = new ArrayList<Thread>(); |
| Random r = new Random(System.nanoTime()); |
| for (int i = 0; i < thread_count; i++) { |
| |
| setup(); // each thread has its own circle of nodes |
| Thread t = new Thread(new Runner(first_node, time, r.nextLong(), optype)); |
| t.start(); |
| thread_list.add(t); |
| } |
| |
| waitForCompletion(thread_list); |
| countNodes(); |
| } |
| |
| public void testWithSharedData(Runner.OperationType optype) { |
| System.out.println("Testing " + optype.name() + " with " + thread_count +" thread and " + node_count + " nodes"); |
| |
| setup(); // All nodes are shared between threads |
| ArrayList<Thread> thread_list = new ArrayList<Thread>(); |
| Random r = new Random(System.nanoTime()); |
| for (int i = 0; i < thread_count; i++) { |
| Thread t = new Thread(new Runner(first_node, time, r.nextLong(), optype)); |
| t.start(); |
| thread_list.add(t); |
| } |
| |
| waitForCompletion(thread_list); |
| countNodes(); |
| } |
| |
| public void waitForCompletion(ArrayList<Thread> thread_list) { |
| // do some waiting |
| try { |
| Thread.sleep(time*1000); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| |
| // wait for all thread to terminate |
| for (int i = 0; i < thread_count; i++) { |
| try { |
| thread_list.get(i).join(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| void countNodes() { |
| epoch++; |
| int count = 0; |
| Node node = first_node; |
| while (node.number() < epoch) { |
| node.setNumber(epoch); |
| count++; |
| node = node.next(); |
| } |
| System.out.println("Program end, found " + count + " nodes"); |
| } |
| |
| // Create a circular linked list |
| public void setup() { |
| first_node = new Node(); |
| Node last_node = first_node; |
| for (int i = 0; i < node_count; i++) { |
| last_node = new Node(last_node); |
| } |
| first_node.setNext(last_node); |
| } |
| } |
| |
| class Runner implements Runnable { |
| |
| OperationType type; |
| Node current; |
| Random r; |
| long time; |
| long seed; |
| |
| long milage = 0; |
| long created = 0; |
| long skipped = 0; |
| int iterations = 0; |
| |
| static final jdk.internal.misc.Unsafe UNSAFE; |
| static final long offset; |
| |
| public enum OperationType { |
| Load("Load"), |
| Swap("Swap"), |
| CAS("CAS"), |
| Weak_CAS("Weak-CAS"), |
| CMPX("CMPX"); |
| |
| private String name; |
| private OperationType(String name) { this.name = name; } |
| } |
| |
| static { |
| try { |
| Field f = jdk.internal.misc.Unsafe.class.getDeclaredField("theUnsafe"); |
| f.setAccessible(true); |
| UNSAFE = (jdk.internal.misc.Unsafe) f.get(null); |
| offset = UNSAFE.objectFieldOffset(Node.class.getDeclaredField("next")); |
| } catch (Exception e) { |
| throw new RuntimeException("Unable to get Unsafe instance.", e); |
| } |
| } |
| |
| public Runner(Node start, int testtime, long seed, OperationType type) { |
| current = start; |
| time = testtime*1000000000L; |
| r = new Random(seed); |
| this.type = type; |
| } |
| |
| @Override |
| public void run() { |
| long starttime = System.nanoTime(); |
| while((System.nanoTime() - starttime) < time) { |
| iterations++; |
| // Run a bit |
| int run_length = r.nextInt() & 0xfff; |
| for (int i = 0; i < run_length; i++) { |
| current = current.next(); |
| milage++; |
| } |
| // find a start node |
| Node startNode = current; |
| Node expectedNext = startNode.next; |
| |
| // Run a bit more |
| int skip_length = (r.nextInt() & 0xff) + 1; |
| for (int i = 0; i < skip_length; i++) { |
| current = current.next(); |
| skipped++; |
| } |
| |
| // create a branch |
| int branch_length = (r.nextInt() & 0xff) + 1; |
| created += branch_length; |
| Node head = makeBranch(current, branch_length); |
| |
| // complete circle, but continue to run on old path |
| boolean test_fail = ((iterations & 0x1) == 0); |
| Node current = merge(startNode, expectedNext, head, test_fail); |
| } |
| System.out.println("Milage: " + milage + " Skipped: " + skipped + " Created: " + created + " iterations: " + iterations); |
| } |
| |
| /* |
| * The reason for the duplicated code that is wrapping the unsafe operations is that we want |
| * to test the operations individually. They must not interfere with each other - checking a field |
| * will heal that reference and no operation after can trigger the barrier. |
| * |
| * All mergeImpl*-method are prevented from being inlined. |
| */ |
| |
| private Node merge(Node startNode, Node expectedNext, Node head, boolean test_fail) { |
| switch (type) { |
| case Load: |
| return mergeImplLoad(startNode, expectedNext, head); |
| case Swap: |
| return mergeImplSwap(startNode, expectedNext, head); |
| case CAS: |
| if (test_fail) { |
| return mergeImplCASFail(startNode, expectedNext, head); |
| } else { |
| return mergeImplCAS(startNode, expectedNext, head); |
| } |
| case Weak_CAS: |
| if (test_fail) { |
| return mergeImplWeakCASFail(startNode, expectedNext, head); |
| } else { |
| return mergeImplWeakCAS(startNode, expectedNext, head); |
| } |
| case CMPX: |
| if (test_fail) { |
| return mergeImplCMPXFail(startNode, expectedNext, head); |
| } else { |
| return mergeImplCMPX(startNode, expectedNext, head); |
| } |
| default: |
| throw new Error("Unimplemented"); |
| } |
| } |
| |
| private Node mergeImplLoad(Node startNode, Node expectedNext, Node head) { |
| // Atomic load version |
| Node temp = (Node) UNSAFE.getObject(startNode, offset); |
| startNode.setNext(head); |
| return temp; |
| } |
| |
| private Node mergeImplSwap(Node startNode, Node expectedNext, Node head) { |
| // Swap version |
| return (Node) UNSAFE.getAndSetObject(startNode, offset, head); |
| } |
| |
| private Node mergeImplCAS(Node startNode, Node expectedNext, Node head) { |
| // CAS - should always be true within a single thread - no other thread can have overwritten |
| if (!UNSAFE.compareAndSetObject(startNode, offset, expectedNext, head)) { |
| throw new Error("CAS should always succeed on thread local objects, check you barrier implementation"); |
| } |
| return expectedNext; // continue on old circle |
| } |
| |
| private Node mergeImplCASFail(Node startNode, Node expectedNext, Node head) { |
| // Force a fail |
| if (UNSAFE.compareAndSetObject(startNode, offset, "fail", head)) { |
| throw new Error("This CAS should always fail, check you barrier implementation"); |
| } |
| if (startNode.next() != expectedNext) { |
| throw new Error("Shouldn't have changed"); |
| } |
| return current; |
| } |
| |
| private Node mergeImplWeakCAS(Node startNode, Node expectedNext, Node head) { |
| // Weak CAS - should always be true within a single thread - no other thread can have overwritten |
| if (!UNSAFE.weakCompareAndSetObject(startNode, offset, expectedNext, head)) { |
| throw new Error("Weak CAS should always succeed on thread local objects, check you barrier implementation"); |
| } |
| return expectedNext; // continue on old circle |
| } |
| |
| private Node mergeImplWeakCASFail(Node startNode, Node expectedNext, Node head) { |
| // Force a fail |
| if (UNSAFE.weakCompareAndSetObject(startNode, offset, "fail", head)) { |
| throw new Error("This weak CAS should always fail, check you barrier implementation"); |
| } |
| if (startNode.next() != expectedNext) { |
| throw new Error("Shouldn't have changed"); |
| } |
| return current; |
| } |
| |
| private Node mergeImplCMPX(Node startNode, Node expectedNext, Node head) { |
| // CmpX - should always be true within a single thread - no other thread can have overwritten |
| Object res = UNSAFE.compareAndExchangeObject(startNode, offset, expectedNext, head); |
| if (!res.equals(expectedNext)) { |
| throw new Error("Fail CmpX should always succeed on thread local objects, check you barrier implementation"); |
| } |
| return expectedNext; // continue on old circle |
| } |
| |
| private Node mergeImplCMPXFail(Node startNode, Node expectedNext, Node head) { |
| Object res = UNSAFE.compareAndExchangeObject(startNode, offset, head, head); |
| if (startNode.next() != expectedNext) { |
| throw new Error("Shouldn't have changed"); |
| } |
| if (head == expectedNext) { |
| throw new Error("Test malfunction"); |
| } |
| if (!res.equals(expectedNext)) { |
| throw new Error("This CmpX should have returned 'expectedNext' when it failed"); |
| } |
| if (res.equals(head)) { |
| throw new Error("This CmpX shouldn't have returned head when it failed. count: "+ iterations); |
| } |
| |
| return current; |
| } |
| |
| // Create a new branch that will replace a part of the circle |
| public Node makeBranch(Node end_node, int count) { |
| Node head = end_node; |
| for (int i = 0; i < count; i++) { |
| head = new Node(head); |
| } |
| return head; |
| } |
| } |
| |
| class Node { |
| Node next; |
| int number = 0; |
| |
| public int number() { |
| return number; |
| } |
| |
| public void setNumber(int v) { |
| number = v; |
| } |
| |
| public Node() { |
| } |
| |
| public Node(Node link) { |
| next = link; |
| } |
| |
| public void setNext(Node next) { |
| this.next = next; |
| } |
| public Node next() { |
| return next; |
| } |
| } |