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