/*
 * 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
    @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},
        applyIfAnd = {"OptoPeephole", "false", "UseAVX", ">=2"})
    @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=13"},
        phase = {CompilePhase.FINAL_CODE},
        applyIfAnd = {"OptoPeephole", "false", "UseAVX", "<2"})
    // 2 decodes get removed
    @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=3"},
        phase = {CompilePhase.FINAL_CODE},
        applyIfAnd = {"OptoPeephole", "true", "UseAVX", ">=2"})
    @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=11"},
        phase = {CompilePhase.FINAL_CODE},
        applyIfAnd = {"OptoPeephole", "true", "UseAVX", "<2"})
    @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
    @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;
    }
}
