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 * @test id=GetAndSet
26 * @bug 8020282
27 * @summary Test that we do not generate redundant leas on x86 for AtomicReference.getAndSet.
28 * @requires os.simpleArch == "x64" & vm.opt.TieredCompilation != false
29 * @modules jdk.compiler/com.sun.tools.javac.util
30 * @library /test/lib /
31 * @run driver compiler.codegen.TestRedundantLea GetAndSet
32 */
33
34 /*
35 * @test id=StringEquals
36 * @bug 8020282
37 * @summary Test that we do not generate redundant leas on x86 for String.Equals.
38 * @requires os.simpleArch == "x64" & vm.opt.TieredCompilation != false
39 * @modules jdk.compiler/com.sun.tools.javac.util
40 * @library /test/lib /
41 * @run driver compiler.codegen.TestRedundantLea StringEquals
42 */
43
44 /*
45 * @test id=StringInflate
46 * @bug 8020282
47 * @summary Test that we do not generate redundant leas on x86 for StringConcat intrinsics.
48 * @requires os.simpleArch == "x64" & vm.opt.TieredCompilation != false
49 * @modules jdk.compiler/com.sun.tools.javac.util
50 * @library /test/lib /
51 * @run driver compiler.codegen.TestRedundantLea StringInflate
52 */
53
54 /*
55 * @test id=RegexFind
56 * @bug 8020282
57 * @summary Test that we do not generate redundant leas on x86 when performing regex matching.
58 * @requires os.simpleArch == "x64" & vm.opt.TieredCompilation != false & vm.opt.UseAvx == 3
59 * @modules jdk.compiler/com.sun.tools.javac.util
60 * @library /test/lib /
61 * @run driver compiler.codegen.TestRedundantLea RegexFind
62 */
63
64 /*
65 * @test id=StoreNSerial
66 * @bug 8020282
67 * @summary Test that we do not generate redundant leas on x86 when storing narrow oops to object arrays.
68 * @requires os.simpleArch == "x64" & vm.gc.Serial
69 * @modules jdk.compiler/com.sun.tools.javac.util
70 * @library /test/lib /
71 * @run driver compiler.codegen.TestRedundantLea StoreNSerial
72 */
73
74 /*
75 * @test id=StoreNParallel
76 * @bug 8020282
77 * @summary Test that we do not generate redundant leas on x86 when storing narrow oops to object arrays.
78 * @requires os.simpleArch == "x64" & vm.gc.Parallel
79 * @modules jdk.compiler/com.sun.tools.javac.util
80 * @library /test/lib /
81 * @run driver compiler.codegen.TestRedundantLea StoreNParallel
82 */
83
84
85 package compiler.codegen;
86
87 import java.util.concurrent.atomic.*;
88 import java.util.regex.Matcher;
89 import java.util.regex.Pattern;
90
91 import com.sun.tools.javac.util.*;
92
93 import compiler.lib.ir_framework.*;
94
95 // The following tests ensure that we do not generate a redundant lea instruction on x86.
96 // These get generated on chained dereferences for the rules leaPCompressedOopOffset,
97 // leaP8Narrow, and leaP32Narrow and stem from a decodeHeapOopNotNull that is not needed
98 // unless the derived oop is added to an oop map. The redundant lea is removed with an
99 // opto assembly peephole optimization. Hence, all tests below feature a negative test
100 // run with -XX:-OptoPeephole to detect changes that obsolete that peephole.
101 // Further, all tests are run with different max heap sizes to trigger the generation of
102 // different lea match rules: -XX:MaxHeapSize=32m generates leaP(8|32)Narrow and
103 // -XX:MaxHeapSize=4g generates leaPCompressedOopOffset, since the address computation
104 // needs to shift left by 3.
105 public class TestRedundantLea {
106 public static void main(String[] args) {
107 String testName = args[0];
108 TestFramework framework;
109 switch (testName) {
110 case "GetAndSet" -> {
111 framework = new TestFramework(GetAndSetTest.class);
112 }
113 case "StringEquals" -> {
114 framework = new TestFramework(StringEqualsTest.class);
115 framework.addHelperClasses(StringEqualsTestHelper.class);
116 }
117 case "StringInflate" -> {
118 framework = new TestFramework(StringInflateTest.class);
119 framework.addFlags("--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED");
120 }
121 case "RegexFind" -> {
122 framework = new TestFramework(RegexFindTest.class);
123 }
124 case "StoreNSerial" -> {
125 framework = new TestFramework(StoreNTest.class);
126 framework.addFlags("-XX:+UseSerialGC");
127 }
128 case "StoreNParallel" -> {
129 framework = new TestFramework(StoreNTest.class);
130 framework.addFlags("-XX:+UseParallelGC");
131 }
132 default -> {
133 throw new IllegalArgumentException("Unknown test name \"" + testName +"\"");
134 }
135 }
136
137 Scenario[] scenarios = new Scenario[2];
138 // Scenario for the negative test without peephole optimizations.
139 scenarios[0] = new Scenario(0, "-XX:+IgnoreUnrecognizedVMOptions", "-XX:-OptoPeephole");
140 // Scenario for the positive test with +OptoPeephole (the default on x64).
141 scenarios[1] = new Scenario(1);
142 framework.addScenarios(scenarios).start();
143 }
144 }
145
146 // This generates a leaP* rule for the chained dereference of obj.value that
147 // gets passed to the get and set VM intrinsic.
148 class GetAndSetTest {
149 private static final Object CURRENT = new Object();
150 private final AtomicReference<Object> obj = new AtomicReference<Object>();
151
152 @Test
153 @IR(counts = {IRNode.LEA_P, "=1"},
154 phase = {CompilePhase.FINAL_CODE},
155 applyIfPlatform = {"mac", "false"})
156 // Negative test
157 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
158 phase = {CompilePhase.FINAL_CODE},
159 applyIf = {"OptoPeephole", "false"})
160 // Test that the peephole worked for leaP(8|32)Narrow
161 @IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
162 phase = {CompilePhase.FINAL_CODE},
163 applyIf = {"OptoPeephole", "true"})
164 public void testGetAndSet() {
165 obj.getAndSet(CURRENT);
166 }
167 }
168
169 // This generates leaP* rules for the chained dereferences of the String.value
170 // fields that are used in the string_equals VM intrinsic.
171 class StringEqualsTest {
172 final StringEqualsTestHelper strEqHelper = new StringEqualsTestHelper("I am the string that is tested against");
173
174 @Setup
175 private static Object[] setup() {
176 return new Object[]{"I will be compared!"};
177 }
178
179 @Test
180 @IR(counts = {IRNode.LEA_P, "=2"},
181 phase = {CompilePhase.FINAL_CODE},
182 applyIfPlatform = {"mac", "false"})
183 // Negative test
184 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=3"},
185 phase = {CompilePhase.FINAL_CODE},
186 applyIf = {"OptoPeephole", "false"})
187 // Test that the peephole worked for leaPCompressedOopOffset
188 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
189 phase = {CompilePhase.FINAL_CODE},
190 applyIf = {"OptoPeephole", "true"})
191 @Arguments(setup = "setup")
192 public boolean test(String str) {
193 return strEqHelper.doEquals(str);
194 }
195 }
196
197 class StringEqualsTestHelper {
198 private String str;
199
200 public StringEqualsTestHelper(String str) {
201 this.str = str;
202 }
203
204 @ForceInline
205 public boolean doEquals(String other) {
206 return this.str.equals(other);
207 }
208 }
209
210 // With all VM instrinsics disabled, this test only generates a leaP* rule
211 // before the string_inflate intrinsic (with -XX:-OptimizeStringConcat no
212 // leaP* rule is generated). With VM intrinsics enabled (this is the case
213 // here) leaP* rules are also generated for the string_equals and arrays_hashcode
214 // VM instrinsics.
215 // This generates a larger number of decodes for -XX:UseAVX={0,1} than for
216 // other flags.
217 class StringInflateTest {
218 @Setup
219 private static Object[] setup() {
220 Names names = new Names(new Context());
221 Name n1 = names.fromString("one");
222 Name n2 = names.fromString("two");
223 return new Object[] {n1, n2};
224 }
225
226 @Test
227 // TODO: Make tests more precise
228 @IR(counts = {IRNode.LEA_P, "=2"},
229 phase = {CompilePhase.FINAL_CODE},
230 applyIfPlatform = {"mac", "false"})
231 // Negative
232 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, ">=5"},
233 phase = {CompilePhase.FINAL_CODE},
234 applyIf = {"OptoPeephole", "false"})
235 // 2 decodes get removed
236 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, ">=3"},
237 phase = {CompilePhase.FINAL_CODE},
238 applyIf = {"OptoPeephole", "true"})
239 @Arguments(setup = "setup")
240 public static Name test(Name n1, Name n2) {
241 return n1.append(n2);
242 }
243 }
244
245 // This test case generates leaP* rules before arrayof_jint_fill intrinsics,
246 // but only with -XX:+UseAVX3.
247 class RegexFindTest {
248 @Setup
249 private static Object[] setup() {
250 Pattern pat = Pattern.compile("27");
251 Matcher m = pat.matcher(" 274 leaPCompressedOopOffset === _ 275 277 [[ 2246 165 294 ]] #16/0x0000000000000010byte[int:>=0]");
252 return new Object[] { m };
253 }
254
255 @Test
256 // TODO: Make tests more precise
257 @IR(counts = {IRNode.LEA_P, "=1"},
258 phase = {CompilePhase.FINAL_CODE},
259 applyIfPlatform = {"mac", "false"})
260 // Due to unpredictable code generation, we cannot match the exact number of decodes below.
261 // Negative test
262 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, ">=7"},
263 phase = {CompilePhase.FINAL_CODE},
264 applyIfAnd = {"OptoPeephole", "false", "UseAVX", "=3"})
265 // Test that the peephole worked for leaPCompressedOopOffset
266 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, ">=6"},
267 phase = {CompilePhase.FINAL_CODE},
268 applyIfAnd = {"OptoPeephole", "true", "UseAVX", "=3"})
269 @Arguments(setup = "setup")
270 public boolean test(Matcher m) {
271 return m.find();
272 }
273 }
274
275 // The matcher generates leaP* rules for storing an object in an array of objects
276 // at a constant offset, but only when using the Serial or Parallel GC.
277 // Here, we can also manipulate the offset such that we get a leaP32Narrow rule
278 // and we can demonstrate that the peephole also removes simple cases of unneeded
279 // spills.
280 class StoreNTest {
281 private static final int SOME_SIZE = 42;
282 private static final int OFFSET8BIT_IDX = 3;
283 private static final int OFFSET32BIT_IDX = 33;
284
285 private static final Object CURRENT = new Object();
286 private static final Object OTHER = new Object();
287
288 private StoreNTestHelper[] classArr8bit = new StoreNTestHelper[SOME_SIZE];
289 private StoreNTestHelper[] classArr32bit = new StoreNTestHelper[SOME_SIZE];
290 private Object[] objArr8bit = new Object[SOME_SIZE];
291 private Object[] objArr32bit = new Object[SOME_SIZE];
292
293 @Test
294 @IR(counts = {IRNode.LEA_P, "=2"},
295 phase = {CompilePhase.FINAL_CODE},
296 applyIfPlatform = {"mac", "false"})
297 // Negative test
298 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=2"},
299 phase = {CompilePhase.FINAL_CODE},
300 applyIf = {"OptoPeephole", "false"})
301 // Test that the peephole worked for leaPCompressedOopOffset
302 @IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
303 phase = {CompilePhase.FINAL_CODE},
304 applyIf = {"OptoPeephole", "true"})
305 // Test that the peephole removes a spill.
306 @IR(counts = {IRNode.MEM_TO_REG_SPILL_COPY, "=4"},
307 phase = {CompilePhase.FINAL_CODE},
308 applyIfAnd ={"OptoPeephole", "false", "UseCompactObjectHeaders", "false"})
309 @IR(counts = {IRNode.MEM_TO_REG_SPILL_COPY, "=3"},
310 phase = {CompilePhase.FINAL_CODE},
311 applyIfAnd ={"OptoPeephole", "true", "UseCompactObjectHeaders", "false"})
312 public void testRemoveSpill() {
313 this.classArr8bit[OFFSET8BIT_IDX] = new StoreNTestHelper(CURRENT, OTHER);
314 this.classArr32bit[OFFSET32BIT_IDX] = new StoreNTestHelper(OTHER, CURRENT);
315 }
316
317 // This variation of the test above generates a split spill register path.
318 // Due to the complicated graph structure with the phis, the peephole
319 // cannot remove the redundant decode shared by both leaP*s.
320 @Test
321 @IR(counts = {IRNode.LEA_P, "=2"},
322 phase = {CompilePhase.FINAL_CODE},
323 applyIfPlatform = {"mac", "false"})
324 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
325 phase = {CompilePhase.FINAL_CODE},
326 applyIf = {"OptoPeephole", "false"})
327 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
328 phase = {CompilePhase.FINAL_CODE},
329 applyIf = {"OptoPeephole", "true"})
330 public void testPhiSpill() {
331 this.classArr8bit[OFFSET8BIT_IDX] = new StoreNTestHelper(CURRENT, OTHER);
332 this.classArr8bit[OFFSET32BIT_IDX] = new StoreNTestHelper(CURRENT, OTHER);
333 }
334
335 @Test
336 @IR(counts = {IRNode.LEA_P, "=2"},
337 phase = {CompilePhase.FINAL_CODE},
338 applyIfPlatform = {"mac", "false"})
339 // Negative test
340 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=2"},
341 phase = {CompilePhase.FINAL_CODE},
342 applyIf = {"OptoPeephole", "false"})
343 // Test that the peephole worked for leaPCompressedOopOffset
344 @IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
345 phase = {CompilePhase.FINAL_CODE},
346 applyIf = {"OptoPeephole", "true"})
347 public void testNoAlloc() {
348 this.objArr8bit[OFFSET8BIT_IDX] = CURRENT;
349 this.objArr32bit[OFFSET32BIT_IDX] = OTHER;
350 }
351
352 @Test
353 @IR(counts = {IRNode.LEA_P, "=2"},
354 phase = {CompilePhase.FINAL_CODE},
355 applyIfPlatform = {"mac", "false"})
356 // Negative test
357 @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
358 phase = {CompilePhase.FINAL_CODE},
359 applyIf = {"OptoPeephole", "false"})
360 // Test that the peephole worked for leaPCompressedOopOffset
361 @IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
362 phase = {CompilePhase.FINAL_CODE},
363 applyIf = {"OptoPeephole", "true"})
364 public void testNoAllocSameArray() {
365 this.objArr8bit[OFFSET8BIT_IDX] = CURRENT;
366 this.objArr8bit[OFFSET32BIT_IDX] = OTHER;
367 }
368 }
369
370 class StoreNTestHelper {
371 Object o1;
372 Object o2;
373
374 public StoreNTestHelper(Object o1, Object o2) {
375 this.o1 = o1;
376 this.o2 = o2;
377 }
378 }