1 /* 2 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package compiler.gcbarriers; 25 26 import compiler.lib.ir_framework.*; 27 import java.lang.invoke.VarHandle; 28 import java.lang.invoke.MethodHandles; 29 import java.lang.ref.Reference; 30 import java.lang.ref.ReferenceQueue; 31 import java.lang.ref.SoftReference; 32 import java.lang.ref.WeakReference; 33 import jdk.test.lib.Asserts; 34 35 /** 36 * @test 37 * @summary Test that implicit null checks are generated as expected for 38 different GC memory accesses. 39 * @library /test/lib / 40 * @run driver compiler.gcbarriers.TestImplicitNullChecks 41 */ 42 43 44 public class TestImplicitNullChecks { 45 46 static class Outer { 47 Object f; 48 } 49 50 static class OuterWithVolatileField { 51 volatile Object f; 52 } 53 54 static final VarHandle fVarHandle; 55 static { 56 MethodHandles.Lookup l = MethodHandles.lookup(); 57 try { 58 fVarHandle = l.findVarHandle(Outer.class, "f", Object.class); 59 } catch (Exception e) { 60 throw new Error(e); 61 } 62 } 63 64 public static void main(String[] args) { 65 TestFramework.runWithFlags("-XX:CompileCommand=inline,java.lang.ref.*::*", 66 "-XX:-TieredCompilation"); 67 } 68 69 @Test 70 // On AIX, implicit null checks are limited because the zero page is 71 // readable (but not writable). See os::zero_page_read_protected(). 72 @IR(applyIfPlatform = {"aix", "false"}, 73 applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 74 counts = {IRNode.NULL_CHECK, "1"}, 75 phase = CompilePhase.FINAL_CODE) 76 static Object testLoad(Outer o) { 77 return o.f; 78 } 79 80 @Test 81 // On aarch64, volatile loads always use indirect memory operands, which 82 // leads to a pattern that cannot be exploited by the current C2 analysis. 83 // On PPC64, volatile loads are preceded by membar_volatile instructions, 84 // which also inhibits the current C2 analysis. 85 @IR(applyIfPlatformAnd = {"aarch64", "false", "ppc", "false"}, 86 applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 87 counts = {IRNode.NULL_CHECK, "1"}, 88 phase = CompilePhase.FINAL_CODE) 89 static Object testLoadVolatile(OuterWithVolatileField o) { 90 return o.f; 91 } 92 93 @Run(test = {"testLoad", 94 "testLoadVolatile"}, 95 mode = RunMode.STANDALONE) 96 static void runLoadTests() { 97 { 98 Outer o = new Outer(); 99 // Trigger compilation with implicit null check. 100 for (int i = 0; i < 10_000; i++) { 101 testLoad(o); 102 } 103 // Trigger null pointer exception. 104 o = null; 105 boolean nullPointerException = false; 106 try { 107 testLoad(o); 108 } catch (NullPointerException e) { nullPointerException = true; } 109 Asserts.assertTrue(nullPointerException); 110 } 111 { 112 OuterWithVolatileField o = new OuterWithVolatileField(); 113 // Trigger compilation with implicit null check. 114 for (int i = 0; i < 10_000; i++) { 115 testLoadVolatile(o); 116 } 117 // Trigger null pointer exception. 118 o = null; 119 boolean nullPointerException = false; 120 try { 121 testLoadVolatile(o); 122 } catch (NullPointerException e) { nullPointerException = true; } 123 Asserts.assertTrue(nullPointerException); 124 } 125 } 126 127 @Test 128 // G1 and ZGC stores cannot be currently used to implement implicit null 129 // checks, because they expand into multiple memory access instructions that 130 // are not necessarily located at the initial instruction start address. 131 @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 132 failOn = IRNode.NULL_CHECK, 133 phase = CompilePhase.FINAL_CODE) 134 static void testStore(Outer o, Object o1) { 135 o.f = o1; 136 } 137 138 @Run(test = {"testStore"}) 139 static void runStoreTests() { 140 { 141 Outer o = new Outer(); 142 Object o1 = new Object(); 143 testStore(o, o1); 144 } 145 } 146 147 @Test 148 // G1 and ZGC compare-and-exchange operations cannot be currently used to 149 // implement implicit null checks, because they expand into multiple memory 150 // access instructions that are not necessarily located at the initial 151 // instruction start address. The same holds for testCompareAndSwap and 152 // testGetAndSet below. 153 @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 154 failOn = IRNode.NULL_CHECK, 155 phase = CompilePhase.FINAL_CODE) 156 static Object testCompareAndExchange(Outer o, Object oldVal, Object newVal) { 157 return fVarHandle.compareAndExchange(o, oldVal, newVal); 158 } 159 160 @Test 161 @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 162 failOn = IRNode.NULL_CHECK, 163 phase = CompilePhase.FINAL_CODE) 164 static boolean testCompareAndSwap(Outer o, Object oldVal, Object newVal) { 165 return fVarHandle.compareAndSet(o, oldVal, newVal); 166 } 167 168 @Test 169 @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 170 failOn = IRNode.NULL_CHECK, 171 phase = CompilePhase.FINAL_CODE) 172 static Object testGetAndSet(Outer o, Object newVal) { 173 return fVarHandle.getAndSet(o, newVal); 174 } 175 176 @Run(test = {"testCompareAndExchange", 177 "testCompareAndSwap", 178 "testGetAndSet"}) 179 static void runAtomicTests() { 180 { 181 Outer o = new Outer(); 182 Object oldVal = new Object(); 183 Object newVal = new Object(); 184 testCompareAndExchange(o, oldVal, newVal); 185 } 186 { 187 Outer o = new Outer(); 188 Object oldVal = new Object(); 189 Object newVal = new Object(); 190 testCompareAndSwap(o, oldVal, newVal); 191 } 192 { 193 Outer o = new Outer(); 194 Object oldVal = new Object(); 195 Object newVal = new Object(); 196 testGetAndSet(o, newVal); 197 } 198 } 199 200 @Test 201 // G1 reference loads use indirect memory operands, which leads to a pattern 202 // that cannot be exploited by the current C2 analysis. The same holds for 203 // testLoadWeakReference. 204 @IR(applyIf = {"UseZGC", "true"}, 205 counts = {IRNode.NULL_CHECK, "1"}, 206 phase = CompilePhase.FINAL_CODE) 207 static Object testLoadSoftReference(SoftReference<Object> ref) { 208 return ref.get(); 209 } 210 211 @Test 212 @IR(applyIf = {"UseZGC", "true"}, 213 counts = {IRNode.NULL_CHECK, "1"}, 214 phase = CompilePhase.FINAL_CODE) 215 static Object testLoadWeakReference(WeakReference<Object> ref) { 216 return ref.get(); 217 } 218 219 @Run(test = {"testLoadSoftReference", 220 "testLoadWeakReference"}) 221 static void runReferenceTests() { 222 { 223 Object o1 = new Object(); 224 SoftReference<Object> sref = new SoftReference<Object>(o1); 225 Object o2 = testLoadSoftReference(sref); 226 } 227 { 228 Object o1 = new Object(); 229 WeakReference<Object> wref = new WeakReference<Object>(o1); 230 Object o2 = testLoadWeakReference(wref); 231 } 232 } 233 234 }