/* * Copyright (c) 2023, 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. */ package compiler.gcbarriers; import compiler.lib.ir_framework.*; import java.lang.invoke.VarHandle; import java.lang.invoke.MethodHandles; import java.util.concurrent.ThreadLocalRandom; /** * @test * @summary Test that the ZGC barrier elision optimization does not elide * necessary barriers. The tests use volatile memory accesses and * blackholes to prevent C2 from simply optimizing them away. * @library /test/lib / * @requires vm.gc.ZGenerational * @run driver compiler.gcbarriers.TestZGCBarrierElision test-correctness */ /** * @test * @summary Test that the ZGC barrier elision optimization elides unnecessary * barriers following simple allocation and domination rules. * @library /test/lib / * @requires vm.gc.ZGenerational & (vm.simpleArch == "x64" | vm.simpleArch == "aarch64") * @run driver compiler.gcbarriers.TestZGCBarrierElision test-effectiveness */ class Inner {} class Outer { volatile Inner field1; volatile Inner field2; Outer() {} } class Common { static Inner inner = new Inner(); static Outer outer = new Outer(); static Outer outer2 = new Outer(); static Outer[] outerArray = new Outer[42]; static final VarHandle field1VarHandle; static final VarHandle field2VarHandle; static { MethodHandles.Lookup l = MethodHandles.lookup(); try { field1VarHandle = l.findVarHandle(Outer.class, "field1", Inner.class); field2VarHandle = l.findVarHandle(Outer.class, "field2", Inner.class); } catch (Exception e) { throw new Error(e); } } static final VarHandle outerArrayVarHandle = MethodHandles.arrayElementVarHandle(Outer[].class); static final String REMAINING = "strong"; static final String ELIDED = "elided"; static void blackhole(Object o) {} static void nonInlinedMethod() {} } public class TestZGCBarrierElision { public static void main(String[] args) { if (args.length != 1) { throw new IllegalArgumentException(); } Class testClass; if (args[0].equals("test-correctness")) { testClass = TestZGCCorrectBarrierElision.class; } else if (args[0].equals("test-effectiveness")) { testClass = TestZGCEffectiveBarrierElision.class; } else { throw new IllegalArgumentException(); } String commonName = Common.class.getName(); TestFramework test = new TestFramework(testClass); test.addFlags("-XX:+UseZGC", "-XX:+ZGenerational", "-XX:+UnlockExperimentalVMOptions", "-XX:CompileCommand=blackhole," + commonName + "::blackhole", "-XX:CompileCommand=dontinline," + commonName + "::nonInlinedMethod", "-XX:LoopMaxUnroll=0"); test.start(); } } class TestZGCCorrectBarrierElision { @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testLoadThenStore(Outer o, Inner i) { Common.blackhole(o.field1); o.field1 = i; } @Test @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) static void testLoadThenLoadAnotherField(Outer o) { Common.blackhole(o.field1); Common.blackhole(o.field2); } @Test @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) static void testLoadThenLoadFromAnotherObject(Outer o1, Outer o2) { Common.blackhole(o1.field1); Common.blackhole(o2.field1); } @Run(test = {"testLoadThenStore", "testLoadThenLoadAnotherField", "testLoadThenLoadFromAnotherObject"}) void runBasicTests() { testLoadThenStore(Common.outer, Common.inner); testLoadThenLoadAnotherField(Common.outer); testLoadThenLoadFromAnotherObject(Common.outer, Common.outer2); } @Test @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testArrayLoadThenStore(Outer[] a, Outer o) { Common.blackhole(Common.outerArrayVarHandle.getVolatile(a, 0)); Common.outerArrayVarHandle.setVolatile(a, 0, o); } @Test @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) static void testArrayLoadThenLoadAnotherElement(Outer[] a) { Common.blackhole(Common.outerArrayVarHandle.getVolatile(a, 0)); Common.blackhole(Common.outerArrayVarHandle.getVolatile(a, 10)); } @Run(test = {"testArrayLoadThenStore", "testArrayLoadThenLoadAnotherElement"}) void runArrayTests() { testArrayLoadThenStore(Common.outerArray, Common.outer); testArrayLoadThenLoadAnotherElement(Common.outerArray); } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) static void testConditionalStoreThenStore(Outer o, Inner i, int value) { if (value % 2 == 0) { o.field1 = i; } o.field1 = i; } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) static void testStoreThenCallThenStore(Outer o, Inner i) { o.field1 = i; Common.nonInlinedMethod(); o.field1 = i; } @Run(test = {"testConditionalStoreThenStore", "testStoreThenCallThenStore"}) void runControlFlowTests() { testConditionalStoreThenStore(Common.outer, Common.inner, ThreadLocalRandom.current().nextInt(0, 100)); testStoreThenCallThenStore(Common.outer, Common.inner); } @Test @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateThenAtomic(Inner i) { Outer o = new Outer(); Common.blackhole(o); Common.field1VarHandle.getAndSet​(o, i); } @Test @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testLoadThenAtomic(Outer o, Inner i) { Common.blackhole(o.field1); Common.field1VarHandle.getAndSet​(o, i); } @Test @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) static void testAtomicThenAtomicAnotherField(Outer o, Inner i) { Common.field1VarHandle.getAndSet​(o, i); Common.field2VarHandle.getAndSet​(o, i); } @Test @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateArrayThenAtomicAtKnownIndex(Outer o) { Outer[] a = new Outer[42]; Common.blackhole(a); Common.outerArrayVarHandle.getAndSet(a, 2, o); } @Test @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateArrayThenAtomicAtUnknownIndex(Outer o, int index) { Outer[] a = new Outer[42]; Common.blackhole(a); Common.outerArrayVarHandle.getAndSet(a, index, o); } @Test @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) static void testArrayAtomicThenAtomicAtUnknownIndices(Outer[] a, Outer o, int index1, int index2) { Common.outerArrayVarHandle.getAndSet(a, index1, o); Common.outerArrayVarHandle.getAndSet(a, index2, o); } @Run(test = {"testAllocateThenAtomic", "testLoadThenAtomic", "testAtomicThenAtomicAnotherField", "testAllocateArrayThenAtomicAtKnownIndex", "testAllocateArrayThenAtomicAtUnknownIndex", "testArrayAtomicThenAtomicAtUnknownIndices"}) void runAtomicOperationTests() { testAllocateThenAtomic(Common.inner); testLoadThenAtomic(Common.outer, Common.inner); testAtomicThenAtomicAnotherField(Common.outer, Common.inner); testAllocateArrayThenAtomicAtKnownIndex(Common.outer); testAllocateArrayThenAtomicAtUnknownIndex(Common.outer, 10); testArrayAtomicThenAtomicAtUnknownIndices(Common.outerArray, Common.outer, 10, 20); } } class TestZGCEffectiveBarrierElision { @Test @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateThenLoad() { Outer o1 = new Outer(); Common.blackhole(o1); // This load is directly optimized away by C2. Common.blackhole(o1.field1); Common.blackhole(o1.field1); } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateThenStore(Inner i) { Outer o1 = new Outer(); Common.blackhole(o1); o1.field1 = i; } @Test @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testLoadThenLoad(Outer o) { Common.blackhole(o.field1); Common.blackhole(o.field1); } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testStoreThenStore(Outer o, Inner i) { o.field1 = i; o.field1 = i; } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testStoreThenLoad(Outer o, Inner i) { o.field1 = i; Common.blackhole(o.field1); } @Run(test = {"testAllocateThenLoad", "testAllocateThenStore", "testLoadThenLoad", "testStoreThenStore", "testStoreThenLoad"}) void runBasicTests() { testAllocateThenLoad(); testAllocateThenStore(Common.inner); testLoadThenLoad(Common.outer); testStoreThenStore(Common.outer, Common.inner); testStoreThenLoad(Common.outer, Common.inner); } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateArrayThenStoreAtKnownIndex(Outer o) { Outer[] a = new Outer[42]; Common.blackhole(a); Common.outerArrayVarHandle.setVolatile(a, 0, o); } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateArrayThenStoreAtUnknownIndex(Outer o, int index) { Outer[] a = new Outer[42]; Common.blackhole(a); Common.outerArrayVarHandle.setVolatile(a, index, o); } @Test @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testArrayLoadThenLoad(Outer[] a) { Common.blackhole(Common.outerArrayVarHandle.getVolatile(a, 0)); Common.blackhole(Common.outerArrayVarHandle.getVolatile(a, 0)); } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testArrayStoreThenStore(Outer[] a, Outer o) { Common.outerArrayVarHandle.setVolatile(a, 0, o); Common.outerArrayVarHandle.setVolatile(a, 0, o); } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testArrayStoreThenLoad(Outer[] a, Outer o) { Common.outerArrayVarHandle.setVolatile(a, 0, o); Common.blackhole(Common.outerArrayVarHandle.getVolatile(a, 0)); } @Run(test = {"testAllocateArrayThenStoreAtKnownIndex", "testAllocateArrayThenStoreAtUnknownIndex", "testArrayLoadThenLoad", "testArrayStoreThenStore", "testArrayStoreThenLoad"}) void runArrayTests() { testAllocateArrayThenStoreAtKnownIndex(Common.outer); testAllocateArrayThenStoreAtUnknownIndex(Common.outer, 10); testArrayLoadThenLoad(Common.outerArray); testArrayStoreThenStore(Common.outerArray, Common.outer); testArrayStoreThenLoad(Common.outerArray, Common.outer); } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testStoreThenConditionalStore(Outer o, Inner i, int value) { o.field1 = i; if (value % 2 == 0) { o.field1 = i; } } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testStoreThenStoreInLoop(Outer o, Inner i) { o.field1 = i; for (int j = 0; j < 100; j++) { o.field1 = i; } } @Run(test = {"testStoreThenConditionalStore", "testStoreThenStoreInLoop"}) void runControlFlowTests() { testStoreThenConditionalStore(Common.outer, Common.inner, ThreadLocalRandom.current().nextInt(0, 100)); testStoreThenStoreInLoop(Common.outer, Common.inner); } @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testStoreThenAtomic(Outer o, Inner i) { o.field1 = i; Common.field1VarHandle.getAndSet​(o, i); } @Test @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAtomicThenLoad(Outer o, Inner i) { Common.field1VarHandle.getAndSet​(o, i); Common.blackhole(o.field1); } @Test @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAtomicThenStore(Outer o, Inner i) { Common.field1VarHandle.getAndSet​(o, i); o.field1 = i; } @Test @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAtomicThenAtomic(Outer o, Inner i) { Common.field1VarHandle.getAndSet​(o, i); Common.field1VarHandle.getAndSet​(o, i); } @Test @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testArrayAtomicThenAtomic(Outer[] a, Outer o) { Common.outerArrayVarHandle.getAndSet(a, 0, o); Common.outerArrayVarHandle.getAndSet(a, 0, o); } @Run(test = {"testStoreThenAtomic", "testAtomicThenLoad", "testAtomicThenStore", "testAtomicThenAtomic", "testArrayAtomicThenAtomic"}) void runAtomicOperationTests() { testStoreThenAtomic(Common.outer, Common.inner); testAtomicThenLoad(Common.outer, Common.inner); testAtomicThenStore(Common.outer, Common.inner); testAtomicThenAtomic(Common.outer, Common.inner); testArrayAtomicThenAtomic(Common.outerArray, Common.outer); } }