/*
 * Copyright (c) 2023, 2024, 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
 * @summary Test pinned objects lifecycle from young gen to eventual reclamation.
 * @requires vm.gc.G1
 * @library /test/lib
 * @modules java.base/jdk.internal.misc
 *          java.management
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run driver gc.g1.pinnedobjs.TestPinnedObjectsEvacuation
 */

package gc.g1.pinnedobjs;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jdk.test.lib.Asserts;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import jdk.test.whitebox.WhiteBox;

public class TestPinnedObjectsEvacuation {

    public static void main(String[] args) throws Exception {
        testPinnedEvacuation(0, 0, 0, 1);
        testPinnedEvacuation(1, 1, 0, 1);
        testPinnedEvacuation(2, 1, 1, 0);
        testPinnedEvacuation(3, 1, 1, 0);
    }

    private static int numMatches(String stringToMatch, String pattern) {
        Pattern r = Pattern.compile(pattern);
        Matcher m = r.matcher(stringToMatch);
        return (int)m.results().count();
    }

    private static void assertMatches(int expected, int actual, String what) {
        if (expected != actual) {
          Asserts.fail("Expected " + expected + " " + what + " events but got " + actual);
        }
    }

    private static void testPinnedEvacuation(int younGCsBeforeUnpin, int expectedSkipEvents, int expectedDropEvents, int expectedReclaimEvents) throws Exception {
        OutputAnalyzer output = ProcessTools.executeLimitedTestJava("-XX:+UseG1GC",
                                                                    "-XX:+UnlockDiagnosticVMOptions",
                                                                    "-XX:+WhiteBoxAPI",
                                                                    "-Xbootclasspath/a:.",
                                                                    "-Xmx32M",
                                                                    "-Xmn16M",
                                                                    "-XX:G1NumCollectionsKeepPinned=2",
                                                                    "-XX:+VerifyAfterGC",
                                                                    "-Xlog:gc,gc+ergo+cset=trace",
                                                                    TestObjectPin.class.getName(),
                                                                    String.valueOf(younGCsBeforeUnpin));

        System.out.println(output.getStdout());
        output.shouldHaveExitValue(0);

        assertMatches(expectedSkipEvents, numMatches(output.getStdout(), ".*Retained candidate \\d+ can not be reclaimed currently. Skipping.*"), "skip");
        assertMatches(expectedDropEvents, numMatches(output.getStdout(), ".*Retained candidate \\d+ can not be reclaimed currently. Dropping.*"), "drop");
        assertMatches(expectedReclaimEvents, numMatches(output.getStdout(), ".*Finish adding retained candidates to collection set. Initial: 1,.*"), "reclaim");
    }

}

class TestObjectPin {

    private static final WhiteBox wb = WhiteBox.getWhiteBox();

    public static long pinAndGetAddress(Object o) {
        wb.pinObject(o);
        return wb.getObjectAddress(o);
    }

    public static void unpinAndCompareAddress(Object o, long expectedAddress) {
        Asserts.assertEQ(expectedAddress, wb.getObjectAddress(o), "Object has moved during pinning.");
        wb.unpinObject(o);
    }

    public static void main(String[] args) {

        int youngGCBeforeUnpin = Integer.parseInt(args[0]);

        // Remove garbage from VM initialization.
        wb.fullGC();

        Object o = new int[100];
        Asserts.assertTrue(!wb.isObjectInOldGen(o), "should not be pinned in old gen");

        long address = pinAndGetAddress(o);

        // First young GC: should move the object into old gen.
        wb.youngGC();
        Asserts.assertTrue(wb.isObjectInOldGen(o), "Pinned object not in old gen after young GC");

        // The object is (still) pinned. Do some configurable young gcs that fail to add it to the
        // collection set candidates.
        for (int i = 0; i < youngGCBeforeUnpin; i++) {
          wb.youngGC();
        }
        unpinAndCompareAddress(o, address);

        // Unpinned the object. This next gc should take the region if not dropped.
        wb.youngGC();
    }
}
