/* * Copyright (c) 2024, 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.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.concurrent.ThreadLocalRandom; import jdk.test.lib.Asserts; /** * @test * @summary Test that G1 barriers are generated and optimized as expected. * @library /test/lib / * @requires vm.gc.G1 * @run driver compiler.gcbarriers.TestG1BarrierGeneration */ public class TestG1BarrierGeneration { static final String PRE_ONLY = "pre"; static final String POST_ONLY = "post"; static final String POST_ONLY_NOT_NULL = "post notnull"; static final String PRE_AND_POST = "pre post"; static final String PRE_AND_POST_NOT_NULL = "pre post notnull"; static class Outer { Object f; } static class OuterWithVolatileField { volatile Object f; } static class OuterWithFewFields implements Cloneable { Object f1; Object f2; public Object clone() throws CloneNotSupportedException { return super.clone(); } } static class OuterWithManyFields implements Cloneable { Object f1; Object f2; Object f3; Object f4; Object f5; Object f6; Object f7; Object f8; Object f9; Object f10; public Object clone() throws CloneNotSupportedException { return super.clone(); } } static final VarHandle fVarHandle; static { MethodHandles.Lookup l = MethodHandles.lookup(); try { fVarHandle = l.findVarHandle(Outer.class, "f", Object.class); } catch (Exception e) { throw new Error(e); } } public static void main(String[] args) { TestFramework framework = new TestFramework(); Scenario[] scenarios = new Scenario[2*2]; int scenarioIndex = 0; for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { scenarios[scenarioIndex] = new Scenario(scenarioIndex, "-XX:CompileCommand=inline,java.lang.ref.*::*", "-XX:" + (i == 0 ? "-" : "+") + "UseCompressedOops", "-XX:" + (j == 0 ? "-" : "+") + "ReduceInitialCardMarks"); scenarioIndex++; } } framework.addScenarios(scenarios); framework.start(); } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) public static void testStore(Outer o, Object o1) { o.f = o1; } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_STORE_N_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) public static void testStoreNull(Outer o) { o.f = null; } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_STORE_N_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) public static void testStoreObfuscatedNull(Outer o, Object o1) { Object o2 = o1; for (int i = 0; i < 4; i++) { if ((i % 2) == 0) { o2 = null; } } // o2 is null here, but this is only known to C2 after applying some // optimizations (loop unrolling, IGVN). o.f = o2; } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_AND_POST_NOT_NULL, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, PRE_AND_POST_NOT_NULL, "1"}, phase = CompilePhase.FINAL_CODE) public static void testStoreNotNull(Outer o, Object o1) { if (o1.hashCode() == 42) { return; } o.f = o1; } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_AND_POST, "2"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, PRE_AND_POST, "2"}, phase = CompilePhase.FINAL_CODE) public static void testStoreTwice(Outer o, Outer p, Object o1) { o.f = o1; p.f = o1; } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) public static void testStoreVolatile(OuterWithVolatileField o, Object o1) { o.f = o1; } @Test @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, POST_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "false"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, POST_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "true"}, failOn = {IRNode.G1_STORE_P}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "true"}, failOn = {IRNode.G1_STORE_N, IRNode.G1_ENCODE_P_AND_STORE_N}, phase = CompilePhase.FINAL_CODE) public static Outer testStoreOnNewObject(Object o1) { Outer o = new Outer(); o.f = o1; return o; } @Test @IR(failOn = {IRNode.STORE_P, IRNode.STORE_N}, phase = CompilePhase.BEFORE_MACRO_EXPANSION) public static Outer testStoreNullOnNewObject() { Outer o = new Outer(); o.f = null; return o; } @Test @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, POST_ONLY_NOT_NULL, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "false"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, POST_ONLY_NOT_NULL, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "true"}, failOn = {IRNode.G1_STORE_P}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "true"}, failOn = {IRNode.G1_STORE_N, IRNode.G1_ENCODE_P_AND_STORE_N}, phase = CompilePhase.FINAL_CODE) public static Outer testStoreNotNullOnNewObject(Object o1) { if (o1.hashCode() == 42) { return null; } Outer o = new Outer(); o.f = o1; return o; } @Test @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, POST_ONLY, "2"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "false"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, POST_ONLY, "2"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "true"}, failOn = {IRNode.G1_STORE_P}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "true"}, failOn = {IRNode.G1_STORE_N, IRNode.G1_ENCODE_P_AND_STORE_N}, phase = CompilePhase.FINAL_CODE) public static Outer testStoreOnNewObjectInTwoPaths(Object o1, boolean c) { Outer o; if (c) { o = new Outer(); o.f = o1; } else { o = new Outer(); o.f = o1; } return o; } @Run(test = {"testStore", "testStoreNull", "testStoreObfuscatedNull", "testStoreNotNull", "testStoreTwice", "testStoreVolatile", "testStoreOnNewObject", "testStoreNullOnNewObject", "testStoreNotNullOnNewObject", "testStoreOnNewObjectInTwoPaths"}) public void runStoreTests() { { Outer o = new Outer(); Object o1 = new Object(); testStore(o, o1); Asserts.assertEquals(o1, o.f); } { Outer o = new Outer(); testStoreNull(o); Asserts.assertNull(o.f); } { Outer o = new Outer(); Object o1 = new Object(); testStoreObfuscatedNull(o, o1); Asserts.assertNull(o.f); } { Outer o = new Outer(); Object o1 = new Object(); testStoreNotNull(o, o1); Asserts.assertEquals(o1, o.f); } { Outer o = new Outer(); Outer p = new Outer(); Object o1 = new Object(); testStoreTwice(o, p, o1); Asserts.assertEquals(o1, o.f); Asserts.assertEquals(o1, p.f); } { OuterWithVolatileField o = new OuterWithVolatileField(); Object o1 = new Object(); testStoreVolatile(o, o1); Asserts.assertEquals(o1, o.f); } { Object o1 = new Object(); Outer o = testStoreOnNewObject(o1); Asserts.assertEquals(o1, o.f); } { Outer o = testStoreNullOnNewObject(); Asserts.assertNull(o.f); } { Object o1 = new Object(); Outer o = testStoreNotNullOnNewObject(o1); Asserts.assertEquals(o1, o.f); } { Object o1 = new Object(); Outer o = testStoreOnNewObjectInTwoPaths(o1, ThreadLocalRandom.current().nextBoolean()); Asserts.assertEquals(o1, o.f); } } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) public static void testArrayStore(Object[] a, int index, Object o1) { a[index] = o1; } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_STORE_N_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) public static void testArrayStoreNull(Object[] a, int index) { a[index] = null; } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_AND_POST_NOT_NULL, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, PRE_AND_POST_NOT_NULL, "1"}, phase = CompilePhase.FINAL_CODE) public static void testArrayStoreNotNull(Object[] a, int index, Object o1) { if (o1.hashCode() == 42) { return; } a[index] = o1; } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, PRE_AND_POST, "2"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_ENCODE_P_AND_STORE_N_WITH_BARRIER_FLAG, PRE_AND_POST, "2"}, phase = CompilePhase.FINAL_CODE) public static void testArrayStoreTwice(Object[] a, Object[] b, int index, Object o1) { a[index] = o1; b[index] = o1; } @Test @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "true"}, failOn = {IRNode.G1_STORE_P}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "true"}, failOn = {IRNode.G1_STORE_N, IRNode.G1_ENCODE_P_AND_STORE_N}, phase = CompilePhase.FINAL_CODE) public static Object[] testStoreOnNewArray(Object o1) { Object[] a = new Object[10]; // The index needs to be concrete for C2 to detect that it is safe to // remove the pre-barrier. a[4] = o1; return a; } @Run(test = {"testArrayStore", "testArrayStoreNull", "testArrayStoreNotNull", "testArrayStoreTwice", "testStoreOnNewArray"}) public void runArrayStoreTests() { { Object[] a = new Object[10]; Object o1 = new Object(); testArrayStore(a, 4, o1); Asserts.assertEquals(o1, a[4]); } { Object[] a = new Object[10]; testArrayStoreNull(a, 4); Asserts.assertNull(a[4]); } { Object[] a = new Object[10]; Object o1 = new Object(); testArrayStoreNotNull(a, 4, o1); Asserts.assertEquals(o1, a[4]); } { Object[] a = new Object[10]; Object[] b = new Object[10]; Object o1 = new Object(); testArrayStoreTwice(a, b, 4, o1); Asserts.assertEquals(o1, a[4]); Asserts.assertEquals(o1, b[4]); } { Object o1 = new Object(); Object[] a = testStoreOnNewArray(o1); Asserts.assertEquals(o1, a[4]); } } @Test public static Object[] testCloneArrayOfObjects(Object[] a) { Object[] a1 = null; try { a1 = a.clone(); } catch (Exception e) {} return a1; } @Test @IR(applyIf = {"ReduceInitialCardMarks", "true"}, failOn = {IRNode.G1_STORE_P, IRNode.G1_STORE_N, IRNode.G1_ENCODE_P_AND_STORE_N}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"ReduceInitialCardMarks", "false", "UseCompressedOops", "false"}, counts = {IRNode.G1_STORE_P_WITH_BARRIER_FLAG, POST_ONLY, "2"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"ReduceInitialCardMarks", "false", "UseCompressedOops", "true"}, counts = {IRNode.G1_STORE_N_WITH_BARRIER_FLAG, POST_ONLY, "2"}, phase = CompilePhase.FINAL_CODE) public static OuterWithFewFields testCloneObjectWithFewFields(OuterWithFewFields o) { Object o1 = null; try { o1 = o.clone(); } catch (Exception e) {} return (OuterWithFewFields)o1; } @Test @IR(applyIf = {"ReduceInitialCardMarks", "true"}, counts = {IRNode.CALL_OF, "jlong_disjoint_arraycopy", "1"}) @IR(applyIf = {"ReduceInitialCardMarks", "false"}, counts = {IRNode.CALL_OF, "G1BarrierSetRuntime::clone", "1"}) public static OuterWithManyFields testCloneObjectWithManyFields(OuterWithManyFields o) { Object o1 = null; try { o1 = o.clone(); } catch (Exception e) {} return (OuterWithManyFields)o1; } @Run(test = {"testCloneArrayOfObjects", "testCloneObjectWithFewFields", "testCloneObjectWithManyFields"}) public void runCloneTests() { { Object o1 = new Object(); Object[] a = new Object[4]; for (int i = 0; i < 4; i++) { a[i] = o1; } Object[] a1 = testCloneArrayOfObjects(a); for (int i = 0; i < 4; i++) { Asserts.assertEquals(o1, a1[i]); } } { Object a = new Object(); Object b = new Object(); OuterWithFewFields o = new OuterWithFewFields(); o.f1 = a; o.f2 = b; OuterWithFewFields o1 = testCloneObjectWithFewFields(o); Asserts.assertEquals(a, o1.f1); Asserts.assertEquals(b, o1.f2); } { Object a = new Object(); Object b = new Object(); Object c = new Object(); Object d = new Object(); Object e = new Object(); Object f = new Object(); Object g = new Object(); Object h = new Object(); Object i = new Object(); Object j = new Object(); OuterWithManyFields o = new OuterWithManyFields(); o.f1 = a; o.f2 = b; o.f3 = c; o.f4 = d; o.f5 = e; o.f6 = f; o.f7 = g; o.f8 = h; o.f9 = i; o.f10 = j; OuterWithManyFields o1 = testCloneObjectWithManyFields(o); Asserts.assertEquals(a, o1.f1); Asserts.assertEquals(b, o1.f2); Asserts.assertEquals(c, o1.f3); Asserts.assertEquals(d, o1.f4); Asserts.assertEquals(e, o1.f5); Asserts.assertEquals(f, o1.f6); Asserts.assertEquals(g, o1.f7); Asserts.assertEquals(h, o1.f8); Asserts.assertEquals(i, o1.f9); Asserts.assertEquals(j, o1.f10); } } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_COMPARE_AND_EXCHANGE_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_COMPARE_AND_EXCHANGE_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) static Object testCompareAndExchange(Outer o, Object oldVal, Object newVal) { return fVarHandle.compareAndExchange(o, oldVal, newVal); } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_COMPARE_AND_SWAP_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) static boolean testCompareAndSwap(Outer o, Object oldVal, Object newVal) { return fVarHandle.compareAndSet(o, oldVal, newVal); } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_GET_AND_SET_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_GET_AND_SET_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) static Object testGetAndSet(Outer o, Object newVal) { return fVarHandle.getAndSet(o, newVal); } @Run(test = {"testCompareAndExchange", "testCompareAndSwap", "testGetAndSet"}) public void runAtomicTests() { { Outer o = new Outer(); Object oldVal = new Object(); o.f = oldVal; Object newVal = new Object(); Object oldVal2 = testCompareAndExchange(o, oldVal, newVal); Asserts.assertEquals(oldVal, oldVal2); Asserts.assertEquals(o.f, newVal); } { Outer o = new Outer(); Object oldVal = new Object(); o.f = oldVal; Object newVal = new Object(); boolean b = testCompareAndSwap(o, oldVal, newVal); Asserts.assertTrue(b); Asserts.assertEquals(o.f, newVal); } { Outer o = new Outer(); Object oldVal = new Object(); o.f = oldVal; Object newVal = new Object(); Object oldVal2 = testGetAndSet(o, newVal); Asserts.assertEquals(oldVal, oldVal2); Asserts.assertEquals(o.f, newVal); } } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_LOAD_P_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_LOAD_N_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) static Object testLoadSoftReference(SoftReference ref) { return ref.get(); } @Test @IR(applyIf = {"UseCompressedOops", "false"}, counts = {IRNode.G1_LOAD_P_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, counts = {IRNode.G1_LOAD_N_WITH_BARRIER_FLAG, PRE_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) static Object testLoadWeakReference(WeakReference ref) { return ref.get(); } @Run(test = {"testLoadSoftReference", "testLoadWeakReference"}) public void runReferenceTests() { { Object o1 = new Object(); SoftReference sref = new SoftReference(o1); Object o2 = testLoadSoftReference(sref); Asserts.assertTrue(o2 == o1 || o2 == null); } { Object o1 = new Object(); WeakReference wref = new WeakReference(o1); Object o2 = testLoadWeakReference(wref); Asserts.assertTrue(o2 == o1 || o2 == null); } } }