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.ref.SoftReference; 28 import java.lang.ref.WeakReference; 29 import jdk.test.lib.Asserts; 30 import jdk.internal.misc.Unsafe; 31 32 /** 33 * @test 34 * @summary Test that implicit null checks are generated as expected for 35 different GC memory accesses. 36 * @library /test/lib / 37 * @modules java.base/jdk.internal.misc 38 * @run driver compiler.gcbarriers.TestImplicitNullChecks 39 */ 40 41 42 public class TestImplicitNullChecks { 43 44 static class Outer { 45 Object f; 46 } 47 48 static class OuterWithVolatileField { 49 volatile Object f; 50 } 51 52 static final Unsafe UNSAFE = Unsafe.getUnsafe(); 53 static final long F_OFFSET; 54 static { 55 try { 56 F_OFFSET = UNSAFE.objectFieldOffset(Outer.class.getDeclaredField("f")); 57 } catch (Exception e) { 58 throw new Error(e); 59 } 60 } 61 62 public static void main(String[] args) { 63 TestFramework.runWithFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", 64 "-XX:CompileCommand=inline,java.lang.ref.*::*", 65 "-XX:-TieredCompilation"); 66 TestFramework.runWithFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", 67 "-XX:CompileCommand=inline,java.lang.ref.*::*", 68 "-XX:+TieredCompilation", "-XX:TieredStopAtLevel=1"); 69 } 70 71 @Test 72 // On AIX, implicit null checks are limited because the zero page is 73 // readable (but not writable). See os::zero_page_read_protected(). 74 @IR(applyIfPlatform = {"aix", "false"}, 75 applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 76 counts = {IRNode.NULL_CHECK, "1"}, 77 phase = CompilePhase.FINAL_CODE) 78 static Object testLoad(Outer o) { 79 return o.f; 80 } 81 82 @Test 83 // On aarch64, volatile loads always use indirect memory operands, which 84 // leads to a pattern that cannot be exploited by the current C2 analysis. 85 // On PPC64, volatile loads are preceded by membar_volatile instructions, 86 // which also inhibits the current C2 analysis. 87 @IR(applyIfPlatformAnd = {"aarch64", "false", "ppc", "false"}, 88 applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 89 counts = {IRNode.NULL_CHECK, "1"}, 90 phase = CompilePhase.FINAL_CODE) 91 static Object testLoadVolatile(OuterWithVolatileField o) { 92 return o.f; 93 } 94 95 @Run(test = {"testLoad", 96 "testLoadVolatile"}, 97 mode = RunMode.STANDALONE) 98 static void runLoadTests() { 99 { 100 Outer o = new Outer(); 101 // Trigger compilation with implicit null check. 102 for (int i = 0; i < 10_000; i++) { 103 testLoad(o); 104 } 105 // Trigger null pointer exception. 106 o = null; 107 boolean nullPointerException = false; 108 try { 109 testLoad(o); 110 } catch (NullPointerException e) { nullPointerException = true; } 111 Asserts.assertTrue(nullPointerException); 112 } 113 { 114 OuterWithVolatileField o = new OuterWithVolatileField(); 115 // Trigger compilation with implicit null check. 116 for (int i = 0; i < 10_000; i++) { 117 testLoadVolatile(o); 118 } 119 // Trigger null pointer exception. 120 o = null; 121 boolean nullPointerException = false; 122 try { 123 testLoadVolatile(o); 124 } catch (NullPointerException e) { nullPointerException = true; } 125 Asserts.assertTrue(nullPointerException); 126 } 127 } 128 129 @Test 130 // G1 and ZGC stores cannot be currently used to implement implicit null 131 // checks, because they expand into multiple memory access instructions that 132 // are not necessarily located at the initial instruction start address. 133 @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 134 failOn = IRNode.NULL_CHECK, 135 phase = CompilePhase.FINAL_CODE) 136 static void testStore(Outer o, Object o1) { 137 o.f = o1; 138 } 139 140 @Run(test = {"testStore"}) 141 static void runStoreTests() { 142 { 143 Outer o = new Outer(); 144 Object o1 = new Object(); 145 testStore(o, o1); 146 } 147 } 148 149 @Test 150 // G1 and ZGC compare-and-exchange operations cannot be currently used to 151 // implement implicit null checks, because they expand into multiple memory 152 // access instructions that are not necessarily located at the initial 153 // instruction start address. The same holds for testCompareAndSwap and 154 // testGetAndSet below. 155 @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 156 failOn = IRNode.NULL_CHECK, 157 phase = CompilePhase.FINAL_CODE) 158 static Object testCompareAndExchange(Outer o, Object oldVal, Object newVal) { 159 return UNSAFE.compareAndExchangeReference(o, F_OFFSET, oldVal, newVal); 160 } 161 162 @Test 163 @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 164 failOn = IRNode.NULL_CHECK, 165 phase = CompilePhase.FINAL_CODE) 166 static boolean testCompareAndSwap(Outer o, Object oldVal, Object newVal) { 167 return UNSAFE.compareAndSetReference(o, F_OFFSET, oldVal, newVal); 168 } 169 170 @Test 171 @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"}, 172 failOn = IRNode.NULL_CHECK, 173 phase = CompilePhase.FINAL_CODE) 174 static Object testGetAndSet(Outer o, Object newVal) { 175 return UNSAFE.getAndSetReference(o, F_OFFSET, newVal); 176 } 177 178 @Run(test = {"testCompareAndExchange", 179 "testCompareAndSwap", 180 "testGetAndSet"}) 181 static void runAtomicTests() { 182 { 183 Outer o = new Outer(); 184 Object oldVal = new Object(); 185 Object newVal = new Object(); 186 testCompareAndExchange(o, oldVal, newVal); 187 } 188 { 189 Outer o = new Outer(); 190 Object oldVal = new Object(); 191 Object newVal = new Object(); 192 testCompareAndSwap(o, oldVal, newVal); 193 } 194 { 195 Outer o = new Outer(); 196 Object oldVal = new Object(); 197 Object newVal = new Object(); 198 testGetAndSet(o, newVal); 199 } 200 } 201 202 @Test 203 // G1 reference loads use indirect memory operands, which leads to a pattern 204 // that cannot be exploited by the current C2 analysis. The same holds for 205 // testLoadWeakReference. 206 @IR(applyIf = {"UseZGC", "true"}, 207 counts = {IRNode.NULL_CHECK, "1"}, 208 phase = CompilePhase.FINAL_CODE) 209 static Object testLoadSoftReference(SoftReference<Object> ref) { 210 return ref.get(); 211 } 212 213 @Test 214 @IR(applyIf = {"UseZGC", "true"}, 215 counts = {IRNode.NULL_CHECK, "1"}, 216 phase = CompilePhase.FINAL_CODE) 217 static Object testLoadWeakReference(WeakReference<Object> ref) { 218 return ref.get(); 219 } 220 221 @Run(test = {"testLoadSoftReference", 222 "testLoadWeakReference"}) 223 static void runReferenceTests() { 224 { 225 Object o1 = new Object(); 226 SoftReference<Object> sref = new SoftReference<Object>(o1); 227 Object o2 = testLoadSoftReference(sref); 228 } 229 { 230 Object o1 = new Object(); 231 WeakReference<Object> wref = new WeakReference<Object>(o1); 232 Object o2 = testLoadWeakReference(wref); 233 } 234 } 235 236 } --- EOF ---