1 /*
  2  * Copyright (c) 2018, 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.constant.ClassDesc;
 27 import java.lang.constant.MethodTypeDesc;
 28 import java.lang.invoke.*;
 29 import java.lang.ref.*;
 30 import java.util.concurrent.*;
 31 
 32 import jdk.internal.value.ValueClass;
 33 import jdk.internal.vm.annotation.ImplicitlyConstructible;
 34 import jdk.internal.vm.annotation.LooselyConsistentValue;
 35 import jdk.internal.vm.annotation.NullRestricted;
 36 
 37 import static jdk.test.lib.Asserts.*;
 38 import jdk.test.lib.Utils;
 39 import jdk.test.whitebox.WhiteBox;
 40 import runtime.valhalla.inlinetypes.InlineOops.FooValue;
 41 import test.java.lang.invoke.lib.InstructionHelper;
 42 import static test.java.lang.invoke.lib.InstructionHelper.classDesc;
 43 
 44 /**
 45  * @test id=Serial
 46  * @requires vm.gc.Serial
 47  * @summary Test embedding oops into Inline types
 48  * @modules java.base/jdk.internal.value
 49  *          java.base/jdk.internal.vm.annotation
 50  * @library /test/lib /test/jdk/java/lang/invoke/common
 51  * @build test.java.lang.invoke.lib.InstructionHelper
 52  * @enablePreview
 53  * @compile Person.java InlineOops.java
 54  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 55  *                   jdk.test.whitebox.WhiteBox$WhiteBoxPermission
 56  * @run main/othervm -XX:+UseSerialGC -Xmx128m -XX:InlineFieldMaxFlatSize=128
 57  *                   -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 58  *                   runtime.valhalla.inlinetypes.InlineOops
 59  */
 60 
 61 /**
 62  * @test id=G1
 63  * @requires vm.gc.G1
 64  * @summary Test embedding oops into Inline types
 65  * @modules java.base/jdk.internal.value
 66  *          java.base/jdk.internal.vm.annotation
 67  * @library /test/lib /test/jdk/java/lang/invoke/common
 68  * @build test.java.lang.invoke.lib.InstructionHelper
 69  * @enablePreview
 70  * @compile Person.java InlineOops.java
 71  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 72  *                   jdk.test.whitebox.WhiteBox$WhiteBoxPermission
 73  * @run main/othervm -XX:+UseG1GC -Xmx128m -XX:InlineFieldMaxFlatSize=128
 74  *                   -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 75  *                   runtime.valhalla.inlinetypes.InlineOops 20
 76  */
 77 
 78 /**
 79  * @test id=Parallel
 80  * @requires vm.gc.Parallel
 81  * @summary Test embedding oops into Inline types
 82  * @modules java.base/jdk.internal.value
 83  *          java.base/jdk.internal.vm.annotation
 84  * @library /test/lib /test/jdk/java/lang/invoke/common
 85  * @build test.java.lang.invoke.lib.InstructionHelper
 86  * @enablePreview
 87  * @compile Person.java InlineOops.java
 88  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 89  *                   jdk.test.whitebox.WhiteBox$WhiteBoxPermission
 90  * @run main/othervm -XX:+UseParallelGC -Xmx128m -XX:InlineFieldMaxFlatSize=128
 91  *                   -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 92  *                   runtime.valhalla.inlinetypes.InlineOops
 93  */
 94 
 95 /**
 96  * @test id=Z
 97  * @requires vm.gc.Z
 98  * @summary Test embedding oops into Inline types
 99  * @modules java.base/jdk.internal.value
100  *          java.base/jdk.internal.vm.annotation
101  * @library /test/lib /test/jdk/java/lang/invoke/common
102  * @build test.java.lang.invoke.lib.InstructionHelper
103  * @enablePreview
104  * @compile Person.java InlineOops.java
105  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
106  *                   jdk.test.whitebox.WhiteBox$WhiteBoxPermission
107  * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx128m
108  *                   -XX:+UnlockDiagnosticVMOptions -XX:+ZVerifyViews -XX:InlineFieldMaxFlatSize=128
109  *                   -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
110  *                   runtime.valhalla.inlinetypes.InlineOops
111  */
112 
113 /**
114  * @test id=ZGen
115  * @requires vm.gc.Z & vm.opt.final.ZGenerational
116  * @summary Test embedding oops into Inline types
117  * @modules java.base/jdk.internal.value
118  *          java.base/jdk.internal.vm.annotation
119  * @library /test/lib /test/jdk/java/lang/invoke/common
120  * @build test.java.lang.invoke.lib.InstructionHelper
121  * @enablePreview
122  * @compile Person.java InlineOops.java
123  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
124  *                   jdk.test.whitebox.WhiteBox$WhiteBoxPermission
125  * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -XX:+ZGenerational -Xmx128m
126  *                   -XX:+UnlockDiagnosticVMOptions -XX:+ZVerifyViews -XX:InlineFieldMaxFlatSize=128
127  *                   -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
128  *                   runtime.valhalla.inlinetypes.InlineOops
129  */
130 public class InlineOops {
131 
132     // Extra debug: -XX:+VerifyOops -XX:+VerifyStack -XX:+VerifyLastFrame -XX:+VerifyBeforeGC -XX:+VerifyAfterGC -XX:+VerifyDuringGC -XX:VerifySubSet=threads,heap
133     // Even more debugging: -XX:+TraceNewOopMapGeneration -Xlog:gc*=info
134 
135     static final int NOF_PEOPLE = 10000; // Exercise arrays of this size
136 
137     static int MIN_ACTIVE_GC_COUNT = 10; // Run active workload for this number of GC passes
138 
139     static int MED_ACTIVE_GC_COUNT = 4;  // Medium life span in terms of GC passes
140 
141     static final String TEST_STRING1 = "Test String 1";
142     static final String TEST_STRING2 = "Test String 2";
143 
144     static WhiteBox WB = WhiteBox.getWhiteBox();
145 
146     static boolean USE_COMPILER = WB.getBooleanVMFlag("UseCompiler");
147 
148     static MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
149 
150     public static void main(String[] args) {
151         if (args.length > 0) {
152             MIN_ACTIVE_GC_COUNT = Integer.parseInt(args[0]);
153         }
154         testClassLoad();
155         testValues();
156 
157         if (!USE_COMPILER) {
158             testOopMaps();
159         }
160 
161         // Check we survive GC...
162         testOverGc();   // Exercise root scan / oopMap
163         testActiveGc(); // Brute force
164     }
165 
166     /**
167      * Test ClassFileParser can load inline types with reference fields
168      */
169     public static void testClassLoad() {
170         String s = Person.class.toString();
171         new Bar();
172         new BarWithValue();
173         s = BarValue.class.toString();
174         s = ObjectWithObjectValue.class.toString();
175         s = ObjectWithObjectValues.class.toString();
176     }
177 
178 
179     static class Couple {
180         @NullRestricted
181         public Person onePerson;
182         @NullRestricted
183         public Person otherPerson;
184     }
185 
186     @ImplicitlyConstructible
187     @LooselyConsistentValue
188     static value class Composition {
189         @NullRestricted
190         public Person onePerson;
191         @NullRestricted
192         public Person otherPerson;
193 
194         public Composition(Person onePerson, Person otherPerson) {
195             this.onePerson = onePerson;
196             this.otherPerson = otherPerson;
197         }
198     }
199 
200     /**
201      * Check inline type operations with "Valhalla Inline Types" (VVT)
202      */
203     public static void testValues() {
204         // Exercise creation, getfield, vreturn with null refs
205         validateDefaultPerson(createDefaultPerson());
206 
207         // anewarray, aaload, aastore
208         int index = 7;
209         Person[] array = (Person[])ValueClass.newNullRestrictedArray(Person.class, NOF_PEOPLE);
210         validateDefaultPerson(array[index]);
211 
212         // Now with refs...
213         validateIndexedPerson(createIndexedPerson(index), index);
214         array[index] = createIndexedPerson(index);
215         validateIndexedPerson(array[index], index);
216 
217         // Check the neighbours
218         validateDefaultPerson(array[index - 1]);
219         validateDefaultPerson(array[index + 1]);
220 
221         // getfield/putfield
222         Couple couple = new Couple();
223         validateDefaultPerson(couple.onePerson);
224         validateDefaultPerson(couple.otherPerson);
225 
226         couple.onePerson = createIndexedPerson(index);
227         validateIndexedPerson(couple.onePerson, index);
228 
229         Composition composition = new Composition(couple.onePerson, couple.onePerson);
230         validateIndexedPerson(composition.onePerson, index);
231         validateIndexedPerson(composition.otherPerson, index);
232     }
233 
234     /**
235      * Check oop map generation for klass layout and frame...
236      */
237     public static void testOopMaps() {
238         Object[] objects = WB.getObjectsViaKlassOopMaps(new Couple());
239         assertTrue(objects.length == 4, "Expected 4 oops");
240         for (int i = 0; i < objects.length; i++) {
241             assertTrue(objects[i] == null, "not-null");
242         }
243 
244         String fn1 = "Sam";
245         String ln1 = "Smith";
246         String fn2 = "Jane";
247         String ln2 = "Jones";
248         Couple couple = new Couple();
249         couple.onePerson = new Person(0, fn1, ln1);
250         couple.otherPerson = new Person(1, fn2, ln2);
251         objects = WB.getObjectsViaKlassOopMaps(couple);
252         assertTrue(objects.length == 4, "Expected 4 oops");
253         assertTrue(objects[0] == fn1, "Bad oop fn1");
254         assertTrue(objects[1] == ln1, "Bad oop ln1");
255         assertTrue(objects[2] == fn2, "Bad oop fn2");
256         assertTrue(objects[3] == ln2, "Bad oop ln2");
257 
258         objects = WB.getObjectsViaOopIterator(couple);
259         assertTrue(objects.length == 4, "Expected 4 oops");
260         assertTrue(objects[0] == fn1, "Bad oop fn1");
261         assertTrue(objects[1] == ln1, "Bad oop ln1");
262         assertTrue(objects[2] == fn2, "Bad oop fn2");
263         assertTrue(objects[3] == ln2, "Bad oop ln2");
264 
265         // Array..
266         objects = WB.getObjectsViaOopIterator(createPeople());
267         assertTrue(objects.length == NOF_PEOPLE * 2, "Unexpected length: " + objects.length);
268         int o = 0;
269         for (int i = 0; i < NOF_PEOPLE; i++) {
270             assertTrue(objects[o++].equals(firstName(i)), "Bad firstName");
271             assertTrue(objects[o++].equals(lastName(i)), "Bad lastName");
272         }
273 
274         // Sanity check, FixMe need more test cases
275         objects = testFrameOops(couple);
276         assertTrue(objects.length == 5, "Number of frame oops incorrect = " + objects.length);
277         assertTrue(objects[0] == couple, "Bad oop 0");
278         assertTrue(objects[1] == fn1, "Bad oop 1");
279         assertTrue(objects[2] == ln1, "Bad oop 2");
280         assertTrue(objects[3] == TEST_STRING1, "Bad oop 3");
281         assertTrue(objects[4] == TEST_STRING2, "Bad oop 4");
282 
283         testFrameOopsVBytecodes();
284     }
285 
286     static final String GET_OOP_MAP_NAME = "getOopMap";
287     static final MethodTypeDesc GET_OOP_MAP_DESC = MethodTypeDesc.ofDescriptor("()[Ljava/lang/Object;");
288 
289     public static Object[] getOopMap() {
290         Object[] oopMap = WB.getObjectsViaFrameOopIterator(2);
291         /* Remove this frame (class mirror for this method), and above class mirror */
292         Object[] trimmedOopMap = new Object[oopMap.length - 2];
293         System.arraycopy(oopMap, 2, trimmedOopMap, 0, trimmedOopMap.length);
294         return trimmedOopMap;
295     }
296 
297     // Expecting Couple couple, Person couple.onePerson, and Person (created here)
298     public static Object[] testFrameOops(Couple couple) {
299         int someId = 89898;
300         Person person = couple.onePerson;
301         assertTrue(person.getId() == 0, "Bad Person");
302         Person anotherPerson = new Person(someId, TEST_STRING1, TEST_STRING2);
303         assertTrue(anotherPerson.getId() == someId, "Bad Person");
304         return getOopMap();
305     }
306 
307     // Debug...
308     static void dumpOopMap(Object[] oopMap) {
309         System.out.println("Oop Map len: " + oopMap.length);
310         for (int i = 0; i < oopMap.length; i++) {
311             System.out.println("[" + i + "] = " + oopMap[i]);
312         }
313     }
314 
315     /**
316      * Just some check sanity checks with aconst_init, withfield, astore and aload
317      *
318      * Changes to javac slot usage may well break this test
319      */
320     public static void testFrameOopsVBytecodes() {
321         int nofOopMaps = 4;
322         Object[][] oopMaps = new Object[nofOopMaps][];
323         String[] inputArgs = new String[] { "aName", "aDescription", "someNotes" };
324 
325         FooValue.testFrameOopsDefault(oopMaps);
326 
327         // Test-D0 Slots=R Stack=Q(RRR)RV
328         assertTrue(oopMaps[0].length == 5 &&
329                 oopMaps[0][1] == null &&
330                 oopMaps[0][2] == null &&
331                 oopMaps[0][3] == null, "Test-D0 incorrect");
332 
333         // Test-D1 Slots=R Stack=RV
334         assertTrue(oopMaps[1].length == 2, "Test-D1 incorrect");
335 
336         // Test-D2 Slots=RQ(RRR) Stack=RV
337         assertTrue(oopMaps[2].length == 5 &&
338                 oopMaps[2][1] == null &&
339                 oopMaps[2][2] == null &&
340                 oopMaps[2][3] == null, "Test-D2 incorrect");
341 
342         // Test-D3 Slots=R Stack=Q(RRR)RV
343         assertTrue(oopMaps[3].length == 6 &&
344                 oopMaps[3][1] == null &&
345                 oopMaps[3][2] == null &&
346                 oopMaps[3][3] == null &&
347                 oopMaps[3][4] == null, "Test-D3 incorrect");
348 
349         // With ref fields...
350         String name = "TestName";
351         String desc = "TestDesc";
352         String note = "TestNotes";
353         FooValue.testFrameOopsRefs(name, desc, note, oopMaps);
354 
355         // Test-R0 Slots=RR Stack=Q(RRR)RV
356         assertTrue(oopMaps[0].length == 6 &&
357                 oopMaps[0][2] == name &&
358                 oopMaps[0][3] == desc &&
359                 oopMaps[0][4] == note, "Test-R0 incorrect");
360 
361         /**
362          * TODO: vwithfield from method handle cooked from anonymous class within the inline class
363          *       even with "MethodHandles.privateLookupIn()" will fail final putfield rules
364          */
365     }
366 
367     /**
368      * Check forcing GC for combination of VT on stack/LVT etc works
369      */
370     public static void testOverGc() {
371         try {
372             Class<?> vtClass = Person.class;
373 
374             System.out.println("vtClass="+vtClass);
375 
376             doGc();
377 
378             // VT on stack and lvt, null refs, see if GC flies
379             MethodHandle moveValueThroughStackAndLvt = InstructionHelper.buildMethodHandle(
380                     LOOKUP,
381                     "gcOverPerson",
382                     MethodType.methodType(vtClass, vtClass),
383                     CODE->{
384                         CODE
385                         .aload(0)
386                         .invokestatic(classDesc(InlineOops.class), "doGc", MethodTypeDesc.ofDescriptor("()V")) // Stack
387                         .astore(0)
388                         .invokestatic(classDesc(InlineOops.class), "doGc", MethodTypeDesc.ofDescriptor("()V")) // LVT
389                         .aload(0)
390                         .astore(1024) // LVT wide index
391                         .aload(1024)
392                         .iconst_1()  // push a litte further down
393                         .invokestatic(classDesc(InlineOops.class), "doGc", MethodTypeDesc.ofDescriptor("()V")) // Stack,LVT
394                         .pop()
395                         .areturn();
396                     });
397             Person person = (Person) moveValueThroughStackAndLvt.invokeExact(createDefaultPerson());
398             validateDefaultPerson(person);
399             doGc();
400 
401             int index = 4711;
402             person = (Person) moveValueThroughStackAndLvt.invokeExact(createIndexedPerson(index));
403             validateIndexedPerson(person, index);
404             doGc();
405             person = createDefaultPerson();
406             doGc();
407         }
408         catch (Throwable t) { fail("testOverGc", t); }
409     }
410 
411     static void submitNewWork(ForkJoinPool fjPool) {
412         for (int j = 0; j < 100; j++) {
413             fjPool.execute(InlineOops::testValues);
414         }
415     }
416 
417     static void sleepNoThrow(long ms) {
418         try {
419             Thread.sleep(ms);
420         }
421         catch (Throwable t) {}
422     }
423 
424     /**
425      * Run some workloads with different object/value life times...
426      */
427     public static void testActiveGc() {
428         try {
429             int nofThreads = 1;
430 
431             Object longLivedObjects = createLongLived();
432             Object longLivedPeople = createPeople();
433 
434             Object medLivedObjects = createLongLived();
435             Object medLivedPeople = createPeople();
436 
437             doGc();
438 
439             // Setup some background work, where GC roots are stack local only, short lifetimes...
440             ForkJoinPool fjPool = new ForkJoinPool(nofThreads, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
441 
442             // Work on this stack's long and medium lived objects
443             for (int nofActiveGc = 0; nofActiveGc < MIN_ACTIVE_GC_COUNT; nofActiveGc++) {
444                 // Medium lifetime, check and renew
445                 if (nofActiveGc % MED_ACTIVE_GC_COUNT == 0) {
446                     validateLongLived(medLivedObjects);
447                     validatePeople(medLivedPeople);
448 
449                     medLivedObjects = createLongLived();
450                     medLivedPeople = createPeople();
451                 }
452                 // More short lived background, if needed
453                 if (!fjPool.hasQueuedSubmissions()) {
454                     submitNewWork(fjPool);
455                 }
456                 // Forced, synchronous GC
457                 doGc();
458             }
459 
460             fjPool.shutdown();
461 
462             validateLongLived(medLivedObjects);
463             validatePeople(medLivedPeople);
464             medLivedObjects = null;
465             medLivedPeople = null;
466 
467             validateLongLived(longLivedObjects);
468             validatePeople(longLivedPeople);
469 
470             longLivedObjects = null;
471             longLivedPeople = null;
472 
473             doGc();
474         }
475         catch (Throwable t) { fail("testActiveGc", t); }
476     }
477 
478     static final ReferenceQueue<Object> REFQ = new ReferenceQueue<>();
479 
480     public static void doGc() {
481         WB.fullGC();
482     }
483 
484     static void validatePerson(Person person, int id, String fn, String ln, boolean equals) {
485         assertTrue(person.id == id);
486         if (equals) {
487             assertTrue(fn.equals(person.getFirstName()), "Invalid field firstName");
488             assertTrue(ln.equals(person.getLastName()), "Invalid  field lastName");
489         }
490         else {
491             assertTrue(person.getFirstName() == fn, "Invalid field firstName");
492             assertTrue(person.getLastName() == ln, "Invalid  field lastName");
493         }
494     }
495 
496     static Person createIndexedPerson(int i) {
497         return new Person(i, firstName(i), lastName(i));
498     }
499 
500     static void validateIndexedPerson(Person person, int i) {
501         validatePerson(person, i, firstName(i), lastName(i), true);
502     }
503 
504     static Person createDefaultPerson() {
505         return (Person)ValueClass.newNullRestrictedArray(Person.class, 1)[0];
506     }
507 
508     static void validateDefaultPerson(Person person) {
509         validatePerson(person, 0, null, null, false);
510     }
511 
512     static String firstName(int i) {
513         return "FirstName-" + i;
514     }
515 
516     static String lastName(int i) {
517         return "LastName-" + i;
518     }
519 
520     static Object createLongLived()  throws Throwable {
521         Object[] population = new Object[1];
522         population[0] = createPeople();
523         return population;
524     }
525 
526     static void validateLongLived(Object pop) throws Throwable {
527         Object[] population = (Object[]) pop;
528         validatePeople(population[0]);
529     }
530 
531     static Object createPeople() {
532         int arrayLength = NOF_PEOPLE;
533         Person[] people = new Person[arrayLength];
534         for (int i = 0; i < arrayLength; i++) {
535             people[i] = createIndexedPerson(i);
536         }
537         return people;
538     }
539 
540     static void validatePeople(Object array) {
541         Person[] people = (Person[]) array;
542         int arrayLength = people.length;
543         assertTrue(arrayLength == NOF_PEOPLE);
544         for (int i = 0; i < arrayLength; i++) {
545             validateIndexedPerson(people[i], i);
546         }
547     }
548 
549     // Various field layouts...sanity testing, see MVTCombo testing for full-set
550 
551     @ImplicitlyConstructible
552     @LooselyConsistentValue
553     static value class ObjectValue {
554         final Object object;
555 
556         private ObjectValue(Object obj) {
557             object = obj;
558         }
559     }
560 
561     static class ObjectWithObjectValue {
562         ObjectValue value1;
563         Object      ref1;
564     }
565 
566     static class ObjectWithObjectValues {
567         ObjectValue value1;
568         ObjectValue value2;
569         Object      ref1;
570     }
571 
572     static class Foo {
573         int id;
574         String name;
575         String description;
576         long timestamp;
577         String notes;
578     }
579 
580     static class Bar extends Foo {
581         long extendedId;
582         String moreNotes;
583         int count;
584         String otherStuff;
585     }
586 
587     @ImplicitlyConstructible
588     @LooselyConsistentValue
589     public static value class FooValue {
590         public final int id;
591         public final String name;
592         public final String description;
593         public final long timestamp;
594         public final String notes;
595 
596         public FooValue() {
597             id = 0;
598             name = null;
599             description = null;
600             timestamp = 0L;
601             notes = null;
602         }
603 
604         public FooValue(int id, String name, String description, long timestamp, String notes) {
605             this.id = id;
606             this.name = name;
607             this.description = description;
608             this.timestamp = timestamp;
609             this.notes = notes;
610         }
611 
612         public static void testFrameOopsDefault(Object[][] oopMaps) {
613             MethodType mt = MethodType.methodType(Void.TYPE, oopMaps.getClass());
614             int oopMapsSlot   = 0;
615             int vtSlot        = 1;
616 
617             // Slots 1=oopMaps
618             // OopMap Q=RRR (.name .description .someNotes)
619             try {
620                 InstructionHelper.buildMethodHandle(
621                         LOOKUP, "exerciseVBytecodeExprStackWithDefault", mt,
622                         CODE->{
623                             CODE
624                             .new_(classDesc(FooValue.class))
625                             .dup()
626                             .invokespecial(classDesc(FooValue.class), "<init>", MethodTypeDesc.ofDescriptor("()V"))
627                             .aload(oopMapsSlot)
628                             .iconst_0()  // Test-D0 Slots=R Stack=Q(RRR)RV
629                             .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC)
630                             .aastore()
631                             .pop()
632                             .aload(oopMapsSlot)
633                             .iconst_1()  // Test-D1 Slots=R Stack=RV
634                             .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC)
635                             .aastore()
636                             .new_(classDesc(FooValue.class))
637                             .dup()
638                             .invokespecial(classDesc(FooValue.class), "<init>", MethodTypeDesc.ofDescriptor("()V"))
639                             .astore(vtSlot)
640                             .aload(oopMapsSlot)
641                             .iconst_2()  // Test-D2 Slots=RQ(RRR) Stack=RV
642                             .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC)
643                             .aastore()
644                             .aload(vtSlot)
645                             .aconst_null()
646                             .astore(vtSlot) // Storing null over the Q slot won't remove the ref, but should be single null ref
647                             .aload(oopMapsSlot)
648                             .iconst_3()  // Test-D3 Slots=R Stack=Q(RRR)RV
649                             .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC)
650                             .aastore()
651                             .pop()
652                             .return_();
653                         }).invoke(oopMaps);
654             } catch (Throwable t) { fail("exerciseVBytecodeExprStackWithDefault", t); }
655         }
656 
657         public static void testFrameOopsRefs(String name, String description, String notes, Object[][] oopMaps) {
658             FooValue f = new FooValue(4711, name, description, 9876543231L, notes);
659             FooValue[] fa = (FooValue[])ValueClass.newNullRestrictedArray(FooValue.class, 1);
660             fa[0] = f;
661             MethodType mt = MethodType.methodType(Void.TYPE, fa.getClass(), oopMaps.getClass());
662             int fooArraySlot  = 0;
663             int oopMapsSlot   = 1;
664             try {
665                 InstructionHelper.buildMethodHandle(LOOKUP, "exerciseVBytecodeExprStackWithRefs", mt,
666                         CODE->{
667                             CODE
668                             .aload(fooArraySlot)
669                             .iconst_0()
670                             .aaload()
671                             .aload(oopMapsSlot)
672                             .iconst_0()  // Test-R0 Slots=RR Stack=Q(RRR)RV
673                             .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC)
674                             .aastore()
675                             .pop()
676                             .return_();
677                         }).invoke(fa, oopMaps);
678             } catch (Throwable t) { fail("exerciseVBytecodeExprStackWithRefs", t); }
679         }
680     }
681 
682     static class BarWithValue {
683         FooValue foo;
684         long extendedId;
685         String moreNotes;
686         int count;
687         String otherStuff;
688     }
689 
690     @ImplicitlyConstructible
691     @LooselyConsistentValue
692     static value class BarValue {
693         @NullRestricted
694         FooValue foo;
695         long extendedId;
696         String moreNotes;
697         int count;
698         String otherStuff;
699 
700         private BarValue(FooValue f, long extId, String mNotes, int c, String other) {
701             foo = f;
702             extendedId = extId;
703             moreNotes = mNotes;
704             count = c;
705             otherStuff = other;
706         }
707     }
708 
709 }