jdk/test/hotspot/jtreg/compiler/codegen/TestRedundantLea.java
Manuel Hässig 2349304bb1 8361040: compiler/codegen/TestRedundantLea.java#StringInflate fails with failed IR rules
Co-authored-by: Matthias Baesken <mbaesken@openjdk.org>
Reviewed-by: chagedorn, mbaesken
2025-07-08 12:44:36 +00:00

379 lines
14 KiB
Java

/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test id=GetAndSet
* @bug 8020282
* @summary Test that we do not generate redundant leas on x86 for AtomicReference.getAndSet.
* @requires os.simpleArch == "x64" & vm.opt.TieredCompilation != false
* @modules jdk.compiler/com.sun.tools.javac.util
* @library /test/lib /
* @run driver compiler.codegen.TestRedundantLea GetAndSet
*/
/*
* @test id=StringEquals
* @bug 8020282
* @summary Test that we do not generate redundant leas on x86 for String.Equals.
* @requires os.simpleArch == "x64" & vm.opt.TieredCompilation != false
* @modules jdk.compiler/com.sun.tools.javac.util
* @library /test/lib /
* @run driver compiler.codegen.TestRedundantLea StringEquals
*/
/*
* @test id=StringInflate
* @bug 8020282
* @summary Test that we do not generate redundant leas on x86 for StringConcat intrinsics.
* @requires os.simpleArch == "x64" & vm.opt.TieredCompilation != false
* @modules jdk.compiler/com.sun.tools.javac.util
* @library /test/lib /
* @run driver compiler.codegen.TestRedundantLea StringInflate
*/
/*
* @test id=RegexFind
* @bug 8020282
* @summary Test that we do not generate redundant leas on x86 when performing regex matching.
* @requires os.simpleArch == "x64" & vm.opt.TieredCompilation != false & vm.opt.UseAvx == 3
* @modules jdk.compiler/com.sun.tools.javac.util
* @library /test/lib /
* @run driver compiler.codegen.TestRedundantLea RegexFind
*/
/*
* @test id=StoreNSerial
* @bug 8020282
* @summary Test that we do not generate redundant leas on x86 when storing narrow oops to object arrays.
* @requires os.simpleArch == "x64" & vm.gc.Serial
* @modules jdk.compiler/com.sun.tools.javac.util
* @library /test/lib /
* @run driver compiler.codegen.TestRedundantLea StoreNSerial
*/
/*
* @test id=StoreNParallel
* @bug 8020282
* @summary Test that we do not generate redundant leas on x86 when storing narrow oops to object arrays.
* @requires os.simpleArch == "x64" & vm.gc.Parallel
* @modules jdk.compiler/com.sun.tools.javac.util
* @library /test/lib /
* @run driver compiler.codegen.TestRedundantLea StoreNParallel
*/
package compiler.codegen;
import java.util.concurrent.atomic.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.sun.tools.javac.util.*;
import compiler.lib.ir_framework.*;
// The following tests ensure that we do not generate a redundant lea instruction on x86.
// These get generated on chained dereferences for the rules leaPCompressedOopOffset,
// leaP8Narrow, and leaP32Narrow and stem from a decodeHeapOopNotNull that is not needed
// unless the derived oop is added to an oop map. The redundant lea is removed with an
// opto assembly peephole optimization. Hence, all tests below feature a negative test
// run with -XX:-OptoPeephole to detect changes that obsolete that peephole.
// Further, all tests are run with different max heap sizes to trigger the generation of
// different lea match rules: -XX:MaxHeapSize=32m generates leaP(8|32)Narrow and
// -XX:MaxHeapSize=4g generates leaPCompressedOopOffset, since the address computation
// needs to shift left by 3.
public class TestRedundantLea {
public static void main(String[] args) {
String testName = args[0];
TestFramework framework;
switch (testName) {
case "GetAndSet" -> {
framework = new TestFramework(GetAndSetTest.class);
}
case "StringEquals" -> {
framework = new TestFramework(StringEqualsTest.class);
framework.addHelperClasses(StringEqualsTestHelper.class);
}
case "StringInflate" -> {
framework = new TestFramework(StringInflateTest.class);
framework.addFlags("--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED");
}
case "RegexFind" -> {
framework = new TestFramework(RegexFindTest.class);
}
case "StoreNSerial" -> {
framework = new TestFramework(StoreNTest.class);
framework.addFlags("-XX:+UseSerialGC");
}
case "StoreNParallel" -> {
framework = new TestFramework(StoreNTest.class);
framework.addFlags("-XX:+UseParallelGC");
}
default -> {
throw new IllegalArgumentException("Unknown test name \"" + testName +"\"");
}
}
Scenario[] scenarios = new Scenario[2];
// Scenario for the negative test without peephole optimizations.
scenarios[0] = new Scenario(0, "-XX:+IgnoreUnrecognizedVMOptions", "-XX:-OptoPeephole");
// Scenario for the positive test with +OptoPeephole (the default on x64).
scenarios[1] = new Scenario(1);
framework.addScenarios(scenarios).start();
}
}
// This generates a leaP* rule for the chained dereference of obj.value that
// gets passed to the get and set VM intrinsic.
class GetAndSetTest {
private static final Object CURRENT = new Object();
private final AtomicReference<Object> obj = new AtomicReference<Object>();
@Test
@IR(counts = {IRNode.LEA_P, "=1"},
phase = {CompilePhase.FINAL_CODE},
applyIfPlatform = {"mac", "false"})
// Negative test
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
// Test that the peephole worked for leaP(8|32)Narrow
@IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
public void testGetAndSet() {
obj.getAndSet(CURRENT);
}
}
// This generates leaP* rules for the chained dereferences of the String.value
// fields that are used in the string_equals VM intrinsic.
class StringEqualsTest {
final StringEqualsTestHelper strEqHelper = new StringEqualsTestHelper("I am the string that is tested against");
@Setup
private static Object[] setup() {
return new Object[]{"I will be compared!"};
}
@Test
@IR(counts = {IRNode.LEA_P, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIfPlatform = {"mac", "false"})
// Negative test
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=3"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
// Test that the peephole worked for leaPCompressedOopOffset
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
@Arguments(setup = "setup")
public boolean test(String str) {
return strEqHelper.doEquals(str);
}
}
class StringEqualsTestHelper {
private String str;
public StringEqualsTestHelper(String str) {
this.str = str;
}
@ForceInline
public boolean doEquals(String other) {
return this.str.equals(other);
}
}
// With all VM instrinsics disabled, this test only generates a leaP* rule
// before the string_inflate intrinsic (with -XX:-OptimizeStringConcat no
// leaP* rule is generated). With VM intrinsics enabled (this is the case
// here) leaP* rules are also generated for the string_equals and arrays_hashcode
// VM instrinsics.
// This generates a larger number of decodes for -XX:UseAVX={0,1} than for
// other flags.
class StringInflateTest {
@Setup
private static Object[] setup() {
Names names = new Names(new Context());
Name n1 = names.fromString("one");
Name n2 = names.fromString("two");
return new Object[] {n1, n2};
}
@Test
// TODO: Make tests more precise
@IR(counts = {IRNode.LEA_P, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIfPlatform = {"mac", "false"})
// Negative
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, ">=5"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
// 2 decodes get removed
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, ">=3"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
@Arguments(setup = "setup")
public static Name test(Name n1, Name n2) {
return n1.append(n2);
}
}
// This test case generates leaP* rules before arrayof_jint_fill intrinsics,
// but only with -XX:+UseAVX3.
class RegexFindTest {
@Setup
private static Object[] setup() {
Pattern pat = Pattern.compile("27");
Matcher m = pat.matcher(" 274 leaPCompressedOopOffset === _ 275 277 [[ 2246 165 294 ]] #16/0x0000000000000010byte[int:>=0]");
return new Object[] { m };
}
@Test
// TODO: Make tests more precise
@IR(counts = {IRNode.LEA_P, "=1"},
phase = {CompilePhase.FINAL_CODE},
applyIfPlatform = {"mac", "false"})
// Due to unpredictable code generation, we cannot match the exact number of decodes below.
// Negative test
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, ">=7"},
phase = {CompilePhase.FINAL_CODE},
applyIfAnd = {"OptoPeephole", "false", "UseAVX", "=3"})
// Test that the peephole worked for leaPCompressedOopOffset
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, ">=6"},
phase = {CompilePhase.FINAL_CODE},
applyIfAnd = {"OptoPeephole", "true", "UseAVX", "=3"})
@Arguments(setup = "setup")
public boolean test(Matcher m) {
return m.find();
}
}
// The matcher generates leaP* rules for storing an object in an array of objects
// at a constant offset, but only when using the Serial or Parallel GC.
// Here, we can also manipulate the offset such that we get a leaP32Narrow rule
// and we can demonstrate that the peephole also removes simple cases of unneeded
// spills.
class StoreNTest {
private static final int SOME_SIZE = 42;
private static final int OFFSET8BIT_IDX = 3;
private static final int OFFSET32BIT_IDX = 33;
private static final Object CURRENT = new Object();
private static final Object OTHER = new Object();
private StoreNTestHelper[] classArr8bit = new StoreNTestHelper[SOME_SIZE];
private StoreNTestHelper[] classArr32bit = new StoreNTestHelper[SOME_SIZE];
private Object[] objArr8bit = new Object[SOME_SIZE];
private Object[] objArr32bit = new Object[SOME_SIZE];
@Test
@IR(counts = {IRNode.LEA_P, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIfPlatform = {"mac", "false"})
// Negative test
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
// Test that the peephole worked for leaPCompressedOopOffset
@IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
// Test that the peephole removes a spill.
@IR(counts = {IRNode.MEM_TO_REG_SPILL_COPY, "=4"},
phase = {CompilePhase.FINAL_CODE},
applyIfAnd ={"OptoPeephole", "false", "UseCompactObjectHeaders", "false"})
@IR(counts = {IRNode.MEM_TO_REG_SPILL_COPY, "=3"},
phase = {CompilePhase.FINAL_CODE},
applyIfAnd ={"OptoPeephole", "true", "UseCompactObjectHeaders", "false"})
public void testRemoveSpill() {
this.classArr8bit[OFFSET8BIT_IDX] = new StoreNTestHelper(CURRENT, OTHER);
this.classArr32bit[OFFSET32BIT_IDX] = new StoreNTestHelper(OTHER, CURRENT);
}
// This variation of the test above generates a split spill register path.
// Due to the complicated graph structure with the phis, the peephole
// cannot remove the redundant decode shared by both leaP*s.
@Test
@IR(counts = {IRNode.LEA_P, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIfPlatform = {"mac", "false"})
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
public void testPhiSpill() {
this.classArr8bit[OFFSET8BIT_IDX] = new StoreNTestHelper(CURRENT, OTHER);
this.classArr8bit[OFFSET32BIT_IDX] = new StoreNTestHelper(CURRENT, OTHER);
}
@Test
@IR(counts = {IRNode.LEA_P, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIfPlatform = {"mac", "false"})
// Negative test
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
// Test that the peephole worked for leaPCompressedOopOffset
@IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
public void testNoAlloc() {
this.objArr8bit[OFFSET8BIT_IDX] = CURRENT;
this.objArr32bit[OFFSET32BIT_IDX] = OTHER;
}
@Test
@IR(counts = {IRNode.LEA_P, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIfPlatform = {"mac", "false"})
// Negative test
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
// Test that the peephole worked for leaPCompressedOopOffset
@IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
public void testNoAllocSameArray() {
this.objArr8bit[OFFSET8BIT_IDX] = CURRENT;
this.objArr8bit[OFFSET32BIT_IDX] = OTHER;
}
}
class StoreNTestHelper {
Object o1;
Object o2;
public StoreNTestHelper(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
}