1 /*
2 * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
3 * Copyright (c) 2025, Red Hat, Inc. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.
9 *
10 * This code is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * version 2 for more details (a copy is included in the LICENSE file that
14 * accompanied this code).
15 *
16 * You should have received a copy of the GNU General Public License version
17 * 2 along with this work; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21 * or visit www.oracle.com if you need additional information or have any
22 * questions.
23 */
24
25 import jdk.internal.misc.Unsafe;
26 import jdk.test.whitebox.WhiteBox;
27
28 import java.lang.reflect.Field;
29 import java.util.ArrayList;
30 import java.util.List;
31
32 /*
33 * @test id=no_coops_no_coh
34 * @library /test/lib
35 * @modules java.base/jdk.internal.misc
36 * java.management
37 * @build jdk.test.whitebox.WhiteBox
38 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
39 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:-UseCompressedOops -XX:-UseCompactObjectHeaders TestOopMapSizeMinimal
40 */
41
42 /*
43 * @test id=coops_no_coh
44 * @library /test/lib
45 * @modules java.base/jdk.internal.misc
46 * java.management
47 * @build jdk.test.whitebox.WhiteBox
48 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
49 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:+UseCompressedOops -XX:-UseCompactObjectHeaders TestOopMapSizeMinimal
50 */
51
52 /*
53 * @test id=no_coops_coh
54 * @library /test/lib
55 * @modules java.base/jdk.internal.misc
56 * java.management
57 * @build jdk.test.whitebox.WhiteBox
58 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
59 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:-UseCompressedOops -XX:+UseCompactObjectHeaders TestOopMapSizeMinimal
60 */
61
62 /*
63 * @test id=coops_coh
64 * @library /test/lib
65 * @modules java.base/jdk.internal.misc
66 * java.management
67 * @build jdk.test.whitebox.WhiteBox
68 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
69 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+UnlockExperimentalVMOptions -XX:+UseCompressedOops -XX:+UseCompactObjectHeaders TestOopMapSizeMinimal
70 */
71
72 public class TestOopMapSizeMinimal {
73
74 public static int OOP_SIZE_IN_BYTES = -1;
75 public static int HEADER_SIZE_IN_BYTES = -1;
76
77 static {
78 WhiteBox WB = WhiteBox.getWhiteBox();
79 boolean is_64_bit = System.getProperty("sun.arch.data.model").equals("64");
80 if (is_64_bit) {
81 if (System.getProperty("java.vm.compressedOopsMode") == null) {
82 OOP_SIZE_IN_BYTES = 8;
83 } else {
84 OOP_SIZE_IN_BYTES = 4;
85 }
86 } else {
87 OOP_SIZE_IN_BYTES = 4;
88 }
89 if (is_64_bit) {
90 if (WB.getBooleanVMFlag("UseCompactObjectHeaders")) {
91 HEADER_SIZE_IN_BYTES = 4;
92 } else {
93 HEADER_SIZE_IN_BYTES = 12;
94 }
95 } else {
96 HEADER_SIZE_IN_BYTES = 8;
97 }
98 }
99
100 public static long alignUp(long value, long alignment) {
101 return (value + alignment - 1) & ~(alignment - 1);
102 }
103
104 public static long alignForOop(long position) {
105 return alignUp(position, OOP_SIZE_IN_BYTES);
106 }
107
108 private static final Unsafe U = Unsafe.getUnsafe();
109
110 public static class BASE {
111 int i1;
112 Object o1;
113 }
114
115 public static class DERIVED1 extends BASE {
116 int i2;
117 Object o2;
118 }
119
120 public static class DERIVED2 extends DERIVED1 {
121 int i3;
122 Object o3;
123 }
124
125 public static class DERIVED3 extends DERIVED2 {
126 int i4;
127 Object o4;
128 }
129
130 static boolean mismatch = false;
131
132 private static void checkOffset(Field f, long expectedOffset) {
133 long realOffset = U.objectFieldOffset(f);
134 System.out.println("Offset for field " + f.getName() +
135 ": expected " + expectedOffset + ", got " + realOffset + ".");
136 if (U.objectFieldOffset(f) != expectedOffset) {
137 mismatch = true;
138 System.out.println(" ... mimatch");
139 }
140 }
141
142 private static List<Field> allFieldsOf(Class c) {
143 ArrayList<Field> l = new ArrayList<>();
144 while (c != null) {
145 for (Field f : c.getDeclaredFields()) {
146 l.add(f);
147 }
148 c = c.getSuperclass();
149 }
150 return l;
151 }
152
153 public static void main(String[] args) throws Exception {
154
155 System.out.println("HEADER_SIZE_IN_BYTES " + HEADER_SIZE_IN_BYTES + ", OOP_SIZE_IN_BYTES " + OOP_SIZE_IN_BYTES);
156
157 long i1_loc_expected;
158 long o1_loc_expected;
159 long o2_loc_expected;
160 long i2_loc_expected;
161 long i3_loc_expected;
162 long o3_loc_expected;
163 long o4_loc_expected;
164 long i4_loc_expected;
165
166 // We expect the layouter to reverse order of oop- and non-oop fields
167 // when it is useful to minimize the number of oop map entries.
168 //
169 // If we have no gaps, this should be the layout:
170 // BASE i1
171 // o1 oopmap entry 1
172 // DERIVED1 o2 oopmap entry 1 (reversed order)
173 // i2
174 // DERIVED3 i3
175 // o3 oopmap entry 2
176 // DERIVED4 o4 oopmap entry 2 (reversed order)
177 // i4
178
179 // There is one combination that has gaps:
180 // -UseCompressedOops + +COH: A gap will be following i1, and i2 will therefore nestle into that gap.
181 // Otherwise, the same logic applies.
182
183 if (OOP_SIZE_IN_BYTES == 4 || // oop size == int size
184 (OOP_SIZE_IN_BYTES == 8 && (HEADER_SIZE_IN_BYTES == 12 || HEADER_SIZE_IN_BYTES == 4))
185 ) {
186 // No gaps
187
188 // Expected layout for BASE: int, object
189 i1_loc_expected = HEADER_SIZE_IN_BYTES;
190 o1_loc_expected = i1_loc_expected + 4;
191
192 // Expected layout for DERIVED1: object, int (to make o2 border o1)
193 o2_loc_expected = o1_loc_expected + OOP_SIZE_IN_BYTES;
194 i2_loc_expected = o2_loc_expected + OOP_SIZE_IN_BYTES;
195
196 // Expected layout for DERIVED2: int, object (to trail with oops, for derived classes to nestle against)
197 i3_loc_expected = i2_loc_expected + 4;
198 o3_loc_expected = i3_loc_expected + 4;
199
200 // Expected layout for DERIVED3: object, int (to make o4 border o3)
201 o4_loc_expected = o3_loc_expected + OOP_SIZE_IN_BYTES;
202 i4_loc_expected = o4_loc_expected + OOP_SIZE_IN_BYTES;
203
204 } else if (OOP_SIZE_IN_BYTES == 8) {
205
206 // gap after i1
207
208 i1_loc_expected = HEADER_SIZE_IN_BYTES;
209 o1_loc_expected = i1_loc_expected + 4 + 4; // + alignment gap
210
211 o2_loc_expected = o1_loc_expected + OOP_SIZE_IN_BYTES;
212 i2_loc_expected = i1_loc_expected + 4; // into gap following i1
213
214 o3_loc_expected = o2_loc_expected + OOP_SIZE_IN_BYTES;
215 i3_loc_expected = o3_loc_expected + OOP_SIZE_IN_BYTES;
216
217 i4_loc_expected = i3_loc_expected + 4;
218 o4_loc_expected = i4_loc_expected + 4;
219 } else {
220 throw new RuntimeException("Unexpected");
221 }
222
223 List<Field> l = allFieldsOf(DERIVED3.class);
224 for (Field f : l) {
225 switch (f.getName()) {
226 case "i1" : checkOffset(f, i1_loc_expected); break;
227 case "o1" : checkOffset(f, o1_loc_expected); break;
228 case "i2" : checkOffset(f, i2_loc_expected); break;
229 case "o2" : checkOffset(f, o2_loc_expected); break;
230 case "i3" : checkOffset(f, i3_loc_expected); break;
231 case "o3" : checkOffset(f, o3_loc_expected); break;
232 case "i4" : checkOffset(f, i4_loc_expected); break;
233 case "o4" : checkOffset(f, o4_loc_expected); break;
234 default: throw new RuntimeException("Unexpected");
235 }
236 }
237 if (mismatch) {
238 throw new RuntimeException("Mismatch!");
239 }
240 System.out.println("All good.");
241 }
242
243 }
244