1 /*
  2  * Copyright (c) 2013, 2021, 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 // These tests test that narrow Klass pointer encoding/decoding work.
 25 //
 26 // Note that we do not enforce the encoding base directly. We enforce base and size of the compressed class space.
 27 // The hotspot then decides on the best encoding range and scheme to chose for the given range.
 28 //
 29 // So what we really test here is that for a given range-to-encode:
 30 //  - the chosen encoding range and architecture-specific mode makes sense - e.g. if range fits into low address
 31 //    space, use base=0 and zero-based encoding.
 32 //  - and that the chosen encoding actually works by starting a simple program which loads a bunch of classes.
 33 //
 34 //  In order for that to work, we have to switch of CDS. Switching off CDS means the hotspot choses the encoding base
 35 //  based on the class space base address (we just know this - see CompressedKlassPointers::initialize() - and if this
 36 //  changes, we may have to adapt this test).
 37 //
 38 //  Switching off CDS also means we use the class space much more fully. More Klass structures stored in that range
 39 //  and we exercise the ability of Metaspace to allocate Klass structures with the correct alignment, compatible to
 40 //  encoding.
 41 
 42 /*
 43  * @test id=x64-area-beyond-encoding-range-use-xor
 44  * @requires os.arch=="amd64" | os.arch=="x86_64"
 45  * @requires vm.flagless
 46  * @library /test/lib
 47  * @modules java.base/jdk.internal.misc
 48  *          java.management
 49  * @run driver CompressedClassPointerEncoding
 50  */
 51 
 52 import jdk.test.lib.Platform;
 53 import jdk.test.lib.process.OutputAnalyzer;
 54 import jdk.test.lib.process.ProcessTools;
 55 
 56 import java.io.IOException;
 57 import java.util.Arrays;
 58 
 59 public class CompressedClassPointerEncoding {
 60 
 61     // Replace:
 62     // $1 with force base address
 63     // $2 with compressed class space size
 64     final static String[] vmOptionsTemplate = new String[] {
 65       "-XX:CompressedClassSpaceBaseAddress=$1",
 66       "-XX:CompressedClassSpaceSize=$2",
 67       "-Xshare:off",                         // Disable CDS
 68       "-Xlog:metaspace*",                    // for analysis
 69       "-XX:+PrintMetaspaceStatisticsAtExit", // for analysis
 70       "-version"
 71     };
 72 
 73     // Replace:
 74     // $1 with expected ccs base address (extended hex printed)
 75     // $2 with expected encoding base (extended hex printed)
 76     // $3 with expected encoding shift
 77     // $4 with expected encoding range
 78     // $5 with expected encoding mode
 79     final String[] expectedOutputTemplate = new String[] {
 80             ".*Sucessfully forced class space address to $1.*",
 81             ".*CDS archive(s) not mapped.*",
 82             ".*Narrow klass base: $2, Narrow klass shift: $3, Narrow klass range: $4, Encoding mode $5.*"
 83     };
 84 
 85     final static long M = 1024 * 1024;
 86     final static long G = 1024 * M;
 87 
 88     final static long expectedShift = 9;
 89     final static long expectedEncodingRange = 2 * G;
 90     final static long defaultCCSSize = 32 * M;
 91 
 92     enum EPlatform {
 93         // Add more where needed
 94         // (Note: this would be useful in Platform.java)
 95         linux_aarch64,
 96         linux_x64,
 97         unknown
 98     };
 99 
100     static EPlatform getCurrentPlatform() {
101         if (Platform.isAArch64() && Platform.isLinux()) {
102             return EPlatform.linux_aarch64;
103         } else if (Platform.isX64() && Platform.isLinux()) {
104             return EPlatform.linux_x64;
105         }
106         return EPlatform.unknown;
107     }
108 
109     static class TestDetails {
110         public final EPlatform platform;
111         public final String name;
112         public final long[] baseAdressesToTry;
113         public final long compressedClassSpaceSize;
114         public final long expectedEncodingBase;
115         public final String expectedEncodingMode;
116 
117         public TestDetails(EPlatform platform, String name, long[] baseAdressesToTry,
118                            long compressedClassSpaceSize, long expectedEncodingBase, String expectedEncodingMode) {
119             this.platform = platform;
120             this.name = name;
121             this.baseAdressesToTry = baseAdressesToTry;
122             this.compressedClassSpaceSize = compressedClassSpaceSize;
123             this.expectedEncodingBase = expectedEncodingBase;
124             this.expectedEncodingMode = expectedEncodingMode;
125         }
126 
127         // Simplified, common version: one base address (which we assume always works) and 32G ccs size
128         public TestDetails(EPlatform platform, String name, long baseAdress,
129                            long expectedEncodingBase, String expectedEncodingMode) {
130             this(platform, name, new long[]{ baseAdress }, defaultCCSSize,
131                  expectedEncodingBase, expectedEncodingMode);
132         }
133     };
134 
135     static TestDetails[] testDetails = new TestDetails[] {
136 
137             //////////////////////////////////////////////////////////////////////////
138             ////// x64 ///////////////////////////////////////////////////////////////
139 
140             // CCS base beyond encoding range (base=2G). Base does does not intersect the uncompressed klass pointer
141             // bits. Encoding cannot be zero, and we should use xor+shift mode.
142             new TestDetails(EPlatform.linux_x64,
143                     "x64-area-beyond-encoding-range-use-xor",
144                     2 * G,
145                     2 * G,
146                     "xor"),
147 
148             // CCS partly contained in encoding range. We cannot use zero based encoding. We cannot use xor either,
149             // since the first part of the ccs intersects the encoding range. Encoding hould use add+shift.
150             new TestDetails(EPlatform.linux_x64,
151                     "x64-area-partly-within-encoding-range-use-add",
152                     0x7fc00000,
153                     2 * G,
154                     "add"),
155 
156             // CCS (just) fully contained in encoding range (base=2G-ccs size). Expect zero-based encoding.
157             new TestDetails(EPlatform.linux_x64,
158                     "x64-area-within-encoding-range-use-zero",
159                     0x7e000000, // 2G - 32M (ccs size)
160                     0,
161                     "zero"),
162 
163             // CCS located far beyond the zero-based limit. Base does not intersect with narrow Klass pointer bits.
164             // We should use xor.
165             new TestDetails(EPlatform.linux_x64,
166                     "x64-area-far-out-no-low-bits-use-xor",
167                     0x800000000L, // 32G
168                     0x800000000L,
169                     "xor"),
170 
171             // CCS located far beyond the zero-based limit. Base address intersects with narrow Klass pointer bits.
172             // We should use add.
173             new TestDetails(EPlatform.linux_x64,
174                     "x64-area-far-out-with-low-bits-use-add",
175                     0x800800000L, // 32G + 8M (4M is minimum ccs alignment)
176                     0x800800000L,
177                     "xor"),
178 
179             //////////////////////////////////////////////////////////////////////////
180             ////// aarch64 ///////////////////////////////////////////////////////////
181 
182 
183             // CCS with a base which is a valid immediate, does not intersect the uncompressed klass pointer bits,
184             // should use xor+shift
185             new TestDetails(EPlatform.linux_aarch64,
186                     "aarch64-area-beyond-encoding-range-base-valid-immediate-use-xor",
187                     0x800000000L, // 32G
188                     800000000L,
189                     "xor")
190 
191             // ... add more
192 
193     };
194 
195     // Helper function. Given a string, replace $1 ... $n with
196     // replacement_strings[0] ... replacement_strings[n]
197     static private String replacePlaceholdersInString(String original, String ...replacement_strings) {
198         String result = original;
199         int repl_id = 1; // 1 based
200         for (String replacement : replacement_strings) {
201             String placeholder = "$" + repl_id;
202             result = result.replace(placeholder, replacement);
203             repl_id ++;
204         }
205         return result;
206     }
207 
208     // Helper function. Given a string array, replace $1 ... $n with
209     // replacement_strings[0] ... replacement_strings[n]
210     static private String[] replacePlaceholdersInArray(String[] original, String ...replacement_strings) {
211         String[] copy = new String[original.length];
212         for (int n = 0; n < copy.length; n ++) {
213             copy[n] = replacePlaceholdersInString(original[n], replacement_strings);
214         }
215         return copy;
216     }
217 
218     static void runTest(TestDetails details) throws IOException {
219         System.err.println("----------------------------------------------------");
220         System.err.println("Running Test: " + details.name);
221         System.err.println(details);
222 
223         long ccsBaseAddress = details.baseAdressesToTry[0];
224         String ccsBaseAddressAsHex = String.format("0x%016x", ccsBaseAddress);
225 
226         // VM Options: replace:
227         // $1 with force base address
228         // $2 with compressed class space size
229         String[] vmOptions = replacePlaceholdersInArray(vmOptionsTemplate,
230                 ccsBaseAddressAsHex,              // $1
231                 (details.compressedClassSpaceSize / M) + "M");    // $2
232 
233         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(vmOptions);
234         OutputAnalyzer output = new OutputAnalyzer(pb.start());
235 
236         System.err.println("----------------------------------------------------");
237         System.err.println(Arrays.toString(vmOptions));
238         output.reportDiagnosticSummary();
239         System.err.println("----------------------------------------------------");
240 
241         output.shouldHaveExitValue(0);
242 
243     }
244 
245     static void runTestsForPlatform(EPlatform platform) throws IOException {
246         for (TestDetails details : testDetails) {
247             if (details.platform == platform) {
248                 runTest(details);
249             }
250         }
251     }
252 
253     public static void main(String[] args) throws Exception {
254         runTestsForPlatform(getCurrentPlatform());
255     }
256 }