From 4e05748f0899cabb235c71ecdf4256d4ad137a0d Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 17 Dec 2025 18:17:24 +0000 Subject: [PATCH 001/406] 8373716: Refactor further java/util tests from TestNG to JUnit Reviewed-by: naoto --- .../Calendar/CalendarDisplayNamesTest.java | 12 +++--- .../util/Calendar/JapaneseLenientEraTest.java | 21 +++++----- .../SupplementalJapaneseEraTestRun.java | 16 ++++---- .../util/Properties/CompatibilityTest.java | 19 +++++----- .../java/util/Properties/EncodingTest.java | 18 +++++---- .../java/util/Properties/InitialCapacity.java | 14 ++++--- .../Properties/PropertiesEntrySetTest.java | 38 +++++++++---------- .../util/Properties/PropertiesStoreTest.java | 38 ++++++++++--------- .../modules/basic/BasicTest.java | 18 +++++---- .../modules/cache/CacheTest.java | 15 ++++---- .../CaseInsensitiveNameClash.java | 14 ++++--- .../modules/visibility/VisibilityTest.java | 36 +++++++++--------- .../java/util/TimeZone/NegativeDSTTest.java | 23 +++++------ .../util/TimeZone/ZoneIdRoundTripTest.java | 21 +++++----- 14 files changed, 162 insertions(+), 141 deletions(-) diff --git a/test/jdk/java/util/Calendar/CalendarDisplayNamesTest.java b/test/jdk/java/util/Calendar/CalendarDisplayNamesTest.java index 7c40714fc02..171bea55fcf 100644 --- a/test/jdk/java/util/Calendar/CalendarDisplayNamesTest.java +++ b/test/jdk/java/util/Calendar/CalendarDisplayNamesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -21,21 +21,21 @@ * questions. */ -import org.testng.Assert; -import org.testng.annotations.Test; import java.util.Calendar; import java.util.Locale; import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; /** * @test * @bug 8262108 8174269 * @summary Verify the results returned by Calendar.getDisplayNames() API * @comment Locale providers: CLDR,SPI - * @run testng/othervm -Djava.locale.providers=CLDR,SPI CalendarDisplayNamesTest + * @run junit/othervm -Djava.locale.providers=CLDR,SPI CalendarDisplayNamesTest * @comment Locale providers: default - * @run testng CalendarDisplayNamesTest + * @run junit CalendarDisplayNamesTest */ public class CalendarDisplayNamesTest { @@ -55,7 +55,7 @@ public class CalendarDisplayNamesTest { continue; } for (final Integer fieldValue : names.values()) { - Assert.assertTrue(fieldValue == Calendar.AM || fieldValue == Calendar.PM, + Assertions.assertTrue(fieldValue == Calendar.AM || fieldValue == Calendar.PM, "Invalid field value " + fieldValue + " for calendar field AM_PM, in locale " + locale + " with style " + style); } diff --git a/test/jdk/java/util/Calendar/JapaneseLenientEraTest.java b/test/jdk/java/util/Calendar/JapaneseLenientEraTest.java index 6a909a23a18..ca726afc29b 100644 --- a/test/jdk/java/util/Calendar/JapaneseLenientEraTest.java +++ b/test/jdk/java/util/Calendar/JapaneseLenientEraTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -25,7 +25,7 @@ * @test * @bug 8206120 * @summary Test whether lenient era is accepted in JapaneseImperialCalendar - * @run testng/othervm JapaneseLenientEraTest + * @run junit/othervm JapaneseLenientEraTest */ import java.text.DateFormat; @@ -34,15 +34,15 @@ import java.util.Calendar; import java.util.Date; import java.util.Locale; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -@Test +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class JapaneseLenientEraTest { - @DataProvider(name="lenientEra") - Object[][] names() { + Object[][] lenientEra() { return new Object[][] { // lenient era/year, strict era/year { "Meiji 123", "Heisei 2" }, @@ -51,7 +51,8 @@ public class JapaneseLenientEraTest { }; } - @Test(dataProvider="lenientEra") + @ParameterizedTest + @MethodSource("lenientEra") public void testLenientEra(String lenient, String strict) throws Exception { Calendar c = new Calendar.Builder() .setCalendarType("japanese") @@ -61,6 +62,6 @@ public class JapaneseLenientEraTest { Date lenDate = df.parse(lenient + "-01-01"); df.setLenient(false); Date strDate = df.parse(strict + "-01-01"); - assertEquals(lenDate, strDate); + assertEquals(strDate, lenDate); } } diff --git a/test/jdk/java/util/Calendar/SupplementalJapaneseEraTestRun.java b/test/jdk/java/util/Calendar/SupplementalJapaneseEraTestRun.java index 8eac4a97ef7..878955fdcc4 100644 --- a/test/jdk/java/util/Calendar/SupplementalJapaneseEraTestRun.java +++ b/test/jdk/java/util/Calendar/SupplementalJapaneseEraTestRun.java @@ -27,7 +27,7 @@ * @summary Test for jdk.calendar.japanese.supplemental.era support * @library /test/lib * @build SupplementalJapaneseEraTest - * @run testng/othervm SupplementalJapaneseEraTestRun + * @run junit/othervm SupplementalJapaneseEraTestRun */ import java.util.Calendar; @@ -45,11 +45,12 @@ import static java.util.Calendar.YEAR; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.Utils; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class SupplementalJapaneseEraTestRun { - @DataProvider(name = "validprop") Object[][] validPropertyData() { return new Object[][] { //Tests with valid property values @@ -58,7 +59,6 @@ public class SupplementalJapaneseEraTestRun { }; } - @DataProvider(name = "invalidprop") Object[][] invalidPropertyData() { return new Object[][] { //Tests with invalid property values @@ -76,7 +76,8 @@ public class SupplementalJapaneseEraTestRun { }; } - @Test(dataProvider = "validprop") + @ParameterizedTest + @MethodSource("validPropertyData") public void ValidPropertyValuesTest(String prop) throws Throwable { //get the start time of the fictional next era @@ -84,7 +85,8 @@ public class SupplementalJapaneseEraTestRun { testRun(prop + startTime, List.of("-t")); } - @Test(dataProvider = "invalidprop") + @ParameterizedTest + @MethodSource("invalidPropertyData") public void InvalidPropertyValuesTest(String prop) throws Throwable { //get the start time of the fictional next era diff --git a/test/jdk/java/util/Properties/CompatibilityTest.java b/test/jdk/java/util/Properties/CompatibilityTest.java index 29c7be6fbcd..839680806c1 100644 --- a/test/jdk/java/util/Properties/CompatibilityTest.java +++ b/test/jdk/java/util/Properties/CompatibilityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -24,19 +24,19 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Properties; -import org.testng.Assert; - -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /* * @test * @bug 8252354 - * @run testng CompatibilityTest + * @run junit CompatibilityTest * @summary Verify compatibility. */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class CompatibilityTest { - @DataProvider(name = "entries") public Object[][] getEntries() throws IOException { return new Object[][]{ {8, 238923}, @@ -53,9 +53,10 @@ public class CompatibilityTest { * @param value the value * @throws IOException */ - @Test(dataProvider = "entries") + @ParameterizedTest + @MethodSource("getEntries") void testThrows(Object key, Object value) throws IOException { - Assert.assertThrows(ClassCastException.class, () -> storeToXML(key, value)); + Assertions.assertThrows(ClassCastException.class, () -> storeToXML(key, value)); } void storeToXML(Object key, Object value) throws IOException { diff --git a/test/jdk/java/util/Properties/EncodingTest.java b/test/jdk/java/util/Properties/EncodingTest.java index d97730a37c7..069d16155f0 100644 --- a/test/jdk/java/util/Properties/EncodingTest.java +++ b/test/jdk/java/util/Properties/EncodingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -27,19 +27,20 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Properties; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /** * @test * @bug 8183743 * @summary Test to verify the new overload method with Charset functions the * same as the existing method that takes a charset name. - * @run testng EncodingTest + * @run junit EncodingTest */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class EncodingTest { - @DataProvider(name = "parameters") public Object[][] getParameters() throws IOException { return new Object[][]{ {StandardCharsets.UTF_8.name(), null}, @@ -51,7 +52,8 @@ public class EncodingTest { * encoding name or a charset can be read with Properties#loadFromXML that * returns the same Properties object. */ - @Test(dataProvider = "parameters") + @ParameterizedTest + @MethodSource("getParameters") void testLoadAndStore(String encoding, Charset charset) throws IOException { Properties props = new Properties(); props.put("k0", "\u6C34"); @@ -74,6 +76,6 @@ public class EncodingTest { } } - Assert.assertEquals(props, p); + Assertions.assertEquals(p, props); } } diff --git a/test/jdk/java/util/Properties/InitialCapacity.java b/test/jdk/java/util/Properties/InitialCapacity.java index 81e5421bbef..d684c37adf4 100644 --- a/test/jdk/java/util/Properties/InitialCapacity.java +++ b/test/jdk/java/util/Properties/InitialCapacity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -22,18 +22,22 @@ */ import java.util.Properties; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; /* * @test * @bug 8189319 * @summary Test that Properties(int initialCapacity) throws exceptions (or doesn't) as expected - * @run testng InitialCapacity + * @run junit InitialCapacity */ public class InitialCapacity { - @Test(expectedExceptions = IllegalArgumentException.class) - public void negativeInitCap() { Properties p = new Properties(-1); } + @Test + public void negativeInitCap() { Assertions.assertThrows(IllegalArgumentException.class, () -> { + Properties p = new Properties(-1); + }); +} @Test public void positiveInitCap() { Properties p = new Properties(10); } diff --git a/test/jdk/java/util/Properties/PropertiesEntrySetTest.java b/test/jdk/java/util/Properties/PropertiesEntrySetTest.java index d7d58be44dd..f3262f734e3 100644 --- a/test/jdk/java/util/Properties/PropertiesEntrySetTest.java +++ b/test/jdk/java/util/Properties/PropertiesEntrySetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -26,17 +26,17 @@ * @bug 8245694 * @summary tests the entrySet() method of Properties class * @author Yu Li - * @run testng PropertiesEntrySetTest + * @run junit PropertiesEntrySetTest */ -import org.testng.annotations.Test; import java.util.Properties; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; public class PropertiesEntrySetTest { @@ -99,13 +99,13 @@ public class PropertiesEntrySetTest { public void testToString() { Properties a = new Properties(); var aEntrySet = a.entrySet(); - assertEquals(aEntrySet.toString(), "[]"); + assertEquals("[]", aEntrySet.toString()); a.setProperty("p1", "1"); - assertEquals(aEntrySet.toString(), "[p1=1]"); + assertEquals("[p1=1]", aEntrySet.toString()); a.setProperty("p2", "2"); - assertEquals(aEntrySet.size(), 2); + assertEquals(2, aEntrySet.size()); assertTrue(aEntrySet.toString().trim().startsWith("[")); assertTrue(aEntrySet.toString().contains("p1=1")); assertTrue(aEntrySet.toString().contains("p2=2")); @@ -115,18 +115,18 @@ public class PropertiesEntrySetTest { b.setProperty("p2", "2"); b.setProperty("p1", "1"); var bEntrySet = b.entrySet(); - assertEquals(bEntrySet.size(), 2); + assertEquals(2, bEntrySet.size()); assertTrue(bEntrySet.toString().trim().startsWith("[")); assertTrue(bEntrySet.toString().contains("p1=1")); assertTrue(bEntrySet.toString().contains("p2=2")); assertTrue(bEntrySet.toString().trim().endsWith("]")); b.setProperty("p0", "0"); - assertEquals(bEntrySet.size(), 3); + assertEquals(3, bEntrySet.size()); assertTrue(bEntrySet.toString().contains("p0=0")); b.remove("p1"); - assertEquals(bEntrySet.size(), 2); + assertEquals(2, bEntrySet.size()); assertFalse(bEntrySet.toString().contains("p1=1")); assertTrue(bEntrySet.toString().trim().startsWith("[")); assertTrue(bEntrySet.toString().contains("p0=0")); @@ -134,7 +134,7 @@ public class PropertiesEntrySetTest { assertTrue(bEntrySet.toString().trim().endsWith("]")); b.remove("p0", "0"); - assertEquals(bEntrySet.size(), 1); + assertEquals(1, bEntrySet.size()); assertFalse(bEntrySet.toString().contains("p0=0")); assertTrue(bEntrySet.toString().trim().startsWith("[")); assertTrue(bEntrySet.toString().contains("p2=2")); @@ -151,13 +151,13 @@ public class PropertiesEntrySetTest { a.setProperty("p1", "1"); a.setProperty("p2", "2"); var aEntrySet = a.entrySet(); - assertEquals(aEntrySet.size(), 2); + assertEquals(2, aEntrySet.size()); var i = aEntrySet.iterator(); var e1 = i.next(); i.remove(); assertFalse(aEntrySet.contains(e1)); - assertEquals(aEntrySet.size(), 1); + assertEquals(1, aEntrySet.size()); var e2 = i.next(); aEntrySet.remove(e2); @@ -172,14 +172,14 @@ public class PropertiesEntrySetTest { var bEntrySet = b.entrySet(); assertFalse(bEntrySet.containsAll(aEntrySet)); - assertEquals(bEntrySet.size(), 2); + assertEquals(2, bEntrySet.size()); assertTrue(bEntrySet.removeAll(aEntrySet)); - assertEquals(bEntrySet.size(), 1); + assertEquals(1, bEntrySet.size()); assertTrue(bEntrySet.retainAll(aEntrySet)); assertTrue(bEntrySet.isEmpty()); - assertEquals(aEntrySet.size(), 2); + assertEquals(2, aEntrySet.size()); aEntrySet.clear(); assertTrue(aEntrySet.isEmpty()); diff --git a/test/jdk/java/util/Properties/PropertiesStoreTest.java b/test/jdk/java/util/Properties/PropertiesStoreTest.java index b5a5b5a45aa..88c24698a14 100644 --- a/test/jdk/java/util/Properties/PropertiesStoreTest.java +++ b/test/jdk/java/util/Properties/PropertiesStoreTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -21,9 +21,6 @@ * questions. */ -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import java.io.BufferedReader; import java.io.IOException; @@ -45,13 +42,18 @@ import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /* * @test * @summary tests the order in which the Properties.store() method writes out the properties * @bug 8231640 8282023 - * @run testng/othervm PropertiesStoreTest + * @run junit/othervm PropertiesStoreTest */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class PropertiesStoreTest { private static final String DATE_FORMAT_PATTERN = "EEE MMM dd HH:mm:ss zzz uuuu"; @@ -60,7 +62,6 @@ public class PropertiesStoreTest { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT_PATTERN, Locale.US); private static final Locale PREV_LOCALE = Locale.getDefault(); - @DataProvider(name = "propsProvider") private Object[][] createProps() { final Properties simple = new Properties(); simple.setProperty("1", "one"); @@ -101,7 +102,6 @@ public class PropertiesStoreTest { /** * Returns a {@link Locale} to use for testing */ - @DataProvider(name = "localeProvider") private Object[][] provideLocales() { // pick a non-english locale for testing Set locales = Arrays.stream(Locale.getAvailableLocales()) @@ -122,7 +122,8 @@ public class PropertiesStoreTest { * Tests that the {@link Properties#store(Writer, String)} API writes out the properties * in the expected order */ - @Test(dataProvider = "propsProvider") + @ParameterizedTest + @MethodSource("createProps") public void testStoreWriterKeyOrder(final Properties props, final String[] expectedOrder) throws Exception { // Properties.store(...) to a temp file final Path tmpFile = Files.createTempFile("8231640", "props"); @@ -136,7 +137,8 @@ public class PropertiesStoreTest { * Tests that the {@link Properties#store(OutputStream, String)} API writes out the properties * in the expected order */ - @Test(dataProvider = "propsProvider") + @ParameterizedTest + @MethodSource("createProps") public void testStoreOutputStreamKeyOrder(final Properties props, final String[] expectedOrder) throws Exception { // Properties.store(...) to a temp file final Path tmpFile = Files.createTempFile("8231640", "props"); @@ -161,7 +163,7 @@ public class PropertiesStoreTest { try (final InputStream is = Files.newInputStream(storedProps)) { loaded.load(is); } - Assert.assertEquals(loaded, props, "Unexpected properties loaded from stored state"); + Assertions.assertEquals(props, loaded, "Unexpected properties loaded from stored state"); // now read lines from the stored file and keep track of the order in which the keys were // found in that file. Compare that order with the expected store order of the keys. @@ -169,10 +171,10 @@ public class PropertiesStoreTest { try (final BufferedReader reader = Files.newBufferedReader(storedProps)) { actualOrder = readInOrder(reader); } - Assert.assertEquals(actualOrder.size(), expectedOrder.length, + Assertions.assertEquals(expectedOrder.length, actualOrder.size(), "Unexpected number of keys read from stored properties"); if (!Arrays.equals(actualOrder.toArray(new String[0]), expectedOrder)) { - Assert.fail("Unexpected order of stored property keys. Expected order: " + Arrays.toString(expectedOrder) + Assertions.fail("Unexpected order of stored property keys. Expected order: " + Arrays.toString(expectedOrder) + ", found order: " + actualOrder); } } @@ -180,7 +182,8 @@ public class PropertiesStoreTest { /** * Tests that {@link Properties#store(Writer, String)} writes out a proper date comment */ - @Test(dataProvider = "localeProvider") + @ParameterizedTest + @MethodSource("provideLocales") public void testStoreWriterDateComment(final Locale testLocale) throws Exception { // switch the default locale to the one being tested Locale.setDefault(testLocale); @@ -202,7 +205,8 @@ public class PropertiesStoreTest { /** * Tests that {@link Properties#store(OutputStream, String)} writes out a proper date comment */ - @Test(dataProvider = "localeProvider") + @ParameterizedTest + @MethodSource("provideLocales") public void testStoreOutputStreamDateComment(final Locale testLocale) throws Exception { // switch the default locale to the one being tested Locale.setDefault(testLocale); @@ -232,19 +236,19 @@ public class PropertiesStoreTest { while ((line = reader.readLine()) != null) { if (line.startsWith("#")) { if (comment != null) { - Assert.fail("More than one comment line found in the stored properties file " + file); + Assertions.fail("More than one comment line found in the stored properties file " + file); } comment = line.substring(1); } } } if (comment == null) { - Assert.fail("No comment line found in the stored properties file " + file); + Assertions.fail("No comment line found in the stored properties file " + file); } try { FORMATTER.parse(comment); } catch (DateTimeParseException pe) { - Assert.fail("Unexpected date comment: " + comment, pe); + Assertions.fail("Unexpected date comment: " + comment, pe); } } diff --git a/test/jdk/java/util/ResourceBundle/modules/basic/BasicTest.java b/test/jdk/java/util/ResourceBundle/modules/basic/BasicTest.java index 69f0db83258..c17c4622ecb 100644 --- a/test/jdk/java/util/ResourceBundle/modules/basic/BasicTest.java +++ b/test/jdk/java/util/ResourceBundle/modules/basic/BasicTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -43,7 +43,7 @@ * jdk.test.lib.compiler.CompilerUtils * jdk.test.lib.process.ProcessTools * ModuleTestUtil - * @run testng BasicTest + * @run junit BasicTest */ import java.nio.file.Path; @@ -54,13 +54,15 @@ import jdk.test.lib.JDKToolLauncher; import jdk.test.lib.Utils; import jdk.test.lib.compiler.CompilerUtils; import jdk.test.lib.process.ProcessTools; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static jdk.test.lib.Asserts.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -@Test +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class BasicTest { private static final String SRC_DIR_APPBASIC = "srcAppbasic"; private static final String SRC_DIR_APPBASIC2 = "srcAppbasic2"; @@ -92,7 +94,6 @@ public class BasicTest { private static final String MAIN = "test/jdk.test.Main"; - @DataProvider(name = "basicTestData") Object[][] basicTestData() { return new Object[][] { // Named module "test" contains resource bundles for root and en, @@ -122,7 +123,8 @@ public class BasicTest { }; } - @Test(dataProvider = "basicTestData") + @ParameterizedTest + @MethodSource("basicTestData") public void runBasicTest(String src, String mod, List moduleList, List localeList, String resFormat) throws Throwable { Path srcPath = Paths.get(Utils.TEST_SRC, src); diff --git a/test/jdk/java/util/ResourceBundle/modules/cache/CacheTest.java b/test/jdk/java/util/ResourceBundle/modules/cache/CacheTest.java index 5655eb5de2d..df72af38855 100644 --- a/test/jdk/java/util/ResourceBundle/modules/cache/CacheTest.java +++ b/test/jdk/java/util/ResourceBundle/modules/cache/CacheTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -27,7 +27,7 @@ * @library /test/lib * @modules jdk.compiler * @build CacheTest jdk.test.lib.compiler.CompilerUtils - * @run testng CacheTest + * @run junit CacheTest */ import java.nio.file.Files; @@ -37,11 +37,12 @@ import java.nio.file.Paths; import static jdk.test.lib.process.ProcessTools.*; import jdk.test.lib.compiler.CompilerUtils; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; -@Test +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class CacheTest { private static final String TEST_SRC = System.getProperty("test.src"); @@ -55,7 +56,7 @@ public class CacheTest { private static final String MAIN = "test/jdk.test.Main"; private static final String MAIN_CLASS = "jdk.test.Main"; - @BeforeTest + @BeforeAll public void compileTestModules() throws Exception { for (String mn : new String[] {MAIN_BUNDLES_MODULE, TEST_MODULE}) { diff --git a/test/jdk/java/util/ResourceBundle/modules/casesensitive/CaseInsensitiveNameClash.java b/test/jdk/java/util/ResourceBundle/modules/casesensitive/CaseInsensitiveNameClash.java index 914ebf6bbf0..45f52b512c8 100644 --- a/test/jdk/java/util/ResourceBundle/modules/casesensitive/CaseInsensitiveNameClash.java +++ b/test/jdk/java/util/ResourceBundle/modules/casesensitive/CaseInsensitiveNameClash.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -28,7 +28,7 @@ * @modules jdk.compiler * @build jdk.test.lib.compiler.CompilerUtils * jdk.test.lib.process.ProcessTools CaseInsensitiveNameClash - * @run testng CaseInsensitiveNameClash + * @run junit CaseInsensitiveNameClash */ import java.nio.file.Files; @@ -37,10 +37,12 @@ import java.nio.file.Paths; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.compiler.CompilerUtils; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class CaseInsensitiveNameClash { private static final String TEST_SRC = System.getProperty("test.src"); @@ -54,7 +56,7 @@ public class CaseInsensitiveNameClash { /** * Compiles the module used by the test */ - @BeforeTest + @BeforeAll public void compileAll() throws Exception { Path msrc = SRC_DIR.resolve(MODULE); assertTrue(CompilerUtils.compile(msrc, MODS_DIR, diff --git a/test/jdk/java/util/ResourceBundle/modules/visibility/VisibilityTest.java b/test/jdk/java/util/ResourceBundle/modules/visibility/VisibilityTest.java index e0fdb9a93ab..ee9da88d705 100644 --- a/test/jdk/java/util/ResourceBundle/modules/visibility/VisibilityTest.java +++ b/test/jdk/java/util/ResourceBundle/modules/visibility/VisibilityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -34,7 +34,7 @@ * jdk.test.lib.compiler.CompilerUtils * jdk.test.lib.process.ProcessTools * ModuleTestUtil - * @run testng VisibilityTest + * @run junit VisibilityTest */ import java.nio.file.Path; @@ -46,13 +46,13 @@ import jdk.test.lib.JDKToolLauncher; import jdk.test.lib.Utils; import jdk.test.lib.process.ProcessTools; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -import static org.testng.Assert.assertEquals; - -@Test +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class VisibilityTest { private static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "src"); private static final Path MODS_DIR = Paths.get(Utils.TEST_CLASSES, "mods"); @@ -63,7 +63,7 @@ public class VisibilityTest { private static final List MODULE_LIST = List.of("embargo", "exported.named.bundles", "named.bundles", "test"); - @BeforeTest + @BeforeAll public void prepareTestEnv() throws Throwable { MODULE_LIST.forEach(mn -> ModuleTestUtil.prepareModule(SRC_DIR, MODS_DIR, mn, ".properties")); @@ -93,7 +93,6 @@ public class VisibilityTest { * "exported.named.bundle" are exported to unnamed modules. */ - @DataProvider(name = "RunWithTestResData") Object[][] RunWithTestResData() { return new Object[][] { // Tests using jdk.test.TestWithNoModuleArg and jdk.embargo.TestWithNoModuleArg. @@ -188,7 +187,6 @@ public class VisibilityTest { }; } - @DataProvider(name = "RunWithExportedResData") Object[][] RunWithExportedResData() { return new Object[][] { // Tests using jdk.test.TestWithNoModuleArg and jdk.embargo.TestWithNoModuleArg @@ -285,7 +283,6 @@ public class VisibilityTest { }; } - @DataProvider(name = "RunWithPkgResData") Object[][] RunWithPkgResData() { return new Object[][] { // jdk.pkg.resources.* are in an unnamed module. @@ -300,10 +297,11 @@ public class VisibilityTest { /** * Test cases with jdk.test.resources.* */ - @Test(dataProvider = "RunWithTestResData") + @ParameterizedTest + @MethodSource("RunWithTestResData") public void RunWithTestRes(List argsList) throws Throwable { int exitCode = runCmd(argsList); - assertEquals(exitCode, 0, "Execution of the tests with " + assertEquals(0, exitCode, "Execution of the tests with " + "jdk.test.resources.* failed. " + "Unexpected exit code: " + exitCode); } @@ -311,10 +309,11 @@ public class VisibilityTest { /** * Test cases with jdk.test.resources.exported.* */ - @Test(dataProvider = "RunWithExportedResData") + @ParameterizedTest + @MethodSource("RunWithExportedResData") public void RunWithExportedRes(List argsList) throws Throwable { int exitCode = runCmd(argsList); - assertEquals(exitCode, 0, "Execution of the tests with " + assertEquals(0, exitCode, "Execution of the tests with " + "jdk.test.resources.exported.* failed. " + "Unexpected exit code: " + exitCode); } @@ -322,10 +321,11 @@ public class VisibilityTest { /** * Test cases with jdk.pkg.resources.* */ - @Test(dataProvider = "RunWithPkgResData") + @ParameterizedTest + @MethodSource("RunWithPkgResData") public void RunWithPkgRes(List argsList) throws Throwable { int exitCode = runCmd(argsList); - assertEquals(exitCode, 0, "Execution of the tests with " + assertEquals(0, exitCode, "Execution of the tests with " + "jdk.pkg.resources.* failed. " + "Unexpected exit code: " + exitCode); } diff --git a/test/jdk/java/util/TimeZone/NegativeDSTTest.java b/test/jdk/java/util/TimeZone/NegativeDSTTest.java index eb46b8d4b29..ab9438f2388 100644 --- a/test/jdk/java/util/TimeZone/NegativeDSTTest.java +++ b/test/jdk/java/util/TimeZone/NegativeDSTTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -21,7 +21,7 @@ * questions. */ -import static org.testng.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.time.Instant; import java.time.LocalDate; @@ -31,18 +31,19 @@ import java.time.ZoneId; import java.util.Date; import java.util.TimeZone; -import org.testng.annotations.Test; -import org.testng.annotations.DataProvider; -import static org.testng.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /** * @test * @bug 8212970 8324065 * @summary Test whether the savings are positive in time zones that have * negative savings in the source TZ files. - * @run testng NegativeDSTTest + * @run junit NegativeDSTTest */ -@Test +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class NegativeDSTTest { private static final TimeZone DUBLIN = TimeZone.getTimeZone("Europe/Dublin"); @@ -51,7 +52,6 @@ public class NegativeDSTTest { private static final TimeZone CASABLANCA = TimeZone.getTimeZone("Africa/Casablanca"); private static final int ONE_HOUR = 3600_000; - @DataProvider private Object[][] negativeDST () { return new Object[][] { // TimeZone, localDate, offset, isDaylightSavings @@ -88,10 +88,11 @@ public class NegativeDSTTest { }; } - @Test(dataProvider="negativeDST") + @ParameterizedTest + @MethodSource("negativeDST") public void test_NegativeDST(TimeZone tz, LocalDate ld, int offset, boolean isDST) { Date d = Date.from(Instant.from(ZonedDateTime.of(ld, LocalTime.MIN, tz.toZoneId()))); - assertEquals(tz.getOffset(d.getTime()), offset); - assertEquals(tz.inDaylightTime(d), isDST); + assertEquals(offset, tz.getOffset(d.getTime())); + assertEquals(isDST, tz.inDaylightTime(d)); } } diff --git a/test/jdk/java/util/TimeZone/ZoneIdRoundTripTest.java b/test/jdk/java/util/TimeZone/ZoneIdRoundTripTest.java index 0f1eeb88328..16e24f7cb27 100644 --- a/test/jdk/java/util/TimeZone/ZoneIdRoundTripTest.java +++ b/test/jdk/java/util/TimeZone/ZoneIdRoundTripTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -25,20 +25,20 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.util.TimeZone; -import org.testng.annotations.Test; -import org.testng.annotations.DataProvider; -import static org.testng.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /** * @test * @bug 8285844 * @summary Checks round-trips between TimeZone and ZoneId are consistent - * @run testng ZoneIdRoundTripTest + * @run junit ZoneIdRoundTripTest */ -@Test +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class ZoneIdRoundTripTest { - @DataProvider private Object[][] testZoneIds() { return new Object[][] { {ZoneId.of("Z"), 0}, @@ -60,11 +60,12 @@ public class ZoneIdRoundTripTest { }; } - @Test(dataProvider="testZoneIds") + @ParameterizedTest + @MethodSource("testZoneIds") public void test_ZoneIdRoundTrip(ZoneId zid, int offset) { var tz = TimeZone.getTimeZone(zid); - assertEquals(tz.getRawOffset(), offset); - assertEquals(tz.toZoneId().normalized(), zid.normalized()); + assertEquals(offset, tz.getRawOffset()); + assertEquals(zid.normalized(), tz.toZoneId().normalized()); } } From f3a48560b5e3a280f6f76031eb3d475ff9ee49f4 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Wed, 17 Dec 2025 18:44:49 +0000 Subject: [PATCH 002/406] 8373807: test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java getURI() uses "localhost" Reviewed-by: jpai --- .../net/httpclient/websocket/DummyWebSocketServer.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java b/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java index 9034cf9f28a..abc1748e3f2 100644 --- a/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java +++ b/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java @@ -351,7 +351,14 @@ public class DummyWebSocketServer implements Closeable { if (!started.get()) { throw new IllegalStateException("Not yet started"); } - return URI.create("ws://localhost:" + address.getPort()); + String ip = address.getAddress().isAnyLocalAddress() + ? InetAddress.getLoopbackAddress().getHostAddress() + : address.getAddress().getHostAddress(); + if (ip.indexOf(':') >= 0) { + ip = String.format("[%s]", ip); + } + + return URI.create("ws://" + ip + ":" + address.getPort()); } private boolean readRequest(SocketChannel channel, StringBuilder request) From e75726ee03ca4664827ca5d680c02bcf2a96f4ea Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Wed, 17 Dec 2025 20:52:14 +0000 Subject: [PATCH 003/406] 8373832: Test java/lang/invoke/TestVHInvokerCaching.java tests nothing Reviewed-by: jvernee, shade --- test/jdk/java/lang/invoke/TestVHInvokerCaching.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/jdk/java/lang/invoke/TestVHInvokerCaching.java b/test/jdk/java/lang/invoke/TestVHInvokerCaching.java index ccd97f82e9b..0a1ae5914ca 100644 --- a/test/jdk/java/lang/invoke/TestVHInvokerCaching.java +++ b/test/jdk/java/lang/invoke/TestVHInvokerCaching.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -37,7 +37,7 @@ import java.util.ArrayList; import java.util.List; import static java.lang.invoke.MethodHandles.lookup; -import static org.testng.Assert.assertSame; +import static org.testng.Assert.*; public class TestVHInvokerCaching { @@ -74,7 +74,7 @@ public class TestVHInvokerCaching { MethodHandles.Lookup lookup = lookup(); - for (Field field : Holder.class.getFields()) { + for (Field field : Holder.class.getDeclaredFields()) { String fieldName = field.getName(); Class fieldType = field.getType(); @@ -82,6 +82,8 @@ public class TestVHInvokerCaching { testHandles.add(lookup.findVarHandle(Holder.class, fieldName, fieldType)); } + assertFalse(testHandles.isEmpty()); + return testHandles.stream().map(vh -> new Object[]{ vh }).toArray(Object[][]::new); } } From b3fab41460eabf253879d140b55b6b12036c7c10 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Wed, 17 Dec 2025 22:14:39 +0000 Subject: [PATCH 004/406] 8373654: Tests in sources/ should only run once Reviewed-by: shade, lmesnik --- test/hotspot/jtreg/sources/TestIncludesAreSorted.java | 2 ++ test/hotspot/jtreg/sources/TestNoNULL.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java index e0e77992ace..f8694da6c5a 100644 --- a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java +++ b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java @@ -24,6 +24,8 @@ /* * @test * @bug 8343802 + * @comment Only need to run this once, in tier1. + * @requires vm.flagless & vm.debug * @summary Tests that HotSpot C++ files have sorted includes * @build SortIncludes * @run main TestIncludesAreSorted diff --git a/test/hotspot/jtreg/sources/TestNoNULL.java b/test/hotspot/jtreg/sources/TestNoNULL.java index 9c993572aea..b914ea6c799 100644 --- a/test/hotspot/jtreg/sources/TestNoNULL.java +++ b/test/hotspot/jtreg/sources/TestNoNULL.java @@ -24,6 +24,8 @@ /* * @test * @bug 8343802 + * @comment Only need to run this once, in tier1. + * @requires vm.flagless & vm.debug * @summary Test prevent NULL backsliding in hotspot code and tests * @run main TestNoNULL */ From 232b41b2227bc9d03d88d316aa28d0cbe87086f7 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Wed, 17 Dec 2025 22:16:38 +0000 Subject: [PATCH 005/406] 8373392: Replace CDS object subgraphs with @AOTSafeClassInitializer Reviewed-by: liach, heidinga --- src/hotspot/share/cds/aotArtifactFinder.cpp | 6 +- src/hotspot/share/cds/aotClassInitializer.cpp | 6 +- src/hotspot/share/cds/aotMetaspace.cpp | 2 +- src/hotspot/share/cds/cdsConfig.cpp | 10 +- src/hotspot/share/cds/cdsConfig.hpp | 1 - src/hotspot/share/cds/cdsEnumKlass.cpp | 4 +- src/hotspot/share/cds/cdsEnumKlass.hpp | 2 +- src/hotspot/share/cds/cdsHeapVerifier.cpp | 2 +- src/hotspot/share/cds/finalImageRecipes.cpp | 2 + src/hotspot/share/cds/heapShared.cpp | 48 ++-- .../share/classes/java/lang/Byte.java | 2 + .../share/classes/java/lang/Character.java | 2 + .../share/classes/java/lang/Integer.java | 75 +++-- .../share/classes/java/lang/Long.java | 2 + .../share/classes/java/lang/Module.java | 2 + .../share/classes/java/lang/ModuleLayer.java | 2 + .../share/classes/java/lang/Short.java | 2 + .../java/lang/module/Configuration.java | 2 + .../java/util/ImmutableCollections.java | 15 +- .../classes/java/util/jar/Attributes.java | 4 + .../internal/loader/ArchivedClassLoaders.java | 2 + .../jdk/internal/math/FDBigInteger.java | 3 + .../internal/module/ArchivedBootLayer.java | 4 +- .../internal/module/ArchivedModuleGraph.java | 5 +- .../classes/sun/util/locale/BaseLocale.java | 14 +- test/hotspot/jtreg/TEST.groups | 1 + .../cds/SharedSymbolTableBucketSize.java | 44 ++- .../cds/appcds/aotCache/AOTLoggingTag.java | 11 - .../appcds/aotCache/HeapObjectIdentity.java | 261 ++++++++++++++++++ .../cacheObject/ArchiveHeapTestClass.java | 156 +---------- 30 files changed, 456 insertions(+), 236 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/HeapObjectIdentity.java diff --git a/src/hotspot/share/cds/aotArtifactFinder.cpp b/src/hotspot/share/cds/aotArtifactFinder.cpp index 5f346e832a8..f85f1e46520 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.cpp +++ b/src/hotspot/share/cds/aotArtifactFinder.cpp @@ -145,7 +145,7 @@ void AOTArtifactFinder::find_artifacts() { #if INCLUDE_CDS_JAVA_HEAP // Keep scanning until we discover no more class that need to be AOT-initialized. - if (CDSConfig::is_initing_classes_at_dump_time()) { + if (CDSConfig::is_dumping_aot_linked_classes()) { while (_pending_aot_inited_classes->length() > 0) { InstanceKlass* ik = _pending_aot_inited_classes->pop(); HeapShared::copy_and_rescan_aot_inited_mirror(ik); @@ -188,7 +188,7 @@ void AOTArtifactFinder::end_scanning_for_oops() { } void AOTArtifactFinder::add_aot_inited_class(InstanceKlass* ik) { - if (CDSConfig::is_initing_classes_at_dump_time()) { + if (CDSConfig::is_dumping_aot_linked_classes()) { if (RegeneratedClasses::is_regenerated_object(ik)) { precond(RegeneratedClasses::get_original_object(ik)->is_initialized()); } else { @@ -258,7 +258,7 @@ void AOTArtifactFinder::add_cached_instance_class(InstanceKlass* ik) { return; } scan_oops_in_instance_class(ik); - if (ik->is_hidden() && CDSConfig::is_initing_classes_at_dump_time()) { + if (ik->is_hidden() && CDSConfig::is_dumping_aot_linked_classes()) { bool succeed = AOTClassLinker::try_add_candidate(ik); guarantee(succeed, "All cached hidden classes must be aot-linkable"); add_aot_inited_class(ik); diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp index 00db747622f..06fc3af6f30 100644 --- a/src/hotspot/share/cds/aotClassInitializer.cpp +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -40,7 +40,7 @@ DEBUG_ONLY(InstanceKlass* _aot_init_class = nullptr;) bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { assert(!ArchiveBuilder::is_active() || !ArchiveBuilder::current()->is_in_buffer_space(ik), "must be source klass"); - if (!CDSConfig::is_initing_classes_at_dump_time()) { + if (!CDSConfig::is_dumping_aot_linked_classes()) { return false; } @@ -64,7 +64,7 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { // Automatic selection for aot-inited classes // ========================================== // - // When CDSConfig::is_initing_classes_at_dump_time is enabled, + // When CDSConfig::is_dumping_aot_linked_classes is enabled, // AOTArtifactFinder::find_artifacts() finds the classes of all // heap objects that are reachable from HeapShared::_run_time_special_subgraph, // and mark these classes as aot-inited. This preserves the initialized @@ -310,7 +310,7 @@ void AOTClassInitializer::init_test_class(TRAPS) { // // -XX:AOTInitTestClass is NOT a general mechanism for including user-defined objects into // the AOT cache. Therefore, this option is NOT available in product JVM. - if (AOTInitTestClass != nullptr && CDSConfig::is_initing_classes_at_dump_time()) { + if (AOTInitTestClass != nullptr && CDSConfig::is_dumping_aot_linked_classes()) { log_info(aot)("Debug build only: force initialization of AOTInitTestClass %s", AOTInitTestClass); TempNewSymbol class_name = SymbolTable::new_symbol(AOTInitTestClass); Handle app_loader(THREAD, SystemDictionary::java_system_loader()); diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index 098d3baed58..3824a2be3e2 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -1141,7 +1141,7 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS AOTReferenceObjSupport::initialize(CHECK); AOTReferenceObjSupport::stabilize_cached_reference_objects(CHECK); - if (CDSConfig::is_initing_classes_at_dump_time()) { + if (CDSConfig::is_dumping_aot_linked_classes()) { // java.lang.Class::reflectionFactory cannot be archived yet. We set this field // to null, and it will be initialized again at runtime. log_debug(aot)("Resetting Class::reflectionFactory"); diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 86533e212d8..5f6b568dd6e 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -1026,23 +1026,19 @@ void CDSConfig::set_has_aot_linked_classes(bool has_aot_linked_classes) { _has_aot_linked_classes |= has_aot_linked_classes; } -bool CDSConfig::is_initing_classes_at_dump_time() { - return is_dumping_heap() && is_dumping_aot_linked_classes(); -} - bool CDSConfig::is_dumping_invokedynamic() { // Requires is_dumping_aot_linked_classes(). Otherwise the classes of some archived heap // objects used by the archive indy callsites may be replaced at runtime. return AOTInvokeDynamicLinking && is_dumping_aot_linked_classes() && is_dumping_heap(); } -// When we are dumping aot-linked classes and we are able to write archived heap objects, we automatically -// enable the archiving of MethodHandles. This will in turn enable the archiving of MethodTypes and hidden +// When we are dumping aot-linked classes, we automatically enable the archiving of MethodHandles. +// This will in turn enable the archiving of MethodTypes and hidden // classes that are used in the implementation of MethodHandles. // Archived MethodHandles are required for higher-level optimizations such as AOT resolution of invokedynamic // and dynamic proxies. bool CDSConfig::is_dumping_method_handles() { - return is_initing_classes_at_dump_time(); + return is_dumping_aot_linked_classes(); } #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index d199e97eefd..202904e8231 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -187,7 +187,6 @@ public: static void disable_heap_dumping() { CDS_ONLY(_disable_heap_dumping = true); } static bool is_dumping_heap() NOT_CDS_JAVA_HEAP_RETURN_(false); static bool is_loading_heap() NOT_CDS_JAVA_HEAP_RETURN_(false); - static bool is_initing_classes_at_dump_time() NOT_CDS_JAVA_HEAP_RETURN_(false); static bool is_dumping_invokedynamic() NOT_CDS_JAVA_HEAP_RETURN_(false); static bool is_dumping_method_handles() NOT_CDS_JAVA_HEAP_RETURN_(false); diff --git a/src/hotspot/share/cds/cdsEnumKlass.cpp b/src/hotspot/share/cds/cdsEnumKlass.cpp index 1bf6ba4eba8..177d1d6e3ad 100644 --- a/src/hotspot/share/cds/cdsEnumKlass.cpp +++ b/src/hotspot/share/cds/cdsEnumKlass.cpp @@ -40,7 +40,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) { } // !!! This is legacy support for enum classes before JEP 483. This file is not used when -// !!! CDSConfig::is_initing_classes_at_dump_time()==true. +// !!! CDSConfig::is_dumping_aot_linked_classes()==true. // // Java Enum classes have synthetic methods that look like this // enum MyEnum {FOO, BAR} @@ -63,7 +63,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) { void CDSEnumKlass::handle_enum_obj(int level, KlassSubGraphInfo* subgraph_info, oop orig_obj) { - assert(!CDSConfig::is_initing_classes_at_dump_time(), "only for legacy support of enums"); + assert(!CDSConfig::is_dumping_aot_linked_classes(), "only for legacy support of enums"); assert(level > 1, "must never be called at the first (outermost) level"); assert(is_enum_obj(orig_obj), "must be"); diff --git a/src/hotspot/share/cds/cdsEnumKlass.hpp b/src/hotspot/share/cds/cdsEnumKlass.hpp index e6019ff705e..a4829368430 100644 --- a/src/hotspot/share/cds/cdsEnumKlass.hpp +++ b/src/hotspot/share/cds/cdsEnumKlass.hpp @@ -35,7 +35,7 @@ class JavaFieldStream; class KlassSubGraphInfo; // This is legacy support for enum classes before JEP 483. This code is not needed when -// CDSConfig::is_initing_classes_at_dump_time()==true. +// CDSConfig::is_dumping_aot_linked_classes()==true. class CDSEnumKlass: AllStatic { public: static bool is_enum_obj(oop orig_obj); diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index 65063b4b005..3ed0dce1f66 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -156,7 +156,7 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) # undef ADD_EXCL - if (CDSConfig::is_initing_classes_at_dump_time()) { + if (CDSConfig::is_dumping_aot_linked_classes()) { add_shared_secret_accessors(); } ClassLoaderDataGraph::classes_do(this); diff --git a/src/hotspot/share/cds/finalImageRecipes.cpp b/src/hotspot/share/cds/finalImageRecipes.cpp index bf8a760904c..8ba4514dfed 100644 --- a/src/hotspot/share/cds/finalImageRecipes.cpp +++ b/src/hotspot/share/cds/finalImageRecipes.cpp @@ -206,6 +206,8 @@ void FinalImageRecipes::load_all_classes(TRAPS) { if (ik->has_aot_safe_initializer() && (flags & WAS_INITED) != 0) { assert(ik->class_loader() == nullptr, "supported only for boot classes for now"); + ResourceMark rm(THREAD); + log_info(aot, init)("Initializing %s", ik->external_name()); ik->initialize(CHECK); } } diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index f2382289c7d..fdc335f3799 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -209,8 +209,14 @@ static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], Instan } bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) { - return is_subgraph_root_class_of(archive_subgraph_entry_fields, ik) || - is_subgraph_root_class_of(fmg_archive_subgraph_entry_fields, ik); + assert(CDSConfig::is_dumping_heap(), "dump-time only"); + if (!CDSConfig::is_dumping_aot_linked_classes()) { + // Legacy CDS archive support (to be deprecated) + return is_subgraph_root_class_of(archive_subgraph_entry_fields, ik) || + is_subgraph_root_class_of(fmg_archive_subgraph_entry_fields, ik); + } else { + return false; + } } oop HeapShared::CachedOopInfo::orig_referrer() const { @@ -934,12 +940,16 @@ void HeapShared::scan_java_class(Klass* orig_k) { void HeapShared::archive_subgraphs() { assert(CDSConfig::is_dumping_heap(), "must be"); - archive_object_subgraphs(archive_subgraph_entry_fields, - false /* is_full_module_graph */); + if (!CDSConfig::is_dumping_aot_linked_classes()) { + archive_object_subgraphs(archive_subgraph_entry_fields, + false /* is_full_module_graph */); + if (CDSConfig::is_dumping_full_module_graph()) { + archive_object_subgraphs(fmg_archive_subgraph_entry_fields, + true /* is_full_module_graph */); + } + } if (CDSConfig::is_dumping_full_module_graph()) { - archive_object_subgraphs(fmg_archive_subgraph_entry_fields, - true /* is_full_module_graph */); Modules::verify_archived_modules(); } } @@ -1295,8 +1305,10 @@ void HeapShared::resolve_classes(JavaThread* current) { if (!is_archived_heap_in_use()) { return; // nothing to do } - resolve_classes_for_subgraphs(current, archive_subgraph_entry_fields); - resolve_classes_for_subgraphs(current, fmg_archive_subgraph_entry_fields); + if (!CDSConfig::is_using_aot_linked_classes()) { + resolve_classes_for_subgraphs(current, archive_subgraph_entry_fields); + resolve_classes_for_subgraphs(current, fmg_archive_subgraph_entry_fields); + } } void HeapShared::resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]) { @@ -1734,13 +1746,13 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap } } - if (CDSConfig::is_initing_classes_at_dump_time()) { + if (CDSConfig::is_dumping_aot_linked_classes()) { if (java_lang_Class::is_instance(orig_obj)) { orig_obj = scratch_java_mirror(orig_obj); assert(orig_obj != nullptr, "must be archived"); } } else if (java_lang_Class::is_instance(orig_obj) && subgraph_info != _dump_time_special_subgraph) { - // Without CDSConfig::is_initing_classes_at_dump_time(), we only allow archived objects to + // Without CDSConfig::is_dumping_aot_linked_classes(), we only allow archived objects to // point to the mirrors of (1) j.l.Object, (2) primitive classes, and (3) box classes. These are initialized // very early by HeapShared::init_box_classes(). if (orig_obj == vmClasses::Object_klass()->java_mirror() @@ -1808,9 +1820,9 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap orig_obj->oop_iterate(&pusher); } - if (CDSConfig::is_initing_classes_at_dump_time()) { - // The classes of all archived enum instances have been marked as aot-init, - // so there's nothing else to be done in the production run. + if (CDSConfig::is_dumping_aot_linked_classes()) { + // The enum klasses are archived with aot-initialized mirror. + // See AOTClassInitializer::can_archive_initialized_mirror(). } else { // This is legacy support for enum classes before JEP 483 -- we cannot rerun // the enum's in the production run, so special handling is needed. @@ -1949,7 +1961,7 @@ void HeapShared::verify_reachable_objects_from(oop obj) { #endif void HeapShared::check_special_subgraph_classes() { - if (CDSConfig::is_initing_classes_at_dump_time()) { + if (CDSConfig::is_dumping_aot_linked_classes()) { // We can have aot-initialized classes (such as Enums) that can reference objects // of arbitrary types. Currently, we trust the JEP 483 implementation to only // aot-initialize classes that are "safe". @@ -2136,9 +2148,11 @@ void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], void HeapShared::init_subgraph_entry_fields(TRAPS) { assert(CDSConfig::is_dumping_heap(), "must be"); _dump_time_subgraph_info_table = new (mtClass)DumpTimeKlassSubGraphInfoTable(); - init_subgraph_entry_fields(archive_subgraph_entry_fields, CHECK); - if (CDSConfig::is_dumping_full_module_graph()) { - init_subgraph_entry_fields(fmg_archive_subgraph_entry_fields, CHECK); + if (!CDSConfig::is_dumping_aot_linked_classes()) { + init_subgraph_entry_fields(archive_subgraph_entry_fields, CHECK); + if (CDSConfig::is_dumping_full_module_graph()) { + init_subgraph_entry_fields(fmg_archive_subgraph_entry_fields, CHECK); + } } } diff --git a/src/java.base/share/classes/java/lang/Byte.java b/src/java.base/share/classes/java/lang/Byte.java index d9913e354a4..0f3f7f40d05 100644 --- a/src/java.base/share/classes/java/lang/Byte.java +++ b/src/java.base/share/classes/java/lang/Byte.java @@ -26,6 +26,7 @@ package java.lang; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -103,6 +104,7 @@ public final class Byte extends Number implements Comparable, Constable { return Optional.of(DynamicConstantDesc.ofNamed(BSM_EXPLICIT_CAST, DEFAULT_NAME, CD_byte, intValue())); } + @AOTSafeClassInitializer private static final class ByteCache { private ByteCache() {} diff --git a/src/java.base/share/classes/java/lang/Character.java b/src/java.base/share/classes/java/lang/Character.java index b71849eaee7..ffda729a45a 100644 --- a/src/java.base/share/classes/java/lang/Character.java +++ b/src/java.base/share/classes/java/lang/Character.java @@ -26,6 +26,7 @@ package java.lang; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -9379,6 +9380,7 @@ class Character implements java.io.Serializable, Comparable, Constabl this.value = value; } + @AOTSafeClassInitializer private static final class CharacterCache { private CharacterCache(){} diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 2742ec40abf..a9da1c32490 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -28,6 +28,8 @@ package java.lang; import jdk.internal.misc.CDS; import jdk.internal.misc.VM; import jdk.internal.util.DecimalDigits; +import jdk.internal.vm.annotation.AOTRuntimeSetup; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -891,15 +893,20 @@ public final class Integer extends Number * with new Integer object(s) after initialization. */ + @AOTSafeClassInitializer private static final class IntegerCache { static final int low = -128; - static final int high; + @Stable static int high; - @Stable - static final Integer[] cache; + @Stable static Integer[] cache; static Integer[] archivedCache; static { + runtimeSetup(); + } + + @AOTRuntimeSetup + private static void runtimeSetup() { // high value may be configured by property int h = 127; String integerCacheHighPropValue = @@ -915,34 +922,50 @@ public final class Integer extends Number } high = h; - // Load IntegerCache.archivedCache from archive, if possible - CDS.initializeFromArchive(IntegerCache.class); - int size = (high - low) + 1; - - // Use the archived cache if it exists and is large enough - if (archivedCache == null || size > archivedCache.length) { - Integer[] c = new Integer[size]; - int j = low; - // If archive has Integer cache, we must use all instances from it. - // Otherwise, the identity checks between archived Integers and - // runtime-cached Integers would fail. - int archivedSize = (archivedCache == null) ? 0 : archivedCache.length; - for (int i = 0; i < archivedSize; i++) { - c[i] = archivedCache[i]; - assert j == archivedCache[i]; - j++; - } - // Fill the rest of the cache. - for (int i = archivedSize; i < size; i++) { - c[i] = new Integer(j++); - } - archivedCache = c; + Integer[] precomputed = null; + if (cache != null) { + // IntegerCache has been AOT-initialized. + precomputed = cache; + } else { + // Legacy CDS archive support (to be deprecated): + // Load IntegerCache.archivedCache from archive, if possible + CDS.initializeFromArchive(IntegerCache.class); + precomputed = archivedCache; } - cache = archivedCache; + + cache = loadOrInitializeCache(precomputed); + archivedCache = cache; // Legacy CDS archive support (to be deprecated) // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } + private static Integer[] loadOrInitializeCache(Integer[] precomputed) { + int size = (high - low) + 1; + + // Use the precomputed cache if it exists and is large enough + if (precomputed != null && size <= precomputed.length) { + return precomputed; + } + + Integer[] c = new Integer[size]; + int j = low; + // If we loading a precomputed cache (from AOT cache or CDS archive), + // we must use all instances from it. + // Otherwise, the Integers from the AOT cache (or CDS archive) will not + // have the same object identity as items in IntegerCache.cache[]. + int precomputedSize = (precomputed == null) ? 0 : precomputed.length; + for (int i = 0; i < precomputedSize; i++) { + c[i] = precomputed[i]; + assert j == precomputed[i]; + j++; + } + // Fill the rest of the cache. + for (int i = precomputedSize; i < size; i++) { + c[i] = new Integer(j++); + } + return c; + } + private IntegerCache() {} } diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 3077e7c0a38..c5cd9650f2d 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -35,6 +35,7 @@ import java.util.Optional; import jdk.internal.misc.CDS; import jdk.internal.util.DecimalDigits; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -911,6 +912,7 @@ public final class Long extends Number return Long.valueOf(parseLong(s, 10)); } + @AOTSafeClassInitializer private static final class LongCache { private LongCache() {} diff --git a/src/java.base/share/classes/java/lang/Module.java b/src/java.base/share/classes/java/lang/Module.java index cd2b8095ee4..bd04345554b 100644 --- a/src/java.base/share/classes/java/lang/Module.java +++ b/src/java.base/share/classes/java/lang/Module.java @@ -69,6 +69,7 @@ import jdk.internal.module.ServicesCatalog; import jdk.internal.module.Resources; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; /** @@ -391,6 +392,7 @@ public final class Module implements AnnotatedElement { private static final Module EVERYONE_MODULE; private static final Set EVERYONE_SET; + @AOTSafeClassInitializer private static class ArchivedData { private static ArchivedData archivedData; private final Module allUnnamedModule; diff --git a/src/java.base/share/classes/java/lang/ModuleLayer.java b/src/java.base/share/classes/java/lang/ModuleLayer.java index 9d922f787a6..a073de6b14a 100644 --- a/src/java.base/share/classes/java/lang/ModuleLayer.java +++ b/src/java.base/share/classes/java/lang/ModuleLayer.java @@ -53,6 +53,7 @@ import jdk.internal.module.ServicesCatalog; import jdk.internal.misc.CDS; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; /** @@ -145,6 +146,7 @@ import jdk.internal.vm.annotation.Stable; * @see Module#getLayer() */ +@AOTSafeClassInitializer public final class ModuleLayer { // the empty layer (may be initialized from the CDS archive) diff --git a/src/java.base/share/classes/java/lang/Short.java b/src/java.base/share/classes/java/lang/Short.java index 4c64427b6df..920500a7fa3 100644 --- a/src/java.base/share/classes/java/lang/Short.java +++ b/src/java.base/share/classes/java/lang/Short.java @@ -26,6 +26,7 @@ package java.lang; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -230,6 +231,7 @@ public final class Short extends Number implements Comparable, Constable return Optional.of(DynamicConstantDesc.ofNamed(BSM_EXPLICIT_CAST, DEFAULT_NAME, CD_short, intValue())); } + @AOTSafeClassInitializer private static final class ShortCache { private ShortCache() {} diff --git a/src/java.base/share/classes/java/lang/module/Configuration.java b/src/java.base/share/classes/java/lang/module/Configuration.java index a76a32cfb28..40eeddc3f0b 100644 --- a/src/java.base/share/classes/java/lang/module/Configuration.java +++ b/src/java.base/share/classes/java/lang/module/Configuration.java @@ -44,6 +44,7 @@ import java.util.stream.Stream; import jdk.internal.misc.CDS; import jdk.internal.module.ModuleReferenceImpl; import jdk.internal.module.ModuleTarget; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; /** @@ -155,6 +156,7 @@ import jdk.internal.vm.annotation.Stable; * @since 9 * @see java.lang.ModuleLayer */ +@AOTSafeClassInitializer public final class Configuration { // @see Configuration#empty() diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index abc48ff5ed9..e7fe22490da 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -42,6 +42,9 @@ import java.util.function.UnaryOperator; import jdk.internal.access.JavaUtilCollectionAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTRuntimeSetup; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; +import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; /** @@ -52,6 +55,7 @@ import jdk.internal.vm.annotation.Stable; * classes use a serial proxy and thus have no need to declare serialVersionUID. */ @SuppressWarnings("serial") +@AOTSafeClassInitializer class ImmutableCollections { /** * A "salt" value used for randomizing iteration order. This is initialized once @@ -59,14 +63,20 @@ class ImmutableCollections { * it needs to vary sufficiently from one run to the next so that iteration order * will vary between JVM runs. */ - private static final long SALT32L; + @Stable private static long SALT32L; /** * For set and map iteration, we will iterate in "reverse" stochastically, * decided at bootstrap time. */ - private static final boolean REVERSE; + @Stable private static boolean REVERSE; + static { + runtimeSetup(); + } + + @AOTRuntimeSetup + private static void runtimeSetup() { // to generate a reasonably random and well-mixed SALT, use an arbitrary // value (a slice of pi), multiply with a random seed, then pick // the mid 32-bits from the product. By picking a SALT value in the @@ -102,6 +112,7 @@ class ImmutableCollections { static final MapN EMPTY_MAP; static { + // Legacy CDS archive support (to be deprecated) CDS.initializeFromArchive(ImmutableCollections.class); if (archivedObjects == null) { EMPTY = new Object(); diff --git a/src/java.base/share/classes/java/util/jar/Attributes.java b/src/java.base/share/classes/java/util/jar/Attributes.java index 9322bb9acac..20ff81676c9 100644 --- a/src/java.base/share/classes/java/util/jar/Attributes.java +++ b/src/java.base/share/classes/java/util/jar/Attributes.java @@ -36,6 +36,7 @@ import java.util.Objects; import java.util.Set; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; import sun.nio.cs.UTF_8; @@ -60,6 +61,7 @@ import sun.util.logging.PlatformLogger; * @see Manifest * @since 1.2 */ +@AOTSafeClassInitializer public class Attributes implements Map, Cloneable { /** * The attribute name-value mappings. @@ -450,6 +452,7 @@ public class Attributes implements Map, Cloneable { * * @spec jar/jar.html JAR File Specification */ + @AOTSafeClassInitializer public static class Name { private final String name; private final int hashCode; @@ -669,6 +672,7 @@ public class Attributes implements Map, Cloneable { static { + // Legacy CDS archive support (to be deprecated) CDS.initializeFromArchive(Attributes.Name.class); if (KNOWN_NAMES == null) { diff --git a/src/java.base/share/classes/jdk/internal/loader/ArchivedClassLoaders.java b/src/java.base/share/classes/jdk/internal/loader/ArchivedClassLoaders.java index be3425590fc..439772a8789 100644 --- a/src/java.base/share/classes/jdk/internal/loader/ArchivedClassLoaders.java +++ b/src/java.base/share/classes/jdk/internal/loader/ArchivedClassLoaders.java @@ -27,11 +27,13 @@ package jdk.internal.loader; import java.util.Map; import jdk.internal.misc.CDS; import jdk.internal.module.ServicesCatalog; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; /** * Used to archive the built-in class loaders, their services catalogs, and the * package-to-module map used by the built-in class loaders. */ +@AOTSafeClassInitializer class ArchivedClassLoaders { private static ArchivedClassLoaders archivedClassLoaders; diff --git a/src/java.base/share/classes/jdk/internal/math/FDBigInteger.java b/src/java.base/share/classes/jdk/internal/math/FDBigInteger.java index 1ef9dee3a8a..5413226c112 100644 --- a/src/java.base/share/classes/jdk/internal/math/FDBigInteger.java +++ b/src/java.base/share/classes/jdk/internal/math/FDBigInteger.java @@ -26,6 +26,7 @@ package jdk.internal.math; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; import java.util.Arrays; @@ -33,6 +34,7 @@ import java.util.Arrays; /** * A simple big integer class specifically for floating point base conversion. */ +@AOTSafeClassInitializer final class FDBigInteger { @Stable @@ -53,6 +55,7 @@ final class FDBigInteger { // Initialize FDBigInteger cache of powers of 5. static { + // Legacy CDS archive support (to be deprecated) CDS.initializeFromArchive(FDBigInteger.class); Object[] caches = archivedCaches; if (caches == null) { diff --git a/src/java.base/share/classes/jdk/internal/module/ArchivedBootLayer.java b/src/java.base/share/classes/jdk/internal/module/ArchivedBootLayer.java index 5c806f81dcd..425238dd521 100644 --- a/src/java.base/share/classes/jdk/internal/module/ArchivedBootLayer.java +++ b/src/java.base/share/classes/jdk/internal/module/ArchivedBootLayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -25,10 +25,12 @@ package jdk.internal.module; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; /** * Used by ModuleBootstrap for archiving the boot layer. */ +@AOTSafeClassInitializer class ArchivedBootLayer { private static ArchivedBootLayer archivedBootLayer; diff --git a/src/java.base/share/classes/jdk/internal/module/ArchivedModuleGraph.java b/src/java.base/share/classes/jdk/internal/module/ArchivedModuleGraph.java index 4f9223d0171..deb280c878d 100644 --- a/src/java.base/share/classes/jdk/internal/module/ArchivedModuleGraph.java +++ b/src/java.base/share/classes/jdk/internal/module/ArchivedModuleGraph.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -30,11 +30,13 @@ import java.util.function.Function; import java.lang.module.Configuration; import java.lang.module.ModuleFinder; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; /** * Used by ModuleBootstrap for archiving the configuration for the boot layer, * and the system module finder. */ +@AOTSafeClassInitializer class ArchivedModuleGraph { private static ArchivedModuleGraph archivedModuleGraph; @@ -126,6 +128,7 @@ class ArchivedModuleGraph { } static { + // Legacy CDS archive support (to be deprecated) CDS.initializeFromArchive(ArchivedModuleGraph.class); } } diff --git a/src/java.base/share/classes/sun/util/locale/BaseLocale.java b/src/java.base/share/classes/sun/util/locale/BaseLocale.java index 58ec6d76aa5..31078720ddc 100644 --- a/src/java.base/share/classes/sun/util/locale/BaseLocale.java +++ b/src/java.base/share/classes/sun/util/locale/BaseLocale.java @@ -34,11 +34,14 @@ package sun.util.locale; import jdk.internal.misc.CDS; import jdk.internal.util.ReferencedKeySet; +import jdk.internal.vm.annotation.AOTRuntimeSetup; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.Stable; import java.util.StringJoiner; import java.util.function.Supplier; +@AOTSafeClassInitializer public final class BaseLocale { public static @Stable BaseLocale[] constantBaseLocales; @@ -63,6 +66,7 @@ public final class BaseLocale { CANADA_FRENCH = 18, NUM_CONSTANTS = 19; static { + // Legacy CDS archive support (to be deprecated) CDS.initializeFromArchive(BaseLocale.class); BaseLocale[] baseLocales = constantBaseLocales; if (baseLocales == null) { @@ -91,13 +95,21 @@ public final class BaseLocale { } // Interned BaseLocale cache - private static final LazyConstant> CACHE = + @Stable private static LazyConstant> CACHE; + static { + runtimeSetup(); + } + + @AOTRuntimeSetup + private static void runtimeSetup() { + CACHE = LazyConstant.of(new Supplier<>() { @Override public ReferencedKeySet get() { return ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier()); } }); + } public static final String SEP = "_"; diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index d4f1470aea5..2288e8f8876 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -522,6 +522,7 @@ hotspot_aot_classlinking = \ -runtime/cds/appcds/aotFlags \ -runtime/cds/appcds/aotProfile \ -runtime/cds/appcds/BadBSM.java \ + -runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java \ -runtime/cds/appcds/cacheObject/ArchivedIntegerCacheTest.java \ -runtime/cds/appcds/cacheObject/ArchivedModuleCompareTest.java \ -runtime/cds/appcds/CDSandJFR.java \ diff --git a/test/hotspot/jtreg/runtime/cds/SharedSymbolTableBucketSize.java b/test/hotspot/jtreg/runtime/cds/SharedSymbolTableBucketSize.java index 2db4ab2df23..070384a2703 100644 --- a/test/hotspot/jtreg/runtime/cds/SharedSymbolTableBucketSize.java +++ b/test/hotspot/jtreg/runtime/cds/SharedSymbolTableBucketSize.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -31,6 +31,8 @@ * java.management */ +import java.util.regex.Matcher; +import java.util.regex.Pattern; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.process.OutputAnalyzer; @@ -43,8 +45,44 @@ public class SharedSymbolTableBucketSize { + Integer.valueOf(bucket_size)); CDSTestUtils.checkMappingFailure(output); - String regex = "Average bucket size : ([0-9]+\\.[0-9]+).*"; - String s = output.firstMatch(regex, 1); + /* [1] There may be other table stats that precede the symbol tabble. + Skip all thse until we find this: + + [0.677s][info][aot,hashtables] Shared symbol table stats -------- base: 0x0000000800000000 + [0.677s][info][aot,hashtables] Number of entries : 46244 + [0.677s][info][aot,hashtables] Total bytes used : 393792 + [0.677s][info][aot,hashtables] Average bytes per entry : 8.516 + [0.677s][info][aot,hashtables] Average bucket size : 7.734 + [0.677s][info][aot,hashtables] Variance of bucket size : 7.513 + [0.677s][info][aot,hashtables] Std. dev. of bucket size: 2.741 + [0.677s][info][aot,hashtables] Maximum bucket size : 20 + [0.677s][info][aot,hashtables] Empty buckets : 2 + [0.677s][info][aot,hashtables] Value_Only buckets : 24 + [0.677s][info][aot,hashtables] Other buckets : 5953 + .... + */ + Pattern pattern0 = Pattern.compile("Shared symbol table stats.*", Pattern.DOTALL); + Matcher matcher0 = pattern0.matcher(output.getStdout()); + String stat = null; + if (matcher0.find()) { + stat = matcher0.group(0); + } + if (stat == null) { + throw new Exception("FAILED: pattern \"" + pattern0 + "\" not found"); + } + + /* (2) The first "Average bucket size" line in the remaining output is for the + shared symbol table */ + Pattern pattern = Pattern.compile("Average bucket size *: *([0-9]+\\.[0-9]+).*", Pattern.MULTILINE); + Matcher matcher = pattern.matcher(stat); + String s = null; + if (matcher.find()) { + s = matcher.group(1); + } + if (s == null) { + throw new Exception("FAILED: pattern \"" + pattern + "\" not found"); + } + Float f = Float.parseFloat(s); int size = Math.round(f); if (size != bucket_size) { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java index 896df7ca496..4cc6ef81c45 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java @@ -83,17 +83,6 @@ public class AOTLoggingTag { out.shouldContain("[aot] Opened AOT cache hello.aot"); out.shouldHaveExitValue(0); - //---------------------------------------------------------------------- - printTestCase("All old -Xlog:cds+heap logs have been changed to -Xlog:aot+heap should alias to -Xlog:cds+heap"); - pb = ProcessTools.createLimitedTestJavaProcessBuilder( - "-XX:AOTCache=" + aotCacheFile, - "-Xlog:aot+heap", - "-cp", appJar, helloClass); - out = CDSTestUtils.executeAndLog(pb, "prod"); - out.shouldNotContain("No tag set matches selection: aot+heap"); - out.shouldContain("[aot,heap] resolve subgraph java.lang.Integer$IntegerCache"); - out.shouldHaveExitValue(0); - //---------------------------------------------------------------------- printTestCase("Production Run: errors should be printed with [aot] decoration"); pb = ProcessTools.createLimitedTestJavaProcessBuilder( diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HeapObjectIdentity.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HeapObjectIdentity.java new file mode 100644 index 00000000000..cdd0921627a --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HeapObjectIdentity.java @@ -0,0 +1,261 @@ +/* + * 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 AOT cache should preserve heap object identity when required by JLS. For example, Enums and Integers. + * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking + * @requires vm.debug + * @library /test/lib + * @build HeapObjectIdentity + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar dummy.jar + * Dummy + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar + * HeapObjectIdentityApp + * MyAOTInitedClass + * MyAOTInitedClass$MyEnum + * MyAOTInitedClass$Wrapper + * @run driver HeapObjectIdentity AOT --two-step-training + */ + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.helpers.ClassFileInstaller; + +public class HeapObjectIdentity { + static final String appJar = ClassFileInstaller.getJarPath("dummy.jar"); + static final String bootJar = ClassFileInstaller.getJarPath("boot.jar"); + static final String mainClass = "HeapObjectIdentityApp"; // Loaded from boot.jar + + public static void main(String[] args) throws Exception { + Tester t = new Tester(); + t.run(args); + + // Integer$IntegerCache should preserve the object identity of cached Integer objects, + // even when the cache size is different between assembly and production. + t.productionRun(new String[] { + "-XX:AutoBoxCacheMax=2048" + }); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + String bootcp = "-Xbootclasspath/a:" + bootJar; + if (runMode == RunMode.ASSEMBLY) { + return new String[] { + "-Xlog:aot+class=debug", + "-XX:AOTInitTestClass=MyAOTInitedClass", + bootcp + }; + } else { + return new String[] {bootcp}; + } + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + runMode.toString(), + }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (runMode == RunMode.ASSEMBLY) { + out.shouldContain("MyAOTInitedClass aot-linked inited"); + } + } + } +} + +class HeapObjectIdentityApp { + public static void main(String... args) { + MyAOTInitedClass.test(); + } +} + +// This class is loaded by the boot loader, as -XX:AOTInitTestClass is not too friendly +// with classes by other loaders. +class MyAOTInitedClass { + static Object[] archivedObjects; + static { + if (archivedObjects == null) { + archivedObjects = new Object[14]; + archivedObjects[0] = Wrapper.BOOLEAN; + archivedObjects[1] = Wrapper.INT.zero(); + archivedObjects[2] = Wrapper.DOUBLE.zero(); + archivedObjects[3] = MyEnum.DUMMY1; + + archivedObjects[4] = Boolean.class; + archivedObjects[5] = Byte.class; + archivedObjects[6] = Character.class; + archivedObjects[7] = Short.class; + archivedObjects[8] = Integer.class; + archivedObjects[9] = Long.class; + archivedObjects[10] = Float.class; + archivedObjects[11] = Double.class; + archivedObjects[12] = Void.class; + + archivedObjects[13] = Integer.valueOf(1); + } else { + System.out.println("Initialized from CDS"); + } + } + + public static void test() { + if (archivedObjects[0] != Wrapper.BOOLEAN) { + throw new RuntimeException("Huh 0"); + } + + if (archivedObjects[1] != Wrapper.INT.zero()) { + throw new RuntimeException("Huh 1"); + } + + if (archivedObjects[2] != Wrapper.DOUBLE.zero()) { + throw new RuntimeException("Huh 2"); + } + + if (archivedObjects[3] != MyEnum.DUMMY1) { + throw new RuntimeException("Huh 3"); + } + + if (MyEnum.BOOLEAN != true) { + throw new RuntimeException("Huh 10.1"); + } + if (MyEnum.BYTE != -128) { + throw new RuntimeException("Huh 10.2"); + } + if (MyEnum.CHAR != 'c') { + throw new RuntimeException("Huh 10.3"); + } + if (MyEnum.SHORT != -12345) { + throw new RuntimeException("Huh 10.4"); + } + if (MyEnum.INT != -123456) { + throw new RuntimeException("Huh 10.5"); + } + if (MyEnum.LONG != 0x1234567890L) { + throw new RuntimeException("Huh 10.6"); + } + if (MyEnum.LONG2 != -0x1234567890L) { + throw new RuntimeException("Huh 10.7"); + } + if (MyEnum.FLOAT != 567891.0f) { + throw new RuntimeException("Huh 10.8"); + } + if (MyEnum.DOUBLE != 12345678905678.890) { + throw new RuntimeException("Huh 10.9"); + } + + checkClass(4, Boolean.class); + checkClass(5, Byte.class); + checkClass(6, Character.class); + checkClass(7, Short.class); + checkClass(8, Integer.class); + checkClass(9, Long.class); + checkClass(10, Float.class); + checkClass(11, Double.class); + checkClass(12, Void.class); + + if (archivedObjects[13] != Integer.valueOf(1)) { + throw new RuntimeException("Integer cache identity test failed"); + } + + System.out.println("Success!"); + } + + static void checkClass(int index, Class c) { + if (archivedObjects[index] != c) { + throw new RuntimeException("archivedObjects[" + index + "] should be " + c); + } + } + + // Simplified version of sun.invoke.util.Wrapper + public enum Wrapper { + // wrapperType simple primitiveType simple char emptyArray + BOOLEAN( Boolean.class, "Boolean", boolean.class, "boolean", 'Z', new boolean[0]), + INT ( Integer.class, "Integer", int.class, "int", 'I', new int[0]), + DOUBLE ( Double.class, "Double", double.class, "double", 'D', new double[0]) + ; + + public static final int COUNT = 10; + private static final Object DOUBLE_ZERO = (Double)(double)0; + + private final Class wrapperType; + private final Class primitiveType; + private final char basicTypeChar; + private final String basicTypeString; + private final Object emptyArray; + + Wrapper(Class wtype, + String wtypeName, + Class ptype, + String ptypeName, + char tchar, + Object emptyArray) { + this.wrapperType = wtype; + this.primitiveType = ptype; + this.basicTypeChar = tchar; + this.basicTypeString = String.valueOf(this.basicTypeChar); + this.emptyArray = emptyArray; + } + + public Object zero() { + return switch (this) { + case BOOLEAN -> Boolean.FALSE; + case INT -> (Integer)0; + case DOUBLE -> DOUBLE_ZERO; + default -> null; + }; + } + } + + enum MyEnum { + DUMMY1, + DUMMY2; + + static final boolean BOOLEAN = true; + static final byte BYTE = -128; + static final short SHORT = -12345; + static final char CHAR = 'c'; + static final int INT = -123456; + static final long LONG = 0x1234567890L; + static final long LONG2 = -0x1234567890L; + static final float FLOAT = 567891.0f; + static final double DOUBLE = 12345678905678.890; + } +} + +class Dummy {} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java index fed56937f2f..3b1ccff1bfa 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java @@ -36,7 +36,7 @@ * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar * CDSTestClassA CDSTestClassA$XX CDSTestClassA$YY * CDSTestClassB CDSTestClassC CDSTestClassD - * CDSTestClassE CDSTestClassF CDSTestClassG CDSTestClassG$MyEnum CDSTestClassG$Wrapper + * CDSTestClassE CDSTestClassF * pkg.ClassInPackage * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar Hello * @run driver ArchiveHeapTestClass @@ -58,7 +58,6 @@ public class ArchiveHeapTestClass { static final String CDSTestClassD_name = CDSTestClassD.class.getName(); static final String CDSTestClassE_name = CDSTestClassE.class.getName(); static final String CDSTestClassF_name = CDSTestClassF.class.getName(); - static final String CDSTestClassG_name = CDSTestClassG.class.getName(); static final String ClassInPackage_name = pkg.ClassInPackage.class.getName().replace('.', '/'); static final String ARCHIVE_TEST_FIELD_NAME = "archivedObjects"; @@ -162,15 +161,6 @@ public class ArchiveHeapTestClass { output = dumpBootAndHello(CDSTestClassF_name); mustFail(output, "Class java.util.logging.Level not allowed in archive heap"); } - - testCase("Complex enums"); - output = dumpBootAndHello(CDSTestClassG_name, "-XX:+AOTClassLinking", "-Xlog:cds+class=debug"); - mustSucceed(output); - - TestCommon.run("-Xbootclasspath/a:" + bootJar, "-cp", appJar, "-Xlog:aot+heap,cds+init", - CDSTestClassG_name) - .assertNormalExit("init subgraph " + CDSTestClassG_name, - "Initialized from CDS"); } } @@ -287,147 +277,3 @@ class CDSTestClassF { archivedObjects[0] = java.util.logging.Level.OFF; } } - -class CDSTestClassG { - static Object[] archivedObjects; - static { - if (archivedObjects == null) { - archivedObjects = new Object[13]; - archivedObjects[0] = Wrapper.BOOLEAN; - archivedObjects[1] = Wrapper.INT.zero(); - archivedObjects[2] = Wrapper.DOUBLE.zero(); - archivedObjects[3] = MyEnum.DUMMY1; - - archivedObjects[4] = Boolean.class; - archivedObjects[5] = Byte.class; - archivedObjects[6] = Character.class; - archivedObjects[7] = Short.class; - archivedObjects[8] = Integer.class; - archivedObjects[9] = Long.class; - archivedObjects[10] = Float.class; - archivedObjects[11] = Double.class; - archivedObjects[12] = Void.class; - } else { - System.out.println("Initialized from CDS"); - } - } - - public static void main(String args[]) { - if (archivedObjects[0] != Wrapper.BOOLEAN) { - throw new RuntimeException("Huh 0"); - } - - if (archivedObjects[1] != Wrapper.INT.zero()) { - throw new RuntimeException("Huh 1"); - } - - if (archivedObjects[2] != Wrapper.DOUBLE.zero()) { - throw new RuntimeException("Huh 2"); - } - - if (archivedObjects[3] != MyEnum.DUMMY1) { - throw new RuntimeException("Huh 3"); - } - - if (MyEnum.BOOLEAN != true) { - throw new RuntimeException("Huh 10.1"); - } - if (MyEnum.BYTE != -128) { - throw new RuntimeException("Huh 10.2"); - } - if (MyEnum.CHAR != 'c') { - throw new RuntimeException("Huh 10.3"); - } - if (MyEnum.SHORT != -12345) { - throw new RuntimeException("Huh 10.4"); - } - if (MyEnum.INT != -123456) { - throw new RuntimeException("Huh 10.5"); - } - if (MyEnum.LONG != 0x1234567890L) { - throw new RuntimeException("Huh 10.6"); - } - if (MyEnum.LONG2 != -0x1234567890L) { - throw new RuntimeException("Huh 10.7"); - } - if (MyEnum.FLOAT != 567891.0f) { - throw new RuntimeException("Huh 10.8"); - } - if (MyEnum.DOUBLE != 12345678905678.890) { - throw new RuntimeException("Huh 10.9"); - } - - checkClass(4, Boolean.class); - checkClass(5, Byte.class); - checkClass(6, Character.class); - checkClass(7, Short.class); - checkClass(8, Integer.class); - checkClass(9, Long.class); - checkClass(10, Float.class); - checkClass(11, Double.class); - checkClass(12, Void.class); - - System.out.println("Success!"); - } - - static void checkClass(int index, Class c) { - if (archivedObjects[index] != c) { - throw new RuntimeException("archivedObjects[" + index + "] should be " + c); - } - } - - // Simplified version of sun.invoke.util.Wrapper - public enum Wrapper { - // wrapperType simple primitiveType simple char emptyArray - BOOLEAN( Boolean.class, "Boolean", boolean.class, "boolean", 'Z', new boolean[0]), - INT ( Integer.class, "Integer", int.class, "int", 'I', new int[0]), - DOUBLE ( Double.class, "Double", double.class, "double", 'D', new double[0]) - ; - - public static final int COUNT = 10; - private static final Object DOUBLE_ZERO = (Double)(double)0; - - private final Class wrapperType; - private final Class primitiveType; - private final char basicTypeChar; - private final String basicTypeString; - private final Object emptyArray; - - Wrapper(Class wtype, - String wtypeName, - Class ptype, - String ptypeName, - char tchar, - Object emptyArray) { - this.wrapperType = wtype; - this.primitiveType = ptype; - this.basicTypeChar = tchar; - this.basicTypeString = String.valueOf(this.basicTypeChar); - this.emptyArray = emptyArray; - } - - public Object zero() { - return switch (this) { - case BOOLEAN -> Boolean.FALSE; - case INT -> (Integer)0; - case DOUBLE -> DOUBLE_ZERO; - default -> null; - }; - } - } - - enum MyEnum { - DUMMY1, - DUMMY2; - - static final boolean BOOLEAN = true; - static final byte BYTE = -128; - static final short SHORT = -12345; - static final char CHAR = 'c'; - static final int INT = -123456; - static final long LONG = 0x1234567890L; - static final long LONG2 = -0x1234567890L; - static final float FLOAT = 567891.0f; - static final double DOUBLE = 12345678905678.890; - } -} From 17d633a8ee7538625501a90469cb6a68b9ba4820 Mon Sep 17 00:00:00 2001 From: Kelvin Nilsen Date: Wed, 17 Dec 2025 22:21:24 +0000 Subject: [PATCH 006/406] 8373720: GenShen: Count live-at-old mark using Snapshot at Beginning Reviewed-by: ysr --- .../heuristics/shenandoahOldHeuristics.cpp | 9 +++++++-- .../shenandoahGenerationalEvacuationTask.cpp | 3 ++- .../share/gc/shenandoah/shenandoahHeapRegion.hpp | 16 ++++++++++++++++ .../shenandoah/shenandoahHeapRegion.inline.hpp | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp index 1f257560bcb..8bf068df0a8 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp @@ -335,7 +335,13 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() { size_t garbage = region->garbage(); size_t live_bytes = region->get_live_data_bytes(); - live_data += live_bytes; + if (!region->was_promoted_in_place()) { + // As currently implemented, region->get_live_data_bytes() represents bytes concurrently marked. + // Expansion of the region by promotion during concurrent marking is above TAMS, and is not included + // as live-data at [start of] old marking. + live_data += live_bytes; + } + // else, regions that were promoted in place had 0 old live data at mark start if (region->is_regular() || region->is_regular_pinned()) { // Only place regular or pinned regions with live data into the candidate set. @@ -374,7 +380,6 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() { } } - // TODO: subtract from live_data bytes promoted during concurrent GC. _old_generation->set_live_bytes_at_last_mark(live_data); // Unlike young, we are more interested in efficiently packing OLD-gen than in reclaiming garbage first. We sort by live-data. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp index de45877994c..c9b956f9c2f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp @@ -223,7 +223,6 @@ void ShenandoahGenerationalEvacuationTask::promote_in_place(ShenandoahHeapRegion // We do not need to scan above TAMS because restored top equals tams assert(obj_addr == tams, "Expect loop to terminate when obj_addr equals tams"); - { ShenandoahHeapLocker locker(_heap->lock()); @@ -251,6 +250,7 @@ void ShenandoahGenerationalEvacuationTask::promote_in_place(ShenandoahHeapRegion // Transfer this region from young to old, increasing promoted_reserve if available space exceeds plab_min_size() _heap->free_set()->add_promoted_in_place_region_to_old_collector(region); region->set_affiliation(OLD_GENERATION); + region->set_promoted_in_place(); } } @@ -289,6 +289,7 @@ void ShenandoahGenerationalEvacuationTask::promote_humongous(ShenandoahHeapRegio r->index(), p2i(r->bottom()), p2i(r->top())); // We mark the entire humongous object's range as dirty after loop terminates, so no need to dirty the range here r->set_affiliation(OLD_GENERATION); + r->set_promoted_in_place(); } ShenandoahFreeSet* freeset = _heap->free_set(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp index 2ed5614c698..cf0dc5476d0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp @@ -262,6 +262,7 @@ private: HeapWord* volatile _update_watermark; uint _age; + bool _promoted_in_place; CENSUS_NOISE(uint _youth;) // tracks epochs of retrograde ageing (rejuvenation) ShenandoahSharedFlag _recycling; // Used to indicate that the region is being recycled; see try_recycle*(). @@ -354,6 +355,15 @@ public: inline void save_top_before_promote(); inline HeapWord* get_top_before_promote() const { return _top_before_promoted; } + + inline void set_promoted_in_place() { + _promoted_in_place = true; + } + + // Returns true iff this region was promoted in place subsequent to the most recent start of concurrent old marking. + inline bool was_promoted_in_place() { + return _promoted_in_place; + } inline void restore_top_before_promote(); inline size_t garbage_before_padded_for_promote() const; @@ -379,7 +389,13 @@ public: inline void increase_live_data_gc_words(size_t s); inline bool has_live() const; + + // Represents the number of live bytes identified by most recent marking effort. Does not include the bytes + // above TAMS. inline size_t get_live_data_bytes() const; + + // Represents the number of live words identified by most recent marking effort. Does not include the words + // above TAMS. inline size_t get_live_data_words() const; inline size_t garbage() const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp index 69673eb7a60..b9304ee9daa 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp @@ -152,6 +152,7 @@ inline void ShenandoahHeapRegion::internal_increase_live_data(size_t s) { inline void ShenandoahHeapRegion::clear_live_data() { AtomicAccess::store(&_live_data, (size_t)0); + _promoted_in_place = false; } inline size_t ShenandoahHeapRegion::get_live_data_words() const { From c16ce929c7bc127fe18d3faa037d81c2760a44a2 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 17 Dec 2025 22:38:50 +0000 Subject: [PATCH 007/406] 8370970: DocCheck failure in jdkDoctypeBadcharsCheck.java and jdkCheckHtml.java Reviewed-by: liach --- test/docs/ProblemList.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/docs/ProblemList.txt b/test/docs/ProblemList.txt index 4df8bbcc53c..83693eacbd1 100644 --- a/test/docs/ProblemList.txt +++ b/test/docs/ProblemList.txt @@ -41,5 +41,3 @@ ############################################################################# jdk/javadoc/doccheck/checks/jdkCheckLinks.java 8370249 generic-all -jdk/javadoc/doccheck/checks/jdkCheckHtml.java 8370970 generic-all -jdk/javadoc/doccheck/checks/jdkDoctypeBadcharsCheck.java 8370970 generic-all From ea5834415db6410c73271c496811ff6b5dcc87ef Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 18 Dec 2025 01:46:45 +0000 Subject: [PATCH 008/406] 8373887: jpackage tests may potentially deadlock Reviewed-by: almatvee --- .../jdk/jpackage/test/ExecutorTest.java | 8 +++- .../helpers/jdk/jpackage/test/Executor.java | 38 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/ExecutorTest.java b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/ExecutorTest.java index c5d8aed845c..2b075e0f13c 100644 --- a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/ExecutorTest.java +++ b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/ExecutorTest.java @@ -210,7 +210,13 @@ public class ExecutorTest extends JUnitAdapter { assertEquals(0, result[0].getExitCode()); - assertEquals(expectedCapturedSystemOut(commandWithDiscardedStreams), outputCapture.outLines()); + // If we dump the subprocesses's output, and the command produced both STDOUT and STDERR, + // then the captured STDOUT may contain interleaved command's STDOUT and STDERR, + // not in sequential order (STDOUT followed by STDERR). + // In this case don't check the contents of the captured command's STDOUT. + if (toolProvider || outputCapture.outLines().isEmpty() || (command.stdout().isEmpty() || command.stderr().isEmpty())) { + assertEquals(expectedCapturedSystemOut(commandWithDiscardedStreams), outputCapture.outLines()); + } assertEquals(expectedCapturedSystemErr(commandWithDiscardedStreams), outputCapture.errLines()); assertEquals(expectedResultStdout(commandWithDiscardedStreams), result[0].stdout().getOutput()); diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java index 91625603a2b..ef118e525c5 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java @@ -32,6 +32,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.io.StringReader; +import java.io.UncheckedIOException; import java.io.Writer; import java.nio.file.Path; import java.util.ArrayList; @@ -42,12 +43,15 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.spi.ToolProvider; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.internal.util.function.ThrowingSupplier; +import jdk.jpackage.internal.util.function.ExceptionBox; public final class Executor extends CommandArguments { @@ -465,9 +469,37 @@ public final class Executor extends CommandArguments { trace("Execute " + sb.toString() + "..."); Process process = builder.start(); - final var output = combine( - processProcessStream(outputStreamsControl.stdout(), process.getInputStream()), - processProcessStream(outputStreamsControl.stderr(), process.getErrorStream())); + var stdoutGobbler = CompletableFuture.>>supplyAsync(() -> { + try { + return processProcessStream(outputStreamsControl.stdout(), process.getInputStream()); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + + var stderrGobbler = CompletableFuture.>>supplyAsync(() -> { + try { + return processProcessStream(outputStreamsControl.stderr(), process.getErrorStream()); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + + final CommandOutput output; + + try { + output = combine(stdoutGobbler.join(), stderrGobbler.join()); + } catch (CompletionException ex) { + var cause = ex.getCause(); + switch (cause) { + case UncheckedIOException uioex -> { + throw uioex.getCause(); + } + default -> { + throw ExceptionBox.toUnchecked(ExceptionBox.unbox(cause)); + } + } + } final int exitCode = process.waitFor(); trace("Done. Exit code: " + exitCode); From 0146077a51635500de771e9cf2c9788ae931b7a0 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 18 Dec 2025 04:27:18 +0000 Subject: [PATCH 009/406] 8373723: Deadlock with JvmtiTagMap::flush_object_free_events() Reviewed-by: dholmes, coleenp --- src/hotspot/share/prims/jvmtiTagMap.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp index 90a3461f321..04cb70863cd 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -1204,8 +1204,10 @@ void JvmtiTagMap::flush_object_free_events() { assert_not_at_safepoint(); if (env()->is_enabled(JVMTI_EVENT_OBJECT_FREE)) { { + // The other thread can block for safepoints during event callbacks, so ensure we + // are safepoint-safe while waiting. + ThreadBlockInVM tbivm(JavaThread::current()); MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag); - // If another thread is posting events, let it finish while (_posting_events) { ml.wait(); } From b4462625413e7c2c12778eaad1f2f21d81f59c52 Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Thu, 18 Dec 2025 07:04:40 +0000 Subject: [PATCH 010/406] 8373682: Test compiler/loopopts/superword/TestReinterpretAndCast.java fails on x86_64 with AVX but without f16c Reviewed-by: kvn, jsikstro, chagedorn --- .../compiler/loopopts/superword/TestReinterpretAndCast.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestReinterpretAndCast.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestReinterpretAndCast.java index ab2801179f2..9c5140d2aaa 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestReinterpretAndCast.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestReinterpretAndCast.java @@ -162,7 +162,7 @@ public class TestReinterpretAndCast { IRNode.STORE_VECTOR, "> 0", IRNode.VECTOR_REINTERPRET, "> 0"}, // We have at least I2F applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeature = {"avx", "true"}) + applyIfCPUFeatureAnd = {"avx", "true", "f16c", "true"}) @IR(counts = {IRNode.LOAD_VECTOR_I, IRNode.VECTOR_SIZE + "min(max_int, max_float, max_short)", "> 0", IRNode.VECTOR_CAST_F2HF, IRNode.VECTOR_SIZE + "min(max_int, max_float, max_short)", "> 0", IRNode.STORE_VECTOR, "> 0", @@ -208,7 +208,7 @@ public class TestReinterpretAndCast { IRNode.STORE_VECTOR, "> 0", IRNode.VECTOR_REINTERPRET, "> 0"}, // We have at least F2I applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeature = {"avx", "true"}) + applyIfCPUFeatureAnd = {"avx", "true", "f16c", "true"}) @IR(counts = {IRNode.LOAD_VECTOR_S, IRNode.VECTOR_SIZE + "min(max_float, max_short, max_long)", "> 0", IRNode.VECTOR_CAST_HF2F, IRNode.VECTOR_SIZE + "min(max_float, max_short, max_long)", "> 0", IRNode.VECTOR_CAST_I2L, IRNode.VECTOR_SIZE + "min(max_float, max_short, max_long)", "> 0", From 00050f84d44f3ec23e9c6da52bffd68770010749 Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Thu, 18 Dec 2025 07:05:05 +0000 Subject: [PATCH 011/406] 8373502: C2 SuperWord: speculative check uses VPointer variable was pinned after speculative check, leading to bad graph Reviewed-by: thartmann, roland --- src/hotspot/share/opto/vectorization.cpp | 25 +++++++ src/hotspot/share/opto/vectorization.hpp | 16 +++++ ...ingCheckVPointerVariablesNotAvailable.java | 71 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingCheckVPointerVariablesNotAvailable.java diff --git a/src/hotspot/share/opto/vectorization.cpp b/src/hotspot/share/opto/vectorization.cpp index 15b2df663b6..230ff280f03 100644 --- a/src/hotspot/share/opto/vectorization.cpp +++ b/src/hotspot/share/opto/vectorization.cpp @@ -1060,6 +1060,29 @@ bool VPointer::can_make_speculative_aliasing_check_with(const VPointer& other) c return false; } + // The speculative check also needs to create the pointer expressions for both + // VPointers. We must check that we can do that, i.e. that all variables of the + // VPointers are available at the speculative check (and not just pre-loop invariant). + if (!this->can_make_pointer_expression_at_speculative_check()) { +#ifdef ASSERT + if (_vloop.is_trace_speculative_aliasing_analysis()) { + tty->print_cr("VPointer::can_make_speculative_aliasing_check_with: not all variables of VPointer are avaialbe at speculative check!"); + this->print_on(tty); + } +#endif + return false; + } + + if (!other.can_make_pointer_expression_at_speculative_check()) { +#ifdef ASSERT + if (_vloop.is_trace_speculative_aliasing_analysis()) { + tty->print_cr("VPointer::can_make_speculative_aliasing_check_with: not all variables of VPointer are avaialbe at speculative check!"); + other.print_on(tty); + } +#endif + return false; + } + return true; } @@ -1147,6 +1170,8 @@ BoolNode* VPointer::make_speculative_aliasing_check_with(const VPointer& other, Node* main_init = new ConvL2INode(main_initL); phase->register_new_node_with_ctrl_of(main_init, pre_init); + assert(vp1.can_make_pointer_expression_at_speculative_check(), "variables must be available early enough to avoid cycles"); + assert(vp2.can_make_pointer_expression_at_speculative_check(), "variables must be available early enough to avoid cycles"); Node* p1_init = vp1.make_pointer_expression(main_init, ctrl); Node* p2_init = vp2.make_pointer_expression(main_init, ctrl); Node* size1 = igvn.longcon(vp1.size()); diff --git a/src/hotspot/share/opto/vectorization.hpp b/src/hotspot/share/opto/vectorization.hpp index aacd406f798..9308712f78a 100644 --- a/src/hotspot/share/opto/vectorization.hpp +++ b/src/hotspot/share/opto/vectorization.hpp @@ -1188,6 +1188,22 @@ private: return true; } + // We already know that all non-iv summands are pre loop invariant. + // See init_are_non_iv_summands_pre_loop_invariant + // That is good enough for alignment computations in the pre-loop limit. But it is not + // sufficient if we want to use the variables of the VPointer at the speculative check, + // which is further up before the pre-loop. + bool can_make_pointer_expression_at_speculative_check() const { + bool success = true; + mem_pointer().for_each_non_empty_summand([&] (const MemPointerSummand& s) { + Node* variable = s.variable(); + if (variable != _vloop.iv() && !_vloop.is_available_for_speculative_check(variable)) { + success = false; + } + }); + return success; + } + // In the pointer analysis, and especially the AlignVector analysis, we assume that // stride and scale are not too large. For example, we multiply "iv_scale * iv_stride", // and assume that this does not overflow the int range. We also take "abs(iv_scale)" diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingCheckVPointerVariablesNotAvailable.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingCheckVPointerVariablesNotAvailable.java new file mode 100644 index 00000000000..47540da5648 --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingCheckVPointerVariablesNotAvailable.java @@ -0,0 +1,71 @@ +/* + * 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=with-flags + * @bug 8373502 + * @summary Test where a VPointer variable was pinned at the pre-loop, but not available at the + * Auto_Vectorization_Check, and so it should not be used for the auto vectorization + * aliasing check, to avoid a bad (circular) graph. + * @run main/othervm + * -XX:CompileCommand=compileonly,*TestAliasingCheckVPointerVariablesNotAvailable::test + * -XX:-TieredCompilation + * -Xcomp + * ${test.main.class} + */ + +/* + * @test id=vanilla + * @bug 8373502 + * @run main ${test.main.class} + */ + +package compiler.loopopts.superword; + +public class TestAliasingCheckVPointerVariablesNotAvailable { + static int iFld; + + public static void main(String[] strArr) { + test(); + } + + static void test() { + int iArr[] = new int[400]; + boolean flag = false; + for (int i = 6; i < 50000; i++) { // Trigger OSR + try { + int x = 234 / iFld; + iFld = iArr[3]; + } catch (ArithmeticException a_e) { + } + for (int j = i; j < 2; j++) { + if (flag) { + iArr[j] = 117; + } else { + iArr[1] = 34; + } + iArr[1] += i; + } + } + } +} From e67805067a8f537862200e808e20464f12d21c9c Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Thu, 18 Dec 2025 07:31:06 +0000 Subject: [PATCH 012/406] 8367341: C2: apply KnownBits and unsigned bounds to And / Or operations Reviewed-by: hgreule, epeter --- src/hotspot/share/opto/addnode.cpp | 91 +--- src/hotspot/share/opto/mulnode.cpp | 78 +--- src/hotspot/share/opto/rangeinference.cpp | 8 +- src/hotspot/share/opto/rangeinference.hpp | 231 +++++++++- src/hotspot/share/opto/type.hpp | 2 + src/hotspot/share/opto/utilities/xor.hpp | 47 -- src/hotspot/share/utilities/intn_t.hpp | 1 + .../gtest/opto/test_rangeinference.cpp | 425 +++++++++++++++++- test/hotspot/gtest/opto/test_xor_node.cpp | 102 ----- 9 files changed, 651 insertions(+), 334 deletions(-) delete mode 100644 src/hotspot/share/opto/utilities/xor.hpp delete mode 100644 test/hotspot/gtest/opto/test_xor_node.cpp diff --git a/src/hotspot/share/opto/addnode.cpp b/src/hotspot/share/opto/addnode.cpp index 6075317d86e..accb3d32b67 100644 --- a/src/hotspot/share/opto/addnode.cpp +++ b/src/hotspot/share/opto/addnode.cpp @@ -31,8 +31,8 @@ #include "opto/movenode.hpp" #include "opto/mulnode.hpp" #include "opto/phaseX.hpp" +#include "opto/rangeinference.hpp" #include "opto/subnode.hpp" -#include "opto/utilities/xor.hpp" #include "runtime/stubRoutines.hpp" // Portions of code courtesy of Clifford Click @@ -1011,35 +1011,8 @@ Node* OrINode::Ideal(PhaseGVN* phase, bool can_reshape) { // the logical operations the ring's ADD is really a logical OR function. // This also type-checks the inputs for sanity. Guaranteed never to // be passed a TOP or BOTTOM type, these are filtered out by pre-check. -const Type *OrINode::add_ring( const Type *t0, const Type *t1 ) const { - const TypeInt *r0 = t0->is_int(); // Handy access - const TypeInt *r1 = t1->is_int(); - - // If both args are bool, can figure out better types - if ( r0 == TypeInt::BOOL ) { - if ( r1 == TypeInt::ONE) { - return TypeInt::ONE; - } else if ( r1 == TypeInt::BOOL ) { - return TypeInt::BOOL; - } - } else if ( r0 == TypeInt::ONE ) { - if ( r1 == TypeInt::BOOL ) { - return TypeInt::ONE; - } - } - - // If either input is all ones, the output is all ones. - // x | ~0 == ~0 <==> x | -1 == -1 - if (r0 == TypeInt::MINUS_1 || r1 == TypeInt::MINUS_1) { - return TypeInt::MINUS_1; - } - - // If either input is not a constant, just return all integers. - if( !r0->is_con() || !r1->is_con() ) - return TypeInt::INT; // Any integer, but still no symbols. - - // Otherwise just OR them bits. - return TypeInt::make( r0->get_con() | r1->get_con() ); +const Type* OrINode::add_ring(const Type* t1, const Type* t2) const { + return RangeInference::infer_or(t1->is_int(), t2->is_int()); } //============================================================================= @@ -1087,22 +1060,8 @@ Node* OrLNode::Ideal(PhaseGVN* phase, bool can_reshape) { } //------------------------------add_ring--------------------------------------- -const Type *OrLNode::add_ring( const Type *t0, const Type *t1 ) const { - const TypeLong *r0 = t0->is_long(); // Handy access - const TypeLong *r1 = t1->is_long(); - - // If either input is all ones, the output is all ones. - // x | ~0 == ~0 <==> x | -1 == -1 - if (r0 == TypeLong::MINUS_1 || r1 == TypeLong::MINUS_1) { - return TypeLong::MINUS_1; - } - - // If either input is not a constant, just return all integers. - if( !r0->is_con() || !r1->is_con() ) - return TypeLong::LONG; // Any integer, but still no symbols. - - // Otherwise just OR them bits. - return TypeLong::make( r0->get_con() | r1->get_con() ); +const Type* OrLNode::add_ring(const Type* t1, const Type* t2) const { + return RangeInference::infer_or(t1->is_long(), t2->is_long()); } //---------------------------Helper ------------------------------------------- @@ -1189,46 +1148,14 @@ const Type* XorINode::Value(PhaseGVN* phase) const { // the logical operations the ring's ADD is really a logical OR function. // This also type-checks the inputs for sanity. Guaranteed never to // be passed a TOP or BOTTOM type, these are filtered out by pre-check. -const Type *XorINode::add_ring( const Type *t0, const Type *t1 ) const { - const TypeInt *r0 = t0->is_int(); // Handy access - const TypeInt *r1 = t1->is_int(); - - if (r0->is_con() && r1->is_con()) { - // compute constant result - return TypeInt::make(r0->get_con() ^ r1->get_con()); - } - - // At least one of the arguments is not constant - - if (r0->_lo >= 0 && r1->_lo >= 0) { - // Combine [r0->_lo, r0->_hi] ^ [r0->_lo, r1->_hi] -> [0, upper_bound] - jint upper_bound = xor_upper_bound_for_ranges(r0->_hi, r1->_hi); - return TypeInt::make(0, upper_bound, MAX2(r0->_widen, r1->_widen)); - } - - return TypeInt::INT; +const Type* XorINode::add_ring(const Type* t1, const Type* t2) const { + return RangeInference::infer_xor(t1->is_int(), t2->is_int()); } //============================================================================= //------------------------------add_ring--------------------------------------- -const Type *XorLNode::add_ring( const Type *t0, const Type *t1 ) const { - const TypeLong *r0 = t0->is_long(); // Handy access - const TypeLong *r1 = t1->is_long(); - - if (r0->is_con() && r1->is_con()) { - // compute constant result - return TypeLong::make(r0->get_con() ^ r1->get_con()); - } - - // At least one of the arguments is not constant - - if (r0->_lo >= 0 && r1->_lo >= 0) { - // Combine [r0->_lo, r0->_hi] ^ [r0->_lo, r1->_hi] -> [0, upper_bound] - julong upper_bound = xor_upper_bound_for_ranges(r0->_hi, r1->_hi); - return TypeLong::make(0, upper_bound, MAX2(r0->_widen, r1->_widen)); - } - - return TypeLong::LONG; +const Type* XorLNode::add_ring(const Type* t1, const Type* t2) const { + return RangeInference::infer_xor(t1->is_long(), t2->is_long()); } Node* XorLNode::Ideal(PhaseGVN* phase, bool can_reshape) { diff --git a/src/hotspot/share/opto/mulnode.cpp b/src/hotspot/share/opto/mulnode.cpp index 280781f686b..aa8d6cfce2e 100644 --- a/src/hotspot/share/opto/mulnode.cpp +++ b/src/hotspot/share/opto/mulnode.cpp @@ -29,6 +29,7 @@ #include "opto/memnode.hpp" #include "opto/mulnode.hpp" #include "opto/phaseX.hpp" +#include "opto/rangeinference.hpp" #include "opto/subnode.hpp" #include "utilities/powerOfTwo.hpp" @@ -620,80 +621,14 @@ const Type* MulHiValue(const Type *t1, const Type *t2, const Type *bot) { return TypeLong::LONG; } -template -static const IntegerType* and_value(const IntegerType* r0, const IntegerType* r1) { - typedef typename IntegerType::NativeType NativeType; - static_assert(std::is_signed::value, "Native type of IntegerType must be signed!"); - - int widen = MAX2(r0->_widen, r1->_widen); - - // If both types are constants, we can calculate a constant result. - if (r0->is_con() && r1->is_con()) { - return IntegerType::make(r0->get_con() & r1->get_con()); - } - - // If both ranges are positive, the result will range from 0 up to the hi value of the smaller range. The minimum - // of the two constrains the upper bound because any higher value in the other range will see all zeroes, so it will be masked out. - if (r0->_lo >= 0 && r1->_lo >= 0) { - return IntegerType::make(0, MIN2(r0->_hi, r1->_hi), widen); - } - - // If only one range is positive, the result will range from 0 up to that range's maximum value. - // For the operation 'x & C' where C is a positive constant, the result will be in the range [0..C]. With that observation, - // we can say that for any integer c such that 0 <= c <= C will also be in the range [0..C]. Therefore, 'x & [c..C]' - // where c >= 0 will be in the range [0..C]. - if (r0->_lo >= 0) { - return IntegerType::make(0, r0->_hi, widen); - } - - if (r1->_lo >= 0) { - return IntegerType::make(0, r1->_hi, widen); - } - - // At this point, all positive ranges will have already been handled, so the only remaining cases will be negative ranges - // and constants. - - assert(r0->_lo < 0 && r1->_lo < 0, "positive ranges should already be handled!"); - - // As two's complement means that both numbers will start with leading 1s, the lower bound of both ranges will contain - // the common leading 1s of both minimum values. In order to count them with count_leading_zeros, the bits are inverted. - NativeType sel_val = ~MIN2(r0->_lo, r1->_lo); - - NativeType min; - if (sel_val == 0) { - // Since count_leading_zeros is undefined at 0, we short-circuit the condition where both ranges have a minimum of -1. - min = -1; - } else { - // To get the number of bits to shift, we count the leading 0-bits and then subtract one, as the sign bit is already set. - int shift_bits = count_leading_zeros(sel_val) - 1; - min = std::numeric_limits::min() >> shift_bits; - } - - NativeType max; - if (r0->_hi < 0 && r1->_hi < 0) { - // If both ranges are negative, then the same optimization as both positive ranges will apply, and the smaller hi - // value will mask off any bits set by higher values. - max = MIN2(r0->_hi, r1->_hi); - } else { - // In the case of ranges that cross zero, negative values can cause the higher order bits to be set, so the maximum - // positive value can be as high as the larger hi value. - max = MAX2(r0->_hi, r1->_hi); - } - - return IntegerType::make(min, max, widen); -} - //============================================================================= //------------------------------mul_ring--------------------------------------- // Supplied function returns the product of the inputs IN THE CURRENT RING. // For the logical operations the ring's MUL is really a logical AND function. // This also type-checks the inputs for sanity. Guaranteed never to // be passed a TOP or BOTTOM type, these are filtered out by pre-check. -const Type *AndINode::mul_ring( const Type *t0, const Type *t1 ) const { - const TypeInt* r0 = t0->is_int(); - const TypeInt* r1 = t1->is_int(); - - return and_value(r0, r1); +const Type* AndINode::mul_ring(const Type* t1, const Type* t2) const { + return RangeInference::infer_and(t1->is_int(), t2->is_int()); } static bool AndIL_is_zero_element_under_mask(const PhaseGVN* phase, const Node* expr, const Node* mask, BasicType bt); @@ -822,11 +757,8 @@ Node *AndINode::Ideal(PhaseGVN *phase, bool can_reshape) { // For the logical operations the ring's MUL is really a logical AND function. // This also type-checks the inputs for sanity. Guaranteed never to // be passed a TOP or BOTTOM type, these are filtered out by pre-check. -const Type *AndLNode::mul_ring( const Type *t0, const Type *t1 ) const { - const TypeLong* r0 = t0->is_long(); - const TypeLong* r1 = t1->is_long(); - - return and_value(r0, r1); +const Type* AndLNode::mul_ring(const Type* t1, const Type* t2) const { + return RangeInference::infer_and(t1->is_long(), t2->is_long()); } const Type* AndLNode::Value(PhaseGVN* phase) const { diff --git a/src/hotspot/share/opto/rangeinference.cpp b/src/hotspot/share/opto/rangeinference.cpp index 40b9da4bde5..cb26e68ef58 100644 --- a/src/hotspot/share/opto/rangeinference.cpp +++ b/src/hotspot/share/opto/rangeinference.cpp @@ -25,7 +25,6 @@ #include "opto/rangeinference.hpp" #include "opto/type.hpp" #include "utilities/intn_t.hpp" -#include "utilities/tuple.hpp" // If the cardinality of a TypeInt is below this threshold, use min widen, see // TypeIntPrototype::normalize_widen @@ -688,6 +687,8 @@ template class TypeIntPrototype, uintn_t<1>>; template class TypeIntPrototype, uintn_t<2>>; template class TypeIntPrototype, uintn_t<3>>; template class TypeIntPrototype, uintn_t<4>>; +template class TypeIntPrototype, uintn_t<5>>; +template class TypeIntPrototype, uintn_t<6>>; // Compute the meet of 2 types. When dual is true, the subset relation in CT is // reversed. This means that the result of 2 CTs would be the intersection of @@ -709,10 +710,7 @@ const Type* TypeIntHelper::int_type_xmeet(const CT* i1, const Type* t2) { if (!i1->_is_dual) { // meet (a.k.a union) - return CT::make_or_top(TypeIntPrototype{{MIN2(i1->_lo, i2->_lo), MAX2(i1->_hi, i2->_hi)}, - {MIN2(i1->_ulo, i2->_ulo), MAX2(i1->_uhi, i2->_uhi)}, - {i1->_bits._zeros & i2->_bits._zeros, i1->_bits._ones & i2->_bits._ones}}, - MAX2(i1->_widen, i2->_widen), false); + return int_type_union(i1, i2); } else { // join (a.k.a intersection) return CT::make_or_top(TypeIntPrototype{{MAX2(i1->_lo, i2->_lo), MIN2(i1->_hi, i2->_hi)}, diff --git a/src/hotspot/share/opto/rangeinference.hpp b/src/hotspot/share/opto/rangeinference.hpp index 73b8b43bd6e..ebfd98ca4a6 100644 --- a/src/hotspot/share/opto/rangeinference.hpp +++ b/src/hotspot/share/opto/rangeinference.hpp @@ -25,6 +25,7 @@ #ifndef SHARE_OPTO_RANGEINFERENCE_HPP #define SHARE_OPTO_RANGEINFERENCE_HPP +#include "cppstdlib/limits.hpp" #include "cppstdlib/type_traits.hpp" #include "utilities/globalDefinitions.hpp" @@ -92,19 +93,6 @@ public: RangeInt _urange; KnownBits _bits; -private: - friend class TypeInt; - friend class TypeLong; - - template - friend void test_canonicalize_constraints_exhaustive(); - - template - friend void test_canonicalize_constraints_simple(); - - template - friend void test_canonicalize_constraints_random(); - // A canonicalized version of a TypeIntPrototype, if the prototype represents // an empty type, _present is false, otherwise, _data is canonical class CanonicalizedTypeIntPrototype { @@ -158,21 +146,33 @@ public: template static const Type* int_type_xmeet(const CT* i1, const Type* t2); - template - static bool int_type_is_equal(const CT* t1, const CT* t2) { + template + static CTP int_type_union(CTP t1, CTP t2) { + using CT = std::conditional_t, std::remove_pointer_t, CTP>; + using S = std::remove_const_t; + using U = std::remove_const_t; + return CT::make(TypeIntPrototype{{MIN2(t1->_lo, t2->_lo), MAX2(t1->_hi, t2->_hi)}, + {MIN2(t1->_ulo, t2->_ulo), MAX2(t1->_uhi, t2->_uhi)}, + {t1->_bits._zeros & t2->_bits._zeros, t1->_bits._ones & t2->_bits._ones}}, + MAX2(t1->_widen, t2->_widen)); + } + + template + static bool int_type_is_equal(const CTP t1, const CTP t2) { return t1->_lo == t2->_lo && t1->_hi == t2->_hi && t1->_ulo == t2->_ulo && t1->_uhi == t2->_uhi && t1->_bits._zeros == t2->_bits._zeros && t1->_bits._ones == t2->_bits._ones; } - template - static bool int_type_is_subset(const CT* super, const CT* sub) { + template + static bool int_type_is_subset(const CTP super, const CTP sub) { + using U = decltype(super->_ulo); return super->_lo <= sub->_lo && super->_hi >= sub->_hi && super->_ulo <= sub->_ulo && super->_uhi >= sub->_uhi && // All bits that are known in super must also be known to be the same // value in sub, &~ (and not) is the same as a set subtraction on bit // sets - (super->_bits._zeros &~ sub->_bits._zeros) == 0 && (super->_bits._ones &~ sub->_bits._ones) == 0; + (super->_bits._zeros &~ sub->_bits._zeros) == U(0) && (super->_bits._ones &~ sub->_bits._ones) == U(0); } template @@ -195,4 +195,199 @@ public: #endif // PRODUCT }; +// A TypeIntMirror is structurally similar to a TypeInt or a TypeLong but it decouples the range +// inference from the Type infrastructure of the compiler. It also allows more flexibility with the +// bit width of the integer type. As a result, it is more efficient to use for intermediate steps +// of inference, as well as more flexible to perform testing on different integer types. +template +class TypeIntMirror { +public: + S _lo; + S _hi; + U _ulo; + U _uhi; + KnownBits _bits; + int _widen = 0; // dummy field to mimic the same field in TypeInt, useful in testing + + static TypeIntMirror make(const TypeIntPrototype& t, int widen) { + auto canonicalized_t = t.canonicalize_constraints(); + assert(!canonicalized_t.empty(), "must not be empty"); + return TypeIntMirror{canonicalized_t._data._srange._lo, canonicalized_t._data._srange._hi, + canonicalized_t._data._urange._lo, canonicalized_t._data._urange._hi, + canonicalized_t._data._bits}; + } + + // These allow TypeIntMirror to mimick the behaviors of TypeInt* and TypeLong*, so they can be + // passed into RangeInference methods. These are only used in testing, so they are implemented in + // the test file. + const TypeIntMirror* operator->() const; + TypeIntMirror meet(const TypeIntMirror& o) const; + bool contains(U u) const; + bool contains(const TypeIntMirror& o) const; + bool operator==(const TypeIntMirror& o) const; + + template + TypeIntMirror cast() const; +}; + +// This class contains methods for inferring the Type of the result of several arithmetic +// operations from those of the corresponding inputs. For example, given a, b such that the Type of +// a is [0, 1] and the Type of b is [-1, 3], then the Type of the sum a + b is [-1, 4]. +// The methods in this class receive one or more template parameters which are often TypeInt* or +// TypeLong*, or they can be TypeIntMirror which behave similar to TypeInt* and TypeLong* during +// testing. This allows us to verify the correctness of the implementation without coupling with +// the hotspot compiler allocation infrastructure. +class RangeInference { +private: + // If CTP is a pointer, get the underlying type. For the test helper classes, using the struct + // directly allows straightfoward equality comparison. + template + using CT = std::remove_const_t, std::remove_pointer_t, CTP>>; + + // The type of CT::_lo, should be jint for TypeInt* and jlong for TypeLong* + template + using S = std::remove_const_t::_lo)>; + + // The type of CT::_ulo, should be juint for TypeInt* and julong for TypeLong* + template + using U = std::remove_const_t::_ulo)>; + + // A TypeInt consists of 1 or 2 simple intervals, each of which will lie either in the interval + // [0, max_signed] or [min_signed, -1]. It is more optimal to analyze each simple interval + // separately when doing inference. For example, consider a, b whose Types are both [-2, 2]. By + // analyzing the interval [-2, -1] and [0, 2] separately, we can easily see that the result of + // a & b must also be in the interval [-2, 2]. This is much harder if we want to work with the + // whole value range at the same time. + // This class offers a convenient way to traverse all the simple interval of a TypeInt. + template + class SimpleIntervalIterable { + private: + TypeIntMirror, U> _first_interval; + TypeIntMirror, U> _second_interval; + int _interval_num; + + public: + SimpleIntervalIterable(CTP t) { + if (U(t->_lo) <= U(t->_hi)) { + _interval_num = 1; + _first_interval = TypeIntMirror, U>{t->_lo, t->_hi, t->_ulo, t->_uhi, t->_bits}; + } else { + _interval_num = 2; + _first_interval = TypeIntMirror, U>::make(TypeIntPrototype, U>{{t->_lo, S(t->_uhi)}, {U(t->_lo), t->_uhi}, t->_bits}, 0); + _second_interval = TypeIntMirror, U>::make(TypeIntPrototype, U>{{S(t->_ulo), t->_hi}, {t->_ulo, U(t->_hi)}, t->_bits}, 0); + } + } + + class Iterator { + private: + const SimpleIntervalIterable& _iterable; + int _current_interval; + + Iterator(const SimpleIntervalIterable& iterable) : _iterable(iterable), _current_interval(0) {} + + friend class SimpleIntervalIterable; + public: + const TypeIntMirror, U>& operator*() const { + assert(_current_interval < _iterable._interval_num, "out of bounds, %d - %d", _current_interval, _iterable._interval_num); + if (_current_interval == 0) { + return _iterable._first_interval; + } else { + return _iterable._second_interval; + } + } + + Iterator& operator++() { + assert(_current_interval < _iterable._interval_num, "out of bounds, %d - %d", _current_interval, _iterable._interval_num); + _current_interval++; + return *this; + } + + bool operator!=(const Iterator& o) const { + assert(&_iterable == &o._iterable, "not on the same iterable"); + return _current_interval != o._current_interval; + } + }; + + Iterator begin() const { + return Iterator(*this); + } + + Iterator end() const { + Iterator res(*this); + res._current_interval = _interval_num; + return res; + } + }; + + // Infer a result given the input types of a binary operation + template + static CTP infer_binary(CTP t1, CTP t2, Inference infer) { + CTP res; + bool is_init = false; + + SimpleIntervalIterable t1_simple_intervals(t1); + SimpleIntervalIterable t2_simple_intervals(t2); + + for (auto& st1 : t1_simple_intervals) { + for (auto& st2 : t2_simple_intervals) { + CTP current = infer(st1, st2); + + if (is_init) { + res = res->meet(current)->template cast>(); + } else { + is_init = true; + res = current; + } + } + } + + assert(is_init, "must be initialized"); + return res; + } + +public: + template + static CTP infer_and(CTP t1, CTP t2) { + return infer_binary(t1, t2, [&](const TypeIntMirror, U>& st1, const TypeIntMirror, U>& st2) { + S lo = std::numeric_limits>::min(); + S hi = std::numeric_limits>::max(); + U ulo = std::numeric_limits>::min(); + // The unsigned value of the result of 'and' is always not greater than both of its inputs + // since there is no position at which the bit is 1 in the result and 0 in either input + U uhi = MIN2(st1._uhi, st2._uhi); + U zeros = st1._bits._zeros | st2._bits._zeros; + U ones = st1._bits._ones & st2._bits._ones; + return CT::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}, MAX2(t1->_widen, t2->_widen)); + }); + } + + template + static CTP infer_or(CTP t1, CTP t2) { + return infer_binary(t1, t2, [&](const TypeIntMirror, U>& st1, const TypeIntMirror, U>& st2) { + S lo = std::numeric_limits>::min(); + S hi = std::numeric_limits>::max(); + // The unsigned value of the result of 'or' is always not less than both of its inputs since + // there is no position at which the bit is 0 in the result and 1 in either input + U ulo = MAX2(st1._ulo, st2._ulo); + U uhi = std::numeric_limits>::max(); + U zeros = st1._bits._zeros & st2._bits._zeros; + U ones = st1._bits._ones | st2._bits._ones; + return CT::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}, MAX2(t1->_widen, t2->_widen)); + }); + } + + template + static CTP infer_xor(CTP t1, CTP t2) { + return infer_binary(t1, t2, [&](const TypeIntMirror, U>& st1, const TypeIntMirror, U>& st2) { + S lo = std::numeric_limits>::min(); + S hi = std::numeric_limits>::max(); + U ulo = std::numeric_limits>::min(); + U uhi = std::numeric_limits>::max(); + U zeros = (st1._bits._zeros & st2._bits._zeros) | (st1._bits._ones & st2._bits._ones); + U ones = (st1._bits._zeros & st2._bits._ones) | (st1._bits._ones & st2._bits._zeros); + return CT::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}, MAX2(t1->_widen, t2->_widen)); + }); + } +}; + #endif // SHARE_OPTO_RANGEINFERENCE_HPP diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index 4666cfbcf2d..73e2ba0045a 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -798,6 +798,7 @@ public: // must always specify w static const TypeInt* make(jint lo, jint hi, int widen); static const Type* make_or_top(const TypeIntPrototype& t, int widen); + static const TypeInt* make(const TypeIntPrototype& t, int widen) { return make_or_top(t, widen)->is_int(); } // Check for single integer bool is_con() const { return _lo == _hi; } @@ -879,6 +880,7 @@ public: // must always specify w static const TypeLong* make(jlong lo, jlong hi, int widen); static const Type* make_or_top(const TypeIntPrototype& t, int widen); + static const TypeLong* make(const TypeIntPrototype& t, int widen) { return make_or_top(t, widen)->is_long(); } // Check for single integer bool is_con() const { return _lo == _hi; } diff --git a/src/hotspot/share/opto/utilities/xor.hpp b/src/hotspot/share/opto/utilities/xor.hpp deleted file mode 100644 index 20edaf0d017..00000000000 --- a/src/hotspot/share/opto/utilities/xor.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef SHARE_OPTO_UTILITIES_XOR_HPP -#define SHARE_OPTO_UTILITIES_XOR_HPP - -#include "utilities/powerOfTwo.hpp" -// Code separated into its own header to allow access from GTEST - -// Given 2 non-negative values in the ranges [0, hi_0] and [0, hi_1], respectively. The bitwise -// xor of these values should also be non-negative. This method calculates an upper bound. - -// S and U type parameters correspond to the signed and unsigned -// variants of an integer to operate on. -template -static S xor_upper_bound_for_ranges(const S hi_0, const S hi_1) { - static_assert(S(-1) < S(0), "S must be signed"); - static_assert(U(-1) > U(0), "U must be unsigned"); - - assert(hi_0 >= 0, "must be non-negative"); - assert(hi_1 >= 0, "must be non-negative"); - - // x ^ y cannot have any bit set that is higher than both the highest bits set in x and y - // x cannot have any bit set that is higher than the highest bit set in hi_0 - // y cannot have any bit set that is higher than the highest bit set in hi_1 - - // We want to find a value that has all 1 bits everywhere up to and including - // the highest bits set in hi_0 as well as hi_1. For this, we can take the next - // power of 2 strictly greater than both hi values and subtract 1 from it. - - // Example 1: - // hi_0 = 5 (0b0101) hi_1=1 (0b0001) - // (5|1)+1 = 0b0110 - // round_up_pow2 = 0b1000 - // -1 = 0b0111 = max - - // Example 2 - this demonstrates need for the +1: - // hi_0 = 4 (0b0100) hi_1=4 (0b0100) - // (4|4)+1 = 0b0101 - // round_up_pow2 = 0b1000 - // -1 = 0b0111 = max - // Without the +1, round_up_pow2 would be 0b0100, resulting in 0b0011 as max - - // Note: cast to unsigned happens before +1 to avoid signed overflow, and - // round_up is safe because high bit is unset (0 <= lo <= hi) - - return round_up_power_of_2(U(hi_0 | hi_1) + 1) - 1; -} - -#endif // SHARE_OPTO_UTILITIES_XOR_HPP diff --git a/src/hotspot/share/utilities/intn_t.hpp b/src/hotspot/share/utilities/intn_t.hpp index 6f43f5c2556..594e62a1694 100644 --- a/src/hotspot/share/utilities/intn_t.hpp +++ b/src/hotspot/share/utilities/intn_t.hpp @@ -79,6 +79,7 @@ public: static_assert(min < max, ""); constexpr bool operator==(intn_t o) const { return (_v & _mask) == (o._v & _mask); } + constexpr bool operator!=(intn_t o) const { return !(*this == o); } constexpr bool operator<(intn_t o) const { return int(*this) < int(o); } constexpr bool operator>(intn_t o) const { return int(*this) > int(o); } constexpr bool operator<=(intn_t o) const { return int(*this) <= int(o); } diff --git a/test/hotspot/gtest/opto/test_rangeinference.cpp b/test/hotspot/gtest/opto/test_rangeinference.cpp index 1a62941a486..61a9ff7fb70 100644 --- a/test/hotspot/gtest/opto/test_rangeinference.cpp +++ b/test/hotspot/gtest/opto/test_rangeinference.cpp @@ -25,15 +25,16 @@ #include "opto/rangeinference.hpp" #include "opto/type.hpp" #include "runtime/os.hpp" -#include "utilities/intn_t.hpp" #include "unittest.hpp" +#include "utilities/intn_t.hpp" +#include "utilities/rbTree.hpp" +#include +#include +#include template -static U uniform_random(); - -template <> -juint uniform_random() { - return os::random(); +static U uniform_random() { + return U(juint(os::random())); } template <> @@ -201,7 +202,7 @@ static void test_canonicalize_constraints_random() { } } -TEST_VM(opto, canonicalize_constraints) { +TEST(opto, canonicalize_constraints) { test_canonicalize_constraints_trivial(); test_canonicalize_constraints_exhaustive, uintn_t<1>>(); test_canonicalize_constraints_exhaustive, uintn_t<2>>(); @@ -212,3 +213,413 @@ TEST_VM(opto, canonicalize_constraints) { test_canonicalize_constraints_random(); test_canonicalize_constraints_random(); } + +// Implementations of TypeIntMirror methods for testing purposes +template +const TypeIntMirror* TypeIntMirror::operator->() const { + return this; +} + +template +TypeIntMirror TypeIntMirror::meet(const TypeIntMirror& o) const { + return TypeIntHelper::int_type_union(*this, o); +} + +template +bool TypeIntMirror::contains(U u) const { + S s = S(u); + return s >= _lo && s <= _hi && u >= _ulo && u <= _uhi && _bits.is_satisfied_by(u); +} + +template +bool TypeIntMirror::contains(const TypeIntMirror& o) const { + return TypeIntHelper::int_type_is_subset(*this, o); +} + +template +bool TypeIntMirror::operator==(const TypeIntMirror& o) const { + return TypeIntHelper::int_type_is_equal(*this, o); +} + +template +template +TypeIntMirror TypeIntMirror::cast() const { + static_assert(std::is_same_v); + return *this; +} + +// The number of TypeIntMirror instances for integral types with a few bits. These values are +// calculated once and written down for usage in constexpr contexts. +template +static constexpr size_t all_instances_size() { + using U = decltype(CTP::_ulo); + constexpr juint max_unsigned = juint(std::numeric_limits::max()); + if constexpr (max_unsigned == 1U) { + // 1 bit + return 3; + } else if constexpr (max_unsigned == 3U) { + // 2 bits + return 15; + } else if constexpr (max_unsigned == 7U) { + // 3 bits + return 134; + } else { + // 4 bits + static_assert(max_unsigned == 15U); + // For more than 4 bits, the number of instances is too large and it is not realistic to + // compute all of them. + return 1732; + } +} + +template +static std::array()> compute_all_instances() { + using S = decltype(CTP::_lo); + using U = decltype(CTP::_ulo); + + class CTPComparator { + public: + static RBTreeOrdering cmp(const CTP& x, const RBNode* node) { + // Quick helper for the tediousness below + auto f = [](auto x, auto y) { + assert(x != y, "we only handle lt and gt cases"); + return x < y ? RBTreeOrdering::LT : RBTreeOrdering::GT; + }; + + const CTP& y = node->key(); + if (x._lo != y._lo) { + return f(x._lo, y._lo); + } else if (x._hi != y._hi) { + return f(x._hi, y._hi); + } else if (x._ulo != y._ulo) { + return f(x._ulo, y._ulo); + } else if (x._uhi != y._uhi) { + return f(x._uhi, y._uhi); + } else if (x._bits._zeros != y._bits._zeros) { + return f(x._bits._zeros, y._bits._zeros); + } else if (x._bits._ones != y._bits._ones) { + return f(x._bits._ones, y._bits._ones); + } else { + return RBTreeOrdering::EQ; + } + } + }; + + RBTreeCHeap collector; + for (jint lo = jint(std::numeric_limits::min()); lo <= jint(std::numeric_limits::max()); lo++) { + for (jint hi = lo; hi <= jint(std::numeric_limits::max()); hi++) { + for (juint ulo = 0; ulo <= juint(std::numeric_limits::max()); ulo++) { + for (juint uhi = ulo; uhi <= juint(std::numeric_limits::max()); uhi++) { + for (juint zeros = 0; zeros <= juint(std::numeric_limits::max()); zeros++) { + for (juint ones = 0; ones <= juint(std::numeric_limits::max()); ones++) { + TypeIntPrototype t{{S(lo), S(hi)}, {U(ulo), U(uhi)}, {U(zeros), U(ones)}}; + auto canonicalized_t = t.canonicalize_constraints(); + if (canonicalized_t.empty()) { + continue; + } + + TypeIntPrototype ct = canonicalized_t._data; + collector.upsert(CTP{ct._srange._lo, ct._srange._hi, ct._urange._lo, ct._urange._hi, ct._bits}, 0); + } + } + } + } + } + } + + assert(collector.size() == all_instances_size(), "unexpected size of all_instance, expected %d, actual %d", jint(all_instances_size()), jint(collector.size())); + std::array()> res; + size_t idx = 0; + collector.visit_in_order([&](RBNode* node) { + res[idx] = node->key(); + idx++; + return true; + }); + return res; +} + +template +static const std::array()>& all_instances() { + static std::array()> res = compute_all_instances(); + static_assert(std::is_trivially_destructible_v); + return res; +} + +// Check the correctness, that is, if v1 is an element of input1, v2 is an element of input2, then +// op(v1, v2) must be an element of infer(input1, input2). This version does the check exhaustively +// on all elements of input1 and input2. +template +static void test_binary_instance_correctness_exhaustive(Operation op, Inference infer, const InputType& input1, const InputType& input2) { + using S = std::remove_const_t_lo)>; + using U = std::remove_const_t_ulo)>; + InputType result = infer(input1, input2); + + for (juint v1 = juint(std::numeric_limits::min()); v1 <= juint(std::numeric_limits::max()); v1++) { + if (!input1.contains(U(v1))) { + continue; + } + + for (juint v2 = juint(std::numeric_limits::min()); v2 <= juint(std::numeric_limits::max()); v2++) { + if (!input2.contains(U(v2))) { + continue; + } + + U r = op(U(v1), U(v2)); + ASSERT_TRUE(result.contains(r)); + } + } +} + +// Check the correctness, that is, if v1 is an element of input1, v2 is an element of input2, then +// op(v1, v2) must be an element of infer(input1, input2). This version does the check randomly on +// a number of elements in input1 and input2. +template +static void test_binary_instance_correctness_samples(Operation op, Inference infer, const InputType& input1, const InputType& input2) { + using U = std::remove_const_t_ulo)>; + auto result = infer(input1, input2); + + constexpr size_t sample_count = 6; + U input1_samples[sample_count] {U(input1._lo), U(input1._hi), input1._ulo, input1._uhi, input1._ulo, input1._ulo}; + U input2_samples[sample_count] {U(input2._lo), U(input2._hi), input2._ulo, input2._uhi, input2._ulo, input2._ulo}; + + auto random_sample = [](U* samples, const InputType& input) { + constexpr size_t max_tries = 100; + constexpr size_t start_random_idx = 4; + for (size_t tries = 0, idx = start_random_idx; tries < max_tries && idx < sample_count; tries++) { + U n = uniform_random(); + if (input.contains(n)) { + samples[idx] = n; + idx++; + } + } + }; + random_sample(input1_samples, input1); + random_sample(input2_samples, input2); + + for (size_t i = 0; i < sample_count; i++) { + for (size_t j = 0; j < sample_count; j++) { + U r = op(input1_samples[i], input2_samples[j]); + ASSERT_TRUE(result.contains(r)); + } + } +} + +// Check the monotonicity, that is, if input1 is a subset of super1, input2 is a subset of super2, +// then infer(input1, input2) must be a subset of infer(super1, super2). This version does the +// check exhaustively on all supersets of input1 and input2. +template +static void test_binary_instance_monotonicity_exhaustive(Inference infer, const InputType& input1, const InputType& input2) { + InputType result = infer(input1, input2); + + for (const InputType& super1 : all_instances()) { + if (!super1.contains(input1) || super1 == input1) { + continue; + } + + for (const InputType& super2 : all_instances()) { + if (!super2.contains(input2) || super2 == input2) { + continue; + } + + ASSERT_TRUE(infer(input1, super2).contains(result)); + ASSERT_TRUE(infer(super1, input2).contains(result)); + ASSERT_TRUE(infer(super1, super2).contains(result)); + } + } +} + +// Check the monotonicity, that is, if input1 is a subset of super1, input2 is a subset of super2, +// then infer(input1, input2) must be a subset of infer(super1, super2). This version does the +// check randomly on a number of supersets of input1 and input2. +template +static void test_binary_instance_monotonicity_samples(Inference infer, const InputType& input1, const InputType& input2) { + using S = std::remove_const_t_lo)>; + using U = std::remove_const_t_ulo)>; + auto result = infer(input1, input2); + + // The set that is a superset of all other sets + InputType universe = InputType{std::numeric_limits::min(), std::numeric_limits::max(), U(0), U(-1), {U(0), U(0)}}; + ASSERT_TRUE(infer(universe, input2).contains(result)); + ASSERT_TRUE(infer(input1, universe).contains(result)); + ASSERT_TRUE(infer(universe, universe).contains(result)); + + auto random_superset = [](const InputType& input) { + S lo = MIN2(input->_lo, S(uniform_random())); + S hi = MAX2(input->_hi, S(uniform_random())); + U ulo = MIN2(input->_ulo, uniform_random()); + U uhi = MAX2(input->_uhi, uniform_random()); + U zeros = input->_bits._zeros & uniform_random(); + U ones = input->_bits._ones & uniform_random(); + InputType super = InputType::make(TypeIntPrototype{{lo, hi}, {ulo, uhi}, {zeros, ones}}, 0); + assert(super.contains(input), "impossible"); + return super; + }; + + InputType super1 = random_superset(input1); + InputType super2 = random_superset(input2); + ASSERT_TRUE(infer(super1, input2).contains(result)); + ASSERT_TRUE(infer(input1, super2).contains(result)); + ASSERT_TRUE(infer(super1, super2).contains(result)); +} + +// Verify the correctness and monotonicity of an inference function by exhautively analyzing all +// instances of InputType +template +static void test_binary_exhaustive(Operation op, Inference infer) { + for (const InputType& input1 : all_instances()) { + for (const InputType& input2 : all_instances()) { + test_binary_instance_correctness_exhaustive(op, infer, input1, input2); + if (all_instances().size() < 100) { + // This effectively covers the cases up to uintn_t<2> + test_binary_instance_monotonicity_exhaustive(infer, input1, input2); + } else { + // This effectively covers the cases of uintn_t<3> + test_binary_instance_monotonicity_samples(infer, input1, input2); + } + } + } +} + +// Verify the correctness and monotonicity of an inference function by randomly sampling instances +// of InputType +template +static void test_binary_random(Operation op, Inference infer) { + using S = std::remove_const_t; + using U = std::remove_const_t; + + constexpr size_t sample_count = 100; + InputType samples[sample_count]; + + // Fill with {0} + for (size_t i = 0; i < sample_count; i++) { + samples[i] = InputType::make(TypeIntPrototype{{S(0), S(0)}, {U(0), U(0)}, {U(0), U(0)}}, 0); + } + + // {1} + samples[1] = InputType::make(TypeIntPrototype{{S(1), S(1)}, {U(1), U(1)}, {U(0), U(0)}}, 0); + // {-1} + samples[2] = InputType::make(TypeIntPrototype{{S(-1), S(-1)}, {U(-1), U(-1)}, {U(0), U(0)}}, 0); + // {0, 1} + samples[3] = InputType::make(TypeIntPrototype{{S(0), S(1)}, {U(0), U(1)}, {U(0), U(0)}}, 0); + // {-1, 0, 1} + samples[4] = InputType::make(TypeIntPrototype{{S(-1), S(1)}, {U(0), U(-1)}, {U(0), U(0)}}, 0); + // {-1, 1} + samples[5] = InputType::make(TypeIntPrototype{{S(-1), S(1)}, {U(1), U(-1)}, {U(0), U(0)}}, 0); + // {0, 1, 2} + samples[6] = InputType::make(TypeIntPrototype{{S(0), S(2)}, {U(0), U(2)}, {U(0), U(0)}}, 0); + // {0, 2} + samples[7] = InputType::make(TypeIntPrototype{{S(0), S(2)}, {U(0), U(2)}, {U(1), U(0)}}, 0); + // [min_signed, max_signed] + samples[8] = InputType::make(TypeIntPrototype{{std::numeric_limits::min(), std::numeric_limits::max()}, {U(0), U(-1)}, {U(0), U(0)}}, 0); + // [0, max_signed] + samples[9] = InputType::make(TypeIntPrototype{{S(0), std::numeric_limits::max()}, {U(0), U(-1)}, {U(0), U(0)}}, 0); + // [min_signed, 0) + samples[10] = InputType::make(TypeIntPrototype{{std::numeric_limits::min(), S(-1)}, {U(0), U(-1)}, {U(0), U(0)}}, 0); + + constexpr size_t max_tries = 1000; + constexpr size_t start_random_idx = 11; + for (size_t tries = 0, idx = start_random_idx; tries < max_tries && idx < sample_count; tries++) { + // Try to have lo < hi + S signed_bound1 = S(uniform_random()); + S signed_bound2 = S(uniform_random()); + S lo = MIN2(signed_bound1, signed_bound2); + S hi = MAX2(signed_bound1, signed_bound2); + + // Try to have ulo < uhi + U unsigned_bound1 = uniform_random(); + U unsigned_bound2 = uniform_random(); + U ulo = MIN2(unsigned_bound1, unsigned_bound2); + U uhi = MAX2(unsigned_bound1, unsigned_bound2); + + // Try to have (zeros & ones) == 0 + U zeros = uniform_random(); + U ones = uniform_random(); + U common = zeros & ones; + zeros = zeros ^ common; + ones = ones ^ common; + + TypeIntPrototype t{{lo, hi}, {ulo, uhi}, {zeros, ones}}; + auto canonicalized_t = t.canonicalize_constraints(); + if (canonicalized_t.empty()) { + continue; + } + + samples[idx] = TypeIntMirror{canonicalized_t._data._srange._lo, canonicalized_t._data._srange._hi, + canonicalized_t._data._urange._lo, canonicalized_t._data._urange._hi, + canonicalized_t._data._bits}; + idx++; + } + + for (size_t i = 0; i < sample_count; i++) { + for (size_t j = 0; j < sample_count; j++) { + test_binary_instance_correctness_samples(op, infer, samples[i], samples[j]); + test_binary_instance_monotonicity_samples(infer, samples[i], samples[j]); + } + } +} + +template