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 }