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