1 /*
  2  * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  *
 23  */
 24 
 25 /**
 26  * @test
 27  * @summary Sanity test of AOT Code Cache with compressed oops configurations
 28  * @requires vm.cds.supports.aot.code.caching
 29  * @requires vm.compMode != "Xcomp"
 30  * @requires vm.bits == 64
 31  * @requires vm.opt.final.UseCompressedOops
 32  * @comment The test verifies AOT checks during VM startup and not code generation.
 33  *          No need to run it with -Xcomp. It takes a lot of time to complete all
 34  *          subtests with this flag.
 35  * @library /test/lib /test/setup_aot
 36  * @build AOTCodeCompressedOopsTest JavacBenchApp
 37  * @run driver/timeout=480 jdk.test.lib.helpers.ClassFileInstaller -jar app.jar
 38  *             JavacBenchApp
 39  *             JavacBenchApp$ClassFile
 40  *             JavacBenchApp$FileManager
 41  *             JavacBenchApp$SourceFile
 42  * @run driver/timeout=480 AOTCodeCompressedOopsTest
 43  */
 44 
 45 import java.util.ArrayList;
 46 import java.util.List;
 47 import java.util.regex.Matcher;
 48 import java.util.regex.Pattern;
 49 
 50 import jdk.test.lib.cds.CDSAppTester;
 51 import jdk.test.lib.process.OutputAnalyzer;
 52 
 53 public class AOTCodeCompressedOopsTest {
 54     public static void main(String... args) throws Exception {
 55         {
 56             Tester t = new Tester();
 57             t.setHeapConfig(Tester.RunMode.ASSEMBLY, true, true);
 58             t.runAOTAssemblyWorkflow();
 59             t.setHeapConfig(Tester.RunMode.PRODUCTION, true, true);
 60             t.productionRun();
 61             t.setHeapConfig(Tester.RunMode.PRODUCTION, true, false);
 62             t.productionRun();
 63             t.setHeapConfig(Tester.RunMode.PRODUCTION, false, false);
 64             t.productionRun();
 65         }
 66         {
 67             Tester t = new Tester();
 68             t.setHeapConfig(Tester.RunMode.ASSEMBLY, true, false);
 69             t.runAOTAssemblyWorkflow();
 70             t.setHeapConfig(Tester.RunMode.PRODUCTION, true, true);
 71             t.productionRun();
 72             t.setHeapConfig(Tester.RunMode.PRODUCTION, true, false);
 73             t.productionRun();
 74             t.setHeapConfig(Tester.RunMode.PRODUCTION, false, false);
 75             t.productionRun();
 76         }
 77         {
 78             Tester t = new Tester();
 79             t.setHeapConfig(Tester.RunMode.ASSEMBLY, false, false);
 80             t.runAOTAssemblyWorkflow();
 81             t.setHeapConfig(Tester.RunMode.PRODUCTION, true, true);
 82             t.productionRun();
 83             t.setHeapConfig(Tester.RunMode.PRODUCTION, true, false);
 84             t.productionRun();
 85             t.setHeapConfig(Tester.RunMode.PRODUCTION, false, false);
 86             t.productionRun();
 87         }
 88     }
 89     static class Tester extends CDSAppTester {
 90         boolean zeroBaseInAsmPhase, zeroBaseInProdPhase;
 91         boolean zeroShiftInAsmPhase, zeroShiftInProdPhase;
 92 
 93         public Tester() {
 94             super("AOTCodeCompressedOopsTest");
 95         }
 96 
 97         public void setHeapConfig(RunMode runMode, boolean isBaseZero, boolean isShiftZero) {
 98             if (runMode == RunMode.ASSEMBLY) {
 99                 zeroBaseInAsmPhase = isBaseZero;
100                 zeroShiftInAsmPhase = isShiftZero;
101             } else if (runMode == RunMode.PRODUCTION) {
102                 zeroBaseInProdPhase = isBaseZero;
103                 zeroShiftInProdPhase = isShiftZero;
104             }
105         }
106 
107         @Override
108         public String classpath(RunMode runMode) {
109             return "app.jar";
110         }
111 
112         List<String> getVMArgsForHeapConfig(boolean isBaseZero, boolean isShiftZero) {
113             List<String> list = new ArrayList<String>();
114             // Note the VM options used are best-effort to get the desired base and shift,
115             // but it is OS dependent, so we may not get the desired configuration.
116             if (isBaseZero && isShiftZero) {
117                 list.add("-Xmx128m"); // Set max heap < 4G;
118             } else if (isBaseZero && !isShiftZero) {
119                 list.add("-Xmx6g"); // Set max heap > 4G for shift to be non-zero
120             } else if (!isBaseZero && !isShiftZero) {
121                 list.add("-Xmx6g");
122                 list.add("-XX:HeapBaseMinAddress=32g");
123             }
124             return list;
125        }
126 
127         @Override
128         public String[] vmArgs(RunMode runMode) {
129             switch (runMode) {
130             case RunMode.ASSEMBLY: {
131                     List<String> args = getVMArgsForHeapConfig(zeroBaseInAsmPhase, zeroShiftInAsmPhase);
132                     args.addAll(List.of("-XX:+UnlockDiagnosticVMOptions",
133                                         "-Xlog:aot=info",
134                                         "-Xlog:aot+codecache+init=debug",
135                                         "-Xlog:aot+codecache+exit=debug"));
136                     return args.toArray(new String[0]);
137                 }
138             case RunMode.PRODUCTION: {
139                     List<String> args = getVMArgsForHeapConfig(zeroBaseInProdPhase, zeroShiftInProdPhase);
140                     args.addAll(List.of("-XX:+UnlockDiagnosticVMOptions",
141                                         "-Xlog:aot=info", // we need this to parse CompressedOops settings
142                                         "-Xlog:aot+codecache+init=debug",
143                                         "-Xlog:aot+codecache+exit=debug"));
144                     return args.toArray(new String[0]);
145                 }
146             }
147             return new String[] {};
148         }
149 
150         @Override
151         public String[] appCommandLine(RunMode runMode) {
152             return new String[] {
153                 "JavacBenchApp", "10"
154             };
155         }
156 
157         @Override
158         public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {
159             if (runMode == RunMode.PRODUCTION) {
160                  int aotCacheShift = -1, currentShift = -1;
161                  long aotCacheBase = -1, currentBase = -1;
162                  List<String> list = out.asLines();
163                  /* We tried to have CompressedOops settings as per the test requirement,
164                   * but it ultimately depends on OS and is not guaranteed that we have got the desired settings.
165                   * So we parse the log output from the production run to get the real settings.
166                   *
167                   * Parse the following Xlog:cds output to get the values of CompressedOops::base and CompressedOops::shift
168                   * used during the AOTCache assembly and production run:
169                   *
170                   *    [0.022s][info][cds] CDS archive was created with max heap size = 1024M, and the following configuration:
171                   *    [0.022s][info][cds]     narrow_klass_base at mapping start address, narrow_klass_pointer_bits = 32, narrow_klass_shift = 0
172                   *    [0.022s][info][cds]     narrow_oop_mode = 1, narrow_oop_base = 0x0000000000000000, narrow_oop_shift = 3
173                   *    [0.022s][info][cds] The current max heap size = 31744M, G1HeapRegion::GrainBytes = 16777216
174                   *    [0.022s][info][cds]     narrow_klass_base = 0x000007fc00000000, arrow_klass_pointer_bits = 32, narrow_klass_shift = 0
175                   *    [0.022s][info][cds]     narrow_oop_mode = 3, narrow_oop_base = 0x0000000300000000, narrow_oop_shift = 3
176                   *    [0.022s][info][cds]     heap range = [0x0000000301000000 - 0x0000000ac1000000]
177                   */
178                  Pattern p = Pattern.compile("narrow_oop_base = 0x([0-9a-fA-F]+), narrow_oop_shift = (\\d)");
179                  for (int i = 0; i < list.size(); i++) {
180                      String line = list.get(i);
181                      if (line.indexOf("CDS archive was created with max heap size") != -1) {
182                          // Parse AOT Cache CompressedOops settings
183                          line = list.get(i+2);
184                          Matcher m = p.matcher(line);
185                          if (!m.find()) {
186                              throw new RuntimeException("Pattern \"" + p + "\" not found in the output");
187                          }
188                          aotCacheBase = Long.valueOf(m.group(1), 16);
189                          aotCacheShift = Integer.valueOf(m.group(2));
190                          // Parse current CompressedOops settings
191                          line = list.get(i+5);
192                          m = p.matcher(line);
193                          if (!m.find()) {
194                              throw new RuntimeException("Pattern \"" + p + "\" not found in the output");
195                          }
196                          currentBase = Long.valueOf(m.group(1), 16);
197                          currentShift = Integer.valueOf(m.group(2));
198                          break;
199                      }
200                  }
201                  if (aotCacheShift == -1 || currentShift == -1 || aotCacheBase == -1 || currentBase == -1) {
202                      throw new RuntimeException("Failed to find CompressedOops settings");
203                  }
204                  if (aotCacheShift != currentShift) {
205                      out.shouldContain("AOT Code Cache disabled: it was created with different CompressedOops::shift()");
206                  } else if ((aotCacheBase == 0 || currentBase == 0) && (aotCacheBase != currentBase)) {
207                      out.shouldContain("AOTStubCaching is disabled: incompatible CompressedOops::base()");
208                  } else {
209                      out.shouldMatch("Read \\d+ entries table at offset \\d+ from AOT Code Cache");
210                  }
211             }
212         }
213     }
214 }