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 }