1 /*
  2  * Copyright (c) 2019, 2024, 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 runtime.valhalla.inlinetypes;
 25 
 26 import java.lang.reflect.Array;
 27 import java.lang.reflect.Field;
 28 import java.util.Arrays;
 29 import java.util.ArrayList;
 30 import java.util.List;
 31 import java.util.function.Supplier;
 32 
 33 import javax.swing.LayoutFocusTraversalPolicy;
 34 
 35 import java.util.Optional;
 36 
 37 import jdk.internal.misc.Unsafe;
 38 import jdk.internal.value.ValueClass;
 39 import jdk.internal.vm.annotation.ImplicitlyConstructible;
 40 import jdk.internal.vm.annotation.LooselyConsistentValue;
 41 import jdk.internal.vm.annotation.NullRestricted;
 42 import jdk.test.whitebox.WhiteBox;
 43 import static jdk.test.lib.Asserts.*;
 44 
 45 /*
 46  * @test ValueTearing
 47  * @summary Test tearing of inline fields and array elements
 48  * @modules java.base/jdk.internal.misc
 49  * @library /test/lib
 50  * @modules java.base/jdk.internal.value
 51  *          java.base/jdk.internal.vm.annotation
 52  * @enablePreview
 53  * @compile ValueTearing.java
 54  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 55  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable=
 56  *                   -DSTEP_COUNT=10000 -XX:InlineFieldMaxFlatSize=128 -XX:FlatArrayElementMaxSize=-1
 57  *                   -Xbootclasspath/a:. -XX:+WhiteBoxAPI
 58  *                                   runtime.valhalla.inlinetypes.ValueTearing
 59  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable=*
 60  *                   -DSTEP_COUNT=10000 -XX:InlineFieldMaxFlatSize=128 -XX:FlatArrayElementMaxSize=-1
 61  *                   -Xbootclasspath/a:. -XX:+WhiteBoxAPI
 62  *                                   runtime.valhalla.inlinetypes.ValueTearing
 63  * @run main/othervm -DSTEP_COUNT=10000000 -XX:InlineFieldMaxFlatSize=128 -XX:FlatArrayElementMaxSize=-1
 64  *                   -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 65  *                                   runtime.valhalla.inlinetypes.ValueTearing
 66  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable=
 67  *                   -DTEAR_MODE=fieldonly -XX:InlineFieldMaxFlatSize=128 -XX:FlatArrayElementMaxSize=-1
 68  *                   -Xbootclasspath/a:. -XX:+WhiteBoxAPI
 69  *                                   runtime.valhalla.inlinetypes.ValueTearing
 70  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable=
 71  *                   -DTEAR_MODE=arrayonly -XX:InlineFieldMaxFlatSize=128 -XX:FlatArrayElementMaxSize=-1
 72  *                   -Xbootclasspath/a:. -XX:+WhiteBoxAPI
 73  *                                   runtime.valhalla.inlinetypes.ValueTearing
 74  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable=*
 75  *                   -DTEAR_MODE=both -XX:InlineFieldMaxFlatSize=128 -XX:FlatArrayElementMaxSize=-1
 76  *                   -Xbootclasspath/a:. -XX:+WhiteBoxAPI
 77  *                                   runtime.valhalla.inlinetypes.ValueTearing
 78  */
 79 public class ValueTearing {
 80     private static final Unsafe UNSAFE = Unsafe.getUnsafe();
 81     private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
 82     private static final boolean USE_COMPILER = WHITE_BOX.getBooleanVMFlag("UseCompiler");
 83     private static final boolean ALWAYS_ATOMIC = WHITE_BOX.getStringVMFlag("ForceNonTearable").contains("*");
 84     private static final String TEAR_MODE = System.getProperty("TEAR_MODE", "both");
 85     private static final boolean TEAR_FIELD = !TEAR_MODE.equals("arrayonly");
 86     private static final boolean TEAR_ARRAY = !TEAR_MODE.equals("fieldonly");
 87     private static final int STEP_COUNT = Integer.getInteger("STEP_COUNT", 100_000);
 88     private static final boolean TFIELD_FLAT, TARRAY_FLAT;
 89     private static final boolean NTFIELD_FLAT, NTARRAY_FLAT;
 90     static {
 91         try {
 92             Field TPB_field = TPointBox.class.getDeclaredField("field");
 93             Field TPB_array = TPointBox.class.getDeclaredField("array");
 94             Field NTPB_field = NTPointBox.class.getDeclaredField("field");
 95             Field NTPB_array = NTPointBox.class.getDeclaredField("array");
 96             TFIELD_FLAT = UNSAFE.isFlatField(TPB_field);
 97             TARRAY_FLAT = UNSAFE.isFlatArray(TPB_array.getType());
 98             NTFIELD_FLAT = UNSAFE.isFlatField(NTPB_field);
 99             NTARRAY_FLAT = UNSAFE.isFlatArray(NTPB_array.getType());
100         } catch (ReflectiveOperationException ex) {
101             throw new AssertionError(ex);
102         }
103     }
104     private static final String SETTINGS =
105         String.format("USE_COMPILER=%s ALWAYS_ATOMIC=%s TEAR_MODE=%s STEP_COUNT=%s FLAT TF/TA=%s/%s NTF/NTA=%s/%s",
106                       USE_COMPILER, ALWAYS_ATOMIC, TEAR_MODE, STEP_COUNT,
107                       TFIELD_FLAT, TARRAY_FLAT, NTFIELD_FLAT, NTARRAY_FLAT);
108     private static final String NOTE_TORN_POINT = "Note: torn point";
109 
110     public static void main(String[] args) throws Exception {
111         System.out.println(SETTINGS);
112         ValueTearing valueTearing = new ValueTearing();
113         valueTearing.run();
114         // Extra representation check:
115         assert(!NTFIELD_FLAT) : "NT field must be indirect not flat";
116         assert(!NTARRAY_FLAT) : "NT array must be indirect not flat";
117         if (ALWAYS_ATOMIC) {
118             assert(!TFIELD_FLAT) : "field must be indirect not flat";
119             assert(!TARRAY_FLAT) : "array must be indirect not flat";
120         }
121     }
122 
123     // A tearable value.
124     @LooselyConsistentValue
125     @ImplicitlyConstructible
126     static value class TPoint {
127         TPoint(long x, long y) { this.x = x; this.y = y; }
128         final long x, y;
129         public String toString() { return String.format("(%d,%d)", x, y); }
130     }
131 
132     static class TooTearable extends AssertionError {
133         final Object badPoint;
134         TooTearable(String msg, Object badPoint) {
135             super(msg);
136             this.badPoint = badPoint;
137         }
138     }
139 
140     interface PointBox {
141         void step();    // mutate inline value state
142         void check();   // check sanity of inline value state
143     }
144 
145     class TPointBox implements PointBox {
146         @NullRestricted
147         TPoint field;
148         TPoint[] array = (TPoint[])ValueClass.newNullRestrictedArray(TPoint.class, 1);
149         // Step the points forward by incrementing their components
150         // "simultaneously".  A racing thread will catch flaws in the
151         // simultaneity.
152         TPoint step(TPoint p) {
153             return new TPoint(p.x + 1, p.y + 1);
154         }
155         public @Override
156         void step() {
157             if (TEAR_FIELD) {
158                 field = step(field);
159             }
160             if (TEAR_ARRAY) {
161                 array[0] = step(array[0]);
162             }
163             check();
164         }
165         // Invariant:  The components of each point are "always" equal.
166         // As long as simultaneity is preserved, this is true.
167         public @Override
168         void check() {
169             if (TEAR_FIELD) {
170                 check(field, "field");
171             }
172             if (TEAR_ARRAY) {
173                 check(array[0], "array element");
174             }
175         }
176         void check(TPoint p, String where) {
177             if (p.x == p.y)  return;
178             String msg = String.format("%s %s in %s; settings = %s",
179                                        NOTE_TORN_POINT,
180                                        p, where, SETTINGS);
181             throw new TooTearable(msg, p);
182         }
183         public String toString() {
184             return String.format("TPB[%s, {%s}]", field, array[0]);
185         }
186     }
187 
188 
189     // A non-tearable version of TPoint.
190     @ImplicitlyConstructible
191     static value class NTPoint {
192         NTPoint(long x, long y) { this.x = x; this.y = y; }
193         final long x, y;
194         public String toString() { return String.format("(%d,%d)", x, y); }
195     }
196 
197     class NTPointBox implements PointBox {
198         @NullRestricted
199         NTPoint field;
200         NTPoint[] array = (NTPoint[])ValueClass.newNullRestrictedArray(NTPoint.class, 1);
201         // Step the points forward by incrementing their components
202         // "simultaneously".  A racing thread will catch flaws in the
203         // simultaneity.
204         NTPoint step(NTPoint p) {
205             return new NTPoint(p.x + 1, p.y + 1);
206         }
207         public @Override
208         void step() {
209             field = step(field);
210             array[0] = step(array[0]);
211             check();
212         }
213         // Invariant:  The components of each point are "always" equal.
214         public @Override
215         void check() {
216             check(field, "field");
217             check(array[0], "array element");
218         }
219         void check(NTPoint p, String where) {
220             if (p.x == p.y)  return;
221             String msg = String.format("%s *NonTearable* %s in %s; settings = %s",
222                                        NOTE_TORN_POINT,
223                                        p, where, SETTINGS);
224             throw new TooTearable(msg, p);
225         }
226         public String toString() {
227             return String.format("NTPB[%s, {%s}]", field, array[0]);
228         }
229     }
230 
231     class AsyncObserver extends Thread {
232         volatile boolean done;
233         long observationCount;
234         final PointBox pointBox;
235         volatile Object badPointObserved;
236         AsyncObserver(PointBox pointBox) {
237             this.pointBox = pointBox;
238         }
239         public void run() {
240             try {
241                 while (!done) {
242                     observationCount++;
243                     pointBox.check();
244                 }
245             } catch (TooTearable ex) {
246                 done = true;
247                 badPointObserved = ex.badPoint;
248                 System.out.println(ex);
249                 if (ALWAYS_ATOMIC || !badPointObserved.getClass().isAnnotationPresent(LooselyConsistentValue.class)) {
250                     throw ex;
251                 }
252             }
253         }
254     }
255 
256     public void run() throws Exception {
257         System.out.println("Test for tearing of NTPoint, which must not happen...");
258         run(new NTPointBox(), false);
259         System.out.println("Test for tearing of TPoint, which "+
260                            (ALWAYS_ATOMIC ? "must not" : "is allowed to")+
261                            " happen...");
262         run(new TPointBox(), ALWAYS_ATOMIC ? false : true);
263     }
264     public void run(PointBox pointBox, boolean canTear) throws Exception {
265         var observer = new AsyncObserver(pointBox);
266         observer.start();
267         for (int i = 0; i < STEP_COUNT; i++) {
268             pointBox.step();
269             if (observer.done)  break;
270         }
271         observer.done = true;
272         observer.join();
273         var obCount = observer.observationCount;
274         var badPoint = observer.badPointObserved;
275         System.out.println(String.format("finished after %d observations at %s; %s",
276                                          obCount, pointBox,
277                                          (badPoint == null
278                                           ? "no tearing observed"
279                                           : "bad point = " + badPoint)));
280         if (canTear && badPoint == null) {
281             var complain = String.format("%s NOT observed after %d observations",
282                                          NOTE_TORN_POINT, obCount);
283             System.out.println("?????? "+complain);
284             if (STEP_COUNT >= 3_000_000) {
285                 // If it's a small count, OK, but if it's big the test is broken.
286                 throw new AssertionError(complain + ", but it should have been");
287             }
288         }
289         if (!canTear && badPoint != null) {
290             throw new AssertionError("should not reach here; other thread must throw");
291         }
292     }
293 }